Skip to content

Commit 7b6a120

Browse files
authored
Fix auto defect regressions (#285)
* Fix auto defect regressions * Address PR review feedback
1 parent e160997 commit 7b6a120

10 files changed

Lines changed: 375 additions & 31 deletions

File tree

apps/web/src/tests/browser/app/functional/test-data/text-schema-grid-sync.spec.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ test.describe('7. Test Data Generation', () => {
153153
expectNoPageErrors(pageErrors);
154154
});
155155

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

173+
await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(true);
174+
await expect.poll(async () => appPage.testDataPanel.getSchemaErrorText()).toBe('');
173175
await expect
174-
.poll(async () => appPage.testDataPanel.getSchemaErrorText())
175-
.toContain('Name failed domain validation');
176-
await expect.poll(async () => appPage.testDataPanel.isRowEditorMode()).toBe(false);
176+
.poll(async () => await appPage.testDataPanel.getSchemaValidationMessage(0).textContent(), { timeout: 2500 })
177+
.toContain('invalid domain params');
177178

178179
expectNoPageErrors(pageErrors);
179180
});

apps/web/src/tests/browser/generator/functional/schema-edit.spec.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ test.describe('Generator Schema Editing', () => {
345345
expectNoPageErrors(pageErrors);
346346
});
347347

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

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

364368
expectNoPageErrors(pageErrors);
365369
});

packages/core-ui/js/gui_components/data-grid-editor/tabulator/gridExtension-tabulator.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,13 @@ class GridExtensionTabulator {
744744
}
745745

746746
_setBulkData(rows) {
747-
if (typeof this.tabulator.replaceData === 'function') {
748-
return this.tabulator.replaceData(rows);
749-
}
750-
return this.tabulator.setData(rows);
747+
const setResult =
748+
typeof this.tabulator.replaceData === 'function'
749+
? this.tabulator.replaceData(rows)
750+
: this.tabulator.setData(rows);
751+
return Promise.resolve(setResult).then(() => {
752+
this._reapplyActiveFiltersAfterBulkMutation();
753+
});
751754
}
752755

753756
async _autoFitFirstColumn() {
@@ -1067,12 +1070,26 @@ class GridExtensionTabulator {
10671070
_refreshTableAfterBulkMutation() {
10681071
if (typeof this.tabulator.refreshData === 'function') {
10691072
this.tabulator.refreshData();
1073+
this._reapplyActiveFiltersAfterBulkMutation();
10701074
return;
10711075
}
10721076

10731077
if (typeof this.tabulator.redraw === 'function') {
10741078
this.tabulator.redraw(true);
10751079
}
1080+
this._reapplyActiveFiltersAfterBulkMutation();
1081+
}
1082+
1083+
_reapplyActiveFiltersAfterBulkMutation() {
1084+
if (typeof this.tabulator.refreshFilter === 'function') {
1085+
this.tabulator.refreshFilter();
1086+
return;
1087+
}
1088+
1089+
const activeGlobalFilter = String(this.tabUtils?.getActiveGlobalFilterQuery?.() || '').trim();
1090+
if (activeGlobalFilter.length > 0) {
1091+
this.tabUtils.filterAcrossAllColumns(activeGlobalFilter);
1092+
}
10761093
}
10771094

10781095
onGridChanged(callback) {

packages/core-ui/js/gui_components/shared/test-data/schema/shared-schema-editor-controller.js

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { applySchemaCommandSelection } from './schema-row-mapper.js';
2121
import { schemaRowsToSpecWithTokens } from './schema-editor-core.js';
2222
import { schemaErrorsToText } from './schema-error-text.js';
23-
import { getSchemaRowSemanticValidationIssues } from './schema-row-validation.js';
23+
import { getSchemaRowSemanticValidationIssues, getStaticSchemaRowValidationIssues } from './schema-row-validation.js';
2424
import { captureActiveFieldState, restoreActiveFieldState } from './schema-focus-state.js';
2525
import { getDefaultDocumentObj, resolveWindowObj } from '../../dom/default-objects.js';
2626
import { createConfirmDialogService } from '../../dialog-services/confirm-dialog-service.js';
@@ -424,31 +424,73 @@ function createSharedSchemaEditorController({
424424
parsed.errors.length > 0 &&
425425
parsed.errors.every((error) => error?.code === 'compiler_validation_error');
426426

427-
const getInvalidSchemaRowIndexes = (parsed) => {
427+
const SCHEMA_UI_BLOCKING_ROW_ERROR_CODES = new Set([
428+
'missing_domain_command',
429+
'unknown_domain_command',
430+
'helpers_not_supported_in_domain',
431+
'missing_faker_command',
432+
'unknown_faker_command',
433+
'forbidden_faker_command',
434+
]);
435+
436+
const getSchemaErrorRowIndex = (parsed, error) => {
428437
const rows = Array.isArray(parsed?.rows) ? parsed.rows : [];
438+
const column = String(error?.column ?? '').trim();
439+
if (column.length > 0) {
440+
const columnIndex = rows.findIndex((row) => String(row?.name ?? '').trim() === column);
441+
if (columnIndex >= 0) {
442+
return columnIndex;
443+
}
444+
}
445+
446+
const line = Number(error?.line);
447+
if (Number.isInteger(line)) {
448+
const ruleIndex = (Array.isArray(parsed?.tokens) ? parsed.tokens : [])
449+
.filter((token) => token?.kind === 'rule')
450+
.findIndex((token) => token?.line === line || token?.ruleLine === line || token?.headerLine === line);
451+
if (ruleIndex >= 0 && ruleIndex < rows.length) {
452+
return ruleIndex;
453+
}
454+
}
455+
456+
return -1;
457+
};
458+
459+
const getInvalidSchemaRowIndexes = (parsed) => {
429460
const invalidIndexes = new Set();
430461
(Array.isArray(parsed?.errors) ? parsed.errors : []).forEach((error) => {
431-
const column = String(error?.column ?? '').trim();
432-
if (column.length > 0) {
433-
rows.forEach((row, index) => {
434-
if (String(row?.name ?? '').trim() === column) {
435-
invalidIndexes.add(index);
436-
}
437-
});
438-
}
439-
const line = Number(error?.line);
440-
if (Number.isInteger(line)) {
441-
const ruleIndex = (Array.isArray(parsed?.tokens) ? parsed.tokens : [])
442-
.filter((token) => token?.kind === 'rule')
443-
.findIndex((token) => token?.line === line || token?.ruleLine === line || token?.headerLine === line);
444-
if (ruleIndex >= 0 && ruleIndex < rows.length) {
445-
invalidIndexes.add(ruleIndex);
446-
}
462+
const rowIndex = getSchemaErrorRowIndex(parsed, error);
463+
if (rowIndex >= 0) {
464+
invalidIndexes.add(rowIndex);
447465
}
448466
});
449467
return invalidIndexes;
450468
};
451469

470+
const isSchemaRowEditableForCompilerValidationError = (row, rowIndex) => {
471+
const sourceType = normaliseSourceType(row?.sourceType);
472+
if (sourceType !== SOURCE_TYPE_DOMAIN && sourceType !== SOURCE_TYPE_FAKER) {
473+
return false;
474+
}
475+
return !getStaticSchemaRowValidationIssues(row, rowIndex).some((issue) =>
476+
SCHEMA_UI_BLOCKING_ROW_ERROR_CODES.has(issue?.code)
477+
);
478+
};
479+
480+
const canEditCompilerValidationErrorsInSchemaRows = (parsed) => {
481+
if (!canRecoverSchemaDefinitionErrorsAsLiterals(parsed)) {
482+
return false;
483+
}
484+
485+
return parsed.errors.every((error) => {
486+
const rowIndex = getSchemaErrorRowIndex(parsed, error);
487+
if (rowIndex < 0) {
488+
return false;
489+
}
490+
return isSchemaRowEditableForCompilerValidationError(parsed.rows[rowIndex], rowIndex);
491+
});
492+
};
493+
452494
const convertInvalidSchemaRowsToLiterals = (parsed) => {
453495
const rows = Array.isArray(parsed?.rows) ? parsed.rows : [];
454496
const ruleTokens = (Array.isArray(parsed?.tokens) ? parsed.tokens : []).filter((token) => token?.kind === 'rule');
@@ -592,6 +634,13 @@ function createSharedSchemaEditorController({
592634
if (session.getTextMode()) {
593635
const parsed = syncFromText({ showErrors: true });
594636
if (parsed?.errors?.length > 0) {
637+
if (canEditCompilerValidationErrorsInSchemaRows(parsed)) {
638+
clearSchemaError();
639+
session.setTextMode(false);
640+
updateModeView();
641+
applySemanticValidationForAllRows();
642+
return { rows: session.getRows(), errors: parsed.errors, tokens: session.getTokens() };
643+
}
595644
if (!canRecoverSchemaDefinitionErrorsAsLiterals(parsed)) {
596645
return parsed;
597646
}

packages/core-ui/src/tests/grid/tabulator-duplicate-column-copy.test.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,47 @@ function createTabulatorStub() {
4646
};
4747
}
4848

49+
function createGenericDataTable({ headers, rows }) {
50+
return {
51+
getColumnCount: () => headers.length,
52+
getHeaders: () => headers,
53+
getRowCount: () => rows.length,
54+
getRowAsObjectUsingHeadings: (rowIndex, fieldNames) =>
55+
Object.fromEntries(fieldNames.map((fieldName, index) => [fieldName, rows[rowIndex][index]])),
56+
};
57+
}
58+
59+
function addFilterSupport(tabulator) {
60+
tabulator._filterPredicate = null;
61+
tabulator._activeRows = tabulator._rowData;
62+
tabulator.setFilter = jest.fn((predicate) => {
63+
tabulator._filterPredicate = predicate;
64+
tabulator.refreshFilter();
65+
});
66+
tabulator.clearFilter = jest.fn(() => {
67+
tabulator._filterPredicate = null;
68+
tabulator._activeRows = tabulator._rowData;
69+
});
70+
tabulator.refreshFilter = jest.fn(() => {
71+
tabulator._activeRows =
72+
typeof tabulator._filterPredicate === 'function'
73+
? tabulator._rowData.filter((row) => tabulator._filterPredicate(row))
74+
: tabulator._rowData;
75+
});
76+
tabulator.getData = jest.fn((mode) => {
77+
if (mode === 'active') {
78+
return tabulator._activeRows;
79+
}
80+
return tabulator._rowData.map((row) => ({ ...row }));
81+
});
82+
tabulator.getDataCount = jest.fn((mode) => {
83+
if (mode === 'active') {
84+
return tabulator._activeRows.length;
85+
}
86+
return tabulator._rowData.length;
87+
});
88+
}
89+
4990
describe('GridExtensionTabulator duplicate column', () => {
5091
test('tabulator helper returns the underlying addData result for row insertion helpers', () => {
5192
const addDataResult = Promise.resolve('row-added');
@@ -175,4 +216,54 @@ describe('GridExtensionTabulator duplicate column', () => {
175216

176217
expect(gridChanged).toHaveBeenCalledTimes(1);
177218
});
219+
220+
test('setGridFromGenericDataTable refreshes active filters after replacing data', async () => {
221+
const tabulator = createTabulatorStub();
222+
tabulator.setColumns = jest.fn((columns) => {
223+
tabulator._columnDefinitions = columns;
224+
});
225+
tabulator.setData = jest.fn((rows) => {
226+
tabulator._rowData = rows;
227+
});
228+
addFilterSupport(tabulator);
229+
tabulator.getColumn = jest.fn(() => createColumnComponent(tabulator._columnDefinitions[0]));
230+
const extension = new GridExtensionTabulator(tabulator);
231+
232+
extension.filterText('200');
233+
expect(tabulator.getData('active')).toEqual([]);
234+
235+
await extension.setGridFromGenericDataTable(
236+
createGenericDataTable({
237+
headers: ['CaseId'],
238+
rows: [['100'], ['200']],
239+
})
240+
);
241+
242+
expect(tabulator.refreshFilter).toHaveBeenCalledTimes(2);
243+
expect(tabulator.getData('active')).toEqual([{ column1: '200' }]);
244+
});
245+
246+
test('applyGeneratedSchemaAmend refreshes active filters after mutating visible rows', async () => {
247+
const tabulator = createTabulatorStub();
248+
tabulator._columnDefinitions = [{ title: 'CaseId', field: 'column1' }];
249+
tabulator._rowData = [{ column1: '2' }, { column1: '3' }];
250+
tabulator.refreshData = jest.fn();
251+
addFilterSupport(tabulator);
252+
const extension = new GridExtensionTabulator(tabulator);
253+
254+
extension.filterText('2');
255+
expect(tabulator.getData('active')).toEqual([{ column1: '2' }]);
256+
257+
await extension.applyGeneratedSchemaAmend({
258+
mode: 'amend-table',
259+
desiredRowCount: 1,
260+
schemaHeaders: ['CaseId'],
261+
generateRow: () => ['100'],
262+
});
263+
264+
expect(tabulator._rowData).toEqual([{ column1: '100' }, { column1: '3' }]);
265+
expect(tabulator.getData('active')).toEqual([]);
266+
expect(tabulator.refreshData).toHaveBeenCalledTimes(1);
267+
expect(tabulator.refreshFilter).toHaveBeenCalledTimes(2);
268+
});
178269
});

0 commit comments

Comments
 (0)