Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Edits] Naming revisions: "submission" -> "instance" #334

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/new-dolls-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll want a broader catch-all changeset for this PR too.

'@getodk/xforms-engine': minor
---

**BREAKING CHANGE** rename instance payload property `definition` to `SubmissionMeta`
5 changes: 5 additions & 0 deletions .changeset/pretty-grapes-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@getodk/xforms-engine': minor
---

**BREAKING CHANGE** instance payload data is always produced as a tuple. The shape for a "chunked" payload is unchanged. The shape for a "monolithic" payload is now more like a "chunked" payload, except that it is guaranteed to always have only one item.
98 changes: 34 additions & 64 deletions packages/scenario/src/assertion/extensions/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ import {
} from '@getodk/common/test/assertions/helpers.ts';
import type { SimpleAssertionResult } from '@getodk/common/test/assertions/vitest/shared-extension-types.ts';
import type { AssertIs } from '@getodk/common/types/assertions/AssertIs.ts';
import type {
SubmissionChunkedType,
SubmissionData,
SubmissionInstanceFile,
SubmissionResult,
} from '@getodk/xforms-engine';
import type { InstanceFile, InstancePayload, InstancePayloadType } from '@getodk/xforms-engine';
import { constants } from '@getodk/xforms-engine';
import { assert, expect } from 'vitest';
import { Scenario } from '../../jr/Scenario.ts';
Expand All @@ -38,10 +33,10 @@ const compareSubmissionXML = (actual: string, expected: string): SimpleAssertion

const assertFormData: AssertIs<FormData> = instanceAssertion(FormData);

type AnySubmissionResult = SubmissionResult<SubmissionChunkedType>;
type AnyInstancePayload = InstancePayload<InstancePayloadType>;

/**
* Validating the full {@link SubmissionResult} type is fairly involved. We
* Validating the full {@link InstancePayload} type is fairly involved. We
* check the basic object shape (expected keys present, gut check a few easy to
* check property types), on the assumption that downstream assertions will fail
* if the runtime and static types disagree.
Expand All @@ -50,13 +45,13 @@ type AnySubmissionResult = SubmissionResult<SubmissionChunkedType>;
* more complete validation here, serving as a smoke test for all tests
* exercising aspects of a prepared submission result.
*/
const assertSubmissionResult: AssertIs<AnySubmissionResult> = (value) => {
const assertInstancePayload: AssertIs<AnyInstancePayload> = (value) => {
assertUnknownObject(value);
assertString(value.status);
if (value.violations !== null) {
assertUnknownArray(value.violations);
}
assertUnknownObject(value.definition);
assertUnknownObject(value.submissionMeta);

if (Array.isArray(value.data)) {
value.data.forEach((item) => {
Expand All @@ -69,47 +64,25 @@ const assertSubmissionResult: AssertIs<AnySubmissionResult> = (value) => {

const assertFile: AssertIs<File> = instanceAssertion(File);

const { SUBMISSION_INSTANCE_FILE_NAME, SUBMISSION_INSTANCE_FILE_TYPE } = constants;
const { INSTANCE_FILE_NAME, INSTANCE_FILE_TYPE } = constants;

const assertSubmissionInstanceFile: AssertIs<SubmissionInstanceFile> = (value) => {
const assertInstanceFile: AssertIs<InstanceFile> = (value) => {
assertFile(value);

if (value.name !== SUBMISSION_INSTANCE_FILE_NAME) {
throw new Error(`Expected file named ${SUBMISSION_INSTANCE_FILE_NAME}, got ${value.name}`);
if (value.name !== INSTANCE_FILE_NAME) {
throw new Error(`Expected file named ${INSTANCE_FILE_NAME}, got ${value.name}`);
}

if (value.type !== SUBMISSION_INSTANCE_FILE_TYPE) {
throw new Error(`Expected file of type ${SUBMISSION_INSTANCE_FILE_TYPE}, got ${value.type}`);
if (value.type !== INSTANCE_FILE_TYPE) {
throw new Error(`Expected file of type ${INSTANCE_FILE_TYPE}, got ${value.type}`);
}
};

type ChunkedSubmissionData = readonly [SubmissionData, ...SubmissionData[]];
const getInstanceFile = (payload: AnyInstancePayload): InstanceFile => {
const [instanceData] = payload.data;
const file = instanceData.get(INSTANCE_FILE_NAME);

const isChunkedSubmissionData = (
data: ChunkedSubmissionData | SubmissionData
): data is ChunkedSubmissionData => {
return Array.isArray(data);
};

const getSubmissionData = (submissionResult: AnySubmissionResult): SubmissionData => {
const { data } = submissionResult;

if (isChunkedSubmissionData(data)) {
const [first] = data;

return first;
}

return data;
};

const getSubmissionInstanceFile = (
submissionResult: AnySubmissionResult
): SubmissionInstanceFile => {
const submissionData = getSubmissionData(submissionResult);
const file = submissionData.get(SUBMISSION_INSTANCE_FILE_NAME);

assertSubmissionInstanceFile(file);
assertInstanceFile(file);

return file;
};
Expand All @@ -125,30 +98,27 @@ export const submissionExtensions = extendExpect(expect, {
}
),

toBeReadyForSubmission: new ArbitraryConditionExpectExtension(
assertSubmissionResult,
(result) => {
try {
expect(result).toMatchObject({
status: 'ready',
violations: null,
});

return true;
} catch (error) {
if (error instanceof Error) {
return error;
}

// eslint-disable-next-line no-console
console.error(error);
return new Error('Unknown error');
toBeReadyForSubmission: new ArbitraryConditionExpectExtension(assertInstancePayload, (result) => {
try {
expect(result).toMatchObject({
status: 'ready',
violations: null,
});

return true;
} catch (error) {
if (error instanceof Error) {
return error;
}

// eslint-disable-next-line no-console
console.error(error);
return new Error('Unknown error');
}
),
}),

toBePendingSubmissionWithViolations: new ArbitraryConditionExpectExtension(
assertSubmissionResult,
assertInstancePayload,
(result) => {
try {
expect(result.status).toBe('pending');
Expand All @@ -173,10 +143,10 @@ export const submissionExtensions = extendExpect(expect, {
),

toHavePreparedSubmissionXML: new AsyncAsymmetricTypedExpectExtension(
assertSubmissionResult,
assertInstancePayload,
assertString,
async (actual, expected): Promise<SimpleAssertionResult> => {
const instanceFile = getSubmissionInstanceFile(actual);
const instanceFile = getInstanceFile(actual);
const actualText = await getBlobText(instanceFile);

return compareSubmissionXML(actualText, expected);
Expand Down
8 changes: 4 additions & 4 deletions packages/scenario/src/jr/Scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { constants as ENGINE_CONSTANTS } from '@getodk/xforms-engine';
import type { Accessor, Setter } from 'solid-js';
import { createMemo, createSignal, runWithOwner } from 'solid-js';
import { afterEach, assert, expect } from 'vitest';
import { SelectValuesAnswer } from '../answer/SelectValuesAnswer.ts';
import { RankValuesAnswer } from '../answer/RankValuesAnswer.ts';
import { SelectValuesAnswer } from '../answer/SelectValuesAnswer.ts';
import type { ValueNodeAnswer } from '../answer/ValueNodeAnswer.ts';
import { answerOf } from '../client/answerOf.ts';
import type { InitializeTestFormOptions, TestFormResource } from '../client/init.ts';
Expand Down Expand Up @@ -964,7 +964,7 @@ export class Scenario {
}

proposed_serializeInstance(): string {
return this.instanceRoot.submissionState.submissionXML;
return this.instanceRoot.instanceState.instanceXML;
}

/**
Expand All @@ -974,8 +974,8 @@ export class Scenario {
* more about Collect's responsibility for submission (beyond serialization,
* already handled by {@link proposed_serializeInstance}).
*/
prepareWebFormsSubmission() {
return this.instanceRoot.prepareSubmission();
prepareWebFormsInstancePayload() {
return this.instanceRoot.prepareInstancePayload();
}

// TODO: consider adapting tests which use the following interfaces to use
Expand Down
34 changes: 17 additions & 17 deletions packages/scenario/test/submission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,26 +750,26 @@ describe('Form submission', () => {
return scenario;
};

describe('submission definition', () => {
it('includes a default submission definition', async () => {
describe('submission meta', () => {
it('includes default submission meta', async () => {
const scenario = await buildSubmissionPayloadScenario();
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult.definition).toMatchObject({
expect(submissionResult.submissionMeta).toMatchObject({
submissionAction: null,
submissionMethod: 'post',
encryptionKey: null,
});
});

it('includes a form-specified submission definition URL', async () => {
it('includes a form-specified submission URL', async () => {
const submissionAction = 'https://example.org';
const scenario = await buildSubmissionPayloadScenario({
submissionElements: [t(`submission action="${submissionAction}"`)],
});
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult.definition).toMatchObject({
expect(submissionResult.submissionMeta).toMatchObject({
submissionAction: new URL(submissionAction),
});
});
Expand All @@ -778,9 +778,9 @@ describe('Form submission', () => {
const scenario = await buildSubmissionPayloadScenario({
submissionElements: [t('submission method="post"')],
});
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult.definition).toMatchObject({
expect(submissionResult.submissionMeta).toMatchObject({
submissionMethod: 'post',
});
});
Expand All @@ -789,9 +789,9 @@ describe('Form submission', () => {
const scenario = await buildSubmissionPayloadScenario({
submissionElements: [t('submission method="form-data-post"')],
});
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult.definition).toMatchObject({
expect(submissionResult.submissionMeta).toMatchObject({
submissionMethod: 'post',
});
});
Expand Down Expand Up @@ -823,9 +823,9 @@ describe('Form submission', () => {
),
],
});
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult.definition).toMatchObject({
expect(submissionResult.submissionMeta).toMatchObject({
encryptionKey: base64RsaPublicKey,
});
});
Expand Down Expand Up @@ -858,13 +858,13 @@ describe('Form submission', () => {
});

it('is ready for submission when instance state is valid', async () => {
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult).toBeReadyForSubmission();
});

it('includes submission instance XML file data', async () => {
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

await expect(submissionResult).toHavePreparedSubmissionXML(validSubmissionXML);
});
Expand Down Expand Up @@ -895,13 +895,13 @@ describe('Form submission', () => {
});

it('is pending submission with violations', async () => {
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

expect(submissionResult).toBePendingSubmissionWithViolations();
});

it('produces submission instance XML file data even when current instance state is invalid', async () => {
const submissionResult = await scenario.prepareWebFormsSubmission();
const submissionResult = await scenario.prepareWebFormsInstancePayload();

await expect(submissionResult).toHavePreparedSubmissionXML(invalidSubmissionXML);
});
Expand Down
16 changes: 8 additions & 8 deletions packages/web-forms/src/components/OdkWebForm.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script setup lang="ts">
import type {
ChunkedSubmissionResult,
ChunkedInstancePayload,
MissingResourceBehavior,
MonolithicSubmissionResult,
MonolithicInstancePayload,
} from '@getodk/xforms-engine';
import { initializeForm, type FetchFormAttachment, type RootNode } from '@getodk/xforms-engine';
import Button from 'primevue/button';
Expand Down Expand Up @@ -33,8 +33,8 @@ const props = defineProps<OdkWebFormsProps>();

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- evidently a type must be used for this to be assigned to a name (which we use!); as an interface, it won't satisfy the `Record` constraint of `defineEmits`.
type OdkWebFormEmits = {
submit: [submissionPayload: MonolithicSubmissionResult];
submitChunked: [submissionPayload: ChunkedSubmissionResult];
submit: [submissionPayload: MonolithicInstancePayload];
submitChunked: [submissionPayload: ChunkedInstancePayload];
};

/**
Expand Down Expand Up @@ -76,8 +76,8 @@ const isEmitSubscribed = (eventKey: EventKey): boolean => {

const emitSubmit = async (root: RootNode) => {
if (isEmitSubscribed('onSubmit')) {
const payload = await root.prepareSubmission({
chunked: 'monolithic',
const payload = await root.prepareInstancePayload({
payloadType: 'monolithic',
});

emit('submit', payload);
Expand All @@ -92,8 +92,8 @@ const emitSubmitChunked = async (root: RootNode) => {
throw new Error('The `submissionMaxSize` prop is required for chunked submissions');
}

const payload = await root.prepareSubmission({
chunked: 'chunked',
const payload = await root.prepareInstancePayload({
payloadType: 'chunked',
maxSize,
});

Expand Down
8 changes: 4 additions & 4 deletions packages/web-forms/src/demo/FormPreview.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { xformFixturesByCategory, XFormResource } from '@getodk/common/fixtures/xforms.ts';
import type {
ChunkedSubmissionResult,
ChunkedInstancePayload,
FetchFormAttachment,
MissingResourceBehavior,
MonolithicSubmissionResult,
MonolithicInstancePayload,
} from '@getodk/xforms-engine';
import { constants as ENGINE_CONSTANTS } from '@getodk/xforms-engine';
import { ref } from 'vue';
Expand Down Expand Up @@ -55,14 +55,14 @@ xformResource
alert('Failed to load the Form XML');
});

const handleSubmit = (payload: MonolithicSubmissionResult) => {
const handleSubmit = (payload: MonolithicInstancePayload) => {
// eslint-disable-next-line no-console
console.log('submission payload:', payload);

alert(`Submit button was pressed`);
};

const handleSubmitChunked = (payload: ChunkedSubmissionResult) => {
const handleSubmitChunked = (payload: ChunkedInstancePayload) => {
// eslint-disable-next-line no-console
console.log('CHUNKED submission payload:', payload);
};
Expand Down
Loading
Loading