diff --git a/app/javascript/packages/document-capture/context/acuant.jsx b/app/javascript/packages/document-capture/context/acuant.jsx index 1440e2c12ad..30e3f201c79 100644 --- a/app/javascript/packages/document-capture/context/acuant.jsx +++ b/app/javascript/packages/document-capture/context/acuant.jsx @@ -1,5 +1,6 @@ import { createContext, useContext, useMemo, useEffect, useState } from 'react'; import DeviceContext from './device'; +import AnalyticsContext from './analytics'; /** @typedef {import('react').ReactNode} ReactNode */ @@ -9,15 +10,25 @@ import DeviceContext from './device'; * @prop {boolean} isCameraSupported Whether camera is supported. */ +/** + * @typedef {1|2|400|401|403} AcuantInitializeCode Acuant initialization callback code. + * + * @see https://github.com/Acuant/JavascriptWebSDKV11/blob/11.4.4/SimpleHTMLApp/webSdk/dist/AcuantJavascriptWebSdk.js#L1327-L1353 + */ + /** * @typedef AcuantCallbackOptions * * @prop {()=>void} onSuccess Success callback. - * @prop {()=>void} onFail Failure callback. + * @prop {(code: AcuantInitializeCode, description: string)=>void} onFail Failure callback. */ /** - * @typedef {(credentials:string?,endpoint:string?,AcuantCallbackOptions)=>void} AcuantInitialize + * @typedef {( + * credentials: string?, + * endpoint: string?, + * callback: AcuantCallbackOptions, + * )=>void} AcuantInitialize */ /** @@ -66,6 +77,7 @@ function AcuantContextProvider({ children, }) { const { isMobile } = useContext(DeviceContext); + const { addPageAction } = useContext(AnalyticsContext); // Only mobile devices should load the Acuant SDK. Consider immediately ready otherwise. const [isReady, setIsReady] = useState(!isMobile); const [isAcuantLoaded, setIsAcuantLoaded] = useState(false); @@ -94,13 +106,29 @@ function AcuantContextProvider({ endpoint, { onSuccess: () => { + addPageAction({ + label: 'IdV: Acuant SDK loaded', + payload: { success: true }, + }); + setIsCameraSupported( /** @type {AcuantGlobal} */ (window).AcuantCamera.isCameraSupported, ); setIsReady(true); setIsAcuantLoaded(true); }, - onFail: () => setIsError(true), + onFail(code, description) { + addPageAction({ + label: 'IdV: Acuant SDK loaded', + payload: { + success: false, + code, + description, + }, + }); + + setIsError(true); + }, }, ); }; diff --git a/app/javascript/packs/document-capture.jsx b/app/javascript/packs/document-capture.jsx index e893e40ad64..c093685b4e1 100644 --- a/app/javascript/packs/document-capture.jsx +++ b/app/javascript/packs/document-capture.jsx @@ -141,34 +141,34 @@ loadPolyfills(['fetch', 'crypto']).then(async () => { render( - - + - - - + + + - - - - - + + + + + , appRoot, ); diff --git a/package.json b/package.json index 03efaab7536..2e0a9e6df30 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@testing-library/user-event": "^12.6.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", + "@types/sinon": "^9.0.11", "chai": "^4.2.0", "dirty-chai": "^2.0.1", "eslint": "^7.16.0", diff --git a/spec/javascripts/packages/document-capture/context/acuant-spec.jsx b/spec/javascripts/packages/document-capture/context/acuant-spec.jsx index 769eff53aea..ff10335aa25 100644 --- a/spec/javascripts/packages/document-capture/context/acuant-spec.jsx +++ b/spec/javascripts/packages/document-capture/context/acuant-spec.jsx @@ -1,6 +1,7 @@ +import sinon from 'sinon'; import { useContext } from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { DeviceContext } from '@18f/identity-document-capture'; +import { DeviceContext, AnalyticsContext } from '@18f/identity-document-capture'; import AcuantContext, { Provider as AcuantContextProvider, } from '@18f/identity-document-capture/context/acuant'; @@ -92,50 +93,97 @@ describe('document-capture/context/acuant', () => { }); }); - it('provides ready context when successfully loaded', () => { - const { result } = renderHook(() => useContext(AcuantContext), { - wrapper: ({ children }) => ( - - {children} - - ), + context('successful initialization', () => { + let result; + let addPageAction; + + beforeEach(() => { + addPageAction = sinon.spy(); + ({ result } = renderHook(() => useContext(AcuantContext), { + wrapper: ({ children }) => ( + + + {children} + + + ), + })); + + window.AcuantJavascriptWebSdk = { + initialize: (_credentials, _endpoint, { onSuccess }) => onSuccess(), + }; + window.AcuantCamera = { isCameraSupported: true }; + window.onAcuantSdkLoaded(); }); - window.AcuantJavascriptWebSdk = { - initialize: (_credentials, _endpoint, { onSuccess }) => onSuccess(), - }; - window.AcuantCamera = { isCameraSupported: true }; - window.onAcuantSdkLoaded(); + it('provides ready context', () => { + expect(result.current).to.eql({ + isReady: true, + isAcuantLoaded: true, + isError: false, + isCameraSupported: true, + credentials: null, + endpoint: null, + }); + }); - expect(result.current).to.eql({ - isReady: true, - isAcuantLoaded: true, - isError: false, - isCameraSupported: true, - credentials: null, - endpoint: null, + it('logs', () => { + expect(addPageAction).to.have.been.calledWith({ + label: 'IdV: Acuant SDK loaded', + payload: { + success: true, + }, + }); }); }); - it('has camera availability at time of ready', () => { - const { result } = renderHook(() => useContext(AcuantContext), { - wrapper: ({ children }) => ( - - {children} - - ), + context('failed initialization', () => { + let result; + let addPageAction; + + beforeEach(() => { + addPageAction = sinon.spy(); + ({ result } = renderHook(() => useContext(AcuantContext), { + wrapper: ({ children }) => ( + + + {children} + + + ), + })); + + window.AcuantJavascriptWebSdk = { + initialize: (_credentials, _endpoint, { onFail }) => + onFail(401, 'Server returned a 401 (missing credentials).'), + }; + window.onAcuantSdkLoaded(); }); - window.AcuantJavascriptWebSdk = { - initialize: (_credentials, _endpoint, { onSuccess }) => onSuccess(), - }; - window.AcuantCamera = { isCameraSupported: true }; - window.onAcuantSdkLoaded(); + it('provides error context', () => { + expect(result.current).to.eql({ + isReady: false, + isAcuantLoaded: false, + isError: true, + isCameraSupported: null, + credentials: null, + endpoint: null, + }); + }); - expect(result.current.isCameraSupported).to.be.true(); + it('logs', () => { + expect(addPageAction).to.have.been.calledWith({ + label: 'IdV: Acuant SDK loaded', + payload: { + success: false, + code: sinon.match.number, + description: sinon.match.string, + }, + }); + }); }); - it('provides error context when failed to loaded', () => { + it('has camera availability at time of ready', () => { const { result } = renderHook(() => useContext(AcuantContext), { wrapper: ({ children }) => ( @@ -145,18 +193,12 @@ describe('document-capture/context/acuant', () => { }); window.AcuantJavascriptWebSdk = { - initialize: (_credentials, _endpoint, { onFail }) => onFail(), + initialize: (_credentials, _endpoint, { onSuccess }) => onSuccess(), }; + window.AcuantCamera = { isCameraSupported: true }; window.onAcuantSdkLoaded(); - expect(result.current).to.eql({ - isReady: false, - isAcuantLoaded: false, - isError: true, - isCameraSupported: null, - credentials: null, - endpoint: null, - }); + expect(result.current.isCameraSupported).to.be.true(); }); it('cleans up after itself on unmount', () => { diff --git a/yarn.lock b/yarn.lock index f431ae34b34..465445f7bc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1222,6 +1222,18 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/sinon@^9.0.11": + version "9.0.11" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.11.tgz#7af202dda5253a847b511c929d8b6dda170562eb" + integrity sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" + integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== + "@types/testing-library__react-hooks@^3.4.0": version "3.4.1" resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.1.tgz#b8d7311c6c1f7db3103e94095fe901f8fef6e433"