Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ test.describe('7. Test Data Generation', () => {
expectNoPageErrors(pageErrors);
});

test('domain rows with invalid params show a schema error after text mode round-trip in the app editor', async ({
test('domain rows with invalid params switch back to schema mode with row validation in the app editor', async ({
page,
}) => {
const { appPage, pageErrors } = await openApp(page);
Expand All @@ -170,10 +170,11 @@ test.describe('7. Test Data Generation', () => {
await appPage.testDataPanel.setSchemaTextMode(true);
await appPage.testDataPanel.schemaEditor.modeToggleButton.click();

await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(true);
await expect.poll(async () => appPage.testDataPanel.getSchemaErrorText()).toBe('');
await expect
.poll(async () => appPage.testDataPanel.getSchemaErrorText())
.toContain('Name failed domain validation');
await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(false);
.poll(async () => await appPage.testDataPanel.getSchemaValidationMessage(0).textContent(), { timeout: 2500 })
.toContain('invalid domain params');

expectNoPageErrors(pageErrors);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ test.describe('Generator Schema Editing', () => {
expectNoPageErrors(pageErrors);
});

test('domain rows with invalid params show a schema error after text mode round-trip in the generator editor', async ({
test('domain rows with invalid params switch back to schema mode with row validation in the generator editor', async ({
page,
}) => {
const { generatorPage, pageErrors } = await openGenerator(page);
Expand All @@ -358,8 +358,12 @@ test.describe('Generator Schema Editing', () => {
await generatorPage.schema.setTextMode(true);
await generatorPage.schema.modeToggleButton.click();

await expect(generatorPage.schema.errorStatus).toContainText('Name failed domain validation');
await expect.poll(async () => generatorPage.schema.editor.isRowEditorMode()).toBe(false);
await expect.poll(async () => generatorPage.schema.editor.isRowEditorMode()).toBe(true);
await expect(generatorPage.schema.errorStatus).toHaveText('');
await expect(generatorPage.schema.row(0).locator('.shared-schema-row-validation')).toContainText(
'invalid domain params',
{ timeout: 2500 }
);

expectNoPageErrors(pageErrors);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,10 +744,13 @@ class GridExtensionTabulator {
}

_setBulkData(rows) {
if (typeof this.tabulator.replaceData === 'function') {
return this.tabulator.replaceData(rows);
}
return this.tabulator.setData(rows);
const setResult =
typeof this.tabulator.replaceData === 'function'
? this.tabulator.replaceData(rows)
: this.tabulator.setData(rows);
return Promise.resolve(setResult).then(() => {
this._reapplyActiveFiltersAfterBulkMutation();
});
}

async _autoFitFirstColumn() {
Expand Down Expand Up @@ -1067,12 +1070,26 @@ class GridExtensionTabulator {
_refreshTableAfterBulkMutation() {
if (typeof this.tabulator.refreshData === 'function') {
this.tabulator.refreshData();
this._reapplyActiveFiltersAfterBulkMutation();
return;
}

if (typeof this.tabulator.redraw === 'function') {
this.tabulator.redraw(true);
}
this._reapplyActiveFiltersAfterBulkMutation();
}

_reapplyActiveFiltersAfterBulkMutation() {
if (typeof this.tabulator.refreshFilter === 'function') {
this.tabulator.refreshFilter();
return;
}

const activeGlobalFilter = String(this.tabUtils?.getActiveGlobalFilterQuery?.() || '').trim();
if (activeGlobalFilter.length > 0) {
this.tabUtils.filterAcrossAllColumns(activeGlobalFilter);
}
}

onGridChanged(callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { applySchemaCommandSelection } from './schema-row-mapper.js';
import { schemaRowsToSpecWithTokens } from './schema-editor-core.js';
import { schemaErrorsToText } from './schema-error-text.js';
import { getSchemaRowSemanticValidationIssues } from './schema-row-validation.js';
import { getSchemaRowSemanticValidationIssues, getStaticSchemaRowValidationIssues } from './schema-row-validation.js';
import { captureActiveFieldState, restoreActiveFieldState } from './schema-focus-state.js';
import { getDefaultDocumentObj, resolveWindowObj } from '../../dom/default-objects.js';
import { createConfirmDialogService } from '../../dialog-services/confirm-dialog-service.js';
Expand Down Expand Up @@ -424,31 +424,73 @@ function createSharedSchemaEditorController({
parsed.errors.length > 0 &&
parsed.errors.every((error) => error?.code === 'compiler_validation_error');

const getInvalidSchemaRowIndexes = (parsed) => {
const SCHEMA_UI_BLOCKING_ROW_ERROR_CODES = new Set([
'missing_domain_command',
'unknown_domain_command',
'helpers_not_supported_in_domain',
'missing_faker_command',
'unknown_faker_command',
'forbidden_faker_command',
]);

const getSchemaErrorRowIndex = (parsed, error) => {
const rows = Array.isArray(parsed?.rows) ? parsed.rows : [];
const column = String(error?.column ?? '').trim();
if (column.length > 0) {
const columnIndex = rows.findIndex((row) => String(row?.name ?? '').trim() === column);
if (columnIndex >= 0) {
return columnIndex;
}
}

const line = Number(error?.line);
if (Number.isInteger(line)) {
const ruleIndex = (Array.isArray(parsed?.tokens) ? parsed.tokens : [])
.filter((token) => token?.kind === 'rule')
.findIndex((token) => token?.line === line || token?.ruleLine === line || token?.headerLine === line);
if (ruleIndex >= 0 && ruleIndex < rows.length) {
return ruleIndex;
}
}

return -1;
};

const getInvalidSchemaRowIndexes = (parsed) => {
const invalidIndexes = new Set();
(Array.isArray(parsed?.errors) ? parsed.errors : []).forEach((error) => {
const column = String(error?.column ?? '').trim();
if (column.length > 0) {
rows.forEach((row, index) => {
if (String(row?.name ?? '').trim() === column) {
invalidIndexes.add(index);
}
});
}
const line = Number(error?.line);
if (Number.isInteger(line)) {
const ruleIndex = (Array.isArray(parsed?.tokens) ? parsed.tokens : [])
.filter((token) => token?.kind === 'rule')
.findIndex((token) => token?.line === line || token?.ruleLine === line || token?.headerLine === line);
if (ruleIndex >= 0 && ruleIndex < rows.length) {
invalidIndexes.add(ruleIndex);
}
const rowIndex = getSchemaErrorRowIndex(parsed, error);
if (rowIndex >= 0) {
invalidIndexes.add(rowIndex);
}
});
return invalidIndexes;
};

const isSchemaRowEditableForCompilerValidationError = (row, rowIndex) => {
const sourceType = normaliseSourceType(row?.sourceType);
if (sourceType !== SOURCE_TYPE_DOMAIN && sourceType !== SOURCE_TYPE_FAKER) {
return false;
}
return !getStaticSchemaRowValidationIssues(row, rowIndex).some((issue) =>
SCHEMA_UI_BLOCKING_ROW_ERROR_CODES.has(issue?.code)
);
};

const canEditCompilerValidationErrorsInSchemaRows = (parsed) => {
if (!canRecoverSchemaDefinitionErrorsAsLiterals(parsed)) {
return false;
}

return parsed.errors.every((error) => {
const rowIndex = getSchemaErrorRowIndex(parsed, error);
if (rowIndex < 0) {
return false;
}
return isSchemaRowEditableForCompilerValidationError(parsed.rows[rowIndex], rowIndex);
});
};

const convertInvalidSchemaRowsToLiterals = (parsed) => {
const rows = Array.isArray(parsed?.rows) ? parsed.rows : [];
const ruleTokens = (Array.isArray(parsed?.tokens) ? parsed.tokens : []).filter((token) => token?.kind === 'rule');
Expand Down Expand Up @@ -592,6 +634,13 @@ function createSharedSchemaEditorController({
if (session.getTextMode()) {
const parsed = syncFromText({ showErrors: true });
if (parsed?.errors?.length > 0) {
if (canEditCompilerValidationErrorsInSchemaRows(parsed)) {
clearSchemaError();
session.setTextMode(false);
updateModeView();
applySemanticValidationForAllRows();
return { rows: session.getRows(), errors: parsed.errors, tokens: session.getTokens() };
}
if (!canRecoverSchemaDefinitionErrorsAsLiterals(parsed)) {
return parsed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,47 @@ function createTabulatorStub() {
};
}

function createGenericDataTable({ headers, rows }) {
return {
getColumnCount: () => headers.length,
getHeaders: () => headers,
getRowCount: () => rows.length,
getRowAsObjectUsingHeadings: (rowIndex, fieldNames) =>
Object.fromEntries(fieldNames.map((fieldName, index) => [fieldName, rows[rowIndex][index]])),
};
}

function addFilterSupport(tabulator) {
tabulator._filterPredicate = null;
tabulator._activeRows = tabulator._rowData;
tabulator.setFilter = jest.fn((predicate) => {
tabulator._filterPredicate = predicate;
tabulator.refreshFilter();
});
tabulator.clearFilter = jest.fn(() => {
tabulator._filterPredicate = null;
tabulator._activeRows = tabulator._rowData;
});
tabulator.refreshFilter = jest.fn(() => {
tabulator._activeRows =
typeof tabulator._filterPredicate === 'function'
? tabulator._rowData.filter((row) => tabulator._filterPredicate(row))
: tabulator._rowData;
});
tabulator.getData = jest.fn((mode) => {
if (mode === 'active') {
return tabulator._activeRows;
}
return tabulator._rowData.map((row) => ({ ...row }));
});
tabulator.getDataCount = jest.fn((mode) => {
if (mode === 'active') {
return tabulator._activeRows.length;
}
return tabulator._rowData.length;
});
}

describe('GridExtensionTabulator duplicate column', () => {
test('tabulator helper returns the underlying addData result for row insertion helpers', () => {
const addDataResult = Promise.resolve('row-added');
Expand Down Expand Up @@ -175,4 +216,54 @@ describe('GridExtensionTabulator duplicate column', () => {

expect(gridChanged).toHaveBeenCalledTimes(1);
});

test('setGridFromGenericDataTable refreshes active filters after replacing data', async () => {
const tabulator = createTabulatorStub();
tabulator.setColumns = jest.fn((columns) => {
tabulator._columnDefinitions = columns;
});
tabulator.setData = jest.fn((rows) => {
tabulator._rowData = rows;
});
addFilterSupport(tabulator);
tabulator.getColumn = jest.fn(() => createColumnComponent(tabulator._columnDefinitions[0]));
const extension = new GridExtensionTabulator(tabulator);

extension.filterText('200');
expect(tabulator.getData('active')).toEqual([]);

await extension.setGridFromGenericDataTable(
createGenericDataTable({
headers: ['CaseId'],
rows: [['100'], ['200']],
})
);

expect(tabulator.refreshFilter).toHaveBeenCalledTimes(2);
expect(tabulator.getData('active')).toEqual([{ column1: '200' }]);
});

test('applyGeneratedSchemaAmend refreshes active filters after mutating visible rows', async () => {
const tabulator = createTabulatorStub();
tabulator._columnDefinitions = [{ title: 'CaseId', field: 'column1' }];
tabulator._rowData = [{ column1: '2' }, { column1: '3' }];
tabulator.refreshData = jest.fn();
addFilterSupport(tabulator);
const extension = new GridExtensionTabulator(tabulator);

extension.filterText('2');
expect(tabulator.getData('active')).toEqual([{ column1: '2' }]);

await extension.applyGeneratedSchemaAmend({
mode: 'amend-table',
desiredRowCount: 1,
schemaHeaders: ['CaseId'],
generateRow: () => ['100'],
});

expect(tabulator._rowData).toEqual([{ column1: '100' }, { column1: '3' }]);
expect(tabulator.getData('active')).toEqual([]);
expect(tabulator.refreshData).toHaveBeenCalledTimes(1);
expect(tabulator.refreshFilter).toHaveBeenCalledTimes(2);
});
});
Loading