From af7f0163b74a748235c4526948b7a28b86238c98 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Mon, 11 Nov 2024 15:03:23 -0600 Subject: [PATCH 1/7] Simple test of existing page-set prompt --- .../src/patterns/page-set/prompt.test.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 packages/forms/src/patterns/page-set/prompt.test.ts diff --git a/packages/forms/src/patterns/page-set/prompt.test.ts b/packages/forms/src/patterns/page-set/prompt.test.ts new file mode 100644 index 00000000..61d82a56 --- /dev/null +++ b/packages/forms/src/patterns/page-set/prompt.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from 'vitest'; + +import { defaultFormConfig } from '..'; +import { createFormSession } from '../../session'; + +import { Input } from '../input/builder'; +import { Page } from '../page/builder'; +import type { Blueprint } from '../../types'; + +import { PageSet } from './builder'; +import { createPrompt } from './prompt'; + +describe('Page prompt', () => { + it('works', async () => { + expect(true).toBe(true); + const form = await createTestForm(); + const session = createFormSession(form, { + params: { + page: '0', + }, + url: '', + }); + const pattern = form.patterns[form.root]; + const prompt = createPrompt(defaultFormConfig, session, pattern, { + validate: true, + }); + expect(prompt).toEqual({ + children: [ + { + children: [ + { + children: [], + props: { + _patternId: 'input-1', + error: { message: 'Required', type: 'custom' }, + inputId: 'input-1', + label: 'Input 1', + required: true, + type: 'input', + }, + }, + ], + props: { + _patternId: 'page-1', + title: 'Page 1', + type: 'page', + }, + }, + ], + props: { + _patternId: 'page-set', + actions: [ + { + submitAction: 'action/page-set/page-set', + text: 'Submit', + type: 'submit', + }, + ], + pages: [ + { + selected: true, + title: 'Page 1', + url: '?page=0', + }, + ], + type: 'page-set', + }, + }); + }); +}); + +const createTestForm = async (): Promise => { + const input1 = new Input( + { label: 'Input 1', required: true, maxLength: 10 }, + 'input-1' + ); + const page1 = new Page({ title: 'Page 1', patterns: [input1.id] }, 'page-1'); + const pageSet = new PageSet({ pages: [page1.id] }, 'page-set'); + return { + summary: { + title: 'Test Form', + description: 'A test form', + }, + root: 'page-set', + patterns: { + 'page-set': pageSet.toPattern(), + 'page-1': page1.toPattern(), + 'input-1': input1.toPattern(), + }, + outputs: [], + }; +}; From 70688905ffa19870cbffc0d3efde1531ac08af05 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 13 Nov 2024 16:00:49 -0600 Subject: [PATCH 2/7] Add "rules" attribute to page pattern. --- .../components/PageSetEdit.stories.tsx | 1 + packages/design/src/test-form.ts | 3 + packages/forms/src/builder/builder.test.ts | 15 +++ .../forms/src/documents/pdf/parsing-api.ts | 1 + packages/forms/src/pattern.ts | 9 +- packages/forms/src/patterns/checkbox.ts | 5 + .../forms/src/patterns/fieldset/builder.ts | 6 + packages/forms/src/patterns/input/builder.ts | 8 +- .../src/patterns/package-download/builder.ts | 8 +- .../patterns/package-download/submit.test.ts | 5 +- .../forms/src/patterns/page-set/builder.ts | 10 +- .../src/patterns/page-set/prompt.test.ts | 5 +- .../src/patterns/page-set/submit.test.ts | 10 +- .../forms/src/patterns/page-set/submit.ts | 23 +++- packages/forms/src/patterns/page/builder.ts | 10 +- packages/forms/src/patterns/page/config.ts | 12 ++ packages/forms/src/patterns/page/index.ts | 1 + .../forms/src/services/submit-form.test.ts | 127 +++++++++++++++++- packages/server/src/pages/forms/[id].test.ts | 1 + 19 files changed, 222 insertions(+), 38 deletions(-) create mode 100644 packages/forms/src/patterns/fieldset/builder.ts diff --git a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx index 3de38a11..ac5e45bb 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx @@ -22,6 +22,7 @@ export const Basic: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); const pagesetHeaderElement = await canvas.findByText(/Page 1/); + return; await testUpdateFormFieldOnSubmitByElement( canvasElement, pagesetHeaderElement, diff --git a/packages/design/src/test-form.ts b/packages/design/src/test-form.ts index 7a5fd5c1..43e73f86 100644 --- a/packages/design/src/test-form.ts +++ b/packages/design/src/test-form.ts @@ -39,6 +39,7 @@ export const createOnePageTwoPatternTestForm = () => { data: { title: 'Page 1', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { @@ -88,6 +89,7 @@ export const createTwoPageTwoPatternTestForm = () => { data: { title: 'First page', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { @@ -96,6 +98,7 @@ export const createTwoPageTwoPatternTestForm = () => { data: { title: 'Second page', patterns: [], + rules: [], }, } satisfies PagePattern, { diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 8ee7c087..023d6e28 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -61,6 +61,7 @@ describe('form builder', () => { data: { title: 'Page 1', patterns: ['element-2', 'element-1'], + rules: [], }, } satisfies PagePattern, 'page-2': { @@ -69,6 +70,7 @@ describe('form builder', () => { data: { title: 'Page 2', patterns: ['element-3'], + rules: [], }, } satisfies PagePattern, 'element-1': { @@ -126,6 +128,7 @@ describe('form builder', () => { data: { title: 'Page 1', patterns: ['element-2'], + rules: [], }, } satisfies PagePattern, 'page-2': { @@ -134,6 +137,7 @@ describe('form builder', () => { data: { title: 'Page 2', patterns: ['element-1', 'element-3'], + rules: [], }, } satisfies PagePattern, 'element-1': { @@ -196,6 +200,7 @@ describe('form builder', () => { data: { title: 'Page 1', patterns: ['element-2'], + rules: [], }, } satisfies PagePattern, 'page-2': { @@ -204,6 +209,7 @@ describe('form builder', () => { data: { title: 'Page 2', patterns: ['element-3', 'element-1'], + rules: [], }, } satisfies PagePattern, 'element-1': { @@ -268,6 +274,7 @@ describe('form builder', () => { 'fieldset-1', 'radio-group-1', ], + rules: [], }, }, 'element-1': { @@ -349,6 +356,7 @@ describe('form builder', () => { 'fieldset-1', 'radio-group-1', ], + rules: [], }, }, 'element-1': { @@ -432,6 +440,7 @@ describe('form builder', () => { newPattern.id, 'radio-group-1', ], + rules: [], }, }, 'element-1': { @@ -511,6 +520,7 @@ describe('form builder', () => { 'radio-group-1', newPattern.id, ], + rules: [], }, }, 'element-1': { @@ -584,6 +594,7 @@ describe('form builder', () => { data: { title: 'Page 1', patterns: ['element-1'], + rules: [], }, } satisfies PagePattern, 'element-1': { @@ -622,6 +633,7 @@ export const createTestBlueprint = () => { data: { title: 'Page 1', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { @@ -671,6 +683,7 @@ export const createTwoPageThreePatternTestForm = () => { data: { title: 'Page 1', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { @@ -679,6 +692,7 @@ export const createTwoPageThreePatternTestForm = () => { data: { title: 'Page 2', patterns: ['element-3'], + rules: [], }, } satisfies PagePattern, { @@ -743,6 +757,7 @@ export const createTestBlueprintMultipleFieldsets = () => { 'fieldset-1', 'radio-group-1', ], + rules: [], }, } satisfies PagePattern, { diff --git a/packages/forms/src/documents/pdf/parsing-api.ts b/packages/forms/src/documents/pdf/parsing-api.ts index 82d44f15..174d2cf6 100644 --- a/packages/forms/src/documents/pdf/parsing-api.ts +++ b/packages/forms/src/documents/pdf/parsing-api.ts @@ -335,6 +335,7 @@ export const processApiResponse = async (json: any): Promise => { { title: `${page}`, patterns, + rules: [], }, undefined, idx diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index b0a39eec..bae0d55e 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -37,13 +37,20 @@ type RemoveChildPattern

= ( export abstract class PatternBuilder

{ public readonly id: PatternId; public readonly data: P['data']; + public abstract readonly type: P['type']; constructor(data: P['data'], id?: PatternId) { this.id = id || generatePatternId(); this.data = data; } - abstract toPattern(): P; + toPattern(): P { + return { + id: this.id, + type: this.type, + data: this.data, + } as P; + } } export const getPattern = ( diff --git a/packages/forms/src/patterns/checkbox.ts b/packages/forms/src/patterns/checkbox.ts index 619b6b19..7b121093 100644 --- a/packages/forms/src/patterns/checkbox.ts +++ b/packages/forms/src/patterns/checkbox.ts @@ -2,6 +2,7 @@ import * as z from 'zod'; import { type Pattern, + PatternBuilder, type PatternConfig, validatePattern, } from '../pattern.js'; @@ -73,3 +74,7 @@ export const checkboxConfig: PatternConfig = { }; }, }; + +export class Checkbox extends PatternBuilder { + type = 'checkbox'; +} diff --git a/packages/forms/src/patterns/fieldset/builder.ts b/packages/forms/src/patterns/fieldset/builder.ts new file mode 100644 index 00000000..b4c40e17 --- /dev/null +++ b/packages/forms/src/patterns/fieldset/builder.ts @@ -0,0 +1,6 @@ +import { PatternBuilder } from '../../pattern'; +import type { FieldsetPattern } from './config'; + +export class FieldSet extends PatternBuilder { + type = 'fieldset'; +} diff --git a/packages/forms/src/patterns/input/builder.ts b/packages/forms/src/patterns/input/builder.ts index 806a5745..7327e934 100644 --- a/packages/forms/src/patterns/input/builder.ts +++ b/packages/forms/src/patterns/input/builder.ts @@ -2,11 +2,5 @@ import { PatternBuilder } from '../../pattern'; import { type InputPattern } from './config'; export class Input extends PatternBuilder { - toPattern(): InputPattern { - return { - id: this.id, - type: 'input', - data: this.data, - }; - } + type = 'input'; } diff --git a/packages/forms/src/patterns/package-download/builder.ts b/packages/forms/src/patterns/package-download/builder.ts index 1abf256a..0071028e 100644 --- a/packages/forms/src/patterns/package-download/builder.ts +++ b/packages/forms/src/patterns/package-download/builder.ts @@ -2,11 +2,5 @@ import { type PackageDownloadPattern } from '.'; import { PatternBuilder } from '../../pattern'; export class PackageDownload extends PatternBuilder { - toPattern(): PackageDownloadPattern { - return { - id: this.id, - type: 'page-set', - data: this.data, - }; - } + type = 'package-download'; } diff --git a/packages/forms/src/patterns/package-download/submit.test.ts b/packages/forms/src/patterns/package-download/submit.test.ts index 53d57a9e..df5464de 100644 --- a/packages/forms/src/patterns/package-download/submit.test.ts +++ b/packages/forms/src/patterns/package-download/submit.test.ts @@ -68,7 +68,10 @@ const createTestForm = async (): Promise => { { label: 'Input 1', required: true, maxLength: 10 }, 'input-1' ); - const page1 = new Page({ title: 'Page 1', patterns: [input1.id] }, 'page-1'); + const page1 = new Page( + { title: 'Page 1', patterns: [input1.id], rules: [] }, + 'page-1' + ); const pageSet = new PageSet({ pages: [page1.id] }, 'page-set'); return { summary: { diff --git a/packages/forms/src/patterns/page-set/builder.ts b/packages/forms/src/patterns/page-set/builder.ts index cdc17dbd..db1d8842 100644 --- a/packages/forms/src/patterns/page-set/builder.ts +++ b/packages/forms/src/patterns/page-set/builder.ts @@ -8,18 +8,12 @@ export class Form { } export class PageSet extends PatternBuilder { + type = 'page-set'; + addPage(page: Page) { return new PageSet({ ...this.data, pages: [...this.data.pages, page.id], }); } - - toPattern(): PageSetPattern { - return { - id: this.id, - type: 'page-set', - data: this.data, - }; - } } diff --git a/packages/forms/src/patterns/page-set/prompt.test.ts b/packages/forms/src/patterns/page-set/prompt.test.ts index 61d82a56..279135e8 100644 --- a/packages/forms/src/patterns/page-set/prompt.test.ts +++ b/packages/forms/src/patterns/page-set/prompt.test.ts @@ -74,7 +74,10 @@ const createTestForm = async (): Promise => { { label: 'Input 1', required: true, maxLength: 10 }, 'input-1' ); - const page1 = new Page({ title: 'Page 1', patterns: [input1.id] }, 'page-1'); + const page1 = new Page( + { title: 'Page 1', patterns: [input1.id], rules: [] }, + 'page-1' + ); const pageSet = new PageSet({ pages: [page1.id] }, 'page-set'); return { summary: { diff --git a/packages/forms/src/patterns/page-set/submit.test.ts b/packages/forms/src/patterns/page-set/submit.test.ts index 0cd041ed..b6e02fec 100644 --- a/packages/forms/src/patterns/page-set/submit.test.ts +++ b/packages/forms/src/patterns/page-set/submit.test.ts @@ -128,8 +128,14 @@ const createTestSession = () => { { label: 'label', required: true, maxLength: 10 }, 'input-2' ); - const page1 = new Page({ title: 'Page 1', patterns: [input1.id] }, 'page-1'); - const page2 = new Page({ title: 'Page 2', patterns: [input2.id] }, 'page-2'); + const page1 = new Page( + { title: 'Page 1', patterns: [input1.id], rules: [] }, + 'page-1' + ); + const page2 = new Page( + { title: 'Page 2', patterns: [input2.id], rules: [] }, + 'page-2' + ); const pageSet = new PageSet({ pages: [page1.id, page2.id] }, 'page-set-1'); const testForm: Blueprint = { summary: { diff --git a/packages/forms/src/patterns/page-set/submit.ts b/packages/forms/src/patterns/page-set/submit.ts index 9cadb644..6ac7b401 100644 --- a/packages/forms/src/patterns/page-set/submit.ts +++ b/packages/forms/src/patterns/page-set/submit.ts @@ -43,11 +43,30 @@ export const submitPage: SubmitHandler = async ( opts.data ); - // Increment the page number if there are no errors and this isn't the last page. + // Evaluate page rules + const ruleMatch = pagePattern.data.data.rules + ? pagePattern.data.data.rules.find(rule => { + if (rule.condition.operator === '=') { + const value = opts.session.data.values[rule.patternId]; + if (value === rule.condition.value) { + return true; + } + } else { + throw new Error( + `Unsupported rule operator: "${rule.condition.operator}"` + ); + } + }) + : undefined; + + // Get the page number for the 1st rule match, or the next page if no rules + // match. const lastPage = opts.pattern.data.pages.length - 1; const nextPage = Object.values(result.errors).length === 0 && pageNumber < lastPage - ? pageNumber + 1 + ? ruleMatch + ? pagePattern.data.data.patterns.indexOf(ruleMatch.patternId) + : pageNumber + 1 : pageNumber; return success({ diff --git a/packages/forms/src/patterns/page/builder.ts b/packages/forms/src/patterns/page/builder.ts index 0b4dc9e4..afe124ad 100644 --- a/packages/forms/src/patterns/page/builder.ts +++ b/packages/forms/src/patterns/page/builder.ts @@ -2,18 +2,12 @@ import { PatternBuilder } from '../../pattern'; import { type PagePattern } from './config'; export class Page extends PatternBuilder { + type = 'page'; + setTitle(title: string) { return new Page({ ...this.data, title, }); } - - toPattern(): PagePattern { - return { - id: this.id, - type: 'page', - data: this.data, - }; - } } diff --git a/packages/forms/src/patterns/page/config.ts b/packages/forms/src/patterns/page/config.ts index 335737d5..2d5aee0f 100644 --- a/packages/forms/src/patterns/page/config.ts +++ b/packages/forms/src/patterns/page/config.ts @@ -21,6 +21,18 @@ const configSchema = z.object({ ) .pipe(z.string().array()), ]), + rules: z + .array( + z.object({ + patternId: z.string(), + condition: z.object({ + operator: z.literal('='), + value: z.string(), + }), + next: z.string(), + }) + ) + .default([]), }); type PageConfigSchema = z.infer; diff --git a/packages/forms/src/patterns/page/index.ts b/packages/forms/src/patterns/page/index.ts index 36421231..5cf032af 100644 --- a/packages/forms/src/patterns/page/index.ts +++ b/packages/forms/src/patterns/page/index.ts @@ -9,6 +9,7 @@ export const pageConfig: PatternConfig = { initial: { title: 'Untitled Page', patterns: [], + rules: [], }, createPrompt, parseConfigData, diff --git a/packages/forms/src/services/submit-form.test.ts b/packages/forms/src/services/submit-form.test.ts index 5dba1149..17fee40f 100644 --- a/packages/forms/src/services/submit-form.test.ts +++ b/packages/forms/src/services/submit-form.test.ts @@ -12,6 +12,10 @@ import { type PageSetPattern } from '../patterns/page-set/config.js'; import { type PagePattern } from '../patterns/page/config.js'; import { type InputPattern } from '../patterns/input/config.js'; import { type Blueprint } from '../types.js'; +import { Checkbox } from '../patterns/checkbox.js'; +import { FieldSet } from '../patterns/fieldset/builder.js'; +import { Page } from '../patterns/page/builder.js'; +import { Form, PageSet } from '../patterns/page-set/builder.js'; describe('submitForm', () => { it('fails with missing action string', async () => { @@ -197,6 +201,7 @@ describe('multi-page form', () => { data: { title: 'Page 1', patterns: ['element-1'], + rules: [], }, } satisfies PagePattern, { @@ -215,6 +220,7 @@ describe('multi-page form', () => { data: { title: 'Page 2', patterns: ['element-2'], + rules: [], }, } satisfies PagePattern, { @@ -363,8 +369,59 @@ describe('multi-page form', () => { }, }); }); +}); + +describe('multi-page form with skip logic', () => { + const setupMultiPageFormWithSkipLogic = async () => { + const form = createMultiPageFormWithSkipLogic(); + const { ctx, id } = await setupTestForm(form); + const session = createFormSession(form); + const formSessionResult = await ctx.repository.upsertFormSession({ + formId: id, + data: session, + }); + if (!formSessionResult.success) { + expect.fail('upsertFormSession failed'); + } + return { ctx, id, formSessionResult, session }; + }; - // You can add more tests here using the setupMultiPageForm function + it('falls back to next page when rule does not match', async () => { + const { ctx, id, formSessionResult, session } = + await setupMultiPageFormWithSkipLogic(); + const result = await submitForm( + ctx, + formSessionResult.data.id, + id, + { + action: 'action/page-set/root', + }, + { url: '#', params: { page: '0' } } + ); + expect(result).toEqual({ + success: true, + data: { + attachments: undefined, + session: { + data: { + errors: {}, + values: { + checkbox1: false, + checkbox2: false, + }, + }, + form: session.form, + route: { + params: { + page: '1', + }, + url: '#', + }, + }, + sessionId: formSessionResult.data.id, + }, + }); + }); }); const setupTestForm = async (form?: Blueprint) => { @@ -388,6 +445,7 @@ const setupTestForm = async (form?: Blueprint) => { data: { title: 'Page 1', patterns: [], + rules: [], }, } satisfies PagePattern, ], @@ -425,6 +483,7 @@ const createOnePatternTestForm = () => { data: { title: 'Page 1', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { @@ -451,3 +510,69 @@ const createOnePatternTestForm = () => { } ); }; + +const createMultiPageFormWithSkipLogic = () => { + const checkbox1 = new Checkbox( + { label: 'Checkbox1', defaultChecked: false }, + 'checkbox1' + ); + const checkbox2 = new Checkbox( + { label: 'Checkbox2', defaultChecked: false }, + 'checkbox2' + ); + const fieldset = new FieldSet({ + legend: 'Constraints', + patterns: [checkbox1.id, checkbox2.id], + }); + const page1 = new Page({ + title: 'Page 1', + patterns: [fieldset.id], + rules: [ + { + patternId: 'rule1', + condition: { operator: '=', value: '' }, + next: 'Eligible', + }, + ], + }); + const checkbox3 = new Checkbox({ label: 'Checkbox3', defaultChecked: false }); + const checkbox4 = new Checkbox({ label: 'Checkbox4', defaultChecked: false }); + const fieldset2 = new FieldSet({ + legend: 'Constraints', + patterns: [checkbox3.id, checkbox4.id], + }); + const page2 = new Page({ + title: 'Page 2', + patterns: [fieldset2.id], + rules: [], + }); + const page3 = new Page({ + title: 'Page 3', + patterns: [], + rules: [], + }); + const pageSet = new PageSet( + { + pages: [page1.id, page2.id, page3.id], + }, + 'root' + ); + const form = new Form({ + summary: { + title: 'Test form', + description: 'Test description', + }, + root: pageSet.id, + patterns: { + [pageSet.id]: pageSet.toPattern(), + [page1.id]: page1.toPattern(), + [page2.id]: page2.toPattern(), + [page3.id]: page3.toPattern(), + [fieldset.id]: fieldset.toPattern(), + [checkbox1.id]: checkbox1.toPattern(), + [checkbox2.id]: checkbox2.toPattern(), + }, + outputs: [], + }); + return form.blueprint; +}; diff --git a/packages/server/src/pages/forms/[id].test.ts b/packages/server/src/pages/forms/[id].test.ts index cf10c2c5..1cff7cdf 100644 --- a/packages/server/src/pages/forms/[id].test.ts +++ b/packages/server/src/pages/forms/[id].test.ts @@ -194,6 +194,7 @@ export const createTestBlueprint = () => { data: { title: 'Page 1', patterns: ['element-1', 'element-2'], + rules: [], }, } satisfies PagePattern, { From 11546bc9c5f8d7a0f6ac964f27ad210f7efdaf90 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Thu, 14 Nov 2024 16:39:12 -0600 Subject: [PATCH 3/7] Start merging page and page-set, by putting them into a subdirectory. --- packages/forms/src/builder/builder.test.ts | 4 ++-- packages/forms/src/builder/index.ts | 2 +- .../forms/src/documents/__tests__/document.test.ts | 4 ++-- packages/forms/src/documents/pdf/parsing-api.ts | 4 ++-- packages/forms/src/patterns/index.ts | 12 ++++++------ .../src/patterns/package-download/submit.test.ts | 4 ++-- .../src/patterns/{ => pages}/page-set/builder.ts | 4 ++-- .../src/patterns/{ => pages}/page-set/config.ts | 4 ++-- .../forms/src/patterns/{ => pages}/page-set/index.ts | 2 +- .../src/patterns/{ => pages}/page-set/prompt.test.ts | 8 ++++---- .../src/patterns/{ => pages}/page-set/prompt.ts | 12 ++++++------ .../forms/src/patterns/{ => pages}/page/builder.ts | 2 +- .../forms/src/patterns/{ => pages}/page/config.ts | 4 ++-- .../forms/src/patterns/{ => pages}/page/index.ts | 2 +- .../forms/src/patterns/{ => pages}/page/prompt.ts | 4 ++-- .../src/patterns/{page-set => pages}/submit.test.ts | 6 +++--- .../forms/src/patterns/{page-set => pages}/submit.ts | 6 +++--- packages/forms/src/services/submit-form.test.ts | 8 ++++---- packages/forms/src/services/submit-form.ts | 2 +- 19 files changed, 47 insertions(+), 47 deletions(-) rename packages/forms/src/patterns/{ => pages}/page-set/builder.ts (80%) rename packages/forms/src/patterns/{ => pages}/page-set/config.ts (83%) rename packages/forms/src/patterns/{ => pages}/page-set/index.ts (92%) rename packages/forms/src/patterns/{ => pages}/page-set/prompt.test.ts (91%) rename packages/forms/src/patterns/{ => pages}/page-set/prompt.ts (89%) rename packages/forms/src/patterns/{ => pages}/page/builder.ts (80%) rename packages/forms/src/patterns/{ => pages}/page/config.ts (93%) rename packages/forms/src/patterns/{ => pages}/page/index.ts (93%) rename packages/forms/src/patterns/{ => pages}/page/prompt.ts (87%) rename packages/forms/src/patterns/{page-set => pages}/submit.test.ts (95%) rename packages/forms/src/patterns/{page-set => pages}/submit.ts (94%) diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 023d6e28..97cc3bfd 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -5,8 +5,8 @@ import { defaultFormConfig } from '../patterns/index.js'; import { type FieldsetPattern } from '../patterns/fieldset/config.js'; import { type FormSummaryPattern } from '../patterns/form-summary.js'; import { type InputPattern } from '../patterns/input/config.js'; -import { type PagePattern } from '../patterns/page/config.js'; -import { type PageSetPattern } from '../patterns/page-set/config.js'; +import { type PagePattern } from '../patterns/pages/page/config.js'; +import { type PageSetPattern } from '../patterns/pages/page-set/config.js'; import { type RadioGroupPattern } from '../patterns/radio-group.js'; import { BlueprintBuilder } from './index.js'; diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 5f109702..57696720 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -21,7 +21,7 @@ import { type PatternMap, } from '../pattern.js'; import { type FieldsetPattern } from '../patterns/fieldset/config.js'; -import { type PageSetPattern } from '../patterns/page-set/config.js'; +import { type PageSetPattern } from '../patterns/pages/page-set/config.js'; import type { Blueprint, FormSummary } from '../types.js'; export class BlueprintBuilder { diff --git a/packages/forms/src/documents/__tests__/document.test.ts b/packages/forms/src/documents/__tests__/document.test.ts index 50ff3d44..5b7e4205 100644 --- a/packages/forms/src/documents/__tests__/document.test.ts +++ b/packages/forms/src/documents/__tests__/document.test.ts @@ -6,8 +6,8 @@ import { describe, expect, it } from 'vitest'; import { getPattern } from '../../index.js'; import { BlueprintBuilder } from '../../builder/index.js'; import { defaultFormConfig } from '../../patterns/index.js'; -import { type PageSetPattern } from '../../patterns/page-set/config.js'; -import { type PagePattern } from '../../patterns/page/config.js'; +import { type PageSetPattern } from '../../patterns/pages/page-set/config.js'; +import { type PagePattern } from '../../patterns/pages/page/config.js'; import { addDocument } from '../document.js'; import { loadSamplePDF } from './sample-data.js'; diff --git a/packages/forms/src/documents/pdf/parsing-api.ts b/packages/forms/src/documents/pdf/parsing-api.ts index 174d2cf6..ddf80569 100644 --- a/packages/forms/src/documents/pdf/parsing-api.ts +++ b/packages/forms/src/documents/pdf/parsing-api.ts @@ -2,8 +2,8 @@ import * as z from 'zod'; import { type FieldsetPattern } from '../../patterns/fieldset/config.js'; import { type InputPattern } from '../../patterns/input/config.js'; -import { PagePattern } from '../../patterns/page/config.js'; -import { PageSetPattern } from '../../patterns/page-set/config.js'; +import { PagePattern } from '../../patterns/pages/page/config.js'; +import { PageSetPattern } from '../../patterns/pages/page-set/config.js'; import { type ParagraphPattern } from '../../patterns/paragraph.js'; import { type CheckboxPattern } from '../../patterns/checkbox.js'; import { type RadioGroupPattern } from '../../patterns/radio-group.js'; diff --git a/packages/forms/src/patterns/index.ts b/packages/forms/src/patterns/index.ts index 4fe5dbcd..06719745 100644 --- a/packages/forms/src/patterns/index.ts +++ b/packages/forms/src/patterns/index.ts @@ -7,8 +7,8 @@ import { fieldsetConfig } from './fieldset/index.js'; import { formSummaryConfig } from './form-summary.js'; import { inputConfig } from './input/index.js'; import { packageDownloadConfig } from './package-download/index.js'; -import { pageConfig } from './page/index.js'; -import { pageSetConfig } from './page-set/index.js'; +import { pageConfig } from './pages/page/index.js'; +import { pageSetConfig } from './pages/page-set/index.js'; import { paragraphConfig } from './paragraph.js'; import { radioGroupConfig } from './radio-group.js'; import { richTextConfig } from './rich-text.js'; @@ -46,10 +46,10 @@ export * from './form-summary.js'; export * from './input/index.js'; export { type InputPattern } from './input/config.js'; export * from './package-download/index.js'; -export * from './page/index.js'; -export { type PagePattern } from './page/config.js'; -export * from './page-set/index.js'; -export { type PageSetPattern } from './page-set/config.js'; +export * from './pages/page/index.js'; +export { type PagePattern } from './pages/page/config.js'; +export * from './pages/page-set/index.js'; +export { type PageSetPattern } from './pages/page-set/config.js'; export * from './paragraph.js'; export * from './radio-group.js'; export * from './select-dropdown/select-dropdown.js'; diff --git a/packages/forms/src/patterns/package-download/submit.test.ts b/packages/forms/src/patterns/package-download/submit.test.ts index df5464de..6ceec324 100644 --- a/packages/forms/src/patterns/package-download/submit.test.ts +++ b/packages/forms/src/patterns/package-download/submit.test.ts @@ -6,8 +6,8 @@ import { type Blueprint, type FormSession, defaultFormConfig } from '../..'; import { downloadPackageHandler } from './submit'; import { PackageDownload } from './builder'; -import { PageSet } from '../page-set/builder'; -import { Page } from '../page/builder'; +import { PageSet } from '../pages/page-set/builder'; +import { Page } from '../pages/page/builder'; import { Input } from '../input/builder'; import { loadSamplePDF } from '../../documents/__tests__/sample-data'; diff --git a/packages/forms/src/patterns/page-set/builder.ts b/packages/forms/src/patterns/pages/page-set/builder.ts similarity index 80% rename from packages/forms/src/patterns/page-set/builder.ts rename to packages/forms/src/patterns/pages/page-set/builder.ts index db1d8842..83b6125d 100644 --- a/packages/forms/src/patterns/page-set/builder.ts +++ b/packages/forms/src/patterns/pages/page-set/builder.ts @@ -1,5 +1,5 @@ -import { type Blueprint } from '../..'; -import { PatternBuilder } from '../../pattern'; +import { type Blueprint } from '../../..'; +import { PatternBuilder } from '../../../pattern'; import { type Page } from '../page/builder'; import { type PageSetPattern } from './config'; diff --git a/packages/forms/src/patterns/page-set/config.ts b/packages/forms/src/patterns/pages/page-set/config.ts similarity index 83% rename from packages/forms/src/patterns/page-set/config.ts rename to packages/forms/src/patterns/pages/page-set/config.ts index 0e8a7423..6144ac6e 100644 --- a/packages/forms/src/patterns/page-set/config.ts +++ b/packages/forms/src/patterns/pages/page-set/config.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { type Pattern, type ParsePatternConfigData } from '../../pattern.js'; -import { safeZodParseFormErrors } from '../../util/zod.js'; +import { type Pattern, type ParsePatternConfigData } from '../../../pattern.js'; +import { safeZodParseFormErrors } from '../../../util/zod.js'; const configSchema = z.object({ pages: z.array(z.string()), diff --git a/packages/forms/src/patterns/page-set/index.ts b/packages/forms/src/patterns/pages/page-set/index.ts similarity index 92% rename from packages/forms/src/patterns/page-set/index.ts rename to packages/forms/src/patterns/pages/page-set/index.ts index e0ad4797..078f2dce 100644 --- a/packages/forms/src/patterns/page-set/index.ts +++ b/packages/forms/src/patterns/pages/page-set/index.ts @@ -1,4 +1,4 @@ -import { type PatternConfig, type PatternId } from '../../pattern.js'; +import { type PatternConfig, type PatternId } from '../../../pattern.js'; import { type PageSetPattern, parseConfigData } from './config.js'; import { createPrompt } from './prompt.js'; diff --git a/packages/forms/src/patterns/page-set/prompt.test.ts b/packages/forms/src/patterns/pages/page-set/prompt.test.ts similarity index 91% rename from packages/forms/src/patterns/page-set/prompt.test.ts rename to packages/forms/src/patterns/pages/page-set/prompt.test.ts index 279135e8..ca88ad47 100644 --- a/packages/forms/src/patterns/page-set/prompt.test.ts +++ b/packages/forms/src/patterns/pages/page-set/prompt.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { defaultFormConfig } from '..'; -import { createFormSession } from '../../session'; +import { defaultFormConfig } from '../..'; +import { createFormSession } from '../../../session'; -import { Input } from '../input/builder'; +import { Input } from '../../input/builder'; import { Page } from '../page/builder'; -import type { Blueprint } from '../../types'; +import type { Blueprint } from '../../../types'; import { PageSet } from './builder'; import { createPrompt } from './prompt'; diff --git a/packages/forms/src/patterns/page-set/prompt.ts b/packages/forms/src/patterns/pages/page-set/prompt.ts similarity index 89% rename from packages/forms/src/patterns/page-set/prompt.ts rename to packages/forms/src/patterns/pages/page-set/prompt.ts index a641776d..fffd61a5 100644 --- a/packages/forms/src/patterns/page-set/prompt.ts +++ b/packages/forms/src/patterns/pages/page-set/prompt.ts @@ -1,18 +1,18 @@ import { z } from 'zod'; -import { type RouteData } from '../../route-data.js'; -import { safeZodParseFormErrors } from '../../util/zod.js'; +import { type RouteData } from '../../../route-data.js'; +import { safeZodParseFormErrors } from '../../../util/zod.js'; import { type PagePattern } from '../page/config.js'; -import { type ActionName, getActionString } from '../../submission.js'; +import { type ActionName, getActionString } from '../../../submission.js'; import { type CreatePrompt, type PageSetProps, type PromptAction, createPromptForPattern, -} from '../../components.js'; -import { getPattern } from '../../pattern.js'; -import { type FormSession } from '../../session.js'; +} from '../../../components.js'; +import { getPattern } from '../../../pattern.js'; +import { type FormSession } from '../../../session.js'; import { type PageSetPattern } from './config.js'; diff --git a/packages/forms/src/patterns/page/builder.ts b/packages/forms/src/patterns/pages/page/builder.ts similarity index 80% rename from packages/forms/src/patterns/page/builder.ts rename to packages/forms/src/patterns/pages/page/builder.ts index afe124ad..1ec6eb54 100644 --- a/packages/forms/src/patterns/page/builder.ts +++ b/packages/forms/src/patterns/pages/page/builder.ts @@ -1,4 +1,4 @@ -import { PatternBuilder } from '../../pattern'; +import { PatternBuilder } from '../../../pattern'; import { type PagePattern } from './config'; export class Page extends PatternBuilder { diff --git a/packages/forms/src/patterns/page/config.ts b/packages/forms/src/patterns/pages/page/config.ts similarity index 93% rename from packages/forms/src/patterns/page/config.ts rename to packages/forms/src/patterns/pages/page/config.ts index 2d5aee0f..4b26d248 100644 --- a/packages/forms/src/patterns/page/config.ts +++ b/packages/forms/src/patterns/pages/page/config.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { type Pattern, type ParsePatternConfigData } from '../../pattern.js'; -import { safeZodParseFormErrors } from '../../util/zod.js'; +import { type Pattern, type ParsePatternConfigData } from '../../../pattern.js'; +import { safeZodParseFormErrors } from '../../../util/zod.js'; const configSchema = z.object({ title: z.string(), diff --git a/packages/forms/src/patterns/page/index.ts b/packages/forms/src/patterns/pages/page/index.ts similarity index 93% rename from packages/forms/src/patterns/page/index.ts rename to packages/forms/src/patterns/pages/page/index.ts index 5cf032af..cd7963ae 100644 --- a/packages/forms/src/patterns/page/index.ts +++ b/packages/forms/src/patterns/pages/page/index.ts @@ -1,4 +1,4 @@ -import { type PatternConfig } from '../../pattern.js'; +import { type PatternConfig } from '../../../pattern.js'; import { type PagePattern, parseConfigData } from './config.js'; import { createPrompt } from './prompt.js'; diff --git a/packages/forms/src/patterns/page/prompt.ts b/packages/forms/src/patterns/pages/page/prompt.ts similarity index 87% rename from packages/forms/src/patterns/page/prompt.ts rename to packages/forms/src/patterns/pages/page/prompt.ts index 0427ec8b..181f7b02 100644 --- a/packages/forms/src/patterns/page/prompt.ts +++ b/packages/forms/src/patterns/pages/page/prompt.ts @@ -2,8 +2,8 @@ import { type CreatePrompt, type PageProps, createPromptForPattern, -} from '../../components.js'; -import { getPattern } from '../../pattern.js'; +} from '../../../components.js'; +import { getPattern } from '../../../pattern.js'; import { type PagePattern } from './config.js'; diff --git a/packages/forms/src/patterns/page-set/submit.test.ts b/packages/forms/src/patterns/pages/submit.test.ts similarity index 95% rename from packages/forms/src/patterns/page-set/submit.test.ts rename to packages/forms/src/patterns/pages/submit.test.ts index b6e02fec..9308f77c 100644 --- a/packages/forms/src/patterns/page-set/submit.test.ts +++ b/packages/forms/src/patterns/pages/submit.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { type Blueprint, defaultFormConfig } from '../..'; -import { Input } from '../input/builder'; +import { type Blueprint, defaultFormConfig } from '../../..'; +import { Input } from '../../input/builder'; import { Page } from '../page/builder'; -import { createFormSession } from '../../session'; +import { createFormSession } from '../../../session'; import { PageSet } from './builder'; import { submitPage } from './submit'; diff --git a/packages/forms/src/patterns/page-set/submit.ts b/packages/forms/src/patterns/pages/submit.ts similarity index 94% rename from packages/forms/src/patterns/page-set/submit.ts rename to packages/forms/src/patterns/pages/submit.ts index 6ac7b401..90d083f2 100644 --- a/packages/forms/src/patterns/page-set/submit.ts +++ b/packages/forms/src/patterns/pages/submit.ts @@ -4,9 +4,9 @@ import { getPatternConfig, getPatternSafely, validatePatternAndChildren, -} from '../../pattern'; -import { type FormSession } from '../../session'; -import { type SubmitHandler } from '../../submission'; +} from '../../../pattern'; +import { type FormSession } from '../../../session'; +import { type SubmitHandler } from '../../../submission'; import { type PagePattern } from '../page/config'; import { type PageSetPattern } from './config'; diff --git a/packages/forms/src/services/submit-form.test.ts b/packages/forms/src/services/submit-form.test.ts index 17fee40f..b1ccc52a 100644 --- a/packages/forms/src/services/submit-form.test.ts +++ b/packages/forms/src/services/submit-form.test.ts @@ -8,14 +8,14 @@ import { } from '../documents/__tests__/test-documents.js'; import { createFormSession } from '../session.js'; import { createForm } from '../blueprint.js'; -import { type PageSetPattern } from '../patterns/page-set/config.js'; -import { type PagePattern } from '../patterns/page/config.js'; +import { type PageSetPattern } from '../patterns/pages/page-set/config.js'; +import { type PagePattern } from '../patterns/pages/page/config.js'; import { type InputPattern } from '../patterns/input/config.js'; import { type Blueprint } from '../types.js'; import { Checkbox } from '../patterns/checkbox.js'; import { FieldSet } from '../patterns/fieldset/builder.js'; -import { Page } from '../patterns/page/builder.js'; -import { Form, PageSet } from '../patterns/page-set/builder.js'; +import { Page } from '../patterns/pages/page/builder.js'; +import { Form, PageSet } from '../patterns/pages/page-set/builder.js'; describe('submitForm', () => { it('fails with missing action string', async () => { diff --git a/packages/forms/src/services/submit-form.ts b/packages/forms/src/services/submit-form.ts index ef917b79..bd77c09e 100644 --- a/packages/forms/src/services/submit-form.ts +++ b/packages/forms/src/services/submit-form.ts @@ -1,7 +1,7 @@ import { failure, success, type Result } from '@atj/common'; import { type FormServiceContext } from '../context/index.js'; -import { submitPage } from '../patterns/page-set/submit'; +import { submitPage } from '../patterns/pages/submit'; import { downloadPackageHandler } from '../patterns/package-download/submit'; import { type FormRoute } from '../route-data.js'; import { SubmissionRegistry } from '../submission'; From 8e2bc3d69fd473484869ebd2d48fa41c96abf67b Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 19 Nov 2024 14:00:56 -0600 Subject: [PATCH 4/7] Add FormClient and supporting code to make it easier to test form session state transitions; add currently failing test "honors first matching page rule". Will follow-up with a refactoring of page references, to refer to its pattern ID rather than array index. --- packages/forms/src/builder/index.ts | 24 +++++ packages/forms/src/pattern.ts | 3 + .../forms/src/patterns/pages/form-client.ts | 78 +++++++++++++++ .../src/patterns/pages/page-set/builder.ts | 5 - .../src/patterns/pages/page-set/index.ts | 9 ++ .../forms/src/patterns/pages/page/config.ts | 23 +++-- packages/forms/src/patterns/pages/rules.ts | 1 + .../forms/src/patterns/pages/submit.test.ts | 99 ++++++++++++++++--- .../forms/src/services/submit-form.test.ts | 8 +- 9 files changed, 219 insertions(+), 31 deletions(-) create mode 100644 packages/forms/src/patterns/pages/form-client.ts create mode 100644 packages/forms/src/patterns/pages/rules.ts diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 57696720..29557129 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -23,6 +23,8 @@ import { import { type FieldsetPattern } from '../patterns/fieldset/config.js'; import { type PageSetPattern } from '../patterns/pages/page-set/config.js'; import type { Blueprint, FormSummary } from '../types.js'; +import type { FormRoute } from '../route-data.js'; +import { PageSet } from '../patterns/pages/page-set/builder.js'; export class BlueprintBuilder { bp: Blueprint; @@ -151,3 +153,25 @@ export class BlueprintBuilder { }; } } + +export class Form { + constructor( + private config: FormConfig, + private readonly _bp: Blueprint + ) {} + + get bp() { + return this._bp; + } + + getInitialFormRoute(): FormRoute { + const pattern = this.bp.patterns[this.bp.root]; + const patternConfig = this.config.patterns[pattern.type]; + if (!patternConfig.getInitialFormRoute) { + throw new Error( + `Can't get getInitialFormRoute for pattern '${pattern.type}'` + ); + } + return patternConfig.getInitialFormRoute(pattern); + } +} diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index ddeb495b..c3c92a93 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -4,6 +4,7 @@ import set from 'set-value'; import { type CreatePrompt } from './components.js'; import { type FormError, type FormErrors } from './error.js'; import { type Blueprint } from './types.js'; +import type { FormRoute } from './route-data.js'; export type Pattern = { type: string; @@ -65,6 +66,7 @@ export const getPatternSafely =

(opts: { form: Blueprint; patternId: PatternId; }): r.Result

=> { + console.log('looking for', opts.patternId); const pattern = opts.form.patterns[opts.patternId]; if (pattern === undefined) { return r.failure(`Pattern with id ${opts.patternId} does not exist`); @@ -102,6 +104,7 @@ export type PatternConfig< ) => Pattern[]; removeChildPattern?: RemoveChildPattern; createPrompt: CreatePrompt; + getInitialFormRoute?: (pattern: ThisPattern) => FormRoute; }; export type FormConfig = { diff --git a/packages/forms/src/patterns/pages/form-client.ts b/packages/forms/src/patterns/pages/form-client.ts new file mode 100644 index 00000000..e3ceb271 --- /dev/null +++ b/packages/forms/src/patterns/pages/form-client.ts @@ -0,0 +1,78 @@ +import { Form } from '../../builder'; +import type { FormConfig } from '../../pattern'; +import type { FormService } from '../../services'; +import { type FormSession, type FormSessionId } from '../../session'; +import { getActionString } from '../../submission'; +import type { Blueprint } from '../../types'; + +type FormClientContext = { + config: FormConfig; + formService: FormService; +}; + +type FormClientState = { + sessionId?: FormSessionId; + session: FormSession; + attachments?: { + fileName: string; + data: Uint8Array; + }[]; +}; + +export class FormClient { + private form: Form; + private _state?: FormClientState; + + constructor( + private ctx: FormClientContext, + private formId: string, + blueprint: Blueprint + ) { + this.form = new Form(ctx.config, blueprint); + } + + async getState(): Promise { + if (!this._state) { + const result = await this.ctx.formService.getFormSession({ + formId: this.formId, + formRoute: this.form.getInitialFormRoute(), + }); + if (!result.success) { + throw new Error('Error getting form session'); + } + this._state = { + sessionId: result.data.id, + session: result.data.data, + }; + } + return this._state; + } + + setState(state: FormClientState) { + this._state = state; + } + + async submitPage(formData: Record): Promise { + const state = await this.getState(); + const result = await this.ctx.formService.submitForm( + state.sessionId, + this.formId, + { + ...formData, + action: getActionString({ + handlerId: 'page-set', + patternId: state.session.form.root, + }), + }, + state.session.route + ); + if (!result.success) { + throw new Error(`Error submitting form: ${result.error}`); + } + this.setState({ + sessionId: result.data.sessionId, + session: result.data.session, + attachments: result.data.attachments, + }); + } +} diff --git a/packages/forms/src/patterns/pages/page-set/builder.ts b/packages/forms/src/patterns/pages/page-set/builder.ts index 83b6125d..e260ae4f 100644 --- a/packages/forms/src/patterns/pages/page-set/builder.ts +++ b/packages/forms/src/patterns/pages/page-set/builder.ts @@ -1,12 +1,7 @@ -import { type Blueprint } from '../../..'; import { PatternBuilder } from '../../../pattern'; import { type Page } from '../page/builder'; import { type PageSetPattern } from './config'; -export class Form { - constructor(public readonly blueprint: Blueprint) {} -} - export class PageSet extends PatternBuilder { type = 'page-set'; diff --git a/packages/forms/src/patterns/pages/page-set/index.ts b/packages/forms/src/patterns/pages/page-set/index.ts index 078f2dce..eddcf8f5 100644 --- a/packages/forms/src/patterns/pages/page-set/index.ts +++ b/packages/forms/src/patterns/pages/page-set/index.ts @@ -42,4 +42,13 @@ export const pageSetConfig: PatternConfig = { }, }; }, + getInitialFormRoute(pattern) { + if (pattern.data.pages.length === 0) { + throw new Error('No route for empty page-set.'); + } + return { + url: '#', + params: { page: '0' }, + }; + }, }; diff --git a/packages/forms/src/patterns/pages/page/config.ts b/packages/forms/src/patterns/pages/page/config.ts index 4b26d248..b30de372 100644 --- a/packages/forms/src/patterns/pages/page/config.ts +++ b/packages/forms/src/patterns/pages/page/config.ts @@ -3,6 +3,16 @@ import { z } from 'zod'; import { type Pattern, type ParsePatternConfigData } from '../../../pattern.js'; import { safeZodParseFormErrors } from '../../../util/zod.js'; +const ruleSchema = z.object({ + patternId: z.string(), + condition: z.object({ + operator: z.literal('='), + value: z.string(), + }), + next: z.string(), +}); +type Rule = z.infer; + const configSchema = z.object({ title: z.string(), patterns: z.union([ @@ -21,18 +31,7 @@ const configSchema = z.object({ ) .pipe(z.string().array()), ]), - rules: z - .array( - z.object({ - patternId: z.string(), - condition: z.object({ - operator: z.literal('='), - value: z.string(), - }), - next: z.string(), - }) - ) - .default([]), + rules: z.array(ruleSchema).default([]), }); type PageConfigSchema = z.infer; diff --git a/packages/forms/src/patterns/pages/rules.ts b/packages/forms/src/patterns/pages/rules.ts new file mode 100644 index 00000000..3830962b --- /dev/null +++ b/packages/forms/src/patterns/pages/rules.ts @@ -0,0 +1 @@ +import type { Pattern } from '../../pattern'; diff --git a/packages/forms/src/patterns/pages/submit.test.ts b/packages/forms/src/patterns/pages/submit.test.ts index 403cd620..a0bb7114 100644 --- a/packages/forms/src/patterns/pages/submit.test.ts +++ b/packages/forms/src/patterns/pages/submit.test.ts @@ -8,6 +8,9 @@ import { submitPage } from './submit'; import { defaultFormConfig } from '..'; import type { Blueprint } from '../../types'; import { Page } from './page/builder'; +import { FormClient } from './form-client'; +import { createTestBrowserFormService } from '../../context'; +import { getActionString } from '../../submission'; describe('Page-set submission', () => { it('stores session data for valid page data', async () => { @@ -88,13 +91,11 @@ describe('Page-set submission', () => { route: { url: '#', params: { - page: '1', + page: '2', }, }, }, - data: { - 'input-2': 'test', - }, + data: {}, }); expect(result).toEqual({ data: { @@ -102,14 +103,12 @@ describe('Page-set submission', () => { ...session, data: { errors: {}, - values: { - 'input-2': 'test', - }, + values: {}, }, route: { url: '#', params: { - page: '1', + page: '2', }, }, form: session.form, @@ -118,9 +117,50 @@ describe('Page-set submission', () => { success: true, }); }); + + it.fails('honors first matching page rule', async () => { + const { id, form, formService } = await createTestFormContext(); + const client = new FormClient( + { + config: defaultFormConfig, + formService, + }, + id, + form + ); + await client.submitPage({ + 'input-1': 'rule-2', + }); + const state = await client.getState(); + expect(state).toEqual( + expect.objectContaining({ + sessionId: expect.any(String), + session: { + data: { + errors: {}, + values: { + 'input-1': 'rule-2', + }, + }, + route: { + url: '#', + params: { + page: '2', + }, + }, + form, + }, + }) + ); + }); }); const createTestSession = () => { + const testForm = createTestForm(); + return createFormSession(testForm, { url: '#', params: { page: '0' } }); +}; + +const createTestForm = () => { const input1 = new Input( { label: 'label', required: true, maxLength: 10 }, 'input-1' @@ -130,14 +170,36 @@ const createTestSession = () => { 'input-2' ); const page1 = new Page( - { title: 'Page 1', patterns: [input1.id], rules: [] }, + { + title: 'Page 1', + patterns: [input1.id], + rules: [ + { + patternId: input1.id, + condition: { value: 'rule-1', operator: '=' }, + next: 'page-2', + }, + { + patternId: input1.id, + condition: { value: 'rule-2', operator: '=' }, + next: 'page-3', + }, + ], + }, 'page-1' ); const page2 = new Page( { title: 'Page 2', patterns: [input2.id], rules: [] }, 'page-2' ); - const pageSet = new PageSet({ pages: [page1.id, page2.id] }, 'page-set-1'); + const page3 = new Page( + { title: 'Page 3', patterns: [], rules: [] }, + 'page-3' + ); + const pageSet = new PageSet( + { pages: [page1.id, page2.id, page3.id] }, + 'page-set-1' + ); const testForm: Blueprint = { summary: { description: 'A test form', @@ -147,11 +209,26 @@ const createTestSession = () => { patterns: { [page1.id]: page1.toPattern(), [page2.id]: page2.toPattern(), + [page3.id]: page3.toPattern(), [pageSet.id]: pageSet.toPattern(), [input1.id]: input1.toPattern(), [input2.id]: input2.toPattern(), }, outputs: [], }; - return createFormSession(testForm, { url: '#', params: { page: '0' } }); + return testForm; +}; + +const createTestFormContext = async () => { + const form = createTestForm(); + const formService = createTestBrowserFormService(); + const addFormResult = await formService.addForm(form); + if (!addFormResult.success) { + expect.fail('Error adding test form'); + } + return { + id: addFormResult.data.id, + form, + formService, + }; }; diff --git a/packages/forms/src/services/submit-form.test.ts b/packages/forms/src/services/submit-form.test.ts index b1ccc52a..e5373ac5 100644 --- a/packages/forms/src/services/submit-form.test.ts +++ b/packages/forms/src/services/submit-form.test.ts @@ -15,7 +15,9 @@ import { type Blueprint } from '../types.js'; import { Checkbox } from '../patterns/checkbox.js'; import { FieldSet } from '../patterns/fieldset/builder.js'; import { Page } from '../patterns/pages/page/builder.js'; -import { Form, PageSet } from '../patterns/pages/page-set/builder.js'; +import { PageSet } from '../patterns/pages/page-set/builder.js'; +import { Form } from '../builder/index.js'; +import { defaultFormConfig } from '../patterns/index.js'; describe('submitForm', () => { it('fails with missing action string', async () => { @@ -557,7 +559,7 @@ const createMultiPageFormWithSkipLogic = () => { }, 'root' ); - const form = new Form({ + const form = new Form(defaultFormConfig, { summary: { title: 'Test form', description: 'Test description', @@ -574,5 +576,5 @@ const createMultiPageFormWithSkipLogic = () => { }, outputs: [], }); - return form.blueprint; + return form.bp; }; From 1612d377f4bf56407db2b36d662ee6e22123cab0 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 19 Nov 2024 16:11:21 -0600 Subject: [PATCH 5/7] Fix array indexing for pages. We should be able to stick with index references, as long as we have a flat list of pages, but will need to revisit if we support navigation hierarchies. --- packages/forms/src/pattern.ts | 1 - .../forms/src/patterns/pages/form-client.ts | 2 + .../forms/src/patterns/pages/submit.test.ts | 3 +- packages/forms/src/patterns/pages/submit.ts | 68 ++++++++++++------- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index c3c92a93..ee0d584a 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -66,7 +66,6 @@ export const getPatternSafely =

(opts: { form: Blueprint; patternId: PatternId; }): r.Result

=> { - console.log('looking for', opts.patternId); const pattern = opts.form.patterns[opts.patternId]; if (pattern === undefined) { return r.failure(`Pattern with id ${opts.patternId} does not exist`); diff --git a/packages/forms/src/patterns/pages/form-client.ts b/packages/forms/src/patterns/pages/form-client.ts index e3ceb271..ed43f46f 100644 --- a/packages/forms/src/patterns/pages/form-client.ts +++ b/packages/forms/src/patterns/pages/form-client.ts @@ -54,6 +54,7 @@ export class FormClient { async submitPage(formData: Record): Promise { const state = await this.getState(); + const result = await this.ctx.formService.submitForm( state.sessionId, this.formId, @@ -69,6 +70,7 @@ export class FormClient { if (!result.success) { throw new Error(`Error submitting form: ${result.error}`); } + this.setState({ sessionId: result.data.sessionId, session: result.data.session, diff --git a/packages/forms/src/patterns/pages/submit.test.ts b/packages/forms/src/patterns/pages/submit.test.ts index a0bb7114..599e3cb4 100644 --- a/packages/forms/src/patterns/pages/submit.test.ts +++ b/packages/forms/src/patterns/pages/submit.test.ts @@ -118,7 +118,7 @@ describe('Page-set submission', () => { }); }); - it.fails('honors first matching page rule', async () => { + it('honors first matching page rule', async () => { const { id, form, formService } = await createTestFormContext(); const client = new FormClient( { @@ -134,6 +134,7 @@ describe('Page-set submission', () => { const state = await client.getState(); expect(state).toEqual( expect.objectContaining({ + attachments: undefined, sessionId: expect.any(String), session: { data: { diff --git a/packages/forms/src/patterns/pages/submit.ts b/packages/forms/src/patterns/pages/submit.ts index a595ae8b..31806108 100644 --- a/packages/forms/src/patterns/pages/submit.ts +++ b/packages/forms/src/patterns/pages/submit.ts @@ -4,11 +4,14 @@ import { getPatternConfig, getPatternSafely, aggregatePatternSessionValues, + type PatternId, + type PatternValue, } from '../../pattern.js'; import { type FormSession } from '../../session'; import { type SubmitHandler } from '../../submission'; import { type PagePattern } from './page/config'; import type { PageSetPattern } from './page-set/config.js'; +import type { FormError } from '../../error.js'; const getPage = (formSession: FormSession) => { const page = formSession.route?.params.page?.toString(); @@ -47,11 +50,44 @@ export const submitPage: SubmitHandler = async ( } ); + const nextPage = getNextPage({ + pageSet: opts.pattern, + page: pagePattern.data, + pageNumber, + data: result, + }); + + return success({ + session: { + ...opts.session, + data: result, + route: opts.session.route + ? { + ...opts.session.route, + params: { + ...opts.session.route.params, + page: nextPage.toString(), + }, + } + : undefined, + }, + }); +}; + +const getNextPage = (opts: { + pageSet: PageSetPattern; + page: PagePattern; + pageNumber: number; + data: { + values: Record; + errors: Record; + }; +}) => { // Evaluate page rules - const ruleMatch = pagePattern.data.data.rules - ? pagePattern.data.data.rules.find(rule => { + const ruleMatch = opts.page.data.rules + ? opts.page.data.rules.find(rule => { if (rule.condition.operator === '=') { - const value = opts.session.data.values[rule.patternId]; + const value = opts.data.values[rule.patternId]; if (value === rule.condition.value) { return true; } @@ -65,27 +101,13 @@ export const submitPage: SubmitHandler = async ( // Get the page number for the 1st rule match, or the next page if no rules // match. - const lastPage = opts.pattern.data.pages.length - 1; + const lastPage = opts.pageSet.data.pages.length - 1; const nextPage = - Object.values(result.errors).length === 0 && pageNumber < lastPage + Object.values(opts.data.errors).length === 0 && opts.pageNumber < lastPage ? ruleMatch - ? pagePattern.data.data.patterns.indexOf(ruleMatch.patternId) - : pageNumber + 1 - : pageNumber; + ? opts.pageSet.data.pages.indexOf(ruleMatch.next) + : opts.pageNumber + 1 + : opts.pageNumber; - return success({ - session: { - ...opts.session, - data: result, - route: opts.session.route - ? { - ...opts.session.route, - params: { - ...opts.session.route.params, - page: nextPage.toString(), - }, - } - : undefined, - }, - }); + return nextPage; }; From 467d451f4ca82fad90a46108be17c4bc8763b05f Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 20 Nov 2024 14:30:08 -0600 Subject: [PATCH 6/7] Add "Create custom rule" button and story to page set edit. --- .../components/PageSetEdit.stories.tsx | 11 ++++++- .../FormEdit/components/PageSetEdit.tsx | 32 +++++++++++++++++-- .../forms/src/patterns/pages/submit.test.ts | 1 - 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx index ac5e45bb..34f1754f 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx @@ -7,6 +7,7 @@ import { } from './common/story-helper.js'; import PageSetEdit from './PageSetEdit.js'; import { createTwoPageTwoPatternTestForm } from '../../../test-form.js'; +import { userEvent } from '@storybook/test'; const blueprint = createTwoPageTwoPatternTestForm(); @@ -22,7 +23,6 @@ export const Basic: StoryObj = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); const pagesetHeaderElement = await canvas.findByText(/Page 1/); - return; await testUpdateFormFieldOnSubmitByElement( canvasElement, pagesetHeaderElement, @@ -31,3 +31,12 @@ export const Basic: StoryObj = { ); }, }; + +export const CreateCustomRule: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + //const pagesetHeaderElement = await canvas.findByText(/Page 1/); + const button = canvas.getByRole('button', { name: /Create custom rule/ }); + await userEvent.click(button); + }, +}; diff --git a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx index 87cdcc2d..9194f129 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx @@ -4,7 +4,6 @@ import { getPattern, type PageSetProps } from '@atj/forms'; import { PatternEditComponent } from '../types.js'; -import ActionBar from '../../../Form/ActionBar/index.js'; import classNames from 'classnames'; import styles from '../../../Form/components/PageSet/PageMenu/pageMenuStyles.module.css'; import { DraggableList } from './PreviewSequencePattern/DraggableList.js'; @@ -13,7 +12,10 @@ import { useSearchParams } from 'react-router-dom'; import { UniqueIdentifier } from '@dnd-kit/core'; import { PageMenuProps } from '../../../Form/components/PageSet/PageMenu/PageMenu.js'; -const PageSetEdit: PatternEditComponent = ({ previewProps }) => { +const PageSetEdit: PatternEditComponent = ({ + context, + previewProps, +}) => { return (

); diff --git a/packages/forms/src/patterns/pages/submit.test.ts b/packages/forms/src/patterns/pages/submit.test.ts index 599e3cb4..8134218d 100644 --- a/packages/forms/src/patterns/pages/submit.test.ts +++ b/packages/forms/src/patterns/pages/submit.test.ts @@ -10,7 +10,6 @@ import type { Blueprint } from '../../types'; import { Page } from './page/builder'; import { FormClient } from './form-client'; import { createTestBrowserFormService } from '../../context'; -import { getActionString } from '../../submission'; describe('Page-set submission', () => { it('stores session data for valid page data', async () => { From 4a56d44b7eb874fd9a5bcfa93b5bdc2be9ace1ae Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Fri, 22 Nov 2024 14:22:25 -0600 Subject: [PATCH 7/7] Page rules UI and Storybook story started --- .../FormEdit/components/PageEdit.stories.tsx | 25 +++++ .../FormEdit/components/PageEdit.tsx | 104 +++++++++++++++++- .../components/PageSetEdit.stories.tsx | 10 -- .../FormEdit/components/PageSetEdit.tsx | 32 +----- packages/design/src/test-form.ts | 76 +++++++++++++ packages/forms/src/components.ts | 2 + .../forms/src/patterns/pages/page/config.ts | 2 +- .../forms/src/patterns/pages/page/prompt.ts | 1 + 8 files changed, 210 insertions(+), 42 deletions(-) create mode 100644 packages/design/src/FormManager/FormEdit/components/PageEdit.stories.tsx diff --git a/packages/design/src/FormManager/FormEdit/components/PageEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/PageEdit.stories.tsx new file mode 100644 index 00000000..34d326a8 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/components/PageEdit.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { within } from '@testing-library/react'; + +import { createThreePageFormWithPageRules } from '../../../test-form.js'; +import { createPatternEditStoryMeta } from './common/story-helper.js'; +import { PageEdit } from './PageEdit.js'; + +const blueprint = createThreePageFormWithPageRules(); + +const storyConfig: Meta = { + title: 'Edit components/PageEdit', + ...createPatternEditStoryMeta({ + blueprint, + }), +}; +export default storyConfig; + +export const CreateCustomRule: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + //const pagesetHeaderElement = await canvas.findByText(/Page 1/); + canvas.getByRole('button', { name: /Create custom rule/ }); + //await userEvent.click(button); + }, +}; diff --git a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx index ec71647c..30383e5e 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx @@ -1,5 +1,5 @@ import classnames from 'classnames'; -import React from 'react'; +import React, { useState } from 'react'; import { PageProps } from '@atj/forms'; import { enLocale as message } from '@atj/common'; @@ -13,6 +13,7 @@ import { PatternEditForm } from './common/PatternEditForm.js'; import { usePatternEditFormContext } from './common/hooks.js'; import { PatternPreviewSequence } from './PreviewSequencePattern/index.js'; import styles from '../formEditStyles.module.css'; +import type { FormManagerContext } from 'FormManager/index.js'; export const PageEdit: PatternEditComponent = props => { const handleParentClick = ( @@ -24,6 +25,9 @@ export const PageEdit: PatternEditComponent = props => { } }; + const [editingRule, setEditingRule] = useState< + PageProps['rules'][number] | null + >(null); const { routeParams } = useRouteParams(); const params = new URLSearchParams(routeParams?.toString()); const pageNumberText = Number(params.get('page')) + 1; @@ -62,6 +66,84 @@ export const PageEdit: PatternEditComponent = props => { children: props.previewProps.children, }} /> + +
+
+ NAVIGATION: After the current page is completed, go + to the next page unless a custom rule applies. +
+ {props.previewProps.rules.map((rule, index) => ( +
+
+ +
+ If{' '} + {' '} + are selected, go to{' '} + {rule.next}. +
+
+
+ + + + +
+
+ ))} +
+
+ +
+
); }; @@ -106,3 +188,23 @@ const PageEditComponent = ({ pattern }: { pattern: PagePattern }) => { ); }; + +const RuleButton = (props: { + context: FormManagerContext; + icon: string; + label: string; +}) => ( + +); diff --git a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx index 34f1754f..3de38a11 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.stories.tsx @@ -7,7 +7,6 @@ import { } from './common/story-helper.js'; import PageSetEdit from './PageSetEdit.js'; import { createTwoPageTwoPatternTestForm } from '../../../test-form.js'; -import { userEvent } from '@storybook/test'; const blueprint = createTwoPageTwoPatternTestForm(); @@ -31,12 +30,3 @@ export const Basic: StoryObj = { ); }, }; - -export const CreateCustomRule: StoryObj = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - //const pagesetHeaderElement = await canvas.findByText(/Page 1/); - const button = canvas.getByRole('button', { name: /Create custom rule/ }); - await userEvent.click(button); - }, -}; diff --git a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx index 9194f129..023c7f2f 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageSetEdit.tsx @@ -1,10 +1,10 @@ +import classNames from 'classnames'; import React from 'react'; import { getPattern, type PageSetProps } from '@atj/forms'; import { PatternEditComponent } from '../types.js'; -import classNames from 'classnames'; import styles from '../../../Form/components/PageSet/PageMenu/pageMenuStyles.module.css'; import { DraggableList } from './PreviewSequencePattern/DraggableList.js'; import { useFormManagerStore } from '../../store.js'; @@ -12,10 +12,7 @@ import { useSearchParams } from 'react-router-dom'; import { UniqueIdentifier } from '@dnd-kit/core'; import { PageMenuProps } from '../../../Form/components/PageSet/PageMenu/PageMenu.js'; -const PageSetEdit: PatternEditComponent = ({ - context, - previewProps, -}) => { +const PageSetEdit: PatternEditComponent = ({ previewProps }) => { return (
); diff --git a/packages/design/src/test-form.ts b/packages/design/src/test-form.ts index 43e73f86..6b15c42b 100644 --- a/packages/design/src/test-form.ts +++ b/packages/design/src/test-form.ts @@ -3,6 +3,7 @@ import { createFormSession, defaultFormConfig, type Blueprint, + type CheckboxPattern, type Pattern, } from '@atj/forms'; import { createTestBrowserFormService } from '@atj/forms/context'; @@ -167,6 +168,81 @@ export const createTwoPatternTestForm = () => { ); }; +export const createThreePageFormWithPageRules = () => { + return createForm( + { + title: 'Test form with page rules', + description: 'This form has three pages and page rules', + }, + { + root: 'root', + patterns: [ + { + type: 'page-set', + id: 'root', + data: { + pages: ['page-1', 'page-2', 'page-3'], + }, + } satisfies PageSetPattern, + { + type: 'page', + id: 'page-1', + data: { + title: 'First page', + patterns: ['checkbox-1', 'checkbox-2'], + rules: [ + { + patternId: 'checkbox-1', + condition: { value: 'on', operator: '=' }, + next: 'page-2', + }, + { + patternId: 'checkbox-1', + condition: { value: 'off', operator: '=' }, + next: 'page-3', + }, + ], + }, + } satisfies PagePattern, + { + type: 'page', + id: 'page-2', + data: { + title: 'Second page', + patterns: [], + rules: [], + }, + } satisfies PagePattern, + { + type: 'page', + id: 'page-3', + data: { + title: 'Third page', + patterns: [], + rules: [], + }, + } satisfies PagePattern, + { + type: 'checkbox', + id: 'checkbox-1', + data: { + label: 'Pattern 1', + defaultChecked: false, + }, + } satisfies CheckboxPattern, + { + type: 'checkbox', + id: 'checkbox-2', + data: { + label: 'Pattern 2', + defaultChecked: false, + }, + } satisfies CheckboxPattern, + ], + } + ); +}; + export const createSimpleTestBlueprint = (pattern: Pattern) => { return createForm( { diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index 5c9d87f5..f7150268 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -6,6 +6,7 @@ import { type PatternId, getPatternConfig, } from './pattern.js'; +import type { PageRule } from './patterns/pages/page/config.js'; import { type FormSession, nullSession, sessionIsComplete } from './session.js'; import { type ActionName } from './submission.js'; @@ -75,6 +76,7 @@ export type PageSetProps = PatternProps<{ export type PageProps = PatternProps<{ type: 'page'; title: string; + rules: PageRule[]; }>; export type RadioGroupProps = PatternProps<{ diff --git a/packages/forms/src/patterns/pages/page/config.ts b/packages/forms/src/patterns/pages/page/config.ts index b30de372..db2b71c4 100644 --- a/packages/forms/src/patterns/pages/page/config.ts +++ b/packages/forms/src/patterns/pages/page/config.ts @@ -11,7 +11,7 @@ const ruleSchema = z.object({ }), next: z.string(), }); -type Rule = z.infer; +export type PageRule = z.infer; const configSchema = z.object({ title: z.string(), diff --git a/packages/forms/src/patterns/pages/page/prompt.ts b/packages/forms/src/patterns/pages/page/prompt.ts index 181f7b02..a929a265 100644 --- a/packages/forms/src/patterns/pages/page/prompt.ts +++ b/packages/forms/src/patterns/pages/page/prompt.ts @@ -22,6 +22,7 @@ export const createPrompt: CreatePrompt = ( _patternId: pattern.id, type: 'page', title: pattern.data.title, + rules: pattern.data.rules, } satisfies PageProps, children, };