Skip to content

Commit fc8da8b

Browse files
authored
✨AI Column: Test unsupported Binding properties (DevExpress#31802)
1 parent 9eca8c6 commit fc8da8b

File tree

4 files changed

+251
-4
lines changed

4 files changed

+251
-4
lines changed

packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/unsupported_base_properties.integration.test.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,4 +693,184 @@ describe('Unsupported properties', () => {
693693
expect(aiCell.getText()).toBe(EMPTY_CELL_TEXT);
694694
});
695695
});
696+
697+
describe('Binding properties', () => {
698+
const aiIntegration = new AIIntegration({
699+
sendRequest(prompt): RequestResult {
700+
return {
701+
promise: new Promise<string>((resolve) => {
702+
const result = {};
703+
Object.entries(prompt.data?.data).forEach(([key, value]) => {
704+
const { name } = value as { name: string };
705+
result[key] = `Response ${name}`;
706+
});
707+
resolve(JSON.stringify(result));
708+
}),
709+
abort: (): void => {},
710+
};
711+
},
712+
});
713+
it('Should not bind AI column to dataField (first load)', async () => {
714+
const { component } = await createDataGrid({
715+
dataSource,
716+
showBorders: true,
717+
keyExpr: 'id',
718+
columns: [
719+
'id',
720+
{
721+
caption: 'AI',
722+
type: 'ai',
723+
name: 'AItest',
724+
dataField: 'name',
725+
ai: {
726+
prompt: 'Provide name for item with value {value}',
727+
aiIntegration,
728+
},
729+
},
730+
],
731+
});
732+
await Promise.resolve();
733+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
734+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
735+
});
736+
737+
it('Should not bind AI column to dataField (dynamic update)', async () => {
738+
const { instance, component } = await createDataGrid({
739+
dataSource,
740+
showBorders: true,
741+
keyExpr: 'id',
742+
columns: [
743+
'id',
744+
{
745+
caption: 'AI',
746+
type: 'ai',
747+
name: 'AItest',
748+
ai: {
749+
prompt: 'Provide name for item with value {value}',
750+
aiIntegration,
751+
},
752+
},
753+
],
754+
});
755+
await Promise.resolve();
756+
instance.columnOption('AItest', 'dataField', 'name');
757+
await Promise.resolve();
758+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
759+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
760+
});
761+
762+
it('Should not take into account calculateCellValue (first load)', async () => {
763+
const { component } = await createDataGrid({
764+
dataSource,
765+
showBorders: true,
766+
keyExpr: 'id',
767+
columns: [
768+
'id',
769+
{
770+
caption: 'AI',
771+
type: 'ai',
772+
name: 'AItest',
773+
dataField: 'name',
774+
calculateCellValue: (data) => data.name,
775+
ai: {
776+
prompt: 'Provide name for item with value {value}',
777+
aiIntegration,
778+
},
779+
},
780+
],
781+
});
782+
await Promise.resolve();
783+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
784+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
785+
});
786+
787+
it('Should not take into account calculateCellValue (dynamic update)', async () => {
788+
const { instance, component } = await createDataGrid({
789+
dataSource,
790+
showBorders: true,
791+
keyExpr: 'id',
792+
columns: [
793+
'id',
794+
{
795+
caption: 'AI',
796+
type: 'ai',
797+
name: 'AItest',
798+
ai: {
799+
prompt: 'Provide name for item with value {value}',
800+
aiIntegration,
801+
},
802+
},
803+
],
804+
});
805+
await Promise.resolve();
806+
instance.columnOption('AItest', 'dataField', 'name');
807+
instance.columnOption('AItest', 'calculateCellValue', (data) => data.name);
808+
await Promise.resolve();
809+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
810+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
811+
});
812+
813+
it('Should not bind AI column to lookup (first load)', async () => {
814+
const { component } = await createDataGrid({
815+
dataSource,
816+
showBorders: true,
817+
keyExpr: 'id',
818+
columns: [
819+
'id',
820+
{
821+
caption: 'AI',
822+
type: 'ai',
823+
name: 'AItest',
824+
lookup: {
825+
dataSource: [
826+
{ id: 1, name: 'Lookup 1' },
827+
{ id: 2, name: 'Lookup 2' },
828+
],
829+
valueExpr: 'id',
830+
displayExpr: 'name',
831+
},
832+
ai: {
833+
prompt: 'Provide name for item with value {value}',
834+
aiIntegration,
835+
},
836+
},
837+
],
838+
});
839+
await Promise.resolve();
840+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
841+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
842+
});
843+
844+
it('Should not bind AI column to lookup (dynamic update)', async () => {
845+
const { instance, component } = await createDataGrid({
846+
dataSource,
847+
showBorders: true,
848+
keyExpr: 'id',
849+
columns: [
850+
'id',
851+
{
852+
caption: 'AI',
853+
type: 'ai',
854+
name: 'AItest',
855+
ai: {
856+
prompt: 'Provide name for item with value {value}',
857+
aiIntegration,
858+
},
859+
},
860+
],
861+
});
862+
await Promise.resolve();
863+
instance.columnOption('AItest', 'lookup', {
864+
dataSource: [
865+
{ id: 1, name: 'Lookup 1' },
866+
{ id: 2, name: 'Lookup 2' },
867+
],
868+
valueExpr: 'id',
869+
displayExpr: 'name',
870+
});
871+
await Promise.resolve();
872+
expect(component.getDataCell(0, 1).getText()).toBe('Response Item 1');
873+
expect(component.getDataCell(1, 1).getText()).toBe('Response Item 2');
874+
});
875+
});
696876
});

packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,10 @@ export class ColumnsController extends modules.Controller {
11871187
let isColumnDataTypesUpdated = false;
11881188

11891189
each(that._columns, (index, column) => {
1190+
if (column.type === AI_COLUMN_NAME) {
1191+
return;
1192+
}
1193+
11901194
let i;
11911195
let value;
11921196
let dataType;
@@ -1758,7 +1762,7 @@ export class ColumnsController extends modules.Controller {
17581762
if (columnOptions.selectedFilterOperation && !('defaultSelectedFilterOperation' in calculatedColumnOptions)) {
17591763
calculatedColumnOptions.defaultSelectedFilterOperation = columnOptions.selectedFilterOperation;
17601764
}
1761-
if (columnOptions.lookup) {
1765+
if (columnOptions.lookup && columnOptions.type !== AI_COLUMN_NAME) {
17621766
calculatedColumnOptions.lookup = {
17631767
calculateCellValue(value, skipDeserialization) {
17641768
if (this.valueExpr) {

packages/devextreme/js/__internal/grids/grid_core/m_utils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import LoadPanel from '@js/ui/load_panel';
2121
import sharedFiltering from '@js/ui/shared/filtering';
2222
import type { ColumnPoint } from '@ts/grids/grid_core/m_types';
2323

24+
import { AI_COLUMN_NAME } from './ai_column/const';
2425
import { isEqualSelectors, isSelectorEqualWithCallback } from './utils/index';
2526

2627
const BASE_LOAD_PANEL_Z_INDEX = 1000;
@@ -342,11 +343,19 @@ export default {
342343
getDisplayValue(column, value, data, rowType?) {
343344
if (column.displayValueMap && column.displayValueMap[value] !== undefined) {
344345
return column.displayValueMap[value];
345-
} if (column.calculateDisplayValue && data && rowType !== 'group') {
346+
}
347+
if (column.calculateDisplayValue && data && rowType !== 'group') {
346348
return column.calculateDisplayValue(data);
347-
} if (column.lookup && !(rowType === 'group' && (column.calculateGroupValue || column.calculateDisplayValue))) {
349+
}
350+
351+
const isCalculatedFromLookup = column.lookup
352+
&& column.type !== AI_COLUMN_NAME
353+
&& (rowType !== 'group' || (!column.calculateGroupValue && !column.calculateDisplayValue));
354+
355+
if (isCalculatedFromLookup) {
348356
return column.lookup.calculateCellValue(value);
349357
}
358+
350359
return value;
351360
},
352361

packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/exportController.tests.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { setupDataGridModules } from '../../helpers/dataGridMocks.js';
88
import ArrayStore from 'common/data/array_store';
99
import messageLocalization from 'common/core/localization/message';
1010
import { prepareItems } from '__internal/grids/grid_core/m_export';
11+
import { AIIntegration } from '__internal/core/ai_integration/core/ai_integration';
1112

1213
QUnit.testStart(function() {
1314
const markup =
@@ -27,7 +28,7 @@ QUnit.module('ExportController', {
2728

2829
initDefaultOptions = initDefaultOptions !== undefined ? initDefaultOptions : true;
2930

30-
setupDataGridModules(this, ['data', 'columns', 'rows', 'editorFactory', 'editing', 'selection', 'grouping', 'summary', 'masterDetail', 'virtualColumns', 'export'], {
31+
setupDataGridModules(this, ['data', 'columns', 'rows', 'editorFactory', 'editing', 'selection', 'grouping', 'summary', 'masterDetail', 'virtualColumns', 'aiColumn', 'export'], {
3132
initViews: true,
3233
initDefaultOptions: initDefaultOptions,
3334
options: $.extend(true, { loadingTimeout: null }, this.options)
@@ -139,6 +140,59 @@ QUnit.module('ExportController', {
139140
assert.ok(columnCompare(columns[2], { width: 90, dataType: 'boolean', alignment: 'center', caption: 'Test Field 4' }), 'column 4');
140141
});
141142

143+
const aiIntegration = new AIIntegration({
144+
sendRequest(prompt) {
145+
return {
146+
promise: new Promise((resolve) => {
147+
const result = {};
148+
const data = prompt.data && prompt.data.data;
149+
if(data) {
150+
Object.entries(data).forEach(([key, value]) => {
151+
const { name } = value;
152+
result[key] = `Response ${name}`;
153+
});
154+
}
155+
resolve(JSON.stringify(result));
156+
}),
157+
abort: () => {},
158+
};
159+
},
160+
});
161+
162+
[true, false].forEach((allowExporting) => {
163+
QUnit.test(`Get columns from data provider when visible columns has AI column and allowExporting=${allowExporting}`, function(assert) {
164+
this.setupModules({
165+
columns: [{
166+
width: 100,
167+
type: 'ai',
168+
allowExporting: allowExporting,
169+
name: 'AIColumn',
170+
caption: 'Test AI Column',
171+
ai: {
172+
prompt: 'Test prompt',
173+
aiIntegration,
174+
},
175+
}, {
176+
dataField: 'TestField2', width: 40, dataType: 'number'
177+
}, {
178+
dataField: 'TestField3', width: 50, dataType: 'date'
179+
}, {
180+
dataField: 'TestField4', width: 90, dataType: 'boolean'
181+
}]
182+
});
183+
184+
const dataProvider = this.exportController.getDataProvider();
185+
186+
dataProvider.ready();
187+
const columns = dataProvider.getColumns();
188+
189+
assert.equal(columns.length, 3, 'columns length');
190+
assert.ok(columnCompare(columns[0], { width: 40, dataType: 'number', alignment: 'right', caption: 'Test Field 2' }), 'column 1');
191+
assert.ok(columnCompare(columns[1], { width: 50, dataType: 'date', alignment: 'left', caption: 'Test Field 3' }), 'column 2');
192+
assert.ok(columnCompare(columns[2], { width: 90, dataType: 'boolean', alignment: 'center', caption: 'Test Field 4' }), 'column 3');
193+
});
194+
});
195+
142196
QUnit.test('Get columns with percent value in width of column', function(assert) {
143197
this.setupModules({
144198
columns: [{

0 commit comments

Comments
 (0)