diff --git a/README.md b/README.md index 74d9e63b..6c8f6d49 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ name rule # compact pict-style alternative -status: enum(active,inactive) +status: active,inactive IF [name] = "Bob" THEN [status] = "active" ENDIF ``` diff --git a/apps/api/src/fromschema.route.test.js b/apps/api/src/fromschema.route.test.js index 90b42a99..76a0086b 100644 --- a/apps/api/src/fromschema.route.test.js +++ b/apps/api/src/fromschema.route.test.js @@ -181,7 +181,7 @@ test('/v1/generate/fromschema accepts comments and blank lines in schema text', const response = await fetch(url('/v1/generate/fromschema?rowCount=2&outputFormat=json'), { method: 'POST', headers: { 'content-type': 'text/plain' }, - body: '# skip me\n\nPriority\nenum(high,medium,low)\n\n# and me\nStatus\nenum(active,inactive,pending)', + body: '# skip me\n\nPriority\nhigh,medium,low\n\n# and me\nStatus\nactive,inactive,pending', }); expect(response.status).toBe(200); diff --git a/apps/cli/src/tests/run-cli.test.js b/apps/cli/src/tests/run-cli.test.js index 2febdc2a..432667b7 100644 --- a/apps/cli/src/tests/run-cli.test.js +++ b/apps/cli/src/tests/run-cli.test.js @@ -239,7 +239,7 @@ test('generates deterministic pairwise output in buffered mode', async () => { test('supports comments and blank lines in input schema', async () => { const platform = makePlatform({ - textSpec: '# comment\n\nPriority\nenum(high,medium,low)\n\nStatus\nenum(active,inactive,pending)', + textSpec: '# comment\n\nPriority\nhigh,medium,low\n\nStatus\nactive,inactive,pending', }); const code = await runCliCommand({ platform, diff --git a/apps/mcp/src/mcp.test.js b/apps/mcp/src/mcp.test.js index 7ae75276..d33cfaa3 100644 --- a/apps/mcp/src/mcp.test.js +++ b/apps/mcp/src/mcp.test.js @@ -82,7 +82,7 @@ test('MCP server accepts comments and blank lines in textSpec', () => { params: { name: 'generate_data_from_spec', arguments: { - textSpec: '# comment\n\nPriority\nenum(high,medium,low)\nStatus\nenum(active,inactive,pending)', + textSpec: '# comment\n\nPriority\nhigh,medium,low\nStatus\nactive,inactive,pending', rowCount: 1, outputFormat: 'json', }, diff --git a/apps/web/src/stories/generator-page.stories.js b/apps/web/src/stories/generator-page.stories.js index 858c9065..d2565920 100644 --- a/apps/web/src/stories/generator-page.stories.js +++ b/apps/web/src/stories/generator-page.stories.js @@ -11,7 +11,7 @@ import { createGeneratorPageComponent } from '../../../../packages/core-ui/js/gu import { createGeneratorPageShellComponent } from '../../../../packages/core-ui/js/gui_components/generator/page/create-generator-page-shell-component.js'; import { GENERATOR_DEFAULT_EXAMPLE_SCHEMA_TEXT } from '../../../../packages/core-ui/js/gui_components/shared/test-data/schema/schema-examples.js'; import { validateSchemaRows as validateSharedSchemaRows } from '../../../../packages/core-ui/js/gui_components/shared/test-data/schema/schema-editor-core.js'; -import { getKnownFakerCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/faker-commands.js'; +import { getAllowedFakerCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/faker-commands.js'; import { getKnownDomainCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/domain-commands.js'; import { buildSchemaModeHelpHtml } from '../../../../packages/core-ui/js/gui_components/shared/test-data/help/schema-mode-help-builder.js'; import { createGeneratorSchemaDefinitionSupport } from '../../../../packages/core-ui/js/gui_components/generator/schema-support/create-generator-schema-definition-support.js'; @@ -50,7 +50,7 @@ function getSchemaHelpText(rootElement) { function createGeneratorSchemaStoryProps(ids = {}) { let rowCounter = 1; - const fakerCommands = getKnownFakerCommandsAlphabetical().filter( + const fakerCommands = getAllowedFakerCommandsAlphabetical().filter( (command) => command !== 'RegEx' && command.startsWith('helpers.') ); const domainCommands = getKnownDomainCommandsAlphabetical(); diff --git a/apps/web/src/stories/generator-schema-panel.stories.js b/apps/web/src/stories/generator-schema-panel.stories.js index f39e7eed..d1861574 100644 --- a/apps/web/src/stories/generator-schema-panel.stories.js +++ b/apps/web/src/stories/generator-schema-panel.stories.js @@ -8,7 +8,7 @@ import { } from '@anywaydata/core/data_generation/schema-rules-adapter.js'; import { createSchemaPanelComponent } from '../../../../packages/core-ui/js/gui_components/shared/schema-panel/index.js'; import { validateSchemaRows as validateSharedSchemaRows } from '../../../../packages/core-ui/js/gui_components/shared/test-data/schema/schema-editor-core.js'; -import { getKnownFakerCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/faker-commands.js'; +import { getAllowedFakerCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/faker-commands.js'; import { getKnownDomainCommandsAlphabetical } from '../../../../packages/core-ui/js/gui_components/shared/domain-commands.js'; import { buildSchemaModeHelpHtml } from '../../../../packages/core-ui/js/gui_components/shared/test-data/help/schema-mode-help-builder.js'; import { createGeneratorSchemaDefinitionSupport } from '../../../../packages/core-ui/js/gui_components/generator/schema-support/create-generator-schema-definition-support.js'; @@ -16,7 +16,7 @@ import { GENERATOR_DEFAULT_EXAMPLE_SCHEMA_TEXT } from '../../../../packages/core function createGeneratorSchemaStoryProps() { let rowCounter = 1; - const fakerCommands = getKnownFakerCommandsAlphabetical().filter( + const fakerCommands = getAllowedFakerCommandsAlphabetical().filter( (command) => command !== 'RegEx' && command.startsWith('helpers.') ); const domainCommands = getKnownDomainCommandsAlphabetical(); diff --git a/apps/web/src/stories/import-export-download-control.stories.js b/apps/web/src/stories/import-export-download-control.stories.js index 053df001..bccaedef 100644 --- a/apps/web/src/stories/import-export-download-control.stories.js +++ b/apps/web/src/stories/import-export-download-control.stories.js @@ -79,7 +79,7 @@ export const Default = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const downloadButton = canvas.getByRole('button', { name: /\.csv Download/i }); + const downloadButton = canvas.getByRole('button', { name: 'Download file' }); await expect(downloadButton).toBeEnabled(); await userEvent.click(downloadButton); await expect(canvas.getByText('action:download')).toBeVisible(); @@ -101,7 +101,7 @@ export const Busy = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - await expect(canvas.getByRole('button', { name: /\.csv Download/i })).toBeDisabled(); + await expect(canvas.getByRole('button', { name: 'Download file' })).toBeDisabled(); await expect(canvas.getByText('Generating export text...')).toBeVisible(); }, }; diff --git a/apps/web/src/stories/import-export-toolbar.stories.js b/apps/web/src/stories/import-export-toolbar.stories.js index 7d7a2ce2..01b65af3 100644 --- a/apps/web/src/stories/import-export-toolbar.stories.js +++ b/apps/web/src/stories/import-export-toolbar.stories.js @@ -202,7 +202,7 @@ export const Default = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); const helpButtons = canvas.getAllByRole('button', { name: 'Show help' }); - const downloadButton = canvas.getByRole('button', { name: /\.csv Download/i }); + const downloadButton = canvas.getByRole('button', { name: 'Download file' }); await expect(helpButtons).toHaveLength(2); await expect(helpButtons[0]).toHaveAttribute('data-help', 'import-export-import'); @@ -259,7 +259,7 @@ export const BusyAndStatus = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); await expect(canvas.getByRole('button', { name: 'Import From Clipboard' })).toBeDisabled(); - await expect(canvas.getByRole('button', { name: /\.csv Download/i })).toBeDisabled(); + await expect(canvas.getByRole('button', { name: 'Download file' })).toBeDisabled(); await expect(canvas.getByText('Importing full data into grid...')).toBeVisible(); await expect(canvas.getByText('Generating export text...')).toBeVisible(); }, diff --git a/apps/web/src/stories/shared-schema-definition.stories.js b/apps/web/src/stories/shared-schema-definition.stories.js index 9e2703f4..2934bd7a 100644 --- a/apps/web/src/stories/shared-schema-definition.stories.js +++ b/apps/web/src/stories/shared-schema-definition.stories.js @@ -306,7 +306,7 @@ export const SampleSchema = { export const ValidationError = { render: renderSharedSchemaDefinitionStory, args: { - initialText: 'Status\nenum(active,inactive)', + initialText: 'Status\nactive,inactive', showErrors: true, startInTextMode: false, }, @@ -335,7 +335,7 @@ export const ValidationError = { export const TextMode = { render: renderSharedSchemaDefinitionStory, args: { - initialText: 'Status\nenum(active,inactive,pending)', + initialText: 'Status\nactive,inactive,pending', showErrors: true, startInTextMode: true, }, @@ -351,7 +351,7 @@ export const TextMode = { expectTextModeVisible(canvasElement); const textArea = within(canvasElement).getByRole('textbox', { name: /schema text/i }); await expect(textArea.value).toContain('Status'); - await expect(textArea.value).toContain('enum(active,inactive,pending)'); + await expect(textArea.value).toContain('active,inactive,pending'); const helpIcon = canvasElement.querySelector('[data-role="schema-mode-help"]'); await userEvent.hover(helpIcon); await expectSchemaHelpContent(helpIcon, { helpHeadingText: 'Edit as Schema' }); @@ -363,9 +363,9 @@ export const ConstrainedTextMode = { render: renderSharedSchemaDefinitionStory, args: { initialText: `Priority -enum(high,low) +enum("high","low") Status -enum(open,closed) +enum("open","closed") IF [Priority] = "high" THEN [Status] = "open" ENDIF`, showErrors: true, startInTextMode: true, diff --git a/apps/web/src/stories/stored-schemas-manager.stories.js b/apps/web/src/stories/stored-schemas-manager.stories.js index 3f3563d2..ab8c9bc1 100644 --- a/apps/web/src/stories/stored-schemas-manager.stories.js +++ b/apps/web/src/stories/stored-schemas-manager.stories.js @@ -16,7 +16,7 @@ function createInMemoryStorageState(args) { { id: 'last-1', name: 'last used - 2026-06-16T10:05:00.000Z', - schemaText: 'Status\nenum(active,inactive)', + schemaText: 'Status\nactive,inactive', updatedAt: '2026-06-16T10:05:00.000Z', }, ] @@ -26,7 +26,7 @@ function createInMemoryStorageState(args) { { id: 'saved-1', name: 'Regression Smoke', - schemaText: 'Browser\nenum(chrome,firefox)', + schemaText: 'Browser\nchrome,firefox', updatedAt: '2026-06-16T10:06:00.000Z', }, ] @@ -203,7 +203,7 @@ export const DraftAndHistory = { const canvas = within(canvasElement); await userEvent.click(canvas.getByText('Managed Stored Schemas (0)')); await userEvent.selectOptions(canvas.getByLabelText('Last Used'), 'last-1'); - await userEvent.click(canvas.getByRole('button', { name: /^load$/i })); + await userEvent.click(canvas.getByRole('button', { name: /load last used schema/i })); await expect(canvas.getByText(/Loaded last used/i)).toBeVisible(); }, }; diff --git a/apps/web/src/tests/browser/app/abstractions/components/test-data-panel.component.js b/apps/web/src/tests/browser/app/abstractions/components/test-data-panel.component.js index c24ea564..704f858a 100644 --- a/apps/web/src/tests/browser/app/abstractions/components/test-data-panel.component.js +++ b/apps/web/src/tests/browser/app/abstractions/components/test-data-panel.component.js @@ -57,8 +57,8 @@ class TestDataPanelComponent { this.storedSchemasSummary = this.panelRoot.getByText(/Managed Stored Schemas/); this.storedSchemasSaveAsButton = this.panelRoot.getByRole('button', { name: 'Save Schema As' }); this.storedSchemasRecoverDraftButton = this.panelRoot.getByRole('button', { name: 'Recover Draft' }); - this.storedSchemasLastUsedSelect = this.panelRoot.getByLabel('Last Used'); - this.storedSchemasLoadLastUsedButton = this.panelRoot.getByRole('button', { name: /^Load$/ }); + this.storedSchemasLastUsedSelect = this.panelRoot.getByRole('combobox', { name: 'Last Used' }); + this.storedSchemasLoadLastUsedButton = this.panelRoot.getByRole('button', { name: 'Load last used schema' }); this.storedSchemasLoadSavedButton = this.panelRoot.getByRole('button', { name: 'Load Saved Schema' }); this.storedSchemasDialog = page.getByRole('dialog', { name: 'Saved Schemas' }); this.schemaGrid = this.schemaEditor.rowsContainer; diff --git a/apps/web/src/tests/browser/app/functional/test-data/grid-to-enum-schema.spec.js b/apps/web/src/tests/browser/app/functional/test-data/grid-to-enum-schema.spec.js index e4eb9f24..175d4432 100644 --- a/apps/web/src/tests/browser/app/functional/test-data/grid-to-enum-schema.spec.js +++ b/apps/web/src/tests/browser/app/functional/test-data/grid-to-enum-schema.spec.js @@ -36,7 +36,7 @@ test.describe('7. Test Data Generation', () => { await expect.poll(async () => appPage.testDataPanel.getSchemaRowCount()).toBe(2); await expect .poll(async () => appPage.testDataPanel.getSchemaText()) - .toContain('Status\nenum(active,pending,inactive)'); + .toContain('Status\nenum("active","pending","inactive")'); await expect(await appPage.testDataPanel.getSchemaCell(0, 'columnName')).toBe('Status'); await expect(await appPage.testDataPanel.getSchemaCell(1, 'columnName')).toBe('Priority'); expectNoPageErrors(pageErrors); @@ -51,7 +51,7 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.submitGridToEnumSchemaLimit(2); await appPage.testDataPanel.confirmDialog.confirm({ confirmLabel: /truncate schema/i }); - await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('Status\nenum(active,pending)'); + await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('Status\nenum("active","pending")'); await expect.poll(async () => appPage.testDataPanel.getSchemaText()).not.toContain('inactive'); expectNoPageErrors(pageErrors); }); diff --git a/apps/web/src/tests/browser/app/functional/test-data/pairwise-generation.spec.js b/apps/web/src/tests/browser/app/functional/test-data/pairwise-generation.spec.js index eadbe96b..ecec4d4c 100644 --- a/apps/web/src/tests/browser/app/functional/test-data/pairwise-generation.spec.js +++ b/apps/web/src/tests/browser/app/functional/test-data/pairwise-generation.spec.js @@ -10,7 +10,7 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.expectExpanded(); await appPage.testDataPanel.setSchemaText( - 'Browser\nenum(chrome,firefox,safari)\n\nPlan\nenum(free,pro,enterprise)\n\nFixed\nliteral(CONSTANT)' + 'Browser\nchrome,firefox,safari\n\nPlan\nfree,pro,enterprise\n\nFixed\nliteral(CONSTANT)' ); const initialRowCount = await appPage.gridEditor.renderer.countRows(); @@ -29,7 +29,7 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.expectExpanded(); await appPage.testDataPanel.setSchemaText( - 'Browser\nenum(chrome,firefox,safari)\n\nPlan\nenum(free,pro,enterprise)\n\nFixed\nliteral(CONSTANT)\n\nCode\n[A-Z]{2}[0-9]{2}\n\nEnabled\ndatatype.boolean' + 'Browser\nchrome,firefox,safari\n\nPlan\nfree,pro,enterprise\n\nFixed\nliteral(CONSTANT)\n\nCode\n[A-Z]{2}[0-9]{2}\n\nEnabled\ndatatype.boolean' ); await appPage.testDataPanel.clickGeneratePairwise(); @@ -86,7 +86,7 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.expectExpanded(); await appPage.testDataPanel.setSchemaText( - 'Browser\nenum(chrome,firefox,safari)\n\nPlan\nenum(free,pro,enterprise)\n\nRegion\nenum(amer,emea,apac)\n\nFixed\nliteral(CONSTANT)' + 'Browser\nchrome,firefox,safari\n\nPlan\nfree,pro,enterprise\n\nRegion\namer,emea,apac\n\nFixed\nliteral(CONSTANT)' ); await appPage.testDataPanel.openGenerateCombinationsDialog(); @@ -127,7 +127,7 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.expectExpanded(); await appPage.testDataPanel.setSchemaText( - 'A\nenum(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)\n\nB\nenum(b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11)\n\nC\nenum(c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11)\n\nD\nenum(d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11)' + 'A\na1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11\n\nB\nb1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11\n\nC\nc1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11\n\nD\nd1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11' ); const initialRowCount = await appPage.gridEditor.renderer.countRows(); diff --git a/apps/web/src/tests/browser/app/functional/test-data/schema-file-load-save.spec.js b/apps/web/src/tests/browser/app/functional/test-data/schema-file-load-save.spec.js index fb0c4e7b..414bdcff 100644 --- a/apps/web/src/tests/browser/app/functional/test-data/schema-file-load-save.spec.js +++ b/apps/web/src/tests/browser/app/functional/test-data/schema-file-load-save.spec.js @@ -10,7 +10,7 @@ test.describe('Test Data Schema File Load Save', () => { await appPage.testDataPanel.loadSchemaFile({ name: 'schema.txt', mimeType: 'text/plain', - buffer: Buffer.from('Loaded Name\nliteral(Ada)\nLoaded Status\nenum(active,inactive)'), + buffer: Buffer.from('Loaded Name\nliteral(Ada)\nLoaded Status\nactive,inactive'), }); await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('Loaded Name'); diff --git a/apps/web/src/tests/browser/app/functional/test-data/text-schema-grid-sync.spec.js b/apps/web/src/tests/browser/app/functional/test-data/text-schema-grid-sync.spec.js index 45b8b4bd..94a374a3 100644 --- a/apps/web/src/tests/browser/app/functional/test-data/text-schema-grid-sync.spec.js +++ b/apps/web/src/tests/browser/app/functional/test-data/text-schema-grid-sync.spec.js @@ -43,11 +43,11 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.expand(); await appPage.testDataPanel.expectExpanded(); - await appPage.testDataPanel.setSchemaText('Priority\nenum(low,medium,high)'); + await appPage.testDataPanel.setSchemaText('Priority\nlow,medium,high'); await expect.poll(async () => appPage.testDataPanel.getSchemaRowCount()).toBe(1); await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'columnName')).toBe('Priority'); - await appPage.testDataPanel.setSchemaText('Priority\nenum(low,medium,high)\nEnabled\ndatatype.boolean'); + await appPage.testDataPanel.setSchemaText('Priority\nlow,medium,high\nEnabled\ndatatype.boolean'); await expect.poll(async () => appPage.testDataPanel.getSchemaRowCount()).toBe(2); await expect.poll(async () => appPage.testDataPanel.getSchemaCell(1, 'columnName')).toBe('Enabled'); @@ -82,7 +82,49 @@ test.describe('7. Test Data Generation', () => { expectNoPageErrors(pageErrors); }); - test('domain rows with invalid params stay domain after text mode round-trip in the app editor', async ({ page }) => { + test('invalid enum text shows a schema error and remains in text mode when editing as schema in the app', async ({ + page, + }) => { + const { appPage, pageErrors } = await openApp(page); + + await appPage.testDataPanel.expand(); + await appPage.testDataPanel.expectExpanded(); + + await appPage.testDataPanel.setSchemaText('Status\ndatatype.enum(values="active,pending)'); + await appPage.testDataPanel.schemaEditor.modeToggleButton.click(); + + await expect + .poll(async () => appPage.testDataPanel.getSchemaErrorText()) + .toContain('Status failed domain validation - Invalid keyword arguments: unbalanced expression'); + await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(false); + await expect(appPage.testDataPanel.schemaEditor.modeToggleButton).toHaveText('Edit as Schema'); + + expectNoPageErrors(pageErrors); + }); + + test('invalid enum text shows a schema error and leaves the grid unchanged when generating in the app', async ({ + page, + }) => { + const { appPage, pageErrors } = await openApp(page); + + await appPage.testDataPanel.expand(); + await appPage.testDataPanel.expectExpanded(); + + const totalRowsBefore = await appPage.gridEditor.totalRows.textContent(); + await appPage.testDataPanel.setSchemaText('Status\ndatatype.enum()'); + await appPage.testDataPanel.clickGenerate(); + + await expect + .poll(async () => appPage.testDataPanel.getSchemaErrorText()) + .toContain('Status failed domain validation - Invalid keyword arguments: argument "values" is required'); + await expect(appPage.gridEditor.totalRows).toHaveText(totalRowsBefore || ''); + + expectNoPageErrors(pageErrors); + }); + + test('domain rows with invalid params show a schema error after text mode round-trip in the app editor', async ({ + page, + }) => { const { appPage, pageErrors } = await openApp(page); await appPage.testDataPanel.expand(); @@ -95,12 +137,12 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.setSchemaCell(0, 'params', '(10)'); await appPage.testDataPanel.setSchemaTextMode(true); - await appPage.testDataPanel.setSchemaTextMode(false); + await appPage.testDataPanel.schemaEditor.modeToggleButton.click(); - await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'type')).toBe('person.fullName'); - await expect.poll(async () => appPage.testDataPanel.getSchemaSourceType(0)).toBe('domain'); - await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'params')).toBe('(10)'); - await expect(appPage.testDataPanel.getSchemaValidationMessage(0)).toContainText('invalid domain params'); + await expect + .poll(async () => appPage.testDataPanel.getSchemaErrorText()) + .toContain('Name failed domain validation'); + await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(false); expectNoPageErrors(pageErrors); }); @@ -157,17 +199,17 @@ test.describe('7. Test Data Generation', () => { await appPage.testDataPanel.addSchemaRow(); await expect.poll(async () => appPage.testDataPanel.getSchemaRowCount()).toBe(1); - await appPage.testDataPanel.setSchemaCell(0, 'columnName', 'Phone'); - await appPage.testDataPanel.setSchemaTypeValue(0, 'phone.number'); - await appPage.testDataPanel.setSchemaCell(0, 'params', 'style=13'); + await appPage.testDataPanel.setSchemaCell(0, 'columnName', 'Code'); + await appPage.testDataPanel.setSchemaTypeValue(0, 'string.alpha'); + await appPage.testDataPanel.setSchemaCell(0, 'params', 'length=4'); await appPage.testDataPanel.setSchemaTextMode(true); - await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('phone.number(style=13)'); + await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('string.alpha(length=4)'); await appPage.testDataPanel.setSchemaTextMode(false); - await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'type')).toBe('phone.number'); + await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'type')).toBe('string.alpha'); await expect.poll(async () => appPage.testDataPanel.getSchemaSourceType(0)).toBe('domain'); - await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'params')).toBe('(style=13)'); + await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'params')).toBe('(length=4)'); expectNoPageErrors(pageErrors); }); @@ -233,7 +275,9 @@ test.describe('7. Test Data Generation', () => { await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'type')).toBe('datatype.enum'); await expect.poll(async () => appPage.testDataPanel.getSchemaCell(0, 'value')).toBe('active,inactive,pending'); - await expect.poll(async () => appPage.testDataPanel.getSchemaText()).toContain('enum(active,inactive,pending)'); + await expect + .poll(async () => appPage.testDataPanel.getSchemaText()) + .toContain('enum("active","inactive","pending")'); expectNoPageErrors(pageErrors); }); diff --git a/apps/web/src/tests/browser/app/functional/test-data/text-schema.spec.js b/apps/web/src/tests/browser/app/functional/test-data/text-schema.spec.js index 57b1c747..f282889d 100644 --- a/apps/web/src/tests/browser/app/functional/test-data/text-schema.spec.js +++ b/apps/web/src/tests/browser/app/functional/test-data/text-schema.spec.js @@ -11,7 +11,7 @@ test.describe('7. Test Data Generation', () => { const beforeSchema = await appPage.testDataPanel.getSchemaRowCount(); await appPage.testDataPanel.setSchemaText( - '# this comment should be ignored\n\nFirst Name\nperson.firstName\n\n# second comment\nStatus\nenum(active,inactive,pending)' + '# this comment should be ignored\n\nFirst Name\nperson.firstName\n\n# second comment\nStatus\nactive,inactive,pending' ); await expect.poll(async () => appPage.testDataPanel.getSchemaRowCount()).toBeGreaterThanOrEqual(beforeSchema + 1); await appPage.testDataPanel.setGenerateCount(5); diff --git a/apps/web/src/tests/browser/generator/abstractions/components/generator-schema.component.js b/apps/web/src/tests/browser/generator/abstractions/components/generator-schema.component.js index cb1cff49..d0340218 100644 --- a/apps/web/src/tests/browser/generator/abstractions/components/generator-schema.component.js +++ b/apps/web/src/tests/browser/generator/abstractions/components/generator-schema.component.js @@ -17,8 +17,8 @@ class GeneratorSchemaComponent { this.storedSchemasSummary = this.container.getByText(/Managed Stored Schemas/); this.storedSchemasSaveAsButton = this.container.getByRole('button', { name: 'Save Schema As' }); this.storedSchemasRecoverDraftButton = this.container.getByRole('button', { name: 'Recover Draft' }); - this.storedSchemasLastUsedSelect = this.container.getByLabel('Last Used'); - this.storedSchemasLoadLastUsedButton = this.container.getByRole('button', { name: /^Load$/ }); + this.storedSchemasLastUsedSelect = this.container.getByRole('combobox', { name: 'Last Used' }); + this.storedSchemasLoadLastUsedButton = this.container.getByRole('button', { name: 'Load last used schema' }); this.storedSchemasLoadSavedButton = this.container.getByRole('button', { name: 'Load Saved Schema' }); this.storedSchemasDialog = page.getByRole('dialog', { name: 'Saved Schemas' }); this.textInputDialog = new TextInputDialogComponent(page); diff --git a/apps/web/src/tests/browser/generator/functional/generate-pairwise-data.spec.js b/apps/web/src/tests/browser/generator/functional/generate-pairwise-data.spec.js index 2d885710..7241ce56 100644 --- a/apps/web/src/tests/browser/generator/functional/generate-pairwise-data.spec.js +++ b/apps/web/src/tests/browser/generator/functional/generate-pairwise-data.spec.js @@ -45,7 +45,7 @@ function validatePairwiseRows(rows) { async function setPairwiseSchema(generatorPage) { await generatorPage.schema.setSchemaText( - 'Browser\nenum(chrome,firefox,safari)\n\nPlan\nenum(free,pro,enterprise)\n\nFixed\nliteral(CONSTANT)\n\nCode\n[A-Z]{2}[0-9]{2}\n\nEnabled\ndatatype.boolean' + 'Browser\nchrome,firefox,safari\n\nPlan\nfree,pro,enterprise\n\nFixed\nliteral(CONSTANT)\n\nCode\n[A-Z]{2}[0-9]{2}\n\nEnabled\ndatatype.boolean' ); } diff --git a/apps/web/src/tests/browser/generator/functional/schema-edit.spec.js b/apps/web/src/tests/browser/generator/functional/schema-edit.spec.js index 2a515a78..fe3d1a5a 100644 --- a/apps/web/src/tests/browser/generator/functional/schema-edit.spec.js +++ b/apps/web/src/tests/browser/generator/functional/schema-edit.spec.js @@ -23,7 +23,7 @@ test.describe('Generator Schema Editing', () => { test('can edit as text then edit as schema', async ({ page }) => { const { generatorPage, pageErrors } = await openGenerator(page); - await generatorPage.schema.setSchemaText('First Name\nliteral(Alice)\n\nStatus\nenum(active,inactive)'); + await generatorPage.schema.setSchemaText('First Name\nliteral(Alice)\n\nStatus\nactive,inactive'); await generatorPage.schema.setTextMode(false); await expect(generatorPage.schema.rows).toHaveCount(2); @@ -77,7 +77,9 @@ test.describe('Generator Schema Editing', () => { await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue( 'active,inactive,pending' ); - await expect.poll(async () => generatorPage.schema.getSchemaText()).toContain('enum(active,inactive,pending)'); + await expect + .poll(async () => generatorPage.schema.getSchemaText()) + .toContain('enum("active","inactive","pending")'); expectNoPageErrors(pageErrors); }); @@ -108,6 +110,48 @@ test.describe('Generator Schema Editing', () => { expectNoPageErrors(pageErrors); }); + test('invalid enum text shows a schema error and remains in text mode when editing as schema', async ({ page }) => { + const { generatorPage, pageErrors } = await openGenerator(page); + + await generatorPage.schema.setSchemaText('Status\ndatatype.enum(values="active,pending)'); + await generatorPage.schema.modeToggleButton.click(); + + await expect(generatorPage.schema.errorStatus).toContainText( + 'Status failed domain validation - Invalid keyword arguments: unbalanced expression' + ); + await expect.poll(async () => generatorPage.schema.editor.isRowEditorMode()).toBe(false); + await expect(generatorPage.schema.modeToggleButton).toHaveText('Edit as Schema'); + + expectNoPageErrors(pageErrors); + }); + + test('invalid enum text shows a schema error when previewing generator data', async ({ page }) => { + const { generatorPage, pageErrors } = await openGenerator(page); + + await generatorPage.schema.setSchemaText('Status\ndatatype.enum(values="")'); + await generatorPage.preview.clickPreview(); + + await expect(generatorPage.schema.errorStatus).toContainText( + 'Status failed domain validation - Invalid keyword arguments: argument "values" is required' + ); + await expect.poll(async () => generatorPage.preview.getOutputPreviewText()).toBe(''); + + expectNoPageErrors(pageErrors); + }); + + test('invalid enum text shows a schema error when generating data', async ({ page }) => { + const { generatorPage, pageErrors } = await openGenerator(page); + + await generatorPage.schema.setSchemaText('Status\ndatatype.enum()'); + await generatorPage.generateOptions.clickGenerateData(); + + await expect(generatorPage.schema.errorStatus).toContainText( + 'Status failed domain validation - Invalid keyword arguments: argument "values" is required' + ); + + expectNoPageErrors(pageErrors); + }); + test('schema help shows tippy tooltip content for faker and literal', async ({ page }) => { const { generatorPage, pageErrors } = await openGenerator(page); @@ -131,7 +175,7 @@ test.describe('Generator Schema Editing', () => { expectNoPageErrors(pageErrors); }); - test('domain rows with invalid params stay domain after text mode round-trip in the generator editor', async ({ + test('domain rows with invalid params show a schema error after text mode round-trip in the generator editor', async ({ page, }) => { const { generatorPage, pageErrors } = await openGenerator(page); @@ -142,14 +186,10 @@ test.describe('Generator Schema Editing', () => { await generatorPage.schema.editor.setRowField(0, 'params', '(10)'); await generatorPage.schema.setTextMode(true); - await generatorPage.schema.setTextMode(false); + await generatorPage.schema.modeToggleButton.click(); - await expect(generatorPage.schema.row(0).locator('select[data-field="sourceType"]')).toHaveValue('domain'); - await expect(generatorPage.schema.row(0).locator('[data-action="pick-command"]')).toHaveText('person.fullName'); - await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue('(10)'); - await expect(generatorPage.schema.row(0).locator('.shared-schema-row-validation')).toContainText( - 'invalid domain params' - ); + await expect(generatorPage.schema.errorStatus).toContainText('Name failed domain validation'); + await expect.poll(async () => generatorPage.schema.editor.isRowEditorMode()).toBe(false); expectNoPageErrors(pageErrors); }); @@ -198,16 +238,16 @@ test.describe('Generator Schema Editing', () => { await generatorPage.schema.setTextMode(false); await generatorPage.schema.setRowName(0, 'Phone'); - await generatorPage.schema.editor.setRowTypeValue(0, 'phone.number'); - await generatorPage.schema.editor.setRowField(0, 'params', 'style=13'); + await generatorPage.schema.editor.setRowTypeValue(0, 'string.alpha'); + await generatorPage.schema.editor.setRowField(0, 'params', 'length=4'); await generatorPage.schema.setTextMode(true); - await expect.poll(async () => generatorPage.schema.getSchemaText()).toContain('phone.number(style=13)'); + await expect.poll(async () => generatorPage.schema.getSchemaText()).toContain('string.alpha(length=4)'); await generatorPage.schema.setTextMode(false); await expect(generatorPage.schema.row(0).locator('select[data-field="sourceType"]')).toHaveValue('domain'); - await expect(generatorPage.schema.row(0).locator('[data-action="pick-command"]')).toHaveText('phone.number'); - await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue('(style=13)'); + await expect(generatorPage.schema.row(0).locator('[data-action="pick-command"]')).toHaveText('string.alpha'); + await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue('(length=4)'); expectNoPageErrors(pageErrors); }); @@ -226,7 +266,9 @@ test.describe('Generator Schema Editing', () => { await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue( '(active,inactive,pending)' ); - await expect.poll(async () => generatorPage.schema.getSchemaText()).toContain('enum(active,inactive,pending)'); + await expect + .poll(async () => generatorPage.schema.getSchemaText()) + .toContain('enum("active","inactive","pending")'); expectNoPageErrors(pageErrors); }); diff --git a/apps/web/src/tests/browser/generator/functional/schema-file-load-save.spec.js b/apps/web/src/tests/browser/generator/functional/schema-file-load-save.spec.js index 607526a0..b62f9240 100644 --- a/apps/web/src/tests/browser/generator/functional/schema-file-load-save.spec.js +++ b/apps/web/src/tests/browser/generator/functional/schema-file-load-save.spec.js @@ -9,7 +9,7 @@ test.describe('Generator Schema File Load Save', () => { await generatorPage.schema.loadSchemaFile({ name: 'schema.txt', mimeType: 'text/plain', - buffer: Buffer.from('Generated Name\nliteral(Ada)\nGenerated Status\nenum(active,inactive)'), + buffer: Buffer.from('Generated Name\nliteral(Ada)\nGenerated Status\nactive,inactive'), }); await expect.poll(async () => generatorPage.schema.getSchemaText()).toContain('Generated Name'); diff --git a/docs-src/blog/2026-05-18-domain-abstraction-over-raw-faker.md b/docs-src/blog/2026-05-18-domain-abstraction-over-raw-faker.md index 58cb7eb4..46265ad2 100644 --- a/docs-src/blog/2026-05-18-domain-abstraction-over-raw-faker.md +++ b/docs-src/blog/2026-05-18-domain-abstraction-over-raw-faker.md @@ -100,7 +100,7 @@ The second version is shorter, easier to read, and less sensitive to upstream li ```text # pairwise enums HTTP Method -enum(GET,POST,PUT,DELETE) +GET,POST,PUT,DELETE # pairwise enums Content Type diff --git a/docs-src/docs/040-test-data/010-test-data-generation.md b/docs-src/docs/040-test-data/010-test-data-generation.md index 9815f20e..b9b79d2e 100644 --- a/docs-src/docs/040-test-data/010-test-data-generation.md +++ b/docs-src/docs/040-test-data/010-test-data-generation.md @@ -1,13 +1,13 @@ --- sidebar_position: 1 title: "Test Data Generation" -description: "Overview of test-data generation workflows in app.html and generate.html." +description: "Overview of test-data generation workflows in app.html and generator.html." --- AnyWayData offers two main web UI workflows for generating and working with test data: - **Data Grid Editable** (`app.html`) for interactive grid-first editing and generation -- **Generate to File** (`generate.html`) for schema-driven generation and direct file output +- **Generate to File** (`generator.html`) for schema-driven generation and direct file output ## Choose a Workflow @@ -21,7 +21,7 @@ Use this when you want to: See [Data Grid Editable](/docs/test-data/data-grid-editable). -### Generate to File (`generate.html`) +### Generate to File (`generator.html`) Use this when you want to: diff --git a/docs-src/docs/040-test-data/016-generate-to-file.md b/docs-src/docs/040-test-data/016-generate-to-file.md index 65317399..19e95cb7 100644 --- a/docs-src/docs/040-test-data/016-generate-to-file.md +++ b/docs-src/docs/040-test-data/016-generate-to-file.md @@ -1,12 +1,12 @@ --- sidebar_position: 1.6 title: "Generate to File" -description: "Use generate.html for schema-first data generation with output preview and direct file download." +description: "Use generator.html for schema-first data generation with output preview and direct file download." --- The **Generate to File** workflow is available at: -- `https://anywaydata.com/generate.html` +- `https://anywaydata.com/generator.html` It is designed for schema-driven generation where your main goal is to produce output files quickly. @@ -50,7 +50,7 @@ See [All Pairs Combinatorial Testing](/docs/test-data/pairwise-testing) for deta ## Relationship to `app.html` -Use `generate.html` when file output speed and schema-driven generation are the priority. +Use `generator.html` when file output speed and schema-driven generation are the priority. Use `app.html` when you need richer interactive table editing before or after generation. diff --git a/docs-src/docs/040-test-data/018-Schema-Definition.md b/docs-src/docs/040-test-data/018-Schema-Definition.md index 1ee8296b..e23b0b7f 100644 --- a/docs-src/docs/040-test-data/018-Schema-Definition.md +++ b/docs-src/docs/040-test-data/018-Schema-Definition.md @@ -4,7 +4,7 @@ title: "Schema Definition" description: "Full reference for schema text format, field rules, and IF ... THEN schema constraints." --- -The schema editor in `app.html` and `generate.html` uses a plain text format. +The schema editor in `app.html` and `generator.html` uses a plain text format. This page explains: diff --git a/docs-src/docs/040-test-data/035-auto-increment-sequences.md b/docs-src/docs/040-test-data/035-auto-increment-sequences.md index 2f0150ac..5bacdb30 100644 --- a/docs-src/docs/040-test-data/035-auto-increment-sequences.md +++ b/docs-src/docs/040-test-data/035-auto-increment-sequences.md @@ -65,9 +65,9 @@ This command is especially useful when you generate rows under constraints. Ticket autoIncrement.sequence(prefix="T-", zeropadding=4) Priority -enum(High,Low) +High,Low Status -enum(Open,Closed) +Open,Closed IF [Priority] = "High" THEN [Status] = "Open"; ``` diff --git a/docs-src/docs/040-test-data/050-pairwise-testing.md b/docs-src/docs/040-test-data/050-pairwise-testing.md index c3f72665..a1872672 100644 --- a/docs-src/docs/040-test-data/050-pairwise-testing.md +++ b/docs-src/docs/040-test-data/050-pairwise-testing.md @@ -96,33 +96,33 @@ For more complex scenarios, you can use function-based formats: ``` # function style enum Priority -enum(high,medium,low) +high,medium,low # another function style enum Status -enum(active,inactive,pending) +active,inactive,pending ``` #### Datatype enum() Function ``` # datatype enum form Priority -datatype.enum(high,medium,low) +datatype.enum(csv="high,medium,low") # second datatype enum Status -datatype.enum(active,inactive,pending) +datatype.enum(csv="active,inactive,pending") ``` #### Full AWD enum() Function ``` # fully-qualified enum form Priority -awd.datatype.enum(high,medium,low) +awd.datatype.enum(csv="high,medium,low") # fully-qualified enum form Status -awd.datatype.enum(active,inactive,pending) +awd.datatype.enum(csv="active,inactive,pending") ``` ### Quoted Values for Complex Data @@ -171,7 +171,7 @@ Consider testing an API with both categorical parameters (that need pairwise cov ``` # pairwise enums HTTP Method -enum(GET,POST,PUT,DELETE) +GET,POST,PUT,DELETE # pairwise enums Content Type diff --git a/docs-src/docs/040-test-data/domain/100-datatype.md b/docs-src/docs/040-test-data/domain/100-datatype.md index a1d932a5..d39753d6 100644 --- a/docs-src/docs/040-test-data/domain/100-datatype.md +++ b/docs-src/docs/040-test-data/domain/100-datatype.md @@ -42,3 +42,31 @@ datatype.boolean(probability=0.5) ``` Returns: `true` + +### `datatype.enum` + +Enum helper accepts CSV values or a string array and returns one value at random. Bare CSV is supported as schema shorthand; function calls use quoted strings, arrays, or named arguments. + +- Canonical: `awd.domain.datatype.enum` + +| Arg | Type | Required | Description | +| --- | --- | --- | --- | +| `values` | `comma-separated list\|array` | yes | List of allowed enum values chosen at random during generation. Named csv="..." is also accepted as a CSV-string alias for this argument. | + +Examples: + +Shows the canonical datatype enum helper using a named CSV argument. The same public enum can also be authored as enum("active","inactive","pending") or the schema shorthand active,inactive,pending. + +```txt +datatype.enum(csv="active,inactive,pending") +``` + +Returns: `inactive` + +Shows the string-array form for values that should be parsed directly instead of as CSV text. The fully-qualified compatibility alias awd.datatype.enum(...) also normalizes to this same datatype.enum command internally. + +```txt +datatype.enum(values=["GET","POST","PUT","PATCH"]) +``` + +Returns: `PUT` diff --git a/docs-src/docs/040-test-data/domain/190-location.md b/docs-src/docs/040-test-data/domain/190-location.md index bf65240c..b7607a17 100644 --- a/docs-src/docs/040-test-data/domain/190-location.md +++ b/docs-src/docs/040-test-data/domain/190-location.md @@ -97,18 +97,28 @@ Returns a random cardinal direction (north, east, south, west). - Canonical: `awd.domain.location.cardinalDirection` - Faker docs: [https://fakerjs.dev/api/location](https://fakerjs.dev/api/location) -No parameters. +| Arg | Type | Required | Description | +| --- | --- | --- | --- | +| `abbreviated` | `boolean` | no | If true this will return abbreviated cardinal directions (N, E, S, W). Otherwise this will return the long name. | Examples: -Shows the default location.cardinalDirection call. +Shows location.cardinalDirection when optional params are omitted. ```txt -location.cardinalDirection +location.cardinalDirection() ``` Returns: `East` +Shows location.cardinalDirection using abbreviated. + +```txt +location.cardinalDirection(abbreviated=true) +``` + +Returns: `E` + ### `location.city` Generates a random localized city name. @@ -344,18 +354,28 @@ Returns a random ordinal direction (northwest, southeast, etc). - Canonical: `awd.domain.location.ordinalDirection` - Faker docs: [https://fakerjs.dev/api/location](https://fakerjs.dev/api/location) -No parameters. +| Arg | Type | Required | Description | +| --- | --- | --- | --- | +| `abbreviated` | `boolean` | no | If true this will return abbreviated ordinal directions (NW, SE, etc). Otherwise this will return the long name. | Examples: -Shows the default location.ordinalDirection call. +Shows location.ordinalDirection when optional params are omitted. ```txt -location.ordinalDirection +location.ordinalDirection() ``` Returns: `Northwest` +Shows location.ordinalDirection using abbreviated. + +```txt +location.ordinalDirection(abbreviated=true) +``` + +Returns: `NW` + ### `location.secondaryAddress` Generates a random localized secondary address. This refers to a specific location at a given address diff --git a/docs-src/docs/040-test-data/faker/000-faker-based-data.mdx b/docs-src/docs/040-test-data/faker/000-faker-based-data.mdx index 070f69dc..229b95ab 100644 --- a/docs-src/docs/040-test-data/faker/000-faker-based-data.mdx +++ b/docs-src/docs/040-test-data/faker/000-faker-based-data.mdx @@ -45,7 +45,7 @@ Use domain docs for domain-first schemas: [Domain Test Data](/docs/test-data/dom ```txt Sentence -helpers.mustache("I found {{count}} instances.", { count: () => `${this.number.int()}` }) +helpers.mustache("Hello {{name}}", { name: "Ada" }) ``` ```txt @@ -55,10 +55,10 @@ helpers.fake("Hi, my name is {{person.firstName}} {{person.lastName}}!") ```txt Direction -faker.location.cardinalDirection({ abbreviated: true }) +location.direction(abbreviated=true) ``` -Note: non-helper faker calls may be superseded by domain mappings over time. +Note: non-helper faker calls may be superseded by domain mappings over time. Prefer the documented domain syntax when a domain equivalent exists. ## See Domain Examples diff --git a/docs-src/docs/040-test-data/faker/010-helpers.md b/docs-src/docs/040-test-data/faker/010-helpers.md index 65fb67ca..3f995521 100644 --- a/docs-src/docs/040-test-data/faker/010-helpers.md +++ b/docs-src/docs/040-test-data/faker/010-helpers.md @@ -35,7 +35,7 @@ helpers.fake("Hi, my name is {{person.firstName}} {{person.lastName}}!") ``` ```txt -helpers.mustache("I found {{count}} items.", { count: () => `${this.number.int()}` }) +helpers.mustache("Hello {{name}}", { name: "Ada" }) ``` ```txt @@ -114,7 +114,7 @@ helpers.multiple(() => this.person.firstName(), { count: 3 }) - Many helper functions can return arrays or objects depending on method and inputs. - Prefer scalar-returning helpers when using grid/display flows that expect single values. -- Helper methods support callback/object shapes from Faker docs and are intentionally not represented in `domain.*`. +- Some Faker helper callback shapes are not supported in browser schema text. Use the executable schema examples on this page when copying examples into AnyWayData. ## Faker Reference diff --git a/docs/frontend-ui-matrix-rationalization-plan.md b/docs/frontend-ui-matrix-rationalization-plan.md index 711c99ba..d7c239eb 100644 --- a/docs/frontend-ui-matrix-rationalization-plan.md +++ b/docs/frontend-ui-matrix-rationalization-plan.md @@ -70,7 +70,7 @@ The frontend test stack should follow these rules: ## Current State -Current matrix suites under `packages/core-ui/src/tests/interaction/matrix/` cover three different concerns: +Current matrix suites under `packages/core-ui/src/tests/interaction/matrix/` cover two retained concerns: - `schema-interaction-runtime-matrix.test.js` - scenario execution against the runtime/data-generation layer @@ -78,10 +78,8 @@ Current matrix suites under `packages/core-ui/src/tests/interaction/matrix/` cov - standalone generator UI scenario execution in JSDOM - `app-schema-interaction-matrix.test.js` - embedded app test-data panel scenario execution in JSDOM -- `ui-schema-interaction-parity.test.js` - - parity comparison between app and generator scenario outputs -The current issue is not that matrix coverage is useless. The issue is that the app/generator UI and parity matrices now overlap heavily with shared component tests and browser flows, while still being expensive and brittle. +The app/generator UI matrices are intentionally small page-wiring smoke suites. Broad app-vs-generator parity coverage was removed because both pages now exercise the same shared internals, while runtime semantics, focused component tests, and browser workflows provide clearer protection. ## Keep / Reduce / Remove Guidance @@ -150,14 +148,14 @@ Exit criteria: - There is a documented target layer for every major test-data behavior. - We have a replacement destination for any matrix assertion we plan to remove. -## Phase 3: Shrink Cross-Page Parity To A Representative Contract +## Phase 3: Retire Broad Cross-Page Parity -Goal: keep only the parity checks that still prove something unique. +Goal: keep cross-page checks only where they prove something unique. -- [ ] Replace the large `ui-schema-interaction-parity.test.js` sweep with a much smaller representative parity set. -- [ ] Keep scenarios only for composition points that can still diverge between app and generator. -- [ ] Prefer exact parity checks for a few canonical scenarios rather than structural parity checks for many scenarios. -- [ ] Remove parity cases that merely re-prove shared component output through both shells. +- [x] Remove the large `ui-schema-interaction-parity.test.js` sweep. +- [x] Remove parity fixture generation and static parity/report artifacts. +- [x] Keep app and generator page coverage as separate smoke suites so failures identify the broken shell directly. +- [x] Rely on runtime matrix coverage for broad scenario semantics and browser tests for user-visible workflows. Candidate parity survivors: @@ -169,8 +167,8 @@ Candidate parity survivors: Exit criteria: -- Cross-page parity coverage is small, fast, and easy to interpret. -- Failing parity tests point to real app-vs-generator divergence, not broad duplicated behavior. +- Cross-page parity coverage no longer duplicates shared component output through both shells. +- Failing page-shell tests point to the app or generator wiring that actually regressed. ## Phase 4: Move Shared Behavior Coverage Down To Components @@ -218,9 +216,9 @@ Exit criteria: Goal: reduce maintenance overhead in the test infrastructure itself. -- [ ] Remove harness code that exists only for broad parity sweeps no longer required. +- [x] Remove harness code that exists only for broad parity sweeps no longer required. - [ ] Simplify app/generator interaction harnesses to the smaller retained scenario sets. -- [ ] Delete helper utilities and fixture complexity that no longer serve a retained suite. +- [x] Delete helper utilities and fixture complexity that no longer serve a retained suite. - [ ] Update contributor docs so the intended role of runtime matrix, component tests, and browser tests is explicit. Exit criteria: @@ -248,7 +246,7 @@ Exit criteria: This plan is complete when: -- broad duplicated app-vs-generator parity coverage has been reduced to a small intentional contract +- broad duplicated app-vs-generator parity coverage has been retired in favor of separate smoke suites - shared behavior is primarily covered by component tests - page-specific behavior is primarily covered by focused integration and browser tests - runtime semantics remain covered independently of page-shell duplication @@ -260,7 +258,7 @@ The safest first implementation slice is: 1. Complete Phase 0 and Phase 1 as a paper audit. 2. Complete Phase 2 as the replacement coverage model. -3. Shrink `ui-schema-interaction-parity.test.js` to a representative subset. +3. Retire `ui-schema-interaction-parity.test.js` once app/generator smoke coverage is in place. 4. Keep the runtime matrix unchanged. 5. Re-run coverage review before shrinking the app and generator JSDOM matrices. @@ -268,18 +266,19 @@ That path removes the most obvious duplication first while preserving the highes ### Current Status -- The runtime matrix remains unchanged. +- The runtime matrix remains the broad generated safety net. - `app-schema-interaction-matrix.test.js` and `generator-schema-interaction-matrix.test.js` now run a page-wiring smoke subset instead of the full shared `uiScenarios` fixture. -- The retained page-shell smoke scenarios are: - - `custom-literal-base` - - `custom-regex-base` - - `faker-helpers-arrayElement-base` - - `domain-commerce-price-example-1` - - `custom-enum-pairwise` +- The retained page-shell smoke scenarios are selected by semantic coverage lane: + - custom literal + - custom regex + - faker `helpers.arrayElement` + - domain `commerce.price` example with guided params + - enum pairwise - The smoke subset keeps: - simple schema editing and generate wiring - regex/text synchronization coverage - one representative faker helper flow - one representative domain/options flow - pairwise wiring coverage -- The broader semantics matrix still lives in `schema-interaction-runtime-matrix.test.js`, with parity sweeps remaining opt-in. +- The broad app-vs-generator parity sweep, parity fixture writer, parity fixture JSON, and static matrix summary artifact have been removed. +- The retained matrix formatting helper only formats chunk labels and command summaries for the active runtime and smoke suites. diff --git a/docs-src/docs/040-test-data/035-method-picker-ui-spec.md b/docs/method-picker-ui-spec.md similarity index 96% rename from docs-src/docs/040-test-data/035-method-picker-ui-spec.md rename to docs/method-picker-ui-spec.md index 81702018..5279b1c6 100644 --- a/docs-src/docs/040-test-data/035-method-picker-ui-spec.md +++ b/docs/method-picker-ui-spec.md @@ -1,7 +1,3 @@ ---- -title: Method Picker UI Spec ---- - # Method Picker UI Spec ## Purpose @@ -60,7 +56,7 @@ Applies to both schema-editing surfaces: ## Data + Metadata Sources - Command lists: - - domain: visible domain catalog (`getVisibleDomainCommands`) + - domain: visible domain catalog (`getVisibleDomainCommands`), including supported domain commands such as `datatype.enum` - faker: approved `helpers.*` commands - top-level schema types: `enum`, `literal`, `regex` - Help metadata: diff --git a/packages/core-ui/js/gui_components/app/import-export-download-control/import-export-download-control-view.js b/packages/core-ui/js/gui_components/app/import-export-download-control/import-export-download-control-view.js index 506cfdbf..675c16d3 100644 --- a/packages/core-ui/js/gui_components/app/import-export-download-control/import-export-download-control-view.js +++ b/packages/core-ui/js/gui_components/app/import-export-download-control/import-export-download-control-view.js @@ -27,7 +27,7 @@ class ImportExportDownloadControlView { return `