diff --git a/imports/_test/png.ts b/imports/_test/png.ts index 4501c40d2..b374286f1 100644 --- a/imports/_test/png.ts +++ b/imports/_test/png.ts @@ -3,6 +3,8 @@ import pngDataURL from '../lib/png/dataURL'; const base64Encoded = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; +export const randomPNGBase64 = () => base64Encoded; + export const randomPNGBuffer = async () => { const {Buffer} = await import('buffer'); return Buffer.from(base64Encoded, 'base64'); diff --git a/imports/api/_dev/populate/eids.ts b/imports/api/_dev/populate/eids.ts index b454bf130..93a244e1d 100644 --- a/imports/api/_dev/populate/eids.ts +++ b/imports/api/_dev/populate/eids.ts @@ -3,6 +3,7 @@ import {faker} from '@faker-js/faker'; import format from 'date-fns/format'; import {makeTemplate} from '../../../_test/fixtures'; +import {randomPNGBase64} from '../../../_test/png'; const DATE_FORMAT = 'yyyyMMdd'; const AGE_MAX = 130; @@ -51,3 +52,59 @@ export const newEidData = makeTemplate({ zip: () => faker.location.zipCode(), }, }); + +export const exampleEidXML = (options?: { + name?: string; + firstname?: string; + middlenames?: string; + nationality?: string; + placeofbirth?: string; +}) => { + const {name, firstname, middlenames, nationality, placeofbirth} = { + name: 'Name', + firstname: 'First Name', + middlenames: 'M', + nationality: 'Belge', + placeofbirth: 'Bruxelles', + ...options, + }; + // TODO: Generalizes and escape values. + return ` + + + ${name} + ${firstname} + ${middlenames} + ${nationality} + ${placeofbirth} + ${randomPNGBase64()} + + + Bruxelles + +
+ Rue de la montagne 58 + 1000 + Bruxelles +
+ + ROOT + CITIZENCA + AUTHENTICATION + SIGNING + RRN + +
`; +}; diff --git a/imports/ui/patients/SelectablePatientCard.tsx b/imports/ui/patients/SelectablePatientCard.tsx index d85743b42..17277a9e5 100644 --- a/imports/ui/patients/SelectablePatientCard.tsx +++ b/imports/ui/patients/SelectablePatientCard.tsx @@ -8,6 +8,8 @@ import RadioButtonUncheckedOutlinedIcon from '@mui/icons-material/RadioButtonUnc import type PropsOf from '../../lib/types/PropsOf'; +import useUniqueId from '../hooks/useUniqueId'; + import GenericStaticPatientCard from './GenericStaticPatientCard'; type Props = { @@ -75,12 +77,14 @@ const SelectablePatientCard = ({ patient, ...rest }: Props) => { + const labelId = useUniqueId('selectable-patient-card'); return ( - + { onClick(patient); diff --git a/test/app/client/fixtures.ts b/test/app/client/fixtures.ts index 015626b70..38314d170 100644 --- a/test/app/client/fixtures.ts +++ b/test/app/client/fixtures.ts @@ -204,21 +204,27 @@ export const createNewPatient = async (app: App, {firstname, lastname}) => { }; export const searchResultsForQuery = async ( - {userWithoutPointerEventsCheck, findByRole, getByRole}: App, + {userWithoutPointerEventsCheck, findByRole}: App, query, ) => { await userWithoutPointerEventsCheck.type( - getByRole('searchbox', {name: 'Patient search'}), + await findByRole('searchbox', {name: 'Patient search'}), query, ); await findByRole('heading', {name: /^Results for query/}, {timeout: 5000}); }; -export const searchForPatient = async (app: App, query, {name, id}) => { +export const searchForPatient = async ( + app: App, + query: string, + {name, id}: {name: string; id?: string}, +) => { const {findByRole, user} = app; await searchResultsForQuery(app, query); await user.click(await findByRole('link', {name}, {timeout: 5000})); - await findByRole('heading', {name: `/patient/${id}`}, {timeout: 10_000}); + if (id !== undefined) { + await findByRole('heading', {name: `/patient/${id}`}, {timeout: 10_000}); + } }; type EditConsultationOptions = { diff --git a/test/app/client/patient/eids.app-tests.ts b/test/app/client/patient/eids.app-tests.ts new file mode 100644 index 000000000..0cde8dd45 --- /dev/null +++ b/test/app/client/patient/eids.app-tests.ts @@ -0,0 +1,139 @@ +import {fireEvent} from '@testing-library/dom'; + +import {exampleEidXML} from '../../../../imports/api/_dev/populate/eids'; + +import { + client, + randomPassword, + randomUserId, +} from '../../../../imports/_test/fixtures'; +import { + setupApp, + createUserWithPasswordAndLogin, + searchForPatient, +} from '../fixtures'; + +type Item = string | File; + +const createFileList = (files: File[]): FileList => { + const list: FileList & Iterable = { + ...files, + length: files.length, + item: (index: number) => list[index]!, + [Symbol.iterator]: () => files[Symbol.iterator](), + }; + list.constructor = FileList; + Object.setPrototypeOf(list, FileList.prototype); + Object.freeze(list); + + return list; +}; + +const itemToDataTransferItem = (item: Item) => + item instanceof File + ? { + kind: 'file', + type: item.type, + getAsFile: () => item, + } + : { + kind: 'string', + type: 'text/plain', + getAsString(resolve: (value: string) => void) { + resolve(item); + }, + }; + +const createItemList = (items: Item[]): DataTransferItemList => { + const list: DataTransferItemList & Iterable = { + ...items.map(itemToDataTransferItem), + length: items.length, + add() { + throw new Error('not-implemented'); + }, + remove() { + throw new Error('not-implemented'); + }, + clear() { + throw new Error('not-implemented'); + }, + *[Symbol.iterator]() { + // eslint-disable-next-line unicorn/no-for-loop,@typescript-eslint/prefer-for-of + for (let i = 0; i < list.length; ++i) { + yield list[i]!; + } + }, + }; + + list.constructor = DataTransferItemList; + Object.setPrototypeOf(list, DataTransferItemList.prototype); + Object.freeze(list); + + return list; +}; + +const createDataTransferFromFiles = (items: Item[] = []): DataTransfer => { + const dt = new DataTransfer(); + Object.defineProperty(dt, 'files', { + get: () => + createFileList( + items.filter((item: Item): item is File => item instanceof File), + ), + }); + Object.defineProperty(dt, 'items', {get: () => createItemList(items)}); + return dt; +}; + +const dropFiles = async ({findByLabelText}, item: Item) => { + fireEvent.drop(await findByLabelText(/drop contents here/i), { + dataTransfer: createDataTransferFromFiles([item]), + }); +}; + +client(__filename, () => { + it('should allow to open and close eid dialog', async () => { + const username = randomUserId(); + const password = randomPassword(); + const app = setupApp(); + await createUserWithPasswordAndLogin(app, username, password); + + const {findByRole, findByText, user} = app; + + await dropFiles(app, exampleEidXML()); + + await findByRole('heading', {name: 'Select record to work with.'}); + + await user.click(await findByRole('button', {name: 'Cancel'})); + + await findByText('Closed eid dialog.'); + }); + + it('should allow to create a patient from eid', async () => { + const username = randomUserId(); + const password = randomPassword(); + const app = setupApp(); + await createUserWithPasswordAndLogin(app, username, password); + + const {findByRole, user} = app; + + const eidXML = exampleEidXML({name: 'Doe', firstname: 'Jane'}); + + await dropFiles(app, eidXML); + + await findByRole('heading', {name: 'Select record to work with.'}); + + await user.click(await findByRole('button', {name: 'Add a new patient'})); + + await user.click(await findByRole('button', {name: 'Next (1)'})); + + await user.click(await findByRole('button', {name: 'Next'})); + + await user.click( + await findByRole('button', {name: 'Create a new patient'}), + ); + + await searchForPatient(app, `Jane Doe`, { + name: `Jane Doe`, + }); + }); +});