Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions apps/web/src/stories/shared-schema-definition.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,55 @@ export const ParamsDialog = {
);
},
};

export const EnumParamsDialog = {
render: renderSharedSchemaDefinitionStory,
args: {
storyMinHeight: '820px',
initialRows: [
{
id: 'country-code-row',
name: 'Country Code',
sourceType: 'domain',
command: 'location.countryCode',
params: '',
value: '',
comments: '',
leadingTextLines: [],
},
],
},
parameters: {
layout: 'fullscreen',
docs: {
description: {
story:
'Enum params editing flow. This story demonstrates explicit `type: "enum"` metadata as a dropdown: `location.countryCode` exposes `variant` choices from `enumValues`, and applying a selection writes the generated named params back into the schema row without free-text entry.',
},
},
},
play: async ({ canvasElement }) => {
expectSchemaModeVisible(canvasElement);
const initialRow = canvasElement.querySelector('.shared-schema-row');
const paramsButton = initialRow.querySelector('[data-action="edit-params"]');
expect(paramsButton).not.toBeNull();
await userEvent.click(paramsButton);

const dialog = within(document.body).getByRole('dialog', { name: /edit params for .*location\.countrycode/i });
const dialogScope = within(dialog);
const variantSelect = dialogScope.getByRole('combobox', { name: /variant value/i });
await expect(variantSelect).toBeVisible();
await expect(dialogScope.queryByRole('textbox', { name: /variant value/i })).toBeNull();
await userEvent.selectOptions(variantSelect, 'alpha-3');
await waitFor(() =>
expect(dialog.querySelector('[data-role="params-editor-preview"]')?.textContent || '').toContain(
'variant="alpha-3"'
)
);
await userEvent.click(dialogScope.getByRole('button', { name: /^apply$/i }));

await waitFor(() =>
expect(canvasElement.querySelector('.shared-schema-row [data-field="params"]').value).toBe('(variant="alpha-3")')
);
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,11 @@ test.describe('Generator Schema Editing', () => {
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.schema.setSchemaText('Status\ndatatype.enum(csv="active,,pending")');
await generatorPage.preview.clickPreview();

await expect(generatorPage.schema.errorStatus).toContainText(
'Status failed domain validation - Invalid keyword arguments: argument "values" is required'
'Status failed domain validation - Invalid keyword arguments: enum values cannot be empty'
);
await expect.poll(async () => generatorPage.preview.getOutputPreviewText()).toBe('');

Expand Down Expand Up @@ -476,6 +476,27 @@ test.describe('Generator Schema Editing', () => {
expectNoPageErrors(pageErrors);
});

test('enum command params can be selected through the guided params dialog', async ({ page }) => {
const { generatorPage, pageErrors } = await openGenerator(page);

await generatorPage.schema.setTextMode(false);
await generatorPage.schema.setRowName(0, 'Country Code');
await generatorPage.schema.editor.setRowTypeValue(0, 'location.countryCode');
await generatorPage.schema.editor.editRowEnumParamsWithDialog(0, {
variant: 'alpha-3',
});

await expect(generatorPage.schema.row(0).locator('[data-action="pick-command"]')).toHaveText(
'location.countryCode'
);
await expect(generatorPage.schema.row(0).locator('input[data-field="params"]')).toHaveValue('(variant="alpha-3")');
await expect
.poll(async () => generatorPage.schema.getSchemaText())
.toContain('location.countryCode(variant="alpha-3")');

expectNoPageErrors(pageErrors);
});

test('schema edit buttons states are correct across top middle and bottom rows', async ({ page }) => {
const { generatorPage, pageErrors } = await openGenerator(page);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ class ParamsEditorDialogComponent {
return this.dialog.getByRole('textbox', { name: new RegExp(`^${escapeRegExp(name)} value$`, 'i') });
}

enumSelect(name) {
return this.dialog.getByRole('combobox', { name: new RegExp(`^${escapeRegExp(name)} value$`, 'i') });
}

async setValue(name, value) {
await this.expectOpen();
await this.valueInput(name).fill(String(value));
}

async selectEnumValue(name, value) {
await this.expectOpen();
const option = String(value).length === 0 ? { label: '""' } : String(value);
await this.enumSelect(name).selectOption(option);
}

async apply() {
await this.expectOpen();
await this.applyButton.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class SchemaEditorComponent {
const mapped = this.resolveField(field);
const input = this.row(index).locator(`[data-field="${mapped}"]`);
await input.fill(String(value));
await input.blur();
await this.page.keyboard.press('Tab');
}

async getRowField(index, field) {
Expand Down Expand Up @@ -187,11 +187,23 @@ class SchemaEditorComponent {
}

async editRowParamsWithDialog(index, valuesByName) {
await this.editRowParamsWithDialogFlow(index, valuesByName, async (name, value) => {
await this.paramsEditor.setValue(name, value);
});
}

async editRowEnumParamsWithDialog(index, valuesByName) {
await this.editRowParamsWithDialogFlow(index, valuesByName, async (name, value) => {
await this.paramsEditor.selectEnumValue(name, value);
});
}

async editRowParamsWithDialogFlow(index, valuesByName, setParamValue) {
await this.ensureSchemaMode();
await this.dismissOpenHelpTooltips();
await this.row(index).locator('[data-action="edit-params"]').click();
for (const [name, value] of Object.entries(valuesByName || {})) {
await this.paramsEditor.setValue(name, value);
await setParamValue(name, value);
}
await this.paramsEditor.apply();
}
Expand Down
1 change: 0 additions & 1 deletion docs/domain-faker-param-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,3 @@ Generated by `node scripts/compare-domain-faker-params.mjs --markdown` from the

- `Missing in domain` must stay at `none` for every row.
- `Domain-only params` must stay at `none` for every Faker-backed domain command.

Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ function isDomainEnumCommand(commandValue) {
return /^(?:datatype\.enum|awd\.datatype\.enum)$/i.test(String(commandValue || '').trim());
}

function buildEditableEnumRuleSpec(enumInput, fallbackRuleSpec) {
try {
return EnumParser.buildSchemaRuleSpecFromInput(enumInput);
} catch {
return String(fallbackRuleSpec ?? enumInput ?? '').trim();
}
}

function buildRuleSpecFromSchemaRow(row) {
const sourceType = normaliseSourceType(row?.sourceType);
if (sourceType === SOURCE_TYPE_FAKER) {
Expand All @@ -65,7 +73,7 @@ function buildRuleSpecFromSchemaRow(row) {
allowUnwrapped: isDomainEnumCommand(command),
});
if (isDomainEnumCommand(command)) {
return EnumParser.buildSchemaRuleSpecFromInput(params);
return buildEditableEnumRuleSpec(params, `${command}${normaliseCommandParams(params)}`);
}
return `${command}${params}`;
}
Expand All @@ -92,7 +100,7 @@ function buildRuleSpecFromSchemaRow(row) {
return `regex(${regexValue})`;
}
if (sourceType === SOURCE_TYPE_ENUM) {
return EnumParser.buildSchemaRuleSpecFromInput(row?.value);
return buildEditableEnumRuleSpec(row?.value);
}
return String(row?.value ?? '').trim();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ function extractSimpleDefaultValue(param = {}) {
function normalizeHelpParam(param = {}) {
return {
...param,
allowedValues: Array.isArray(param.allowedValues) ? param.allowedValues : [],
choices: Array.isArray(param.choices) ? param.choices : [],
enumValues: Array.isArray(param.enumValues) ? param.enumValues : [],
optional: param.optional === true || param.required === false,
defaultValue: extractSimpleDefaultValue(param),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ function createSharedSchemaEditorController({
elements.constraintsSummaryElement || getElementByRole(SCHEMA_CONSTRAINTS_SUMMARY_ROLE);
const getConstraintsTextElement = () =>
elements.constraintsTextElement || getElementByRole(SCHEMA_CONSTRAINTS_TEXT_ROLE);
const focusParamsButtonForRow = (rowId) => {
if (!rowId) {
return;
}
Array.from(rootElement?.querySelectorAll?.('[data-action="edit-params"]') || [])
.find((button) => button.getAttribute('data-row-id') === rowId)
?.focus?.();
};

const refreshHelpHints = () => {
updateHelpHints?.();
Expand Down Expand Up @@ -874,6 +882,7 @@ function createSharedSchemaEditorController({
renderRows();
syncTextFromRows();
scheduleSemanticValidationForRow(rowId, { immediate: true });
focusParamsButtonForRow(rowId);
}
} catch (error) {
console.error('Failed opening params editor dialog.', error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ body.theme-dark {

.params-editor-close,
.params-editor-footer button,
[data-role='params-editor-value'] {
[data-role='params-editor-value'],
[data-role='params-editor-enum'] {
border: 1px solid var(--pe-border, #ddd);
border-radius: 8px;
background: var(--pe-bg, #fff);
Expand Down Expand Up @@ -176,7 +177,8 @@ body.theme-dark {
flex: 0 0 auto;
}

[data-role='params-editor-value'] {
[data-role='params-editor-value'],
[data-role='params-editor-enum'] {
display: block;
width: 100%;
max-width: 100%;
Expand All @@ -185,6 +187,10 @@ body.theme-dark {
box-sizing: border-box;
}

[data-role='params-editor-enum'] {
min-height: 38px;
}

.sr-only {
position: absolute;
width: 1px;
Expand Down Expand Up @@ -269,17 +275,91 @@ body.theme-dark {
}

.params-editor-modal button:focus-visible,
.params-editor-modal input:focus-visible {
.params-editor-modal input:focus-visible,
.params-editor-modal select:focus-visible {
outline: 2px solid var(--pe-focus, #4a80ff);
outline-offset: 2px;
}

@media (max-width: 860px) {
.params-editor-overlay {
padding: 12px;
}

.params-editor-modal {
width: 100%;
}

.params-editor-table-wrap {
overflow: visible;
}
}

@media (max-width: 560px) {
.params-editor-header,
.params-editor-footer {
padding: 10px 12px;
}

.params-editor-body {
padding: 12px;
}

.params-editor-table,
.params-editor-table thead,
.params-editor-table tbody,
.params-editor-table tr,
.params-editor-table th,
.params-editor-table td {
display: block;
}

.params-editor-table {
min-width: 720px;
border: 0;
}

.params-editor-table thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}

.params-editor-table tr {
margin-bottom: 12px;
border: 1px solid var(--pe-border, #ddd);
border-radius: 8px;
overflow: hidden;
}

.params-editor-table tr:last-child {
margin-bottom: 0;
}

.params-editor-table td {
display: grid;
grid-template-columns: minmax(64px, 26%) minmax(0, 1fr);
gap: 10px;
align-items: start;
border-bottom: 1px solid var(--pe-border, #ddd);
}

.params-editor-table td::before {
content: attr(data-label);
color: var(--pe-muted, #555);
font-size: 12px;
font-weight: 700;
}

.params-editor-table td:last-child {
border-bottom: 0;
}

.params-editor-name-cell,
.params-editor-boolean-group,
[data-role='params-editor-value'],
[data-role='params-editor-enum'] {
min-width: 0;
}
}
Loading
Loading