Skip to content

Commit f0ef4eb

Browse files
committed
Add example of extraction test for aboriginal form
1 parent 26eb241 commit f0ef4eb

File tree

4 files changed

+99
-43
lines changed

4 files changed

+99
-43
lines changed
Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { render } from '@testing-library/react';
2-
import { AboriginalForm, runExtract } from './aboriginalFormUtils';
3-
import { condition, patient, resolvedCondition } from './aboriginalFormIntegrationData';
1+
import { vi, beforeAll } from 'vitest';
2+
import { render, waitFor } from '@testing-library/react';
3+
import { AboriginalForm } from './aboriginalFormUtils';
4+
import { nonSnomedCondition, patient } from './aboriginalFormIntegrationData';
5+
import { findByLinkIdOrLabel, inputDate, inputText, invokeExtract, selectTab } from './testUtils';
46
import { FhirResource } from 'fhir/r4';
57

68
vi.mock('fhirclient', () => ({
@@ -17,20 +19,32 @@ beforeAll(() => {
1719

1820
describe('Extraction workflow for', () => {
1921
test('Conditions', async () => {
20-
const { container } = render(<AboriginalForm patient={patient} requestDefinitions={[]} />);
21-
22-
// Fill condition table with data from condition
23-
// Fill condition table with data from resolvedCondition
24-
25-
const extractedBundle = await runExtract({}, {});
26-
expect(extractedBundle.entry).toHaveLength(2);
27-
28-
expect(extractedBundle.entry?.[0]?.resource).toStrictEqual(removeId(condition));
29-
expect(extractedBundle.entry?.[1]?.resource).toStrictEqual(removeId(resolvedCondition));
22+
const onExtractResult = vi.fn();
23+
const { container } = render(
24+
<AboriginalForm patient={patient} requestDefinitions={[]} onExtractResult={onExtractResult} />
25+
);
26+
await waitFor(() => expect(container.innerHTML).toContain('Patient Details'), {
27+
timeout: 5000
28+
});
29+
await selectTab(container, 'Medical history and current problems');
30+
const newDiagnosisContainer = await findByLinkIdOrLabel(container, 'New diagnosis');
31+
await inputText(newDiagnosisContainer, 'Condition', 'Non-SNOMED condition');
32+
await inputDate(newDiagnosisContainer, 'Onset date', '10/10/2025');
33+
await inputText(newDiagnosisContainer, 'Comment', 'Test comment');
34+
35+
const extractedBundle = await invokeExtract(container, onExtractResult);
36+
expect(extractedBundle.entry).toHaveLength(1);
37+
// Verification status is not extracted
38+
expect(extractedBundle.entry?.[0]?.resource).toStrictEqual(
39+
omitResourceFields(nonSnomedCondition, ['id', 'verificationStatus'])
40+
);
3041
});
3142
});
3243

33-
function removeId(resource: FhirResource) {
34-
const { id, ...resourceWithoutId } = resource;
35-
return resourceWithoutId;
44+
function omitResourceFields<T extends FhirResource>(resource: T, fieldsToRemove: (keyof T)[]) {
45+
const resourceCopy = { ...resource };
46+
fieldsToRemove.forEach((field) => {
47+
delete resourceCopy[field];
48+
});
49+
return resourceCopy;
3650
}

apps/smart-forms-app/src/test/aboriginalFormIntegrationData.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ export const patient: Patient = {
211211
]
212212
};
213213

214-
const onsetDateTime = '2025-10-10T00:00:00.000Z';
215-
const abatementDateTime = '2026-01-01T00:00:00.000Z';
214+
const onsetDateTime = '2025-10-10';
215+
const abatementDateTime = '2026-01-01';
216216

217217
export const condition: Condition = {
218218
resourceType: 'Condition',
@@ -261,13 +261,6 @@ export const nonSnomedCondition: Condition = {
261261
subject: { reference: `Patient/${patient.id}` },
262262
onsetDateTime,
263263
code: {
264-
coding: [
265-
{
266-
system: 'http://loinc.org',
267-
code: '78910',
268-
display: 'Non-SNOMED condition'
269-
}
270-
],
271264
text: 'Non-SNOMED condition'
272265
},
273266
clinicalStatus: {
@@ -294,6 +287,11 @@ export const nonSnomedCondition: Condition = {
294287
}
295288
]
296289
}
290+
],
291+
note: [
292+
{
293+
text: 'Test comment'
294+
}
297295
]
298296
};
299297

apps/smart-forms-app/src/test/aboriginalFormUtils.tsx

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ import {
22
BaseRenderer,
33
buildForm,
44
RendererThemeProvider,
5+
useQuestionnaireResponseStore,
6+
useQuestionnaireStore,
57
useRendererQueryClient
68
} from '@aehrc/smart-forms-renderer';
79

810
import aboriginalForm from '../data/resources/Questionnaire/Questionnaire-AboriginalTorresStraitIslanderHealthCheckAssembled-0.4.0.json';
9-
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
11+
import type { Questionnaire } from 'fhir/r4';
1012
import { QueryClientProvider } from '@tanstack/react-query';
1113
import type { Patient } from 'fhir/r4';
1214
import { populateQuestionnaire } from '@aehrc/sdc-populate';
1315
import { useEffect, useState } from 'react';
14-
import { extractResultIsOperationOutcome, inAppExtract } from '@aehrc/sdc-template-extract';
16+
import { inAppExtract, type InAppExtractOutput } from '@aehrc/sdc-template-extract';
17+
import Button from '@mui/material/Button';
1518
const terminologyServerUrl = 'https://r4.ontoserver.csiro.au/fhir';
1619

1720
export type RequestDefinition = {
@@ -23,6 +26,7 @@ export type RequestDefinition = {
2326
interface AboriginalFormProps {
2427
patient?: Patient;
2528
requestDefinitions?: RequestDefinition[];
29+
onExtractResult?: (extractResult: InAppExtractOutput) => void;
2630
}
2731

2832
export function AboriginalForm(props: AboriginalFormProps) {
@@ -91,6 +95,7 @@ function BuildFormWrapperWithPopulate(props: BuildFormWrapperWithPopulateProps)
9195
<RendererThemeProvider>
9296
<QueryClientProvider client={queryClient}>
9397
<BaseRenderer />
98+
<SaveControl onExtractResult={props.onExtractResult} />
9499
</QueryClientProvider>
95100
</RendererThemeProvider>
96101
);
@@ -127,20 +132,23 @@ function buildFetchResourceCallback(requestDefinitions: RequestDefinition[]) {
127132
};
128133
}
129134

130-
export async function runExtract(
131-
questionnaireResponse: QuestionnaireResponse,
132-
questionnaire: Questionnaire
133-
) {
134-
const inAppExtractOutput = await inAppExtract(questionnaireResponse, questionnaire, null);
135+
function SaveControl({
136+
onExtractResult
137+
}: {
138+
onExtractResult?: (extractResult: InAppExtractOutput) => void;
139+
}) {
140+
const qr = useQuestionnaireResponseStore.use.updatableResponse();
141+
const q = useQuestionnaireStore.use.sourceQuestionnaire();
135142

136-
if (!inAppExtractOutput.extractSuccess) {
137-
throw new Error('Extract failed');
138-
}
139-
140-
const extractResult = inAppExtractOutput.extractResult;
141-
if (extractResultIsOperationOutcome(extractResult)) {
142-
throw new Error('Extract failed');
143-
}
144-
145-
return extractResult.extractedBundle;
146-
}
143+
return (
144+
<Button
145+
data-testid="save-button"
146+
onClick={async () => {
147+
const result = await inAppExtract(qr, q, null);
148+
149+
onExtractResult?.(result);
150+
}}>
151+
Save
152+
</Button>
153+
);
154+
}

apps/smart-forms-app/src/test/testUtils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
// 2. Prevent it from showing up in typedoc in the documentation site, which will affect docs search
2222

2323
import { evaluate } from 'fhirpath';
24+
import type { Mock } from 'storybook/internal/test';
2425
import { fireEvent, screen, userEvent, waitFor } from 'storybook/internal/test';
2526
import { questionnaireResponseStore } from '@aehrc/smart-forms-renderer';
2627
import { act } from 'react';
28+
import type { ExtractResult, InAppExtractOutput } from '@aehrc/sdc-template-extract';
29+
import { extractResultIsOperationOutcome } from '@aehrc/sdc-template-extract';
2730

2831
export async function inputText(
2932
canvasElement: HTMLElement,
@@ -431,3 +434,36 @@ export function getBirthDateForAge(ageInYears: number): string {
431434
const birthDate = new Date(today.getFullYear() - ageInYears, today.getMonth(), today.getDate());
432435
return birthDate.toISOString().slice(0, 10);
433436
}
437+
438+
export async function invokeExtract(
439+
canvasElement: HTMLElement,
440+
onExtractResultMock: Mock<(extractResult: InAppExtractOutput) => void>
441+
) {
442+
const button = canvasElement.querySelector('button[data-testid="save-button"]');
443+
if (!button) {
444+
throw new Error('Save button not found');
445+
}
446+
await act(async () => {
447+
fireEvent.click(button);
448+
});
449+
450+
await waitFor(() => expect(onExtractResultMock.mock.lastCall).toBeDefined(), {
451+
timeout: 5000
452+
});
453+
454+
const lastCall = onExtractResultMock.mock.lastCall;
455+
if (!lastCall) {
456+
throw new Error('Expected onExtractResult to be called');
457+
}
458+
console.log('lastCall', lastCall[0]);
459+
460+
return getExtractResultBundle(lastCall[0]);
461+
}
462+
463+
function getExtractResultBundle(extractResultOutput: InAppExtractOutput) {
464+
expect(extractResultOutput.extractSuccess).toBe(true);
465+
const extractResult = extractResultOutput.extractResult as ExtractResult;
466+
expect(extractResultIsOperationOutcome(extractResult)).toBe(false);
467+
468+
return extractResult.extractedBundle;
469+
}

0 commit comments

Comments
 (0)