Skip to content

Commit 4cf80a3

Browse files
authored
DataGrid - AI Assistant: Implement message cleanup (DevExpress#33537)
Co-authored-by: Alyar <>
1 parent d4b2012 commit 4cf80a3

5 files changed

Lines changed: 92 additions & 1 deletion

File tree

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/ai_assistant_view.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,20 @@ describe('AIAssistantView', () => {
281281
});
282282

283283
describe('chat event handlers', () => {
284+
describe('onChatCleared', () => {
285+
it('should call clear on aiChatInstance when triggered', () => {
286+
createAIAssistantView();
287+
288+
const aiChatInstance = (AIChat as jest.Mock)
289+
.mock.results[0].value as { clear: jest.Mock };
290+
291+
const aiChatConfig = (AIChat as jest.Mock).mock.calls[0][0] as AIChatOptions;
292+
aiChatConfig.onChatCleared?.();
293+
294+
expect(aiChatInstance.clear).toHaveBeenCalledTimes(1);
295+
});
296+
});
297+
284298
describe('onMessageEntered', () => {
285299
it('should send request to AI with the entered message', () => {
286300
mockAIAssistantController.sendRequestToAI.mockReturnValue(Promise.resolve());

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/ai_assistant_view_controller.integration.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,5 +357,27 @@ describe('AIAssistantViewController', () => {
357357
expect(getMessageStatusClass($messagesAfter.eq(1)))
358358
.toBe(MessageStatus.Success);
359359
});
360+
361+
it('should clear all messages when clear chat button is clicked', async () => {
362+
const { instance, getLastCallbacks } = await createDataGridWithAIAssistant();
363+
364+
sendAIRequest(instance, 'Sort by Name');
365+
366+
getLastCallbacks().onComplete?.({
367+
actions: [{ name: 'sort', args: { column: 'Name' } }],
368+
});
369+
await flushAsync();
370+
await flushAsync();
371+
372+
expect(findMessageElements().length).toBe(1);
373+
374+
const clearButtonEl = document.querySelector(`.${CLASSES.clearChatButton} .dx-button`) as HTMLElement;
375+
376+
clearButtonEl.click();
377+
await flushAsync();
378+
await flushAsync();
379+
380+
expect(findMessageElements().length).toBe(0);
381+
});
360382
});
361383
});

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export class AIAssistantView extends View {
4444
return {
4545
container: this.element(),
4646
createComponent: this._createComponent.bind(this),
47-
onChatCleared: (): void => {},
47+
onChatCleared: (): void => {
48+
this.aiChatInstance.clear();
49+
},
4850
onRegenerate: (): void => {},
4951
popupOptions,
5052
chatOptions,

packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ const mockPopupInstance = {
3939

4040
const mockChatElement = $('<div>');
4141

42+
const mockDataSource = {
43+
store: jest.fn(),
44+
reload: jest.fn(),
45+
};
46+
4247
const mockChatInstance = {
4348
option: jest.fn(),
4449
$element: jest.fn(() => mockChatElement),
50+
getDataSource: jest.fn(() => mockDataSource),
4551
};
4652

4753
const mockClearChatButtonInstance = {
@@ -130,6 +136,7 @@ const beforeTest = (): void => {
130136
mockChatElement.empty();
131137
mockWidgetInstance.option.mockClear();
132138
mockClearChatButtonInstance.option.mockClear();
139+
mockChatInstance.getDataSource.mockReturnValue(mockDataSource);
133140
};
134141

135142
const afterTest = (): void => {
@@ -919,4 +926,40 @@ describe('AIChat', () => {
919926
});
920927
});
921928
});
929+
930+
describe('clear', () => {
931+
it('should clear store and reload dataSource', () => {
932+
const mockStore = { clear: jest.fn() };
933+
mockDataSource.store.mockReturnValue(mockStore);
934+
935+
const { aiChat } = createAIChat();
936+
triggerContentTemplate();
937+
938+
aiChat.clear();
939+
940+
expect(mockChatInstance.getDataSource).toHaveBeenCalledTimes(1);
941+
expect(mockDataSource.store).toHaveBeenCalledTimes(1);
942+
expect(mockStore.clear).toHaveBeenCalledTimes(1);
943+
expect(mockDataSource.reload).toHaveBeenCalledTimes(1);
944+
});
945+
946+
it('should not throw when dataSource is undefined', () => {
947+
mockChatInstance.getDataSource.mockReturnValue(undefined as any);
948+
949+
const { aiChat } = createAIChat();
950+
triggerContentTemplate();
951+
952+
expect(() => {
953+
aiChat.clear();
954+
}).not.toThrow();
955+
});
956+
957+
it('should not throw when chatInstance is not initialized', () => {
958+
const { aiChat } = createAIChat();
959+
960+
expect(() => {
961+
aiChat.clear();
962+
}).not.toThrow();
963+
});
964+
});
922965
});

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { name as clickEventName } from '@js/common/core/events/click';
22
import eventsEngine from '@js/common/core/events/core/events_engine';
33
import messageLocalization from '@js/common/core/localization/message';
4+
import type { ArrayStore } from '@js/common/data';
45
import type { dxElementWrapper } from '@js/core/renderer';
56
import $ from '@js/core/renderer';
67
import type { Message, Properties as ChatProperties } from '@js/ui/chat';
@@ -333,4 +334,13 @@ export class AIChat {
333334
this.renderMessageHeader($content, message);
334335
this.renderMessageStateContent($content, message);
335336
}
337+
338+
public clear(): void {
339+
const dataSource = this.chatInstance?.getDataSource();
340+
const store = dataSource?.store() as ArrayStore<Message, string>;
341+
342+
store?.clear();
343+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
344+
dataSource?.reload();
345+
}
336346
}

0 commit comments

Comments
 (0)