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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>TestCafe Tests Container</title>

<link rel="dx-theme" data-theme="fluent.blue.light" href="../../../../../../../packages/devextreme/artifacts/css/dx.fluent.blue.light.css" data-active="false">
<link rel="dx-theme" data-theme="fluent.blue.light.compact" href="../../../../../../../packages/devextreme/artifacts/css/dx.fluent.blue.light.compact.css" data-active="false">
<link rel="dx-theme" data-theme="generic.light" href="../../../../../../../packages/devextreme/artifacts/css/dx.light.css" data-active="true">
<link rel="dx-theme" data-theme="generic.light.compact" href="../../../../../../../packages/devextreme/artifacts/css/dx.light.compact.css" data-active="false">
<link rel="dx-theme" data-theme="material.blue.light" href="../../../../../../../packages/devextreme/artifacts/css/dx.material.blue.light.css" data-active="false">
<link rel="dx-theme" data-theme="material.blue.light.compact" href="../../../../../../../packages/devextreme/artifacts/css/dx.material.blue.light.compact.css" data-active="false">

<script type="text/javascript" src="../../../../../../../packages/devextreme/artifacts/js/jquery.min.js"></script>
<script type="text/javascript" src="../../../../../../../packages/devextreme/artifacts/js/dx.all.js"></script>
<script type="text/javascript" src="../../../../../../../packages/devextreme/artifacts/js/dx.ai-integration.js"></script>
<script type="text/javascript" src="../../../../../../../packages/devextreme/artifacts/js/dx.aspnet.data.js"></script>

<style>
* { caret-color: transparent !important; }
.dx-scheduler .dx-scrollable-scroll { visibility: hidden !important; }
</style>
</head>
<body class="dx-surface">
<div id="parentContainer" role="main">
<h1 style="position: fixed; left: 0; top: 0; clip: rect(1px, 1px, 1px, 1px);">Test header</h1>

<div id="container">
</div>
<div id="otherContainer">
</div>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import DataGrid from 'devextreme-testcafe-models/dataGrid';
import { ClientFunction } from 'testcafe';
import url from '../../../../helpers/getPageUrl';
import { createWidget } from '../../../../helpers/createWidget';

fixture`Ai Column.Virtual Scrolling.Functional`
.page(url(__dirname, './pages/containerWithAIIntegration.html'));

const DATA_GRID_SELECTOR = '#container';

const checkAIColumnTexts = async (
t: TestController,
component: DataGrid,
expectedRowCount: number,
): Promise<void> => {
const visibleRows: Record<string, any>[] = await component.apiGetVisibleRows();

await t.expect(visibleRows.length).eql(expectedRowCount);

// eslint-disable-next-line no-restricted-syntax
for (const row of visibleRows) {
await t
.expect(component.getDataCell(row.dataIndex, 3).element.textContent)
.eql(`Response ${row.data.name}`);
}
};

const resolveAIRequest = ClientFunction((): void => {
const { aiResponseData } = (window as any);
const { aiResolve } = (window as any);

if (aiResponseData && aiResolve) {
aiResolve(aiResponseData);

(window as any).aiResponseData = null;
(window as any).aiResolve = null;
}
});

const deleteGlobalVariables = ClientFunction((): void => {
delete (window as any).aiResponseData;
delete (window as any).aiResolve;
});

test('DataGrid should send an AI request for rendered rows after scrolling without changing the page index', async (t) => {
// arrange
const dataGrid = new DataGrid(DATA_GRID_SELECTOR);

// assert
await t
.expect(dataGrid.getLoadPanel().isVisible())
.ok();

// act
await resolveAIRequest();

// assert
await t
.expect(dataGrid.isReady())
.ok()
.expect(dataGrid.getLoadPanel().isVisible())
.notOk();
await checkAIColumnTexts(t, dataGrid, 11);

// act
await dataGrid.scrollTo(t, { y: 1000 });

// assert
await t
.expect(dataGrid.getScrollTop())
.eql(1000)
.expect(dataGrid.apiPageIndex())
.eql(0)
.expect(dataGrid.getDataCell(20, 0).element.textContent)
.eql('21')
.expect(dataGrid.getLoadPanel().isVisible())
.ok();

// act
await resolveAIRequest();

// assert
await t
.expect(dataGrid.isReady())
.ok()
.expect(dataGrid.getLoadPanel().isVisible())
.notOk();
await checkAIColumnTexts(t, dataGrid, 12);
})
.before(async () => createWidget('dxDataGrid', () => {
const generateData = (rowCount: number): Record<string, number | string>[] => {
const result: Record<string, number | string>[] = [];

for (let i = 0; i < rowCount; i += 1) {
result.push({ id: i + 1, name: `Name ${i + 1}`, value: (i + 1) * 10 });
}

return result;
};

return {
dataSource: generateData(200),
height: 500,
keyExpr: 'id',
paging: {
pageSize: 50,
},
scrolling: {
mode: 'virtual',
},
columns: [
{ dataField: 'id', caption: 'ID' },
{ dataField: 'name', caption: 'Name' },
{ dataField: 'value', caption: 'Value' },
{
type: 'ai',
caption: 'AI Column',
name: 'myColumn',
ai: {
prompt: 'Initial prompt',
// eslint-disable-next-line new-cap
aiIntegration: new (window as any).DevExpress.aiIntegration({
sendRequest(prompt) {
return {
promise: new Promise<string>((resolve) => {
const result: Record<string, string> = {};

Object.entries(prompt.data?.data).forEach(([key, value]) => {
result[key] = `Response ${(value as any).name}`;
});

(window as any).aiResponseData = JSON.stringify(result);
(window as any).aiResolve = resolve;
}),
abort: (): void => {},
};
},
}),
},
},
],
};
}))
.after(async () => {
await deleteGlobalVariables();
});

test('DataGrid should send an AI request for rendered rows after scrolling with changing the page index', async (t) => {
// arrange
const dataGrid = new DataGrid(DATA_GRID_SELECTOR);

// assert
await t
.expect(dataGrid.getLoadPanel().isVisible())
.ok();

// act
await resolveAIRequest();

// assert
await t
.expect(dataGrid.isReady())
.ok()
.expect(dataGrid.getLoadPanel().isVisible())
.notOk();
await checkAIColumnTexts(t, dataGrid, 11);

// act
await dataGrid.scrollTo(t, { y: 1000 });

// assert
await t
.expect(dataGrid.getScrollTop())
.eql(1000)
.expect(dataGrid.apiPageIndex())
.eql(1)
.expect(dataGrid.getDataCell(20, 0).element.textContent)
.eql('21')
.expect(dataGrid.getLoadPanel().isVisible())
.ok();

// act
await resolveAIRequest();

// assert
await t
.expect(dataGrid.isReady())
.ok()
.expect(dataGrid.getLoadPanel().isVisible())
.notOk();
await checkAIColumnTexts(t, dataGrid, 12);
})
.before(async () => createWidget('dxDataGrid', () => {
const generateData = (rowCount: number): Record<string, number | string>[] => {
const result: Record<string, number | string>[] = [];

for (let i = 0; i < rowCount; i += 1) {
result.push({ id: i + 1, name: `Name ${i + 1}`, value: (i + 1) * 10 });
}

return result;
};

return {
dataSource: generateData(200),
height: 500,
keyExpr: 'id',
paging: {
pageSize: 20,
},
scrolling: {
mode: 'virtual',
},
columns: [
{ dataField: 'id', caption: 'ID' },
{ dataField: 'name', caption: 'Name' },
{ dataField: 'value', caption: 'Value' },
{
type: 'ai',
caption: 'AI Column',
name: 'myColumn',
ai: {
prompt: 'Initial prompt',
// eslint-disable-next-line new-cap
aiIntegration: new (window as any).DevExpress.aiIntegration({
sendRequest(prompt) {
return {
promise: new Promise<string>((resolve) => {
const result: Record<string, string> = {};

Object.entries(prompt.data?.data).forEach(([key, value]) => {
result[key] = `Response ${(value as any).name}`;
});

(window as any).aiResponseData = JSON.stringify(result);
(window as any).aiResolve = resolve;
}),
abort: (): void => {},
};
},
}),
},
},
],
};
}))
.after(async () => {
await deleteGlobalVariables();
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ test('Load panel should support string height and width', async (t) => {
await dataGrid.apiBeginCustomLoading('test');

await t
.expect(dataGrid.getLoadPanel().content.getStyleProperty('height'))
.expect(dataGrid.getLoadPanel().getContent().getStyleProperty('height'))
.eql('400px')
.expect(dataGrid.getLoadPanel().content.getStyleProperty('width'))
.expect(dataGrid.getLoadPanel().getContent().getStyleProperty('width'))
.eql('330px');
}).before(async () => createWidget('dxDataGrid', {
dataSource: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isDefined } from '@ts/core/utils/m_type';
import type { Column, ColumnsController } from '../../columns_controller/m_columns_controller';
import type { DataController, HandleDataChangedArguments, UserData } from '../../data_controller/m_data_controller';
import { Controller } from '../../m_modules';
import gridCoreUtils from '../../m_utils';
import type { InternalRequestCallbacks } from '../types';
import { getAICommandColumnDefaultOptions, isAIColumnAutoMode, isPromptOption } from '../utils';
import { AIColumnIntegrationController } from './m_ai_column_integration_controller';
Expand All @@ -24,6 +25,8 @@ export class AIColumnController extends Controller {

private storeBeforePushHandler!: ({ changes }: { changes: DataChange[] }) => void;

private dataControllerChangedHandler!: () => void;

private aiColumnOptionChangedHandler!: (
column: Column,
optionName: string,
Expand Down Expand Up @@ -64,6 +67,31 @@ export class AIColumnController extends Controller {
this.dataController.dataSource()?.changed.add(this.dataSourceChangedHandler);
}

private unsubscribeFromDataControllerChanged(): void {
if (!this.dataControllerChangedHandler) {
return;
}

this.dataController.changed.remove(this.dataControllerChangedHandler);
}

private subscribeToDataControllerChanged(): void {
if (!this.getAIColumns().length || !gridCoreUtils.isVirtualRowRendering(this)) {
return;
}

this.dataControllerChangedHandler = this.dataControllerChangedHandler
?? this.handleDataControllerChanged.bind(this);

this.dataController.changed.add(this.dataControllerChangedHandler);
}

private handleDataControllerChanged(): void {
if (this.dataController.isViewportChanging()) {
this.sendRequests();
}
}

private unsubscribeFromStoreEvents(): void {
const store = this.dataController.store();

Expand Down Expand Up @@ -137,12 +165,10 @@ export class AIColumnController extends Controller {
});
}

private handleDataSourceChanged(args?: HandleDataChangedArguments): void {
private sendRequests(): void {
const aiColumns = this.getAIColumns();

if (args?.changeType === 'loadError'
|| aiColumns.length === 0
|| !this.checkStoreKey()) {
if (!aiColumns.length || !this.checkStoreKey()) {
return;
}

Expand All @@ -153,6 +179,14 @@ export class AIColumnController extends Controller {
}
}

private handleDataSourceChanged(args?: HandleDataChangedArguments): void {
if (args?.changeType === 'loadError') {
return;
}

this.sendRequests();
}

protected callbackNames(): string[] {
return ['aiRequestCompleted', 'aiRequestRejected'];
}
Expand All @@ -172,6 +206,9 @@ export class AIColumnController extends Controller {
this.unsubscribeFromStoreEvents();
this.subscribeToStoreEvents();

this.unsubscribeFromDataControllerChanged();
this.subscribeToDataControllerChanged();

this.addAICommandColumn();
}

Expand Down Expand Up @@ -280,5 +317,6 @@ export class AIColumnController extends Controller {
super.dispose();
this.dataController.dataSource()?.changed.remove(this.dataSourceChangedHandler);
this.unsubscribeFromStoreEvents();
this.unsubscribeFromDataControllerChanged();
}
}
Loading
Loading