From 9c1821363df5444bac25007e1450f82205b9372a Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 3 Jun 2026 02:30:59 +0400 Subject: [PATCH 1/9] DataGrid - AI Assistant: error handling e2e tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit error handling across layers — AI-integration, response-format, validation and per-command execution failures, plus the negative/rejection cases; predefined error text is asserted by localization key (plan §2, §4) Co-Authored-By: Claude Opus 4.8 --- .../aiAssistant/errorHandling.functional.ts | 1276 +++++++++++++++++ .../common/aiAssistant/testHelpers.ts | 9 + 2 files changed, 1285 insertions(+) create mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts new file mode 100644 index 000000000000..a016bdf17428 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -0,0 +1,1276 @@ +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import { createWidget } from '../../../../helpers/createWidget'; +import { AI_INTEGRATION_PAGE, formatMessage, GRID_SELECTOR } from './testHelpers'; + +// All §4.1–§4.3 failures surface the same predefined "invalid response" text. +const invalidResponse = (): Promise => formatMessage( + 'dxDataGrid-aiAssistantInvalidResponseMessage', +); + +// === §2.1 Unsupported / unknown intent === + +fixture.disablePageReloads`AI Assistant - Empty Actions` + .page(AI_INTEGRATION_PAGE); + +// 2.1.3 +test('Empty actions array should show no-action message and leave grid unchanged', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Tell me a joke') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §2.3 Unknown field === + +fixture.disablePageReloads`AI Assistant - Unknown Field` + .page(AI_INTEGRATION_PAGE); + +// 2.3.2 +test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by Salary') + .pressKey('enter'); + + const dataSourceSortParams = await dataGrid.apiGetDataSourceSortParams(); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getErrorActionItems(0).count).eql(1); + await t.expect(dataSourceSortParams).eql(null); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §2.4 Request impossible in current state === + +fixture.disablePageReloads`AI Assistant - Impossible State` + .page(AI_INTEGRATION_PAGE); + +// 2.4.1 +test('Page index out of range should show failure status', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Go to page 10000') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getErrorActionItems(0).count).eql(1); + await t.expect(dataGrid.apiPageIndex()).eql(0); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + paging: { pageSize: 2 }, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 2.4.2 +test('Grouping by non-groupable column should show failure entry', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Group by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getErrorActionItems(0).count).eql(1); + await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: [ + { dataField: 'id' }, + { dataField: 'name', allowGrouping: false }, + { dataField: 'value' }, + ], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 2.4.3 +test('Selecting non-existent keys should show failure or no incorrect rows selected', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Select rows 999 and 1000') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getErrorActionItems(0).count).eql(0); + await t.expect(dataGrid.apiOption('selectedRowKeys')).eql([]); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + selection: { mode: 'multiple' }, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ name: 'selectByKeys', args: { keys: [999, 1000] } }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §2.6 Excessively long prompt / provider error === + +fixture.disablePageReloads`AI Assistant - Provider Error` + .page(AI_INTEGRATION_PAGE); + +// 2.6.2 +test('sendRequest rejection should show error message and leave grid unchanged', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getErrorActionItems(0).count).eql(0); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.reject(new Error('Request payload too large')), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §4.1 AI integration failures → "unexpected error" / "invalid response" === +// Note: 4.1.2 (HTTP error) / 4.1.3 (network error) share the rejection path already +// covered by 2.6.2 above. 4.1.4 (synchronous throw from sendRequest) is NOT caught by +// RequestManager (no try/catch around provider.sendRequest), so it is not exercised here. + +fixture.disablePageReloads`AI Assistant - Integration Failure` + .page(AI_INTEGRATION_PAGE); + +// 4.1.1 +// The plan expects "unexpected error", but the current implementation shows "invalid response" +// (known issue 4284). This locks the CURRENT behavior — switch to the unexpected-error key +// once 4284 is fixed. +test('Missing aiIntegration should show an error and leave grid unchanged', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + }, +}))); + +// 4.1.5 +test('Non-JSON string response should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve('not json'), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.1.6 +test('Non-JSON actions string should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: 'not json' }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.1.7 +test('Empty string response should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve(''), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §4.2 Response format failure → "invalid response" === + +fixture.disablePageReloads`AI Assistant - Response Format` + .page(AI_INTEGRATION_PAGE); + +// 4.2.1 +test('Response missing actions should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({}), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.2.2 +test('Object actions (not array) should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: { name: 'sorting' } }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.2.3 +test('Null actions should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: null }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.2.4 +test('Primitive actions should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: 42 }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.2.5 +test('Null response should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve(null), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §4.3 Validation failure (GridCommands.validate) → "invalid response" === + +fixture.disablePageReloads`AI Assistant - Validation` + .page(AI_INTEGRATION_PAGE); + +// 4.3.1 +test('Unknown command name should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 'unknownCmd', args: {} }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.2 +test('Non-string command name should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 123, args: {} }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.3 +test('Empty command name should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: '', args: {} }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.4 +test('Action without name should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ args: {} }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.5 +test('Action without args should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 'sorting' }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.6 +test('Null args should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 'sorting', args: null }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.7 +test('Args missing a required property should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.8 +test('Args with a wrong-typed property should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.9 +test('Args with an extra property should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [{ + name: 'sorting', + args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' }, + }], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.10 +test('Mix of valid and invalid actions should reject the whole response', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort by name') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); + // Even the valid sorting action must not execute. + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'unknownCmd', args: {} }, + ], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.3.11 +test('No-arg command with non-empty args should show invalid-response error', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Clear sorting') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); + await t.expect(aiChat.getSuccessMessages().count).eql(0); + await t.expect(aiChat.getActionItems(0).count).eql(0); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ actions: [{ name: 'clearSorting', args: { foo: 1 } }] }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// === §4.4 Execution failure → per-command failure message === + +fixture.disablePageReloads`AI Assistant - Execution Failure` + .page(AI_INTEGRATION_PAGE); + +// 4.4.3 (single async command rejects) is not covered as e2e: command executors catch +// rejections internally and return failure(), so the observable result is identical to the +// synchronous-failure path already exercised by 4.4.4 / 4.4.5 and §2.3.2 / §2.4.x. Forcing a +// genuine async rejection needs a custom executor, which the public AIIntegration mock cannot +// inject deterministically (same limitation as 4.4.2 sync-throw and 4.4.6 reentrancy guard). + +// 4.4.4 +test('Partial failure should report each action status and apply successes', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Sort, group and search') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getActionItems(0).count).eql(3); + await t.expect(aiChat.getSuccessActionItems(0).count).eql(2); + await t.expect(aiChat.getErrorActionItems(0).count).eql(1); + // The failed grouping action carries its predefined per-command message. + await t.expect(aiChat.getActionItemText(0, 1).innerText).contains('Group data against'); + // Successful actions (#1 sorting, #3 searching) took effect; failed grouping did not. + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).eql('asc'); + await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: [ + { dataField: 'id' }, + { dataField: 'name', allowGrouping: false }, + { dataField: 'value' }, + ], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + { name: 'searching', args: { text: 'Alice' } }, + ], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); + +// 4.4.5 +test('All-commands failure should report failures and leave grid unchanged', async (t) => { + const dataGrid = new DataGrid(GRID_SELECTOR); + + await t.expect(dataGrid.isReady()).ok(); + + await t.click(dataGrid.getAIAssistantButton()); + + const aiChat = dataGrid.getAIAssistantChat(); + + await t + .typeText(aiChat.getInput(), 'Do impossible things') + .pressKey('enter'); + + await t.expect(aiChat.getMessages().count).eql(2); + await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getActionItems(0).count).eql(3); + await t.expect(aiChat.getSuccessActionItems(0).count).eql(0); + await t.expect(aiChat.getErrorActionItems(0).count).eql(3); + // Each failed action keeps its own predefined per-command message. + await t.expect(aiChat.getActionItemText(0, 0).innerText).contains('Sort data against'); + await t.expect(aiChat.getActionItemText(0, 2).innerText).contains('Group data against'); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); + await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); +}).before(async () => createWidget('dxDataGrid', () => ({ + dataSource: [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, + ], + keyExpr: 'id', + columns: [ + { dataField: 'id' }, + { dataField: 'name', allowGrouping: false }, + { dataField: 'value' }, + ], + showBorders: true, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: Promise.resolve({ + actions: [ + { name: 'sorting', args: { dataField: 'NopeOne', sortOrder: 'asc' } }, + { name: 'sorting', args: { dataField: 'NopeTwo', sortOrder: 'desc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + ], + }), + abort: (): void => {}, + }; + }, + }), + }, +}))); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts index 9ba16903336b..82cb1a502090 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts @@ -103,3 +103,12 @@ export const createGridWithAIAssistant = async ( }; export const getRequests = ClientFunction(() => (window as any).__aiRequests); + +export const getRequestColumnNames = ClientFunction( + (index: number) => (window as any).__aiRequests[index].data.context.columns + .map((c: any) => c.dataField), +); + +export const formatMessage = ClientFunction( + (key: string) => (window as any).DevExpress.localization.formatMessage(key), +); From 68e32e6f0e1af4940e122cd6d8eb4f6985e29303 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Fri, 5 Jun 2026 12:32:53 +0400 Subject: [PATCH 2/9] DataGrid - AI Assistant: dedup error handling e2e tests and drop unused POM methods Extract shared setup/assertion helpers (grid factory, openChatAndSubmit, expectInvalidResponse) into the test file; add missing grid-state and error-text assertions for empty-actions, provider rejection, 4.3.11 and 4.4.4 searching; align fixtures with the commands suite. Remove POM/testHelpers methods added but not used by this PR (focusAIAssistantButton, abort-confirm/suggestion/disabled accessors, getUserMessages, getTitle, getRequestColumnNames). --- .../aiAssistant/errorHandling.functional.ts | 1426 ++++------------- .../common/aiAssistant/testHelpers.ts | 9 - 2 files changed, 317 insertions(+), 1118 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index a016bdf17428..e20de7073168 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -1,19 +1,86 @@ +/* eslint-disable no-underscore-dangle */ import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import { ClientFunction } from 'testcafe'; import { createWidget } from '../../../../helpers/createWidget'; -import { AI_INTEGRATION_PAGE, formatMessage, GRID_SELECTOR } from './testHelpers'; +import { AI_INTEGRATION_PAGE, GRID_SELECTOR } from './testHelpers'; + +type AIChat = ReturnType; + +type AIResponseMock = { kind: 'resolve'; value: unknown } + | { kind: 'reject'; error: string } + | { kind: 'noIntegration' }; + +const threeRows = [ + { id: 1, name: 'Alice', value: 30 }, + { id: 2, name: 'Bob', value: 20 }, + { id: 3, name: 'Charlie', value: 10 }, +]; + +const baseGrid = { + dataSource: threeRows, + keyExpr: 'id', + columns: ['id', 'name', 'value'], + showBorders: true, +}; + +const groupingLockedColumns = [ + { dataField: 'id' }, + { dataField: 'name', allowGrouping: false }, + { dataField: 'value' }, +]; + +const formatMessage = ClientFunction( + (key: string) => (window as any).DevExpress.localization.formatMessage(key), +); -// All §4.1–§4.3 failures surface the same predefined "invalid response" text. const invalidResponse = (): Promise => formatMessage( 'dxDataGrid-aiAssistantInvalidResponseMessage', ); -// === §2.1 Unsupported / unknown intent === - -fixture.disablePageReloads`AI Assistant - Empty Actions` - .page(AI_INTEGRATION_PAGE); - -// 2.1.3 -test('Empty actions array should show no-action message and leave grid unchanged', async (t) => { +const setupAIMock = ClientFunction((base: Record, mock: AIResponseMock) => { + (window as any).__aiBase = base; + (window as any).__aiMock = mock; +}); + +const aiGridOptions = (): any => { + const base = (window as any).__aiBase; + const mock = (window as any).__aiMock; + + if (mock.kind === 'noIntegration') { + return { ...base, aiAssistant: { enabled: true } }; + } + + return { + ...base, + aiAssistant: { + enabled: true, + aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ + sendRequest() { + return { + promise: mock.kind === 'reject' + ? Promise.reject(new Error(mock.error)) + : Promise.resolve(mock.value), + abort: (): void => {}, + }; + }, + }), + }, + }; +}; + +const createGridWithAIAssistant = async ( + base: Record, + mock: AIResponseMock, +): Promise => { + await setupAIMock(base, mock); + + return createWidget('dxDataGrid', aiGridOptions); +}; + +const openChatAndSubmit = async ( + t: TestController, + prompt: string, +): Promise<{ dataGrid: DataGrid; aiChat: AIChat }> => { const dataGrid = new DataGrid(GRID_SELECTOR); await t.expect(dataGrid.isReady()).ok(); @@ -23,53 +90,48 @@ test('Empty actions array should show no-action message and leave grid unchanged const aiChat = dataGrid.getAIAssistantChat(); await t - .typeText(aiChat.getInput(), 'Tell me a joke') + .typeText(aiChat.getInput(), prompt) .pressKey('enter'); + return { dataGrid, aiChat }; +}; + +const expectInvalidResponse = async ( + t: TestController, + aiChat: AIChat, + dataGrid: DataGrid, +): Promise => { await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getActionItems(0).count).eql(0); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); +}; -// === §2.3 Unknown field === +// === §2.1 Unsupported / unknown intent === -fixture.disablePageReloads`AI Assistant - Unknown Field` +fixture`AI Assistant - Empty Actions` .page(AI_INTEGRATION_PAGE); -// 2.3.2 -test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// 2.1.3 — empty actions reject through the same "invalid response" path (§4.5). +test('Empty actions array should show no-action message and leave grid unchanged', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Tell me a joke'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [] } }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// === §2.3 Unknown field === - const aiChat = dataGrid.getAIAssistantChat(); +fixture`AI Assistant - Unknown Field` + .page(AI_INTEGRATION_PAGE); - await t - .typeText(aiChat.getInput(), 'Sort by Salary') - .pressKey('enter'); +// 2.3.2 — schema-valid action, executor fails at runtime → per-action error entry. +test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by Salary'); const dataSourceSortParams = await dataGrid.apiGetDataSourceSortParams(); @@ -78,1104 +140,301 @@ test('Sorting by non-existent dataField should show failure message and leave gr await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(dataSourceSortParams).eql(null); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); +}).before(async () => createGridWithAIAssistant(baseGrid, { + kind: 'resolve', + value: { actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }, +})); // === §2.4 Request impossible in current state === -fixture.disablePageReloads`AI Assistant - Impossible State` +fixture`AI Assistant - Impossible State` .page(AI_INTEGRATION_PAGE); // 2.4.1 test('Page index out of range should show failure status', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Go to page 10000') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Go to page 10000'); await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(dataGrid.apiPageIndex()).eql(0); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - paging: { pageSize: 2 }, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, paging: { pageSize: 2 } }, { + kind: 'resolve', + value: { actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }] }, +})); // 2.4.2 test('Grouping by non-groupable column should show failure entry', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Group by name') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Group by name'); await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: [ - { dataField: 'id' }, - { dataField: 'name', allowGrouping: false }, - { dataField: 'value' }, - ], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { + kind: 'resolve', + value: { actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }] }, +})); -// 2.4.3 +// 2.4.3 — selectByKeys with keys absent from the data selects nothing. The response is reported +// as a failure at the message level, but renders no per-action error entry (no row matched). test('Selecting non-existent keys should show failure or no incorrect rows selected', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Select rows 999 and 1000') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Select rows 999 and 1000'); await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(0); await t.expect(dataGrid.apiOption('selectedRowKeys')).eql([]); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - selection: { mode: 'multiple' }, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ name: 'selectByKeys', args: { keys: [999, 1000] } }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, selection: { mode: 'multiple' } }, { + kind: 'resolve', + value: { actions: [{ name: 'selectByKeys', args: { keys: [999, 1000] } }] }, +})); // === §2.6 Excessively long prompt / provider error === -fixture.disablePageReloads`AI Assistant - Provider Error` +fixture`AI Assistant - Provider Error` .page(AI_INTEGRATION_PAGE); // 2.6.2 test('sendRequest rejection should show error message and leave grid unchanged', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getErrorActionItems(0).count).eql(0); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.reject(new Error('Request payload too large')), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'reject', error: 'Request payload too large' }, +)); // === §4.1 AI integration failures → "unexpected error" / "invalid response" === -// Note: 4.1.2 (HTTP error) / 4.1.3 (network error) share the rejection path already -// covered by 2.6.2 above. 4.1.4 (synchronous throw from sendRequest) is NOT caught by -// RequestManager (no try/catch around provider.sendRequest), so it is not exercised here. -fixture.disablePageReloads`AI Assistant - Integration Failure` +fixture`AI Assistant - Integration Failure` .page(AI_INTEGRATION_PAGE); // 4.1.1 -// The plan expects "unexpected error", but the current implementation shows "invalid response" -// (known issue 4284). This locks the CURRENT behavior — switch to the unexpected-error key -// once 4284 is fixed. -test('Missing aiIntegration should show an error and leave grid unchanged', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +test('Missing aiIntegration should show an error and leave grid unchanged', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant(baseGrid, { kind: 'noIntegration' })); // 4.1.5 test('Non-JSON string response should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: 'not json' }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// 4.1.6 +test('Non-JSON actions string should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: 'not json' } }, +)); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.1.7 +test('Empty string response should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve('not json'), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: '' }, +)); -// 4.1.6 -test('Non-JSON actions string should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// === §4.2 Response format failure → "invalid response" === - await t.expect(dataGrid.isReady()).ok(); +fixture`AI Assistant - Response Format` + .page(AI_INTEGRATION_PAGE); - await t.click(dataGrid.getAIAssistantButton()); +// 4.2.1 +test('Response missing actions should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: {} }, +)); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.2.2 +test('Object actions (not array) should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: 'not json' }), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: { name: 'sorting' } } }, +)); -// 4.1.7 -test('Empty string response should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// 4.2.3 +test('Null actions should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: null } }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// 4.2.4 +test('Primitive actions should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: 42 } }, +)); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.2.5 +test('Null response should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve(''), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: null }, +)); -// === §4.2 Response format failure → "invalid response" === +// === §4.3 Validation failure (GridCommands.validate) → "invalid response" === -fixture.disablePageReloads`AI Assistant - Response Format` +fixture`AI Assistant - Validation` .page(AI_INTEGRATION_PAGE); -// 4.2.1 -test('Response missing actions should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// 4.3.1 +test('Unknown command name should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 'unknownCmd', args: {} }] } }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// 4.3.2 +test('Non-string command name should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 123, args: {} }] } }, +)); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.3.3 +test('Empty command name should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({}), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: '', args: {} }] } }, +)); -// 4.2.2 -test('Object actions (not array) should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// 4.3.4 +test('Action without name should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ args: {} }] } }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// 4.3.5 +test('Action without args should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 'sorting' }] } }, +)); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.3.6 +test('Null args should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: { name: 'sorting' } }), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 'sorting', args: null }] } }, +)); -// 4.2.3 -test('Null actions should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); +// 4.3.7 +test('Args missing a required property should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(dataGrid.isReady()).ok(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] } }, +)); - await t.click(dataGrid.getAIAssistantButton()); +// 4.3.8 +test('Args with a wrong-typed property should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - const aiChat = dataGrid.getAIAssistantChat(); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant(baseGrid, { + kind: 'resolve', + value: { actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }] }, +})); - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); +// 4.3.9 +test('Args with an extra property should show invalid-response error', async (t) => { + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: null }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.2.4 -test('Primitive actions should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: 42 }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.2.5 -test('Null response should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve(null), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// === §4.3 Validation failure (GridCommands.validate) → "invalid response" === - -fixture.disablePageReloads`AI Assistant - Validation` - .page(AI_INTEGRATION_PAGE); - -// 4.3.1 -test('Unknown command name should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 'unknownCmd', args: {} }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.2 -test('Non-string command name should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 123, args: {} }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.3 -test('Empty command name should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: '', args: {} }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.4 -test('Action without name should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ args: {} }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.5 -test('Action without args should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 'sorting' }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.6 -test('Null args should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 'sorting', args: null }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.7 -test('Args missing a required property should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.8 -test('Args with a wrong-typed property should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); - -// 4.3.9 -test('Args with an extra property should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [{ - name: 'sorting', - args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' }, - }], - }), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant(baseGrid, { + kind: 'resolve', + value: { actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' } }] }, +})); // 4.3.10 test('Mix of valid and invalid actions should reject the whole response', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort by name') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); - // Even the valid sorting action must not execute. - await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [ - { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, - { name: 'unknownCmd', args: {} }, - ], - }), - abort: (): void => {}, - }; - }, - }), + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); + + // expectInvalidResponse also asserts the valid sorting action did NOT execute. + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant(baseGrid, { + kind: 'resolve', + value: { + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'unknownCmd', args: {} }, + ], }, -}))); +})); // 4.3.11 test('No-arg command with non-empty args should show invalid-response error', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Clear sorting'); - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Clear sorting') - .pressKey('enter'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getActionItems(0).count).eql(0); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ actions: [{ name: 'clearSorting', args: { foo: 1 } }] }), - abort: (): void => {}, - }; - }, - }), - }, -}))); + await expectInvalidResponse(t, aiChat, dataGrid); +}).before(async () => createGridWithAIAssistant( + baseGrid, + { kind: 'resolve', value: { actions: [{ name: 'clearSorting', args: { foo: 1 } }] } }, +)); // === §4.4 Execution failure → per-command failure message === -fixture.disablePageReloads`AI Assistant - Execution Failure` +fixture`AI Assistant - Execution Failure` .page(AI_INTEGRATION_PAGE); -// 4.4.3 (single async command rejects) is not covered as e2e: command executors catch -// rejections internally and return failure(), so the observable result is identical to the -// synchronous-failure path already exercised by 4.4.4 / 4.4.5 and §2.3.2 / §2.4.x. Forcing a -// genuine async rejection needs a custom executor, which the public AIIntegration mock cannot -// inject deterministically (same limitation as 4.4.2 sync-throw and 4.4.6 reentrancy guard). - // 4.4.4 test('Partial failure should report each action status and apply successes', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Sort, group and search') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort, group and search'); await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getErrorMessages().count).eql(1); @@ -1187,51 +446,21 @@ test('Partial failure should report each action status and apply successes', asy // Successful actions (#1 sorting, #3 searching) took effect; failed grouping did not. await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).eql('asc'); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: [ - { dataField: 'id' }, - { dataField: 'name', allowGrouping: false }, - { dataField: 'value' }, - ], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [ - { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, - { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, - { name: 'searching', args: { text: 'Alice' } }, - ], - }), - abort: (): void => {}, - }; - }, - }), + await t.expect(dataGrid.apiOption('searchPanel.text')).eql('Alice'); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { + kind: 'resolve', + value: { + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + { name: 'searching', args: { text: 'Alice' } }, + ], }, -}))); +})); // 4.4.5 test('All-commands failure should report failures and leave grid unchanged', async (t) => { - const dataGrid = new DataGrid(GRID_SELECTOR); - - await t.expect(dataGrid.isReady()).ok(); - - await t.click(dataGrid.getAIAssistantButton()); - - const aiChat = dataGrid.getAIAssistantChat(); - - await t - .typeText(aiChat.getInput(), 'Do impossible things') - .pressKey('enter'); + const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Do impossible things'); await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getErrorMessages().count).eql(1); @@ -1243,34 +472,13 @@ test('All-commands failure should report failures and leave grid unchanged', asy await t.expect(aiChat.getActionItemText(0, 2).innerText).contains('Group data against'); await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createWidget('dxDataGrid', () => ({ - dataSource: [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, - ], - keyExpr: 'id', - columns: [ - { dataField: 'id' }, - { dataField: 'name', allowGrouping: false }, - { dataField: 'value' }, - ], - showBorders: true, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: Promise.resolve({ - actions: [ - { name: 'sorting', args: { dataField: 'NopeOne', sortOrder: 'asc' } }, - { name: 'sorting', args: { dataField: 'NopeTwo', sortOrder: 'desc' } }, - { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, - ], - }), - abort: (): void => {}, - }; - }, - }), +}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { + kind: 'resolve', + value: { + actions: [ + { name: 'sorting', args: { dataField: 'NopeOne', sortOrder: 'asc' } }, + { name: 'sorting', args: { dataField: 'NopeTwo', sortOrder: 'desc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + ], }, -}))); +})); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts index 82cb1a502090..9ba16903336b 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts @@ -103,12 +103,3 @@ export const createGridWithAIAssistant = async ( }; export const getRequests = ClientFunction(() => (window as any).__aiRequests); - -export const getRequestColumnNames = ClientFunction( - (index: number) => (window as any).__aiRequests[index].data.context.columns - .map((c: any) => c.dataField), -); - -export const formatMessage = ClientFunction( - (key: string) => (window as any).DevExpress.localization.formatMessage(key), -); From 00c1dfd84f7a9b22876f86a5af9196f73c44198e Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Fri, 5 Jun 2026 13:33:27 +0400 Subject: [PATCH 3/9] Fix copilot comment --- .../dataGrid/common/aiAssistant/errorHandling.functional.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index e20de7073168..542beda6af2c 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -133,13 +133,11 @@ fixture`AI Assistant - Unknown Field` test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by Salary'); - const dataSourceSortParams = await dataGrid.apiGetDataSourceSortParams(); - await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); - await t.expect(dataSourceSortParams).eql(null); + await t.expect(await dataGrid.apiGetDataSourceSortParams()).notOk(); }).before(async () => createGridWithAIAssistant(baseGrid, { kind: 'resolve', value: { actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }, From 8922119fd26f805869d072fd29b2091a502a6e0e Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Mon, 8 Jun 2026 01:08:29 +0400 Subject: [PATCH 4/9] DataGrid - AI Assistant: fix 2.4.3 to exercise selectByKeys with absent keys Add the required preserve arg so the command runs instead of failing schema validation; assert it succeeds and selects no rows (Copilot review). --- .../aiAssistant/errorHandling.functional.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index 542beda6af2c..c4fd8757ee10 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -37,6 +37,10 @@ const invalidResponse = (): Promise => formatMessage( 'dxDataGrid-aiAssistantInvalidResponseMessage', ); +const getSelectedRowsCount = ClientFunction( + () => (window as any).widget.getSelectedRowsData().length, +); + const setupAIMock = ClientFunction((base: Record, mock: AIResponseMock) => { (window as any).__aiBase = base; (window as any).__aiMock = mock; @@ -176,19 +180,18 @@ test('Grouping by non-groupable column should show failure entry', async (t) => value: { actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }] }, })); -// 2.4.3 — selectByKeys with keys absent from the data selects nothing. The response is reported -// as a failure at the message level, but renders no per-action error entry (no row matched). -test('Selecting non-existent keys should show failure or no incorrect rows selected', async (t) => { - const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Select rows 999 and 1000'); +// 2.4.3 +test('Selecting non-existent keys should succeed and select no rows', async (t) => { + const { aiChat } = await openChatAndSubmit(t, 'Select rows 999 and 1000'); await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getErrorActionItems(0).count).eql(0); - await t.expect(dataGrid.apiOption('selectedRowKeys')).eql([]); + await t.expect(aiChat.getErrorMessages().count).eql(0); + await t.expect(aiChat.getSuccessMessages().count).eql(1); + await t.expect(aiChat.getSuccessActionItems(0).count).eql(1); + await t.expect(getSelectedRowsCount()).eql(0); }).before(async () => createGridWithAIAssistant({ ...baseGrid, selection: { mode: 'multiple' } }, { kind: 'resolve', - value: { actions: [{ name: 'selectByKeys', args: { keys: [999, 1000] } }] }, + value: { actions: [{ name: 'selectByKeys', args: { keys: [999, 1000], preserve: false } }] }, })); // === §2.6 Excessively long prompt / provider error === From 8b955d416c7d036933fd1a570667f66cab14155f Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Mon, 8 Jun 2026 13:33:04 +0400 Subject: [PATCH 5/9] DataGrid - AI Assistant: use shared AI mock setup from testHelpers Replace the kind-based mock with the shared queue helper (raw response / FAIL); keep a local no-integration grid factory for the missing-aiIntegration case. --- .../aiAssistant/errorHandling.functional.ts | 195 +++++++----------- 1 file changed, 73 insertions(+), 122 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index c4fd8757ee10..76f168d3af1c 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -2,26 +2,19 @@ import DataGrid from 'devextreme-testcafe-models/dataGrid'; import { ClientFunction } from 'testcafe'; import { createWidget } from '../../../../helpers/createWidget'; -import { AI_INTEGRATION_PAGE, GRID_SELECTOR } from './testHelpers'; +import { + AI_INTEGRATION_PAGE, + FAIL, + GRID_SELECTOR, + baseGrid as gridDefaults, + createGridWithAIAssistant, + setupAIState, + threeRows, +} from './testHelpers'; type AIChat = ReturnType; -type AIResponseMock = { kind: 'resolve'; value: unknown } - | { kind: 'reject'; error: string } - | { kind: 'noIntegration' }; - -const threeRows = [ - { id: 1, name: 'Alice', value: 30 }, - { id: 2, name: 'Bob', value: 20 }, - { id: 3, name: 'Charlie', value: 10 }, -]; - -const baseGrid = { - dataSource: threeRows, - keyExpr: 'id', - columns: ['id', 'name', 'value'], - showBorders: true, -}; +const baseGrid = { ...gridDefaults, dataSource: threeRows }; const groupingLockedColumns = [ { dataField: 'id' }, @@ -41,44 +34,17 @@ const getSelectedRowsCount = ClientFunction( () => (window as any).widget.getSelectedRowsData().length, ); -const setupAIMock = ClientFunction((base: Record, mock: AIResponseMock) => { - (window as any).__aiBase = base; - (window as any).__aiMock = mock; +const noIntegrationOptions = (): any => ({ + ...(window as any).__aiBase, + aiAssistant: { enabled: true }, }); -const aiGridOptions = (): any => { - const base = (window as any).__aiBase; - const mock = (window as any).__aiMock; - - if (mock.kind === 'noIntegration') { - return { ...base, aiAssistant: { enabled: true } }; - } - - return { - ...base, - aiAssistant: { - enabled: true, - aiIntegration: new (window as any).DevExpress.aiIntegration.AIIntegration({ - sendRequest() { - return { - promise: mock.kind === 'reject' - ? Promise.reject(new Error(mock.error)) - : Promise.resolve(mock.value), - abort: (): void => {}, - }; - }, - }), - }, - }; -}; - -const createGridWithAIAssistant = async ( +const createGridWithoutIntegration = async ( base: Record, - mock: AIResponseMock, ): Promise => { - await setupAIMock(base, mock); + await setupAIState(base, []); - return createWidget('dxDataGrid', aiGridOptions); + return createWidget('dxDataGrid', noIntegrationOptions); }; const openChatAndSubmit = async ( @@ -125,7 +91,7 @@ test('Empty actions array should show no-action message and leave grid unchanged await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [] } }, + [{ actions: [] }], )); // === §2.3 Unknown field === @@ -142,10 +108,10 @@ test('Sorting by non-existent dataField should show failure message and leave gr await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(await dataGrid.apiGetDataSourceSortParams()).notOk(); -}).before(async () => createGridWithAIAssistant(baseGrid, { - kind: 'resolve', - value: { actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }, -})); +}).before(async () => createGridWithAIAssistant( + baseGrid, + [{ actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }], +)); // === §2.4 Request impossible in current state === @@ -161,10 +127,10 @@ test('Page index out of range should show failure status', async (t) => { await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(dataGrid.apiPageIndex()).eql(0); -}).before(async () => createGridWithAIAssistant({ ...baseGrid, paging: { pageSize: 2 } }, { - kind: 'resolve', - value: { actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }] }, -})); +}).before(async () => createGridWithAIAssistant( + { ...baseGrid, paging: { pageSize: 2 } }, + [{ actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }] }], +)); // 2.4.2 test('Grouping by non-groupable column should show failure entry', async (t) => { @@ -175,10 +141,10 @@ test('Grouping by non-groupable column should show failure entry', async (t) => await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { - kind: 'resolve', - value: { actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }] }, -})); +}).before(async () => createGridWithAIAssistant( + { ...baseGrid, columns: groupingLockedColumns }, + [{ actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }] }], +)); // 2.4.3 test('Selecting non-existent keys should succeed and select no rows', async (t) => { @@ -189,10 +155,10 @@ test('Selecting non-existent keys should succeed and select no rows', async (t) await t.expect(aiChat.getSuccessMessages().count).eql(1); await t.expect(aiChat.getSuccessActionItems(0).count).eql(1); await t.expect(getSelectedRowsCount()).eql(0); -}).before(async () => createGridWithAIAssistant({ ...baseGrid, selection: { mode: 'multiple' } }, { - kind: 'resolve', - value: { actions: [{ name: 'selectByKeys', args: { keys: [999, 1000], preserve: false } }] }, -})); +}).before(async () => createGridWithAIAssistant( + { ...baseGrid, selection: { mode: 'multiple' } }, + [{ actions: [{ name: 'selectByKeys', args: { keys: [999, 1000], preserve: false } }] }], +)); // === §2.6 Excessively long prompt / provider error === @@ -206,7 +172,7 @@ test('sendRequest rejection should show error message and leave grid unchanged', await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'reject', error: 'Request payload too large' }, + [FAIL], )); // === §4.1 AI integration failures → "unexpected error" / "invalid response" === @@ -220,7 +186,7 @@ test('Missing aiIntegration should show an error and leave grid unchanged', asyn const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); -}).before(async () => createGridWithAIAssistant(baseGrid, { kind: 'noIntegration' })); +}).before(async () => createGridWithoutIntegration(baseGrid)); // 4.1.5 test('Non-JSON string response should show invalid-response error', async (t) => { @@ -229,7 +195,7 @@ test('Non-JSON string response should show invalid-response error', async (t) => await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: 'not json' }, + ['not json'], )); // 4.1.6 @@ -239,7 +205,7 @@ test('Non-JSON actions string should show invalid-response error', async (t) => await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: 'not json' } }, + [{ actions: 'not json' }], )); // 4.1.7 @@ -249,7 +215,7 @@ test('Empty string response should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: '' }, + [''], )); // === §4.2 Response format failure → "invalid response" === @@ -264,7 +230,7 @@ test('Response missing actions should show invalid-response error', async (t) => await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: {} }, + [{}], )); // 4.2.2 @@ -274,7 +240,7 @@ test('Object actions (not array) should show invalid-response error', async (t) await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: { name: 'sorting' } } }, + [{ actions: { name: 'sorting' } }], )); // 4.2.3 @@ -284,7 +250,7 @@ test('Null actions should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: null } }, + [{ actions: null }], )); // 4.2.4 @@ -294,7 +260,7 @@ test('Primitive actions should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: 42 } }, + [{ actions: 42 }], )); // 4.2.5 @@ -304,7 +270,7 @@ test('Null response should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: null }, + [null], )); // === §4.3 Validation failure (GridCommands.validate) → "invalid response" === @@ -319,7 +285,7 @@ test('Unknown command name should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 'unknownCmd', args: {} }] } }, + [{ actions: [{ name: 'unknownCmd', args: {} }] }], )); // 4.3.2 @@ -329,7 +295,7 @@ test('Non-string command name should show invalid-response error', async (t) => await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 123, args: {} }] } }, + [{ actions: [{ name: 123, args: {} }] }], )); // 4.3.3 @@ -339,7 +305,7 @@ test('Empty command name should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: '', args: {} }] } }, + [{ actions: [{ name: '', args: {} }] }], )); // 4.3.4 @@ -349,7 +315,7 @@ test('Action without name should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ args: {} }] } }, + [{ actions: [{ args: {} }] }], )); // 4.3.5 @@ -359,7 +325,7 @@ test('Action without args should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 'sorting' }] } }, + [{ actions: [{ name: 'sorting' }] }], )); // 4.3.6 @@ -369,7 +335,7 @@ test('Null args should show invalid-response error', async (t) => { await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 'sorting', args: null }] } }, + [{ actions: [{ name: 'sorting', args: null }] }], )); // 4.3.7 @@ -379,7 +345,7 @@ test('Args missing a required property should show invalid-response error', asyn await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] } }, + [{ actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] }], )); // 4.3.8 @@ -387,20 +353,14 @@ test('Args with a wrong-typed property should show invalid-response error', asyn const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); -}).before(async () => createGridWithAIAssistant(baseGrid, { - kind: 'resolve', - value: { actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }] }, -})); +}).before(async () => createGridWithAIAssistant(baseGrid, [{ actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }] }])); // 4.3.9 test('Args with an extra property should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); -}).before(async () => createGridWithAIAssistant(baseGrid, { - kind: 'resolve', - value: { actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' } }] }, -})); +}).before(async () => createGridWithAIAssistant(baseGrid, [{ actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' } }] }])); // 4.3.10 test('Mix of valid and invalid actions should reject the whole response', async (t) => { @@ -408,15 +368,12 @@ test('Mix of valid and invalid actions should reject the whole response', async // expectInvalidResponse also asserts the valid sorting action did NOT execute. await expectInvalidResponse(t, aiChat, dataGrid); -}).before(async () => createGridWithAIAssistant(baseGrid, { - kind: 'resolve', - value: { - actions: [ - { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, - { name: 'unknownCmd', args: {} }, - ], - }, -})); +}).before(async () => createGridWithAIAssistant(baseGrid, [{ + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'unknownCmd', args: {} }, + ], +}])); // 4.3.11 test('No-arg command with non-empty args should show invalid-response error', async (t) => { @@ -425,7 +382,7 @@ test('No-arg command with non-empty args should show invalid-response error', as await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant( baseGrid, - { kind: 'resolve', value: { actions: [{ name: 'clearSorting', args: { foo: 1 } }] } }, + [{ actions: [{ name: 'clearSorting', args: { foo: 1 } }] }], )); // === §4.4 Execution failure → per-command failure message === @@ -448,16 +405,13 @@ test('Partial failure should report each action status and apply successes', asy await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).eql('asc'); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); await t.expect(dataGrid.apiOption('searchPanel.text')).eql('Alice'); -}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { - kind: 'resolve', - value: { - actions: [ - { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, - { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, - { name: 'searching', args: { text: 'Alice' } }, - ], - }, -})); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, [{ + actions: [ + { name: 'sorting', args: { dataField: 'name', sortOrder: 'asc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + { name: 'searching', args: { text: 'Alice' } }, + ], +}])); // 4.4.5 test('All-commands failure should report failures and leave grid unchanged', async (t) => { @@ -473,13 +427,10 @@ test('All-commands failure should report failures and leave grid unchanged', asy await t.expect(aiChat.getActionItemText(0, 2).innerText).contains('Group data against'); await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, { - kind: 'resolve', - value: { - actions: [ - { name: 'sorting', args: { dataField: 'NopeOne', sortOrder: 'asc' } }, - { name: 'sorting', args: { dataField: 'NopeTwo', sortOrder: 'desc' } }, - { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, - ], - }, -})); +}).before(async () => createGridWithAIAssistant({ ...baseGrid, columns: groupingLockedColumns }, [{ + actions: [ + { name: 'sorting', args: { dataField: 'NopeOne', sortOrder: 'asc' } }, + { name: 'sorting', args: { dataField: 'NopeTwo', sortOrder: 'desc' } }, + { name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }, + ], +}])); From ef66f52c8bde8ecfd5ee95dd9f8aa37799743b9a Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Mon, 8 Jun 2026 14:01:44 +0400 Subject: [PATCH 6/9] DataGrid - AI Assistant: merge fixtures into one per file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapse the per-section fixtures into a single named fixture; keep the // === §X === markers as in-file section dividers. --- .../aiAssistant/errorHandling.functional.ts | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index 76f168d3af1c..fe9a93649505 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -79,11 +79,11 @@ const expectInvalidResponse = async ( await t.expect(dataGrid.apiColumnOption('name', 'sortOrder')).notOk(); }; -// === §2.1 Unsupported / unknown intent === - -fixture`AI Assistant - Empty Actions` +fixture`AI Assistant - Error Handling` .page(AI_INTEGRATION_PAGE); +// === §2.1 Unsupported / unknown intent === + // 2.1.3 — empty actions reject through the same "invalid response" path (§4.5). test('Empty actions array should show no-action message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Tell me a joke'); @@ -96,9 +96,6 @@ test('Empty actions array should show no-action message and leave grid unchanged // === §2.3 Unknown field === -fixture`AI Assistant - Unknown Field` - .page(AI_INTEGRATION_PAGE); - // 2.3.2 — schema-valid action, executor fails at runtime → per-action error entry. test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by Salary'); @@ -115,9 +112,6 @@ test('Sorting by non-existent dataField should show failure message and leave gr // === §2.4 Request impossible in current state === -fixture`AI Assistant - Impossible State` - .page(AI_INTEGRATION_PAGE); - // 2.4.1 test('Page index out of range should show failure status', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Go to page 10000'); @@ -162,9 +156,6 @@ test('Selecting non-existent keys should succeed and select no rows', async (t) // === §2.6 Excessively long prompt / provider error === -fixture`AI Assistant - Provider Error` - .page(AI_INTEGRATION_PAGE); - // 2.6.2 test('sendRequest rejection should show error message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -177,9 +168,6 @@ test('sendRequest rejection should show error message and leave grid unchanged', // === §4.1 AI integration failures → "unexpected error" / "invalid response" === -fixture`AI Assistant - Integration Failure` - .page(AI_INTEGRATION_PAGE); - // 4.1.1 test('Missing aiIntegration should show an error and leave grid unchanged', async (t) => { @@ -220,9 +208,6 @@ test('Empty string response should show invalid-response error', async (t) => { // === §4.2 Response format failure → "invalid response" === -fixture`AI Assistant - Response Format` - .page(AI_INTEGRATION_PAGE); - // 4.2.1 test('Response missing actions should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -275,9 +260,6 @@ test('Null response should show invalid-response error', async (t) => { // === §4.3 Validation failure (GridCommands.validate) → "invalid response" === -fixture`AI Assistant - Validation` - .page(AI_INTEGRATION_PAGE); - // 4.3.1 test('Unknown command name should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -387,9 +369,6 @@ test('No-arg command with non-empty args should show invalid-response error', as // === §4.4 Execution failure → per-command failure message === -fixture`AI Assistant - Execution Failure` - .page(AI_INTEGRATION_PAGE); - // 4.4.4 test('Partial failure should report each action status and apply successes', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort, group and search'); From d6e58e4cbb66ac86fc39fb3fb0b9a0326bf2e4c7 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Mon, 8 Jun 2026 14:34:53 +0400 Subject: [PATCH 7/9] DataGrid - AI Assistant: restore scoped getWrapper override + pass async grid reads to t.expect Restore the scoped getWrapper override; drop await inside t.expect so TestCafe retries async grid-state assertions (Copilot review). --- .../dataGrid/common/aiAssistant/errorHandling.functional.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index fe9a93649505..faca73bbb056 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -104,7 +104,7 @@ test('Sorting by non-existent dataField should show failure message and leave gr await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getErrorMessages().count).eql(1); await t.expect(aiChat.getErrorActionItems(0).count).eql(1); - await t.expect(await dataGrid.apiGetDataSourceSortParams()).notOk(); + await t.expect(dataGrid.apiGetDataSourceSortParams()).notOk(); }).before(async () => createGridWithAIAssistant( baseGrid, [{ actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }], From d5941cf057a242ddd84d5dd46cd9d020296b07c1 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Tue, 16 Jun 2026 16:31:12 +0400 Subject: [PATCH 8/9] Fix comments --- .../aiAssistant/errorHandling.functional.ts | 78 ++----------------- .../common/aiAssistant/testHelpers.ts | 8 ++ 2 files changed, 15 insertions(+), 71 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index faca73bbb056..0506d5be3916 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -8,6 +8,7 @@ import { GRID_SELECTOR, baseGrid as gridDefaults, createGridWithAIAssistant, + getLoggedErrorIds, setupAIState, threeRows, } from './testHelpers'; @@ -30,6 +31,10 @@ const invalidResponse = (): Promise => formatMessage( 'dxDataGrid-aiAssistantInvalidResponseMessage', ); +const errorHeader = (): Promise => formatMessage( + 'dxDataGrid-aiAssistantErrorMessageHeader', +); + const getSelectedRowsCount = ClientFunction( () => (window as any).widget.getSelectedRowsData().length, ); @@ -73,6 +78,7 @@ const expectInvalidResponse = async ( ): Promise => { await t.expect(aiChat.getMessages().count).eql(2); await t.expect(aiChat.getErrorMessages().count).eql(1); + await t.expect(aiChat.getMessageHeader(0).innerText).eql(await errorHeader()); await t.expect(aiChat.getMessageErrorText(0).innerText).eql(await invalidResponse()); await t.expect(aiChat.getSuccessMessages().count).eql(0); await t.expect(aiChat.getActionItems(0).count).eql(0); @@ -82,9 +88,6 @@ const expectInvalidResponse = async ( fixture`AI Assistant - Error Handling` .page(AI_INTEGRATION_PAGE); -// === §2.1 Unsupported / unknown intent === - -// 2.1.3 — empty actions reject through the same "invalid response" path (§4.5). test('Empty actions array should show no-action message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Tell me a joke'); @@ -94,9 +97,6 @@ test('Empty actions array should show no-action message and leave grid unchanged [{ actions: [] }], )); -// === §2.3 Unknown field === - -// 2.3.2 — schema-valid action, executor fails at runtime → per-action error entry. test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by Salary'); @@ -110,37 +110,6 @@ test('Sorting by non-existent dataField should show failure message and leave gr [{ actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }], )); -// === §2.4 Request impossible in current state === - -// 2.4.1 -test('Page index out of range should show failure status', async (t) => { - const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Go to page 10000'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getErrorActionItems(0).count).eql(1); - await t.expect(dataGrid.apiPageIndex()).eql(0); -}).before(async () => createGridWithAIAssistant( - { ...baseGrid, paging: { pageSize: 2 } }, - [{ actions: [{ name: 'pageIndex', args: { pageIndex: 9999 } }] }], -)); - -// 2.4.2 -test('Grouping by non-groupable column should show failure entry', async (t) => { - const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Group by name'); - - await t.expect(aiChat.getMessages().count).eql(2); - await t.expect(aiChat.getSuccessMessages().count).eql(0); - await t.expect(aiChat.getErrorMessages().count).eql(1); - await t.expect(aiChat.getErrorActionItems(0).count).eql(1); - await t.expect(dataGrid.apiColumnOption('name', 'groupIndex')).eql(undefined); -}).before(async () => createGridWithAIAssistant( - { ...baseGrid, columns: groupingLockedColumns }, - [{ actions: [{ name: 'grouping', args: { dataField: 'name', groupIndex: 0 } }] }], -)); - -// 2.4.3 test('Selecting non-existent keys should succeed and select no rows', async (t) => { const { aiChat } = await openChatAndSubmit(t, 'Select rows 999 and 1000'); @@ -154,9 +123,6 @@ test('Selecting non-existent keys should succeed and select no rows', async (t) [{ actions: [{ name: 'selectByKeys', args: { keys: [999, 1000], preserve: false } }] }], )); -// === §2.6 Excessively long prompt / provider error === - -// 2.6.2 test('sendRequest rejection should show error message and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -166,17 +132,13 @@ test('sendRequest rejection should show error message and leave grid unchanged', [FAIL], )); -// === §4.1 AI integration failures → "unexpected error" / "invalid response" === - -// 4.1.1 - test('Missing aiIntegration should show an error and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); + await t.expect(await getLoggedErrorIds(t)).contains('E1068'); }).before(async () => createGridWithoutIntegration(baseGrid)); -// 4.1.5 test('Non-JSON string response should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -186,7 +148,6 @@ test('Non-JSON string response should show invalid-response error', async (t) => ['not json'], )); -// 4.1.6 test('Non-JSON actions string should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -196,7 +157,6 @@ test('Non-JSON actions string should show invalid-response error', async (t) => [{ actions: 'not json' }], )); -// 4.1.7 test('Empty string response should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -206,9 +166,6 @@ test('Empty string response should show invalid-response error', async (t) => { [''], )); -// === §4.2 Response format failure → "invalid response" === - -// 4.2.1 test('Response missing actions should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -218,7 +175,6 @@ test('Response missing actions should show invalid-response error', async (t) => [{}], )); -// 4.2.2 test('Object actions (not array) should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -228,7 +184,6 @@ test('Object actions (not array) should show invalid-response error', async (t) [{ actions: { name: 'sorting' } }], )); -// 4.2.3 test('Null actions should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -238,7 +193,6 @@ test('Null actions should show invalid-response error', async (t) => { [{ actions: null }], )); -// 4.2.4 test('Primitive actions should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -248,7 +202,6 @@ test('Primitive actions should show invalid-response error', async (t) => { [{ actions: 42 }], )); -// 4.2.5 test('Null response should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -258,9 +211,6 @@ test('Null response should show invalid-response error', async (t) => { [null], )); -// === §4.3 Validation failure (GridCommands.validate) → "invalid response" === - -// 4.3.1 test('Unknown command name should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -270,7 +220,6 @@ test('Unknown command name should show invalid-response error', async (t) => { [{ actions: [{ name: 'unknownCmd', args: {} }] }], )); -// 4.3.2 test('Non-string command name should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -280,7 +229,6 @@ test('Non-string command name should show invalid-response error', async (t) => [{ actions: [{ name: 123, args: {} }] }], )); -// 4.3.3 test('Empty command name should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -290,7 +238,6 @@ test('Empty command name should show invalid-response error', async (t) => { [{ actions: [{ name: '', args: {} }] }], )); -// 4.3.4 test('Action without name should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -300,7 +247,6 @@ test('Action without name should show invalid-response error', async (t) => { [{ actions: [{ args: {} }] }], )); -// 4.3.5 test('Action without args should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -310,7 +256,6 @@ test('Action without args should show invalid-response error', async (t) => { [{ actions: [{ name: 'sorting' }] }], )); -// 4.3.6 test('Null args should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -320,7 +265,6 @@ test('Null args should show invalid-response error', async (t) => { [{ actions: [{ name: 'sorting', args: null }] }], )); -// 4.3.7 test('Args missing a required property should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -330,21 +274,18 @@ test('Args missing a required property should show invalid-response error', asyn [{ actions: [{ name: 'sorting', args: { sortOrder: 'asc' } }] }], )); -// 4.3.8 test('Args with a wrong-typed property should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant(baseGrid, [{ actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 123 } }] }])); -// 4.3.9 test('Args with an extra property should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); await expectInvalidResponse(t, aiChat, dataGrid); }).before(async () => createGridWithAIAssistant(baseGrid, [{ actions: [{ name: 'sorting', args: { dataField: 'name', sortOrder: 'asc', foo: 'bar' } }] }])); -// 4.3.10 test('Mix of valid and invalid actions should reject the whole response', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by name'); @@ -357,7 +298,6 @@ test('Mix of valid and invalid actions should reject the whole response', async ], }])); -// 4.3.11 test('No-arg command with non-empty args should show invalid-response error', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Clear sorting'); @@ -367,9 +307,6 @@ test('No-arg command with non-empty args should show invalid-response error', as [{ actions: [{ name: 'clearSorting', args: { foo: 1 } }] }], )); -// === §4.4 Execution failure → per-command failure message === - -// 4.4.4 test('Partial failure should report each action status and apply successes', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort, group and search'); @@ -392,7 +329,6 @@ test('Partial failure should report each action status and apply successes', asy ], }])); -// 4.4.5 test('All-commands failure should report failures and leave grid unchanged', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Do impossible things'); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts index 9ba16903336b..5c94d73cc7b7 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/testHelpers.ts @@ -103,3 +103,11 @@ export const createGridWithAIAssistant = async ( }; export const getRequests = ClientFunction(() => (window as any).__aiRequests); + +export const getLoggedErrorIds = async (t: TestController): Promise => { + const consoleMessages = await t.getBrowserConsoleMessages(); + + return (consoleMessages?.error ?? []) + .map((message) => /^E\d+/.exec(message)?.[0]) + .filter((id): id is string => id !== undefined); +}; From 15ac4c337eb906813f28751a8c0abaf49c2f478b Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Tue, 16 Jun 2026 16:38:37 +0400 Subject: [PATCH 9/9] Fix comment (part 2) --- .../dataGrid/common/aiAssistant/errorHandling.functional.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts index 0506d5be3916..24daf421e63a 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/aiAssistant/errorHandling.functional.ts @@ -97,7 +97,7 @@ test('Empty actions array should show no-action message and leave grid unchanged [{ actions: [] }], )); -test('Sorting by non-existent dataField should show failure message and leave grid unchanged', async (t) => { +test('Sorting by a non-existent column should fail: schema is valid but incompatible with grid state', async (t) => { const { dataGrid, aiChat } = await openChatAndSubmit(t, 'Sort by Salary'); await t.expect(aiChat.getMessages().count).eql(2); @@ -110,7 +110,7 @@ test('Sorting by non-existent dataField should show failure message and leave gr [{ actions: [{ name: 'sorting', args: { dataField: 'Salary', sortOrder: 'asc' } }] }], )); -test('Selecting non-existent keys should succeed and select no rows', async (t) => { +test('Selecting non-existent keys should succeed: schema is valid and the missing data is not sent in the request', async (t) => { const { aiChat } = await openChatAndSubmit(t, 'Select rows 999 and 1000'); await t.expect(aiChat.getMessages().count).eql(2);