import * as React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { when, resetAllWhenMocks } from 'jest-when';
import { LivenessClassNames } from '../../types/classNames';
import {
renderWithLivenessProvider,
getMockedFunction,
mockMatchMedia,
} from '../../__mocks__';
import {
useLivenessActor,
useLivenessSelector,
useMediaStreamInVideo,
} from '../../hooks';
import {
LivenessCameraModule,
selectVideoConstraints,
selectVideoStream,
} from '../LivenessCameraModule';
import { FaceMatchState } from '../../service';
import { getDisplayText } from '../../utils/getDisplayText';
jest.mock('../../hooks');
jest.mock('../../hooks/useLivenessSelector');
jest.mock('../../shared/CancelButton');
jest.mock('../../shared/Hint');
const mockUseLivenessActor = getMockedFunction(useLivenessActor);
const mockUseLivenessSelector = getMockedFunction(useLivenessSelector);
const mockUseMediaStreamInVideo = getMockedFunction(useMediaStreamInVideo);
describe('LivenessCameraModule', () => {
const mockActorState: any = {
matches: jest.fn(),
};
const mockActorSend = jest.fn();
let isCheckingCamera = false;
let isNotRecording = false;
let isRecording = false;
const { hintDisplayText, streamDisplayText, errorDisplayText } =
getDisplayText(undefined);
const { cancelLivenessCheckText, recordingIndicatorText } = streamDisplayText;
function mockStateMatchesAndSelectors() {
when(mockActorState.matches)
.calledWith('cameraCheck')
.mockReturnValue(isCheckingCamera)
.calledWith('notRecording')
.mockReturnValue(isNotRecording)
.calledWith('recording')
.mockReturnValue(isRecording);
}
beforeEach(() => {
mockMatchMedia();
mockUseLivenessActor.mockReturnValue([mockActorState, mockActorSend]);
mockUseLivenessSelector.mockReturnValueOnce({}).mockReturnValueOnce({});
mockUseMediaStreamInVideo.mockReturnValue({
videoRef: { current: document.createElement('video') },
videoHeight: 100,
videoWidth: 100,
});
});
afterEach(() => {
isCheckingCamera = false;
isNotRecording = false;
isRecording = false;
jest.clearAllMocks();
jest.clearAllTimers();
resetAllWhenMocks();
});
it('should render centered loader when isCheckingCamera true', () => {
isCheckingCamera = true;
mockStateMatchesAndSelectors();
renderWithLivenessProvider(
);
expect(screen.getByTestId('centered-loader')).toBeInTheDocument();
});
it.skip('should render video and timer when isNotRecording true', async () => {
isNotRecording = true;
mockStateMatchesAndSelectors();
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
expect(screen.getByTestId('centered-loader')).toBeInTheDocument();
expect(videoEl).toBeInTheDocument();
expect(
screen.getByRole('button', { name: cancelLivenessCheckText })
).toBeInTheDocument();
videoEl.dispatchEvent(new Event('canplay'));
expect(screen.queryByTestId('centered-loader')).not.toBeInTheDocument();
expect(screen.getByLabelText('Countdown timer')).toBeInTheDocument();
expect(screen.getByText('Hint')).toBeInTheDocument();
await waitFor(() => expect(mockActorSend).toHaveBeenCalledTimes(1), {
timeout: 5000,
});
expect(mockActorSend).toHaveBeenCalledWith({
type: 'SET_DOM_AND_CAMERA_DETAILS',
data: {
videoEl: expect.any(HTMLVideoElement),
freshnessColorEl: expect.any(HTMLCanvasElement),
canvasEl: expect.any(HTMLCanvasElement),
isMobile: true,
},
});
});
it('should render recording icon when isRecording true', () => {
isRecording = true;
mockStateMatchesAndSelectors();
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
expect(screen.getByTestId('rec-icon')).toBeInTheDocument();
expect(screen.getByText(recordingIndicatorText)).toBeInTheDocument();
});
it('should render MatchIndicator when isRecording and faceMatchState is TOO_FAR', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.TOO_FAR);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(1);
});
it('should render MatchIndicator when isRecording and faceMatchState is CANT_IDENTIFY', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.CANT_IDENTIFY);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(1);
});
it('should render MatchIndicator when isRecording and faceMatchState is FACE_IDENTIFIED', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.FACE_IDENTIFIED);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(1);
});
it('should not render MatchIndicator when isRecording and faceMatchState is TOO_CLOSE', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.TOO_CLOSE);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(0);
});
it('should render MatchIndicator when isRecording and faceMatchState is MATCHED', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.MATCHED);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(1);
});
it('should not render MatchIndicator when isRecording and faceMatchState is TOO_MANY', async () => {
isRecording = true;
mockStateMatchesAndSelectors();
mockUseLivenessSelector
.mockReturnValue(25)
.mockReturnValue(FaceMatchState.TOO_MANY);
const testId = 'cameraModule';
renderWithLivenessProvider(
);
const videoEl = screen.getByTestId('video');
videoEl.dispatchEvent(new Event('canplay'));
const cameraModule = await screen.findByTestId(testId);
const matchIndicator = cameraModule.getElementsByClassName(
LivenessClassNames.MatchIndicator
);
expect(matchIndicator).toHaveLength(0);
});
it('should create appropriate selectors', () => {
const expectedConstraints = { width: 100 };
const expectedStream = { getTracks: () => [] };
const state: any = {
context: {
videoAssociatedParams: {
videoConstraints: expectedConstraints,
videoMediaStream: expectedStream,
},
},
};
const actualConstraints = selectVideoConstraints(state);
const actualStream = selectVideoStream(state);
expect(actualConstraints).toEqual(expectedConstraints);
expect(actualStream).toEqual(expectedStream);
});
});