Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f2b4b61
Make behavioral utils shared
ruscoder Jul 20, 2026
f0c48ff
Add minimal working behavioral test for gpccmp
ruscoder Jul 20, 2026
10fecf1
Add test for enableWhen, enableWhenBehavior, calculation
roman-podrubny Jul 31, 2026
62360d6
Update .gitignore to include .scratch and ensure proper formatting
pavlushkin Aug 24, 2026
9a3bf0c
Add vitest mock for fhirclient in calculation and conditionsEnableWhe…
pavlushkin Aug 24, 2026
5e3870e
Add initial implementation of questionnaire test toolkit with README,…
pavlushkin Aug 24, 2026
9bddbcd
Refactor tests to utilize @aehrc/questionnaire-test-toolkit
pavlushkin Aug 24, 2026
561ba59
Add tests for GPCCMP form calculations and conditions
pavlushkin Aug 24, 2026
41b4faf
Add CI workflow for Vitest GP CCMP Behaviour Tests and update README …
pavlushkin Aug 24, 2026
1b4c41f
Update CI workflow to trigger on pull requests and enhance README wit…
pavlushkin Aug 24, 2026
7e64a22
Update Dockerfile to use Node.js 20 and ensure questionnaire test too…
pavlushkin Aug 24, 2026
8cb24ac
Merge branch 'main' into wip-gpccmp-behavioural-tests
ruscoder Sep 11, 2026
1389c3b
Merge branch 'wip-gpccmp-behavioural-tests' of github.com:beda-softwa…
ruscoder Sep 11, 2026
663bfcc
docs(gpccmp): document local questionnaire setup
roman-podrubny Sep 15, 2026
c1c1c4d
feat(questionnaire-test-toolkit): support user and fhirContext in beh…
roman-podrubny Sep 18, 2026
232d6b6
test(gpccmp): switch terminology expansion to live Ontoserver
roman-podrubny Sep 18, 2026
3073a6a
test(gpccmp): add comprehensive population workflow tests
roman-podrubny Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/vitest_gpccmp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Vitest GP CCMP Behaviour Tests

on: [workflow_dispatch, push, pull_request, workflow_call]

jobs:
vitest-gpccmp-tests:
name: Vitest Questionnaire Behaviour Tests - GP CCMP
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci

- name: Build workspace packages (monorepo dependencies)
run: |
npm run build -w packages/sdc-assemble
npm run build -w packages/sdc-populate
npm run build -w packages/sdc-template-extract
npm run build -w packages/smart-forms-renderer

- name: Typecheck
run: npm run typecheck -w forms/gpccmp

- name: Run vitest tests
run: npm run test -w forms/gpccmp
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ changelog_guide.md
.DS_STORE

# GitHub copilot instructions
.github/copilot-instructions.md
.github/copilot-instructions.md

.scratch
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ storybook-static
*.js
*.config.*
*.html

# Assembled questionnaire snapshots are artifacts, not source — see questionnaire/PROVENANCE.md
forms/*/questionnaire/*.json
4 changes: 3 additions & 1 deletion apps/smart-forms-app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Dockerfile

# Step 1: Use a Node.js image to build the app
FROM --platform=$BUILDPLATFORM node:18 AS builder
FROM --platform=$BUILDPLATFORM node:20 AS builder

# Set working directory inside the container
WORKDIR /app
Expand All @@ -21,6 +21,7 @@ COPY packages/sdc-assemble/package*.json ./packages/sdc-assemble/
COPY packages/sdc-populate/package*.json ./packages/sdc-populate/
COPY packages/sdc-template-extract/package*.json ./packages/sdc-template-extract/
COPY packages/smart-forms-renderer/package*.json ./packages/smart-forms-renderer/
COPY packages/questionnaire-test-toolkit/package*.json ./packages/questionnaire-test-toolkit/
COPY apps/smart-forms-app/package*.json ./apps/smart-forms-app/

# Install all workspace dependencies from the root
Expand All @@ -31,6 +32,7 @@ COPY packages/sdc-assemble/ ./packages/sdc-assemble/
COPY packages/sdc-populate/ ./packages/sdc-populate/
COPY packages/sdc-template-extract/ ./packages/sdc-template-extract/
COPY packages/smart-forms-renderer/ ./packages/smart-forms-renderer/
COPY packages/questionnaire-test-toolkit/ ./packages/questionnaire-test-toolkit/
COPY apps/smart-forms-app/ ./apps/smart-forms-app/

# Build packages in dependency order (mirrors deploy_app.yml)
Expand Down
1 change: 1 addition & 0 deletions apps/smart-forms-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"zustand": "^5.0.14"
},
"devDependencies": {
"@aehrc/questionnaire-test-toolkit": "^0.1.0",
"@jest/globals": "^30.3.0",
"@playwright/test": "^1.60.0",
"@sentry/cli": "^2.57.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
inputDecimal,
getVisibleTab,
checkRadioOption
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
inputDecimal,
getVisibleTab,
inputDateTime
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
findByLinkIdOrLabel,
checkCheckBox,
checkCheckboxOption
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import { AboriginalForm } from './aboriginalFormUtils.tsx';

vi.mock('fhirclient', () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { vi, beforeAll } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import { AboriginalForm, terminologyServerUrl } from './aboriginalFormUtils';
import { AboriginalForm } from './aboriginalFormUtils';
import { terminologyServerUrl } from '@aehrc/questionnaire-test-toolkit';
import {
nonSnomedCondition,
patient,
Expand Down Expand Up @@ -30,7 +31,7 @@ import {
selectTab,
chooseSelectOption,
checkRadioOption
} from './testUtils';
} from '@aehrc/questionnaire-test-toolkit';
import { FhirResource, MedicationStatement } from 'fhir/r4';

vi.mock('fhirclient', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
Patient,
QuestionnaireResponse
} from 'fhir/r4';
import { getBirthDateForAge } from './testUtils';
import { getBirthDateForAge } from '@aehrc/questionnaire-test-toolkit';

export const patient: Patient = {
resourceType: 'Patient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
findAllByLinkIdOrLabel,
getSelectText,
getRadioValue
} from './testUtils.ts';
} from '@aehrc/questionnaire-test-toolkit';
import {
aboutTheHealthCheckInProgressQuestionnaireResponse,
aboutTheHealthCheckQuestionnaireResponse,
Expand Down
155 changes: 4 additions & 151 deletions apps/smart-forms-app/src/test/aboriginalFormUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,155 +1,8 @@
import {
BaseRenderer,
buildForm,
RendererThemeProvider,
useQuestionnaireResponseStore,
useQuestionnaireStore,
useRendererQueryClient
} from '@aehrc/smart-forms-renderer';

import type { BehavioralTestWrapperProps } from '@aehrc/questionnaire-test-toolkit';
import { BehavioralTestWrapper } from '@aehrc/questionnaire-test-toolkit';
import aboriginalForm from '../data/resources/Questionnaire/Questionnaire-AboriginalTorresStraitIslanderHealthCheckAssembled-0.4.0.json';
import type { Questionnaire } from 'fhir/r4';
import { QueryClientProvider } from '@tanstack/react-query';
import type { Patient } from 'fhir/r4';
import { populateQuestionnaire } from '@aehrc/sdc-populate';
import { useEffect, useState } from 'react';
import { inAppExtract, type InAppExtractOutput } from '@aehrc/sdc-template-extract';
import Button from '@mui/material/Button';

export const terminologyServerUrl = 'https://r4.ontoserver.csiro.au/fhir';

export type RequestDefinition = {
urlPrefix: string;
params?: Record<string, string>;
responseBody: any;
};

interface AboriginalFormProps {
patient?: Patient;
requestDefinitions?: RequestDefinition[];
onExtractResult?: (extractResult: InAppExtractOutput) => void;
}

export function AboriginalForm(props: AboriginalFormProps) {
return (
<BuildFormWrapperWithPopulate questionnaire={aboriginalForm as Questionnaire} {...props} />
);
}

interface BuildFormWrapperWithPopulateProps extends AboriginalFormProps {
questionnaire: Questionnaire;
}

function BuildFormWrapperWithPopulate(props: BuildFormWrapperWithPopulateProps) {
const { questionnaire, patient, requestDefinitions } = props;
const queryClient = useRendererQueryClient();

const [isPopulating, setIsPopulating] = useState(false);

useEffect(() => {
const load = async () => {
setIsPopulating(true);

if (patient && requestDefinitions) {
const result = await populateQuestionnaire({
questionnaire: questionnaire,
patient: patient,
fetchResourceCallback: buildFetchResourceCallback(requestDefinitions),
fetchResourceRequestConfig: { sourceServerUrl: 'http://mock.example' }
});

const { populateSuccess, populateResult } = result;
if (!populateSuccess || !populateResult) {
setIsPopulating(false);
return;
}

const { populatedResponse, populatedContext } = populateResult;

await buildForm({
questionnaire: questionnaire,
questionnaireResponse: populatedResponse,
terminologyServerUrl,
additionalContext: {
patient: patient,
...populatedContext
}
});
} else {
await buildForm({
questionnaire: questionnaire,
terminologyServerUrl
});
}

setIsPopulating(false);
};

load();
}, [questionnaire, patient, requestDefinitions]);

if (isPopulating) {
return <div>Loading...</div>;
}

return (
<RendererThemeProvider>
<QueryClientProvider client={queryClient}>
<BaseRenderer />
<SaveControl onExtractResult={props.onExtractResult} />
</QueryClientProvider>
</RendererThemeProvider>
);
}

function buildFetchResourceCallback(requestDefinitions: RequestDefinition[]) {
return async (url: string) => {
const requestUrl = url;
const [path, queryString] = requestUrl.split('?');

const searchParams = new URLSearchParams(queryString ?? '');
const paramsObject: Record<string, string> = {};
searchParams.forEach((value, key) => {
paramsObject[key] = value;
});

const match = requestDefinitions.find((def) => {
if (!path.startsWith(def.urlPrefix)) {
return false;
}

if (!def.params) {
return true;
}

return Object.entries(def.params).every(([key, value]) => paramsObject[key] === value);
});

if (match) {
return Promise.resolve(match.responseBody);
}

return Promise.resolve({});
};
}

function SaveControl({
onExtractResult
}: {
onExtractResult?: (extractResult: InAppExtractOutput) => void;
}) {
const qr = useQuestionnaireResponseStore.use.updatableResponse();
const q = useQuestionnaireStore.use.sourceQuestionnaire();

return (
<Button
data-testid="save-button"
onClick={async () => {
const result = await inAppExtract(qr, q, null);

onExtractResult?.(result);
}}>
Save
</Button>
);
export function AboriginalForm(props: Omit<BehavioralTestWrapperProps, 'questionnaire'>) {
return <BehavioralTestWrapper questionnaire={aboriginalForm as Questionnaire} {...props} />;
}
26 changes: 26 additions & 0 deletions forms/gpccmp/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Local questionnaire server

To serve the GPCCMP questionnaires locally, run the following commands from this directory:

```bash
cd questionnaire
docker run -p 8002:8000 -e CORS_ALLOW_ALL=true -v ./:/app/resources/Questionnaire bedasoftware/fhirsnake
```

The questionnaire server will be available at `http://localhost:8002`.

# Viewing the questionnaire

Before starting the app, set `formsServerUrl` in
`../../apps/smart-forms-app/public/config.json` to the local questionnaire server:

```json
"formsServerUrl": "http://localhost:8002"
```

Then keep the questionnaire server running and start `smart-forms-app` in a second terminal:

```bash
cd ../../apps/smart-forms-app
npm start
```
Loading
Loading