Skip to content
Open
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
Expand Up @@ -2,6 +2,7 @@
@use "../mixins" as *;
@use 'variables' as *;
@use 'layout/cell' as *;
@use "layout/ai-chat" as *;

// adduse

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@forward 'layout/ai-chat/mixins';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@use "../../../icon_fonts" as *;

.dx-ai-chat .dx-popup-content {
padding: 0;
}

.dx-ai-chat__content {
border: none;
min-height: auto;
}

.dx-ai-chat-messagelist-empty-image {
@include dx-icon(sparkle);

border-radius: 999em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@mixin ai-chat-messagelist-empty(
$messagelist-empty-icon-color,
$messagelist-empty-icon-background-color,
) {
.dx-ai-chat-messagelist-empty-image {
color: $messagelist-empty-icon-color;
background-color: $messagelist-empty-icon-background-color;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
@use "../button/colors" as *;
@use "../validation/colors" as *;
@use "../typography/sizes" as *;
@use "../../base/gridBase/mixins" as *;

// adduse
@use "../scrollable";
Expand Down Expand Up @@ -1055,4 +1056,9 @@ $fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-heig
height: $fluent-grid-base-ai-prompt-editor-progressbar-height;
border-radius: 0;
}

@include ai-chat-messagelist-empty(
$button-default-bg,
$button-default-outlined-hover-bg,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@use "../scrollable";
@use "../overlay";
@use "../pagination";
@use "../../base/gridBase/mixins" as *;

$generic-grid-base-border-hidden: 1px solid transparent;
$generic-grid-base-row-border: 1px solid transparent;
Expand Down Expand Up @@ -898,4 +899,9 @@ $generic-grid-base-cell-input-height: math.round($generic-base-line-height * $ge
border: none;
border-radius: 0;
}

@include ai-chat-messagelist-empty(
$button-default-bg,
$button-default-outlined-bg-hover,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
@use "../button/sizes" as *;
@use "../validation/colors" as *;
@use "../typography/sizes" as *;
@use "../../base/gridBase/mixins" as *;

// adduse
@use "variables" as *;
Expand Down Expand Up @@ -1032,4 +1033,9 @@ $material-grid-base-group-panel-message-line-height: $material-button-text-line-
height: $material-grid-base-ai-prompt-editor-progressbar-height;
border-radius: 0;
}

@include ai-chat-messagelist-empty(
$button-default-bg,
$button-default-outlined-hover-bg,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,30 @@ const createAIAssistantView = ({

return undefined;
});
const $columnHeadersElement = $('<div>').appendTo($container);
const $rowsViewElement = $('<div>').css('height', '400px').appendTo($container);

const mockColumnHeadersView = {
getHeight: jest.fn().mockReturnValue(50),
element: jest.fn().mockReturnValue($columnHeadersElement),
};
const mockRowsView = {
element: jest.fn().mockReturnValue($rowsViewElement),
};

const mockComponent = {
element: (): any => $container.get(0),
_createComponent: createComponentMock,
_controllers: {},
_views: {
columnHeadersView: mockColumnHeadersView,
rowsView: mockRowsView,
},
option: optionMock,
};

const aiAssistantView = new AIAssistantView(mockComponent);
aiAssistantView.init();
if (render) {
aiAssistantView.render($container);
}
Expand Down Expand Up @@ -114,13 +130,16 @@ describe('AIAssistantView', () => {
expect(AIChat).toHaveBeenCalledTimes(1);
});

it('should pass container and createComponent to AIChat', () => {
it('should pass container, createComponent, popupOptions, chatOptions, and onChatCleared to AIChat', () => {
const { aiAssistantView } = createAIAssistantView();

expect(AIChat).toHaveBeenCalledWith(
expect.objectContaining({
container: aiAssistantView.element(),
createComponent: expect.any(Function),
popupOptions: expect.any(Object),
chatOptions: expect.any(Object),
onChatCleared: expect.any(Function),
}),
);
});
Expand Down Expand Up @@ -213,20 +232,158 @@ describe('AIAssistantView', () => {
});

describe('visibilityChanged', () => {
it('should fire visibilityChanged callback when popup visibility changes', () => {
it('should fire visibilityChanged callback with true when popup onShowing is triggered', () => {
const { aiAssistantView } = createAIAssistantView();
const callback = jest.fn();

aiAssistantView.visibilityChanged?.add(callback);

const aiChatConfig = (AIChat as jest.Mock).mock.calls[0][0] as AIChatOptions;
aiChatConfig.onVisibilityChanged?.(true);
aiChatConfig.popupOptions?.onShowing?.({} as any);

expect(callback).toHaveBeenCalledWith(true);
});

aiChatConfig.onVisibilityChanged?.(false);
it('should fire visibilityChanged callback with false when popup onHidden is triggered', () => {
const { aiAssistantView } = createAIAssistantView();
const callback = jest.fn();

aiAssistantView.visibilityChanged?.add(callback);

const aiChatConfig = (AIChat as jest.Mock).mock.calls[0][0] as AIChatOptions;
aiChatConfig.popupOptions?.onHidden?.({} as any);

expect(callback).toHaveBeenCalledWith(false);
});
});

describe('optionChanged', () => {
it('should set handled to true for aiAssistant options', () => {
const { aiAssistantView } = createAIAssistantView();

const args = {
name: 'aiAssistant' as const,
fullName: 'aiAssistant.title' as const,
value: 'New Title',
previousValue: 'Old Title',
handled: false,
};

aiAssistantView.optionChanged(args);

expect(args.handled).toBe(true);
});

it('should call _invalidate when aiAssistant.enabled changes to true', () => {
const { aiAssistantView } = createAIAssistantView();
const invalidateSpy = jest.spyOn(aiAssistantView, '_invalidate' as any);

aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant.enabled' as const,
value: true,
previousValue: false,
handled: false,
});

expect(invalidateSpy).toHaveBeenCalledTimes(1);
});

it('should call hide when aiAssistant.enabled changes to false', () => {
const { aiAssistantView, setEnabled } = createAIAssistantView();
const hideSpy = jest.spyOn(aiAssistantView, 'hide');

setEnabled(false);

aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant.enabled' as const,
value: false,
previousValue: true,
handled: false,
});

expect(hideSpy).toHaveBeenCalledTimes(1);
});

it('should call updateOptions on aiChatInstance for title change', () => {
const { aiAssistantView } = createAIAssistantView();

const aiChatInstance = (AIChat as jest.Mock)
.mock.results[0].value as { updateOptions: jest.Mock };

aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant.title' as const,
value: 'New Title',
previousValue: 'Old Title',
handled: false,
});

expect(aiChatInstance.updateOptions).toHaveBeenCalledTimes(1);
expect(aiChatInstance.updateOptions).toHaveBeenCalledWith(
expect.any(Object),
true,
false,
);
});

it('should call updateOptions on aiChatInstance for chat options change', () => {
const { aiAssistantView } = createAIAssistantView();

const aiChatInstance = (AIChat as jest.Mock)
.mock.results[0].value as { updateOptions: jest.Mock };

aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant.chat' as const,
value: { speechToTextEnabled: false },
previousValue: {},
handled: false,
});

expect(aiChatInstance.updateOptions).toHaveBeenCalledTimes(1);
expect(aiChatInstance.updateOptions).toHaveBeenCalledWith(
expect.any(Object),
false,
true,
);
});

it('should call updateOptions with both flags when object value contains title and chat', () => {
const { aiAssistantView } = createAIAssistantView();

const aiChatInstance = (AIChat as jest.Mock)
.mock.results[0].value as { updateOptions: jest.Mock };

aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant' as const,
value: { title: 'New title', chat: { speechToTextEnabled: false } },
previousValue: { title: 'Old title' },
handled: false,
});

expect(aiChatInstance.updateOptions).toHaveBeenCalledTimes(1);
expect(aiChatInstance.updateOptions).toHaveBeenCalledWith(
expect.any(Object),
true,
true,
);
});

it('should not throw when aiChatInstance is not created for non-enabled sub-options', () => {
const { aiAssistantView } = createAIAssistantView({ render: false });

expect(() => {
aiAssistantView.optionChanged({
name: 'aiAssistant' as const,
fullName: 'aiAssistant.title' as const,
value: 'New Title',
previousValue: 'Old Title',
handled: false,
});
}).not.toThrow();
});
});
});
Loading
Loading