Skip to content

Commit 1ae5f62

Browse files
committed
GridCore: add ai assistant toolbar item
1 parent abed32f commit 1ae5f62

10 files changed

Lines changed: 304 additions & 82 deletions

File tree

packages/devextreme/js/__internal/grids/data_grid/module_not_extended/ai_assistant.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import { AIAssistantViewController } from '@ts/grids/grid_core/ai_assistant/m_ai
44
import gridCore from '../m_core';
55

66
gridCore.registerModule('aiAssistant', {
7+
defaultOptions() {
8+
return {
9+
aiAssistant: {
10+
enabled: false,
11+
title: 'AI Assistant', // TODO add localization message
12+
},
13+
};
14+
},
715
controllers: {
816
aiAssistant: AIAssistantViewController,
917
},
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import {
2+
afterEach,
3+
beforeEach,
4+
describe,
5+
expect,
6+
it,
7+
jest,
8+
} from '@jest/globals';
9+
import type { Properties as DataGridProperties } from '@js/ui/data_grid';
10+
11+
import {
12+
afterTest,
13+
beforeTest,
14+
createDataGrid as commonCreateDataGrid,
15+
type DataGridInstance,
16+
} from '../../__tests__/__mock__/helpers/utils';
17+
18+
// TODO remove when types added to public dts
19+
interface AIAssistantDataGridProperties extends DataGridProperties {
20+
aiAssistant?: {
21+
enabled?: boolean;
22+
title?: string;
23+
};
24+
}
25+
26+
// TODO remove when types added to public dts
27+
const createDataGrid = (
28+
options: AIAssistantDataGridProperties = {},
29+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
30+
): ReturnType<typeof commonCreateDataGrid> => commonCreateDataGrid(options as any);
31+
32+
const AI_ASSISTANT_BUTTON_SELECTOR = '.dx-datagrid-ai-assistant-button';
33+
const HIDDEN_CLASS = 'dx-hidden';
34+
35+
const getAiAssistantButton = (
36+
instance: DataGridInstance,
37+
): Element | null => instance
38+
.element()
39+
.querySelector(AI_ASSISTANT_BUTTON_SELECTOR);
40+
41+
const isAiAssistantButtonVisible = (instance: DataGridInstance): boolean => {
42+
const button = getAiAssistantButton(instance);
43+
44+
if (!button) {
45+
return false;
46+
}
47+
48+
return !button.closest(`.${HIDDEN_CLASS}`);
49+
};
50+
51+
describe('AIAssistantViewController', () => {
52+
beforeEach(beforeTest);
53+
afterEach(afterTest);
54+
55+
describe('init', () => {
56+
it('should register toolbar button when aiAssistant.enabled is true', async () => {
57+
const { instance } = await createDataGrid({
58+
dataSource: [{ id: 1 }],
59+
aiAssistant: { enabled: true, title: 'AI Assistant' },
60+
});
61+
62+
const button = getAiAssistantButton(instance);
63+
64+
expect(button).not.toBeNull();
65+
});
66+
67+
it('should not register toolbar button when aiAssistant.enabled is false', async () => {
68+
const { instance } = await createDataGrid({
69+
dataSource: [{ id: 1 }],
70+
aiAssistant: { enabled: false },
71+
});
72+
73+
const button = getAiAssistantButton(instance);
74+
75+
expect(button).toBeNull();
76+
});
77+
78+
it('should not register toolbar button when aiAssistant is not set', async () => {
79+
const { instance } = await createDataGrid({
80+
dataSource: [{ id: 1 }],
81+
});
82+
83+
const button = getAiAssistantButton(instance);
84+
85+
expect(button).toBeNull();
86+
});
87+
});
88+
89+
describe('optionChanged', () => {
90+
it('should add toolbar button when aiAssistant.enabled changes to true', async () => {
91+
const { instance } = await createDataGrid({
92+
dataSource: [{ id: 1 }],
93+
aiAssistant: { enabled: false, title: 'AI Assistant' },
94+
});
95+
96+
instance.option('aiAssistant.enabled', true);
97+
jest.runAllTimers();
98+
await Promise.resolve();
99+
jest.runAllTimers();
100+
101+
const button = getAiAssistantButton(instance);
102+
103+
expect(button).not.toBeNull();
104+
});
105+
106+
it('should hide ai assistant button when aiAssistant.enabled changes to false', async () => {
107+
const { instance } = await createDataGrid({
108+
dataSource: [{ id: 1 }],
109+
aiAssistant: { enabled: true, title: 'AI Assistant' },
110+
});
111+
112+
expect(isAiAssistantButtonVisible(instance)).toBe(true);
113+
114+
instance.option('aiAssistant.enabled', false);
115+
jest.runAllTimers();
116+
await Promise.resolve();
117+
jest.runAllTimers();
118+
119+
expect(isAiAssistantButtonVisible(instance)).toBe(false);
120+
});
121+
});
122+
123+
describe('toolbar button', () => {
124+
it('should have aria-haspopup dialog attribute', async () => {
125+
const { instance } = await createDataGrid({
126+
dataSource: [{ id: 1 }],
127+
aiAssistant: { enabled: true, title: 'AI Assistant' },
128+
});
129+
130+
const button = getAiAssistantButton(instance);
131+
132+
expect(button?.getAttribute('aria-haspopup')).toBe('dialog');
133+
});
134+
135+
it('should have correct hint text from aiAssistant.title', async () => {
136+
const { instance } = await createDataGrid({
137+
dataSource: [{ id: 1 }],
138+
aiAssistant: { enabled: true, title: 'My Custom Title' },
139+
});
140+
141+
const button = getAiAssistantButton(instance);
142+
143+
expect(button?.getAttribute('title')).toBe('My Custom Title');
144+
});
145+
});
146+
147+
describe('show / hide / toggle', () => {
148+
it('should delegate show to aiAssistantView', async () => {
149+
const { instance } = await createDataGrid({
150+
dataSource: [{ id: 1 }],
151+
aiAssistant: { enabled: true, title: 'AI Assistant' },
152+
});
153+
154+
const controller = instance.getController('aiAssistant');
155+
const view = (controller as any).aiAssistantView;
156+
const showSpy = jest.spyOn(view, 'show');
157+
158+
await controller.show();
159+
160+
expect(showSpy).toHaveBeenCalledTimes(1);
161+
});
162+
163+
it('should delegate hide to aiAssistantView', async () => {
164+
const { instance } = await createDataGrid({
165+
dataSource: [{ id: 1 }],
166+
aiAssistant: { enabled: true, title: 'AI Assistant' },
167+
});
168+
169+
const controller = instance.getController('aiAssistant');
170+
const view = (controller as any).aiAssistantView;
171+
const hideSpy = jest.spyOn(view, 'hide');
172+
173+
await controller.hide();
174+
175+
expect(hideSpy).toHaveBeenCalledTimes(1);
176+
});
177+
178+
it('should delegate toggle to aiAssistantView', async () => {
179+
const { instance } = await createDataGrid({
180+
dataSource: [{ id: 1 }],
181+
aiAssistant: { enabled: true, title: 'AI Assistant' },
182+
});
183+
184+
const controller = instance.getController('aiAssistant');
185+
const view = (controller as any).aiAssistantView;
186+
const toggleSpy = jest.spyOn(view, 'toggle');
187+
188+
await controller.toggle();
189+
190+
expect(toggleSpy).toHaveBeenCalledTimes(1);
191+
});
192+
});
193+
});

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/m_ai_assistant_view.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class AIAssistantView extends View {
2121
}
2222

2323
public isVisible(): boolean {
24-
return this.option('aiAssistant.enabled');
24+
return !!this.option('aiAssistant.enabled');
2525
}
2626

2727
public show(): Promise<boolean> {
@@ -31,4 +31,8 @@ export class AIAssistantView extends View {
3131
public hide(): Promise<boolean> {
3232
return this.aiChatInstance?.hide() ?? Promise.resolve(false);
3333
}
34+
35+
public toggle(): Promise<boolean> {
36+
return this.aiChatInstance?.toggle() ?? Promise.resolve(false);
37+
}
3438
}

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/m_ai_assistant_view_controller.test.ts

Lines changed: 0 additions & 79 deletions
This file was deleted.

0 commit comments

Comments
 (0)