@copilotkitnext/angular 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +39 -0
- package/.turbo/turbo-check-types.log +0 -0
- package/.turbo/turbo-test.log +71 -0
- package/LICENSE +10 -0
- package/README-agent-context.md +310 -0
- package/dist/LICENSE +10 -0
- package/dist/components/chat/copilot-chat-assistant-message-buttons.component.d.ts +75 -0
- package/dist/components/chat/copilot-chat-assistant-message-renderer.component.d.ts +31 -0
- package/dist/components/chat/copilot-chat-assistant-message-toolbar.component.d.ts +8 -0
- package/dist/components/chat/copilot-chat-assistant-message.component.d.ts +131 -0
- package/dist/components/chat/copilot-chat-assistant-message.types.d.ts +31 -0
- package/dist/components/chat/copilot-chat-audio-recorder.component.d.ts +40 -0
- package/dist/components/chat/copilot-chat-buttons.component.d.ts +66 -0
- package/dist/components/chat/copilot-chat-input-defaults.d.ts +37 -0
- package/dist/components/chat/copilot-chat-input.component.d.ts +133 -0
- package/dist/components/chat/copilot-chat-input.types.d.ts +129 -0
- package/dist/components/chat/copilot-chat-message-view-cursor.component.d.ts +15 -0
- package/dist/components/chat/copilot-chat-message-view.component.d.ts +293 -0
- package/dist/components/chat/copilot-chat-message-view.types.d.ts +24 -0
- package/dist/components/chat/copilot-chat-textarea.component.d.ts +45 -0
- package/dist/components/chat/copilot-chat-tool-calls-view.component.d.ts +35 -0
- package/dist/components/chat/copilot-chat-toolbar.component.d.ts +8 -0
- package/dist/components/chat/copilot-chat-tools-menu.component.d.ts +20 -0
- package/dist/components/chat/copilot-chat-user-message-branch-navigation.component.d.ts +23 -0
- package/dist/components/chat/copilot-chat-user-message-buttons.component.d.ts +39 -0
- package/dist/components/chat/copilot-chat-user-message-renderer.component.d.ts +9 -0
- package/dist/components/chat/copilot-chat-user-message-toolbar.component.d.ts +8 -0
- package/dist/components/chat/copilot-chat-user-message.component.d.ts +55 -0
- package/dist/components/chat/copilot-chat-user-message.types.d.ts +33 -0
- package/dist/components/chat/copilot-chat-view-disclaimer.component.d.ts +15 -0
- package/dist/components/chat/copilot-chat-view-feather.component.d.ts +15 -0
- package/dist/components/chat/copilot-chat-view-handlers.service.d.ts +11 -0
- package/dist/components/chat/copilot-chat-view-input-container.component.d.ts +23 -0
- package/dist/components/chat/copilot-chat-view-scroll-to-bottom-button.component.d.ts +17 -0
- package/dist/components/chat/copilot-chat-view-scroll-view.component.d.ts +84 -0
- package/dist/components/chat/copilot-chat-view.component.d.ts +205 -0
- package/dist/components/chat/copilot-chat-view.types.d.ts +42 -0
- package/dist/components/chat/copilot-chat.component.d.ts +37 -0
- package/dist/components/copilotkit-tool-render.component.d.ts +25 -0
- package/dist/core/chat-configuration/chat-configuration.providers.d.ts +54 -0
- package/dist/core/chat-configuration/chat-configuration.service.d.ts +75 -0
- package/dist/core/chat-configuration/chat-configuration.types.d.ts +27 -0
- package/dist/core/copilotkit.providers.d.ts +13 -0
- package/dist/core/copilotkit.service.d.ts +119 -0
- package/dist/core/copilotkit.types.d.ts +83 -0
- package/dist/directives/copilotkit-agent-context.directive.d.ts +68 -0
- package/dist/directives/copilotkit-agent.directive.d.ts +106 -0
- package/dist/directives/copilotkit-chat-config.directive.d.ts +84 -0
- package/dist/directives/copilotkit-config.directive.d.ts +44 -0
- package/dist/directives/copilotkit-frontend-tool.directive.d.ts +25 -0
- package/dist/directives/copilotkit-human-in-the-loop.directive.d.ts +124 -0
- package/dist/directives/stick-to-bottom.directive.d.ts +62 -0
- package/dist/fesm2022/copilotkitnext-angular.mjs +9314 -0
- package/dist/fesm2022/copilotkitnext-angular.mjs.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/lib/directives/tooltip.directive.d.ts +33 -0
- package/dist/lib/slots/copilot-slot.component.d.ts +34 -0
- package/dist/lib/slots/slot.types.d.ts +55 -0
- package/dist/lib/slots/slot.utils.d.ts +108 -0
- package/dist/lib/utils.d.ts +6 -0
- package/dist/services/resize-observer.service.d.ts +44 -0
- package/dist/services/scroll-position.service.d.ts +50 -0
- package/dist/styles.css +1963 -0
- package/dist/types/frontend-tool.d.ts +37 -0
- package/dist/types/human-in-the-loop.d.ts +44 -0
- package/dist/utils/agent-context.utils.d.ts +75 -0
- package/dist/utils/agent.utils.d.ts +99 -0
- package/dist/utils/chat-config.utils.d.ts +166 -0
- package/dist/utils/copilotkit.utils.d.ts +16 -0
- package/dist/utils/frontend-tool.utils.d.ts +119 -0
- package/dist/utils/human-in-the-loop.utils.d.ts +92 -0
- package/eslint.config.mjs +20 -0
- package/ng-package.json +19 -0
- package/package.json +96 -0
- package/slots.md +331 -0
- package/src/components/chat/__tests__/copilot-chat-assistant-message.component.spec.ts +282 -0
- package/src/components/chat/__tests__/copilot-chat-input.component.spec.ts +419 -0
- package/src/components/chat/__tests__/copilot-chat-message-view.component.spec.ts +372 -0
- package/src/components/chat/__tests__/copilot-chat-user-message.component.spec.ts +249 -0
- package/src/components/chat/copilot-chat-assistant-message-buttons.component.ts +292 -0
- package/src/components/chat/copilot-chat-assistant-message-renderer.component.ts +472 -0
- package/src/components/chat/copilot-chat-assistant-message-toolbar.component.ts +29 -0
- package/src/components/chat/copilot-chat-assistant-message.component.ts +463 -0
- package/src/components/chat/copilot-chat-assistant-message.types.ts +50 -0
- package/src/components/chat/copilot-chat-audio-recorder.component.ts +241 -0
- package/src/components/chat/copilot-chat-buttons.component.ts +308 -0
- package/src/components/chat/copilot-chat-buttons.component.ts.bak +471 -0
- package/src/components/chat/copilot-chat-input-defaults.ts +47 -0
- package/src/components/chat/copilot-chat-input.component.ts +512 -0
- package/src/components/chat/copilot-chat-input.types.ts +148 -0
- package/src/components/chat/copilot-chat-message-view-cursor.component.ts +51 -0
- package/src/components/chat/copilot-chat-message-view.component.ts +233 -0
- package/src/components/chat/copilot-chat-message-view.types.ts +39 -0
- package/src/components/chat/copilot-chat-textarea.component.ts +220 -0
- package/src/components/chat/copilot-chat-tool-calls-view.component.ts +261 -0
- package/src/components/chat/copilot-chat-toolbar.component.ts +35 -0
- package/src/components/chat/copilot-chat-tools-menu.component.ts +185 -0
- package/src/components/chat/copilot-chat-user-message-branch-navigation.component.ts +121 -0
- package/src/components/chat/copilot-chat-user-message-buttons.component.ts +170 -0
- package/src/components/chat/copilot-chat-user-message-renderer.component.ts +37 -0
- package/src/components/chat/copilot-chat-user-message-toolbar.component.ts +37 -0
- package/src/components/chat/copilot-chat-user-message.component.ts +247 -0
- package/src/components/chat/copilot-chat-user-message.types.ts +42 -0
- package/src/components/chat/copilot-chat-view-disclaimer.component.ts +51 -0
- package/src/components/chat/copilot-chat-view-feather.component.ts +47 -0
- package/src/components/chat/copilot-chat-view-handlers.service.ts +14 -0
- package/src/components/chat/copilot-chat-view-input-container.component.ts +87 -0
- package/src/components/chat/copilot-chat-view-scroll-to-bottom-button.component.ts +79 -0
- package/src/components/chat/copilot-chat-view-scroll-view.component.ts +322 -0
- package/src/components/chat/copilot-chat-view.component.ts +420 -0
- package/src/components/chat/copilot-chat-view.types.ts +52 -0
- package/src/components/chat/copilot-chat.component.ts +232 -0
- package/src/components/copilotkit-tool-render.component.ts +169 -0
- package/src/core/__tests__/copilotkit.service.spec.ts +1051 -0
- package/src/core/__tests__/copilotkit.service.wildcard.spec.ts +316 -0
- package/src/core/chat-configuration/__tests__/chat-configuration.service.spec.ts +287 -0
- package/src/core/chat-configuration/chat-configuration.providers.ts +71 -0
- package/src/core/chat-configuration/chat-configuration.service.ts +162 -0
- package/src/core/chat-configuration/chat-configuration.types.ts +57 -0
- package/src/core/copilotkit.providers.ts +59 -0
- package/src/core/copilotkit.service.ts +542 -0
- package/src/core/copilotkit.types.ts +132 -0
- package/src/directives/__tests__/copilotkit-agent-context.directive.spec.ts +384 -0
- package/src/directives/__tests__/copilotkit-agent.directive.spec.ts +253 -0
- package/src/directives/__tests__/copilotkit-chat-config.directive.spec.ts +385 -0
- package/src/directives/__tests__/copilotkit-config.directive.spec.ts +69 -0
- package/src/directives/__tests__/copilotkit-frontend-tool-simple.directive.spec.ts +60 -0
- package/src/directives/__tests__/copilotkit-frontend-tool.directive.spec.ts +108 -0
- package/src/directives/__tests__/copilotkit-human-in-the-loop.directive.spec.ts +452 -0
- package/src/directives/copilotkit-agent-context.directive.ts +138 -0
- package/src/directives/copilotkit-agent.directive.ts +225 -0
- package/src/directives/copilotkit-chat-config.directive.ts +241 -0
- package/src/directives/copilotkit-config.directive.ts +81 -0
- package/src/directives/copilotkit-frontend-tool.directive.ts +145 -0
- package/src/directives/copilotkit-human-in-the-loop.directive.ts +281 -0
- package/src/directives/stick-to-bottom.directive.ts +204 -0
- package/src/index.ts +105 -0
- package/src/lib/directives/tooltip.directive.ts +292 -0
- package/src/lib/slots/__tests__/slot.utils.spec.ts +377 -0
- package/src/lib/slots/copilot-slot.component.ts +135 -0
- package/src/lib/slots/index.ts +3 -0
- package/src/lib/slots/slot.types.ts +64 -0
- package/src/lib/slots/slot.utils.ts +289 -0
- package/src/lib/utils.ts +10 -0
- package/src/public-api.ts +1 -0
- package/src/services/resize-observer.service.ts +181 -0
- package/src/services/scroll-position.service.ts +169 -0
- package/src/styles/globals.css +266 -0
- package/src/styles/index.css +3 -0
- package/src/test-setup.ts +15 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/testing.utils.ts +248 -0
- package/src/types/frontend-tool.ts +44 -0
- package/src/types/human-in-the-loop.ts +52 -0
- package/src/utils/__tests__/agent.utils.spec.ts +234 -0
- package/src/utils/__tests__/chat-config.utils.spec.ts +306 -0
- package/src/utils/__tests__/frontend-tool-inject.spec.ts +350 -0
- package/src/utils/__tests__/frontend-tool-integration.spec.ts +199 -0
- package/src/utils/__tests__/frontend-tool.utils.spec.ts +272 -0
- package/src/utils/__tests__/human-in-the-loop.utils.spec.ts +365 -0
- package/src/utils/agent-context.utils.ts +133 -0
- package/src/utils/agent.utils.ts +239 -0
- package/src/utils/chat-config.utils.ts +221 -0
- package/src/utils/copilotkit.utils.ts +20 -0
- package/src/utils/frontend-tool.utils.ts +266 -0
- package/src/utils/human-in-the-loop.utils.ts +359 -0
- package/tsconfig.json +33 -0
- package/tsconfig.spec.json +12 -0
- package/vitest.config.mts +34 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { CopilotChatAssistantMessageComponent } from '../copilot-chat-assistant-message.component';
|
|
5
|
+
import { CopilotChatAssistantMessageRendererComponent } from '../copilot-chat-assistant-message-renderer.component';
|
|
6
|
+
import {
|
|
7
|
+
CopilotChatAssistantMessageCopyButtonComponent,
|
|
8
|
+
CopilotChatAssistantMessageThumbsUpButtonComponent,
|
|
9
|
+
CopilotChatAssistantMessageThumbsDownButtonComponent,
|
|
10
|
+
CopilotChatAssistantMessageReadAloudButtonComponent,
|
|
11
|
+
CopilotChatAssistantMessageRegenerateButtonComponent
|
|
12
|
+
} from '../copilot-chat-assistant-message-buttons.component';
|
|
13
|
+
import { CopilotChatAssistantMessageToolbarComponent } from '../copilot-chat-assistant-message-toolbar.component';
|
|
14
|
+
import { CopilotChatViewHandlersService } from '../copilot-chat-view-handlers.service';
|
|
15
|
+
import { provideCopilotKit } from '../../../core/copilotkit.providers';
|
|
16
|
+
import { provideCopilotChatConfiguration } from '../../../core/chat-configuration/chat-configuration.providers';
|
|
17
|
+
import { AssistantMessage } from '@ag-ui/client';
|
|
18
|
+
|
|
19
|
+
describe('CopilotChatAssistantMessageComponent', () => {
|
|
20
|
+
let component: CopilotChatAssistantMessageComponent;
|
|
21
|
+
let fixture: ComponentFixture<CopilotChatAssistantMessageComponent>;
|
|
22
|
+
|
|
23
|
+
const mockMessage: AssistantMessage = {
|
|
24
|
+
id: 'test-msg-1',
|
|
25
|
+
role: 'assistant',
|
|
26
|
+
content: 'Hello! This is a test message with **bold text** and *italic text*.',
|
|
27
|
+
createdAt: new Date()
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
await TestBed.configureTestingModule({
|
|
32
|
+
imports: [
|
|
33
|
+
CommonModule,
|
|
34
|
+
CopilotChatAssistantMessageComponent,
|
|
35
|
+
CopilotChatAssistantMessageRendererComponent,
|
|
36
|
+
CopilotChatAssistantMessageCopyButtonComponent,
|
|
37
|
+
CopilotChatAssistantMessageThumbsUpButtonComponent,
|
|
38
|
+
CopilotChatAssistantMessageThumbsDownButtonComponent,
|
|
39
|
+
CopilotChatAssistantMessageReadAloudButtonComponent,
|
|
40
|
+
CopilotChatAssistantMessageRegenerateButtonComponent,
|
|
41
|
+
CopilotChatAssistantMessageToolbarComponent
|
|
42
|
+
],
|
|
43
|
+
providers: [
|
|
44
|
+
provideCopilotKit({}),
|
|
45
|
+
provideCopilotChatConfiguration({
|
|
46
|
+
labels: {
|
|
47
|
+
assistantMessageToolbarCopyMessageLabel: 'Copy',
|
|
48
|
+
assistantMessageToolbarThumbsUpLabel: 'Good',
|
|
49
|
+
assistantMessageToolbarThumbsDownLabel: 'Bad',
|
|
50
|
+
assistantMessageToolbarReadAloudLabel: 'Read',
|
|
51
|
+
assistantMessageToolbarRegenerateLabel: 'Regenerate'
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
CopilotChatViewHandlersService
|
|
55
|
+
]
|
|
56
|
+
}).compileComponents();
|
|
57
|
+
|
|
58
|
+
fixture = TestBed.createComponent(CopilotChatAssistantMessageComponent);
|
|
59
|
+
component = fixture.componentInstance;
|
|
60
|
+
component.message = mockMessage;
|
|
61
|
+
fixture.detectChanges();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should create', () => {
|
|
65
|
+
expect(component).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should display the message content', () => {
|
|
69
|
+
const element = fixture.nativeElement;
|
|
70
|
+
expect(element.textContent).toContain('Hello! This is a test message');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should set data-message-id attribute', () => {
|
|
74
|
+
const element = fixture.nativeElement.querySelector('[data-message-id]');
|
|
75
|
+
expect(element).toBeTruthy();
|
|
76
|
+
expect(element.getAttribute('data-message-id')).toBe('test-msg-1');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should show toolbar when toolbarVisible is true', () => {
|
|
80
|
+
component.toolbarVisible = true;
|
|
81
|
+
fixture.detectChanges();
|
|
82
|
+
const toolbar = fixture.nativeElement.querySelector('[copilotChatAssistantMessageToolbar]');
|
|
83
|
+
expect(toolbar).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should hide toolbar when toolbarVisible is false', () => {
|
|
87
|
+
// Create a fresh instance with toolbarVisible set to false from the start
|
|
88
|
+
TestBed.resetTestingModule();
|
|
89
|
+
TestBed.configureTestingModule({
|
|
90
|
+
imports: [
|
|
91
|
+
CommonModule,
|
|
92
|
+
CopilotChatAssistantMessageComponent,
|
|
93
|
+
CopilotChatAssistantMessageRendererComponent,
|
|
94
|
+
CopilotChatAssistantMessageToolbarComponent
|
|
95
|
+
],
|
|
96
|
+
providers: [
|
|
97
|
+
provideCopilotKit({}),
|
|
98
|
+
provideCopilotChatConfiguration({})
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const newFixture = TestBed.createComponent(CopilotChatAssistantMessageComponent);
|
|
103
|
+
const newComponent = newFixture.componentInstance;
|
|
104
|
+
newComponent.message = mockMessage;
|
|
105
|
+
newComponent.toolbarVisible = false;
|
|
106
|
+
newFixture.detectChanges();
|
|
107
|
+
|
|
108
|
+
const toolbar = newFixture.nativeElement.querySelector('[copilotChatAssistantMessageToolbar]');
|
|
109
|
+
expect(toolbar).toBeFalsy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should emit thumbsUp event when button is clicked', () => {
|
|
113
|
+
let emittedEvent: any;
|
|
114
|
+
component.thumbsUp.subscribe((event) => {
|
|
115
|
+
emittedEvent = event;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
component.handleThumbsUp();
|
|
119
|
+
expect(emittedEvent).toEqual({ message: mockMessage });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should emit thumbsDown event when button is clicked', () => {
|
|
123
|
+
let emittedEvent: any;
|
|
124
|
+
component.thumbsDown.subscribe((event) => {
|
|
125
|
+
emittedEvent = event;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
component.handleThumbsDown();
|
|
129
|
+
expect(emittedEvent).toEqual({ message: mockMessage });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should emit readAloud event when button is clicked', () => {
|
|
133
|
+
let emittedEvent: any;
|
|
134
|
+
component.readAloud.subscribe((event) => {
|
|
135
|
+
emittedEvent = event;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
component.handleReadAloud();
|
|
139
|
+
expect(emittedEvent).toEqual({ message: mockMessage });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should emit regenerate event when button is clicked', () => {
|
|
143
|
+
let emittedEvent: any;
|
|
144
|
+
component.regenerate.subscribe((event) => {
|
|
145
|
+
emittedEvent = event;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
component.handleRegenerate();
|
|
149
|
+
expect(emittedEvent).toEqual({ message: mockMessage });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should handle empty message content', () => {
|
|
153
|
+
component.message = {
|
|
154
|
+
...mockMessage,
|
|
155
|
+
content: ''
|
|
156
|
+
};
|
|
157
|
+
fixture.detectChanges();
|
|
158
|
+
expect(component).toBeTruthy();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should support custom CSS class', () => {
|
|
162
|
+
const customClass = 'custom-test-class';
|
|
163
|
+
component.inputClass = customClass;
|
|
164
|
+
fixture.detectChanges();
|
|
165
|
+
|
|
166
|
+
const element = fixture.nativeElement.querySelector('div');
|
|
167
|
+
expect(element.className).toContain(customClass);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('CopilotChatAssistantMessageComponent with code blocks', () => {
|
|
172
|
+
let component: CopilotChatAssistantMessageComponent;
|
|
173
|
+
let fixture: ComponentFixture<CopilotChatAssistantMessageComponent>;
|
|
174
|
+
|
|
175
|
+
const codeMessage: AssistantMessage = {
|
|
176
|
+
id: 'test-code-1',
|
|
177
|
+
role: 'assistant',
|
|
178
|
+
content: `Here's a code example:
|
|
179
|
+
\`\`\`typescript
|
|
180
|
+
function hello(name: string): string {
|
|
181
|
+
return \`Hello, \${name}!\`;
|
|
182
|
+
}
|
|
183
|
+
\`\`\``,
|
|
184
|
+
createdAt: new Date()
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
beforeEach(async () => {
|
|
188
|
+
await TestBed.configureTestingModule({
|
|
189
|
+
imports: [
|
|
190
|
+
CommonModule,
|
|
191
|
+
CopilotChatAssistantMessageComponent,
|
|
192
|
+
CopilotChatAssistantMessageRendererComponent
|
|
193
|
+
],
|
|
194
|
+
providers: [
|
|
195
|
+
provideCopilotKit({}),
|
|
196
|
+
provideCopilotChatConfiguration({})
|
|
197
|
+
]
|
|
198
|
+
}).compileComponents();
|
|
199
|
+
|
|
200
|
+
fixture = TestBed.createComponent(CopilotChatAssistantMessageComponent);
|
|
201
|
+
component = fixture.componentInstance;
|
|
202
|
+
component.message = codeMessage;
|
|
203
|
+
fixture.detectChanges();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should render code blocks', () => {
|
|
207
|
+
const element = fixture.nativeElement;
|
|
208
|
+
const codeBlock = element.querySelector('.code-block-container');
|
|
209
|
+
expect(codeBlock).toBeTruthy();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should display language label for code blocks', () => {
|
|
213
|
+
const element = fixture.nativeElement;
|
|
214
|
+
const languageLabel = element.querySelector('.code-block-language');
|
|
215
|
+
expect(languageLabel).toBeTruthy();
|
|
216
|
+
expect(languageLabel.textContent).toBe('typescript');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should have copy button for code blocks', () => {
|
|
220
|
+
const element = fixture.nativeElement;
|
|
221
|
+
const copyButton = element.querySelector('.code-block-copy-button');
|
|
222
|
+
expect(copyButton).toBeTruthy();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('CopilotChatAssistantMessageComponent with custom slots', () => {
|
|
227
|
+
@Component({
|
|
228
|
+
selector: 'test-host',
|
|
229
|
+
template: `
|
|
230
|
+
<copilot-chat-assistant-message [message]="message">
|
|
231
|
+
<ng-template #markdownRenderer let-content="content">
|
|
232
|
+
<div class="custom-renderer">{{ content }}</div>
|
|
233
|
+
</ng-template>
|
|
234
|
+
|
|
235
|
+
<ng-template #copyButton let-onClick="onClick">
|
|
236
|
+
<button class="custom-copy-btn" (click)="onClick()">Custom Copy</button>
|
|
237
|
+
</ng-template>
|
|
238
|
+
</copilot-chat-assistant-message>
|
|
239
|
+
`,
|
|
240
|
+
standalone: true,
|
|
241
|
+
imports: [CommonModule, CopilotChatAssistantMessageComponent]
|
|
242
|
+
})
|
|
243
|
+
class TestHostComponent {
|
|
244
|
+
message: AssistantMessage = {
|
|
245
|
+
id: 'test-custom-1',
|
|
246
|
+
role: 'assistant',
|
|
247
|
+
content: 'Custom slot test message',
|
|
248
|
+
createdAt: new Date()
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let component: TestHostComponent;
|
|
253
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
254
|
+
|
|
255
|
+
beforeEach(async () => {
|
|
256
|
+
await TestBed.configureTestingModule({
|
|
257
|
+
imports: [TestHostComponent],
|
|
258
|
+
providers: [
|
|
259
|
+
provideCopilotKit({}),
|
|
260
|
+
provideCopilotChatConfiguration({})
|
|
261
|
+
]
|
|
262
|
+
}).compileComponents();
|
|
263
|
+
|
|
264
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
265
|
+
component = fixture.componentInstance;
|
|
266
|
+
fixture.detectChanges();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should use custom markdown renderer slot', () => {
|
|
270
|
+
const element = fixture.nativeElement;
|
|
271
|
+
const customRenderer = element.querySelector('.custom-renderer');
|
|
272
|
+
expect(customRenderer).toBeTruthy();
|
|
273
|
+
expect(customRenderer.textContent).toBe('Custom slot test message');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should use custom copy button slot', () => {
|
|
277
|
+
const element = fixture.nativeElement;
|
|
278
|
+
const customCopyBtn = element.querySelector('.custom-copy-btn');
|
|
279
|
+
expect(customCopyBtn).toBeTruthy();
|
|
280
|
+
expect(customCopyBtn.textContent).toBe('Custom Copy');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
2
|
+
import { Component, DebugElement } from '@angular/core';
|
|
3
|
+
import { By } from '@angular/platform-browser';
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
5
|
+
import { CopilotChatInputComponent } from '../copilot-chat-input.component';
|
|
6
|
+
import { CopilotChatTextareaComponent } from '../copilot-chat-textarea.component';
|
|
7
|
+
import { CopilotChatConfigurationService } from '../../../core/chat-configuration/chat-configuration.service';
|
|
8
|
+
import { provideCopilotChatConfiguration } from '../../../core/chat-configuration/chat-configuration.providers';
|
|
9
|
+
|
|
10
|
+
describe('CopilotChatInputComponent', () => {
|
|
11
|
+
let component: CopilotChatInputComponent;
|
|
12
|
+
let fixture: ComponentFixture<CopilotChatInputComponent>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
TestBed.configureTestingModule({
|
|
16
|
+
imports: [CopilotChatInputComponent],
|
|
17
|
+
providers: [
|
|
18
|
+
provideCopilotChatConfiguration({
|
|
19
|
+
labels: {
|
|
20
|
+
chatInputPlaceholder: 'Test placeholder'
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
fixture = TestBed.createComponent(CopilotChatInputComponent);
|
|
27
|
+
component = fixture.componentInstance;
|
|
28
|
+
fixture.detectChanges();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('Basic Rendering', () => {
|
|
32
|
+
it('should create', () => {
|
|
33
|
+
expect(component).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should render textarea by default', () => {
|
|
37
|
+
const textarea = fixture.nativeElement.querySelector('textarea[copilotChatTextarea]');
|
|
38
|
+
expect(textarea).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should render toolbar', () => {
|
|
42
|
+
const toolbar = fixture.nativeElement.querySelector('div[copilotChatToolbar]');
|
|
43
|
+
expect(toolbar).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should apply custom class', () => {
|
|
47
|
+
component.inputClass = 'custom-class';
|
|
48
|
+
fixture.detectChanges();
|
|
49
|
+
|
|
50
|
+
const container = fixture.nativeElement.querySelector('.chat-input-container');
|
|
51
|
+
expect(container).toBeFalsy();
|
|
52
|
+
|
|
53
|
+
const customContainer = fixture.nativeElement.querySelector('.custom-class');
|
|
54
|
+
expect(customContainer).toBeTruthy();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Mode Switching', () => {
|
|
59
|
+
it('should switch to audio recorder in transcribe mode', () => {
|
|
60
|
+
component.mode = 'transcribe';
|
|
61
|
+
fixture.detectChanges();
|
|
62
|
+
|
|
63
|
+
const audioRecorder = fixture.nativeElement.querySelector('copilot-chat-audio-recorder');
|
|
64
|
+
expect(audioRecorder).toBeTruthy();
|
|
65
|
+
|
|
66
|
+
const textarea = fixture.nativeElement.querySelector('textarea[copilotChatTextarea]');
|
|
67
|
+
expect(textarea).toBeFalsy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should switch back to textarea from transcribe mode', () => {
|
|
71
|
+
component.mode = 'transcribe';
|
|
72
|
+
fixture.detectChanges();
|
|
73
|
+
|
|
74
|
+
component.mode = 'input';
|
|
75
|
+
fixture.detectChanges();
|
|
76
|
+
|
|
77
|
+
const textarea = fixture.nativeElement.querySelector('textarea[copilotChatTextarea]');
|
|
78
|
+
expect(textarea).toBeTruthy();
|
|
79
|
+
|
|
80
|
+
const audioRecorder = fixture.nativeElement.querySelector('copilot-chat-audio-recorder');
|
|
81
|
+
expect(audioRecorder).toBeFalsy();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Value Management', () => {
|
|
86
|
+
it('should set initial value', () => {
|
|
87
|
+
component.value = 'Initial value';
|
|
88
|
+
fixture.detectChanges();
|
|
89
|
+
|
|
90
|
+
expect(component.computedValue()).toBe('Initial value');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should emit valueChange when value changes', () => {
|
|
94
|
+
const spy = vi.fn();
|
|
95
|
+
component.valueChange.subscribe(spy);
|
|
96
|
+
|
|
97
|
+
component.handleValueChange('New value');
|
|
98
|
+
|
|
99
|
+
expect(spy).toHaveBeenCalledWith('New value');
|
|
100
|
+
expect(component.computedValue()).toBe('New value');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should sync with chat configuration service', () => {
|
|
104
|
+
const service = TestBed.inject(CopilotChatConfigurationService);
|
|
105
|
+
|
|
106
|
+
component.handleValueChange('Config value');
|
|
107
|
+
|
|
108
|
+
expect(service.inputValue()).toBe('Config value');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Submit Functionality', () => {
|
|
113
|
+
it('should emit submitMessage on send', () => {
|
|
114
|
+
const spy = vi.fn();
|
|
115
|
+
component.submitMessage.subscribe(spy);
|
|
116
|
+
|
|
117
|
+
component.valueSignal.set('Test message');
|
|
118
|
+
component.send();
|
|
119
|
+
|
|
120
|
+
expect(spy).toHaveBeenCalledWith('Test message');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should not submit empty message', () => {
|
|
124
|
+
const spy = vi.fn();
|
|
125
|
+
component.submitMessage.subscribe(spy);
|
|
126
|
+
|
|
127
|
+
component.valueSignal.set(' ');
|
|
128
|
+
component.send();
|
|
129
|
+
|
|
130
|
+
expect(spy).not.toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should clear input after send', () => {
|
|
134
|
+
component.valueSignal.set('Test message');
|
|
135
|
+
component.send();
|
|
136
|
+
|
|
137
|
+
expect(component.computedValue()).toBe('');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle Enter key to send', () => {
|
|
141
|
+
const spy = vi.fn();
|
|
142
|
+
component.submitMessage.subscribe(spy);
|
|
143
|
+
|
|
144
|
+
component.valueSignal.set('Test message');
|
|
145
|
+
|
|
146
|
+
const event = new KeyboardEvent('keydown', {
|
|
147
|
+
key: 'Enter',
|
|
148
|
+
shiftKey: false
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
component.handleKeyDown(event);
|
|
152
|
+
|
|
153
|
+
expect(spy).toHaveBeenCalledWith('Test message');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should not send on Shift+Enter', () => {
|
|
157
|
+
const spy = vi.fn();
|
|
158
|
+
component.submitMessage.subscribe(spy);
|
|
159
|
+
|
|
160
|
+
const event = new KeyboardEvent('keydown', {
|
|
161
|
+
key: 'Enter',
|
|
162
|
+
shiftKey: true
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
component.handleKeyDown(event);
|
|
166
|
+
|
|
167
|
+
expect(spy).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Transcribe Events', () => {
|
|
172
|
+
it('should emit startTranscribe and switch mode', () => {
|
|
173
|
+
const spy = vi.fn();
|
|
174
|
+
component.startTranscribe.subscribe(spy);
|
|
175
|
+
|
|
176
|
+
component.handleStartTranscribe();
|
|
177
|
+
|
|
178
|
+
expect(spy).toHaveBeenCalled();
|
|
179
|
+
expect(component.computedMode()).toBe('transcribe');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should emit cancelTranscribe and switch mode', () => {
|
|
183
|
+
const spy = vi.fn();
|
|
184
|
+
component.cancelTranscribe.subscribe(spy);
|
|
185
|
+
|
|
186
|
+
component.modeSignal.set('transcribe');
|
|
187
|
+
component.handleCancelTranscribe();
|
|
188
|
+
|
|
189
|
+
expect(spy).toHaveBeenCalled();
|
|
190
|
+
expect(component.computedMode()).toBe('input');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should emit finishTranscribe and switch mode', () => {
|
|
194
|
+
const spy = vi.fn();
|
|
195
|
+
component.finishTranscribe.subscribe(spy);
|
|
196
|
+
|
|
197
|
+
component.modeSignal.set('transcribe');
|
|
198
|
+
component.handleFinishTranscribe();
|
|
199
|
+
|
|
200
|
+
expect(spy).toHaveBeenCalled();
|
|
201
|
+
expect(component.computedMode()).toBe('input');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('Tools Menu', () => {
|
|
206
|
+
it('should pass tools menu to toolbar', () => {
|
|
207
|
+
const toolsMenu = [
|
|
208
|
+
{ label: 'Tool 1', action: () => {} },
|
|
209
|
+
'-' as const,
|
|
210
|
+
{ label: 'Tool 2', action: () => {} }
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
component.toolsMenu = toolsMenu;
|
|
214
|
+
fixture.detectChanges();
|
|
215
|
+
|
|
216
|
+
expect(component.computedToolsMenu()).toEqual(toolsMenu);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('File Operations', () => {
|
|
221
|
+
it('should emit addFile event', () => {
|
|
222
|
+
const spy = vi.fn();
|
|
223
|
+
component.addFile.subscribe(spy);
|
|
224
|
+
|
|
225
|
+
component.handleAddFile();
|
|
226
|
+
|
|
227
|
+
expect(spy).toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Slot Overrides', () => {
|
|
232
|
+
it('should support custom textarea component', () => {
|
|
233
|
+
@Component({
|
|
234
|
+
selector: 'custom-textarea',
|
|
235
|
+
template: '<textarea class="custom"></textarea>',
|
|
236
|
+
standalone: true
|
|
237
|
+
})
|
|
238
|
+
class CustomTextarea {}
|
|
239
|
+
|
|
240
|
+
component.textAreaSlot = CustomTextarea;
|
|
241
|
+
fixture.detectChanges();
|
|
242
|
+
|
|
243
|
+
// The slot should accept the custom component
|
|
244
|
+
expect(component.textAreaSlot).toBe(CustomTextarea);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should support CSS class override for textarea', () => {
|
|
248
|
+
component.textAreaSlot = 'custom-textarea-class';
|
|
249
|
+
fixture.detectChanges();
|
|
250
|
+
|
|
251
|
+
expect(component.textAreaSlot).toBe('custom-textarea-class');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Test host component for CopilotChatTextareaComponent directive
|
|
257
|
+
@Component({
|
|
258
|
+
template: `
|
|
259
|
+
<textarea copilotChatTextarea
|
|
260
|
+
[inputValue]="value"
|
|
261
|
+
[inputPlaceholder]="placeholder"
|
|
262
|
+
[inputMaxRows]="maxRows"
|
|
263
|
+
[inputAutoFocus]="autoFocus"
|
|
264
|
+
[inputDisabled]="disabled"
|
|
265
|
+
(valueChange)="onValueChange($event)"
|
|
266
|
+
(keyDown)="onKeyDown($event)">
|
|
267
|
+
</textarea>
|
|
268
|
+
`,
|
|
269
|
+
standalone: true,
|
|
270
|
+
imports: [CopilotChatTextareaComponent]
|
|
271
|
+
})
|
|
272
|
+
class TestHostComponent {
|
|
273
|
+
value = '';
|
|
274
|
+
placeholder = '';
|
|
275
|
+
maxRows = 5;
|
|
276
|
+
autoFocus = false;
|
|
277
|
+
disabled = false;
|
|
278
|
+
onValueChange = vi.fn();
|
|
279
|
+
onKeyDown = vi.fn();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
describe('CopilotChatTextareaComponent', () => {
|
|
283
|
+
let hostComponent: TestHostComponent;
|
|
284
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
285
|
+
let component: CopilotChatTextareaComponent;
|
|
286
|
+
let textareaElement: HTMLTextAreaElement;
|
|
287
|
+
|
|
288
|
+
beforeEach(() => {
|
|
289
|
+
TestBed.configureTestingModule({
|
|
290
|
+
imports: [TestHostComponent, CopilotChatTextareaComponent],
|
|
291
|
+
providers: [
|
|
292
|
+
provideCopilotChatConfiguration({
|
|
293
|
+
labels: {
|
|
294
|
+
chatInputPlaceholder: 'Test placeholder'
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
]
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
301
|
+
hostComponent = fixture.componentInstance;
|
|
302
|
+
fixture.detectChanges();
|
|
303
|
+
|
|
304
|
+
// Get the directive instance
|
|
305
|
+
const textareaDebugElement = fixture.debugElement.query(By.css('textarea'));
|
|
306
|
+
component = textareaDebugElement.injector.get(CopilotChatTextareaComponent);
|
|
307
|
+
textareaElement = textareaDebugElement.nativeElement;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('Textarea Behavior', () => {
|
|
311
|
+
it('should create', () => {
|
|
312
|
+
expect(component).toBeTruthy();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should set placeholder from configuration', () => {
|
|
316
|
+
expect(textareaElement.placeholder).toBe('Test placeholder');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should handle input events', () => {
|
|
320
|
+
textareaElement.value = 'New text';
|
|
321
|
+
textareaElement.dispatchEvent(new Event('input'));
|
|
322
|
+
|
|
323
|
+
expect(hostComponent.onValueChange).toHaveBeenCalledWith('New text');
|
|
324
|
+
expect(component.value()).toBe('New text');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should auto-resize based on content', () => {
|
|
328
|
+
// Mock getComputedStyle to return values
|
|
329
|
+
const originalGetComputedStyle = window.getComputedStyle;
|
|
330
|
+
window.getComputedStyle = vi.fn().mockReturnValue({
|
|
331
|
+
paddingTop: '20px',
|
|
332
|
+
paddingBottom: '0px'
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Set scrollHeight manually for test
|
|
336
|
+
Object.defineProperty(textareaElement, 'scrollHeight', {
|
|
337
|
+
configurable: true,
|
|
338
|
+
value: 100
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Add multiple lines of text
|
|
342
|
+
textareaElement.value = 'Line 1\nLine 2\nLine 3';
|
|
343
|
+
textareaElement.dispatchEvent(new Event('input'));
|
|
344
|
+
fixture.detectChanges();
|
|
345
|
+
|
|
346
|
+
// Height should be set
|
|
347
|
+
const newHeight = textareaElement.style.height;
|
|
348
|
+
expect(newHeight).toBeTruthy();
|
|
349
|
+
|
|
350
|
+
// Restore original getComputedStyle
|
|
351
|
+
window.getComputedStyle = originalGetComputedStyle;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should respect maxRows limit', () => {
|
|
355
|
+
hostComponent.maxRows = 3;
|
|
356
|
+
fixture.detectChanges();
|
|
357
|
+
|
|
358
|
+
expect(component.maxRows()).toBe(3);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should focus when autoFocus is true', async () => {
|
|
362
|
+
// Set autoFocus before creating the component
|
|
363
|
+
TestBed.resetTestingModule();
|
|
364
|
+
TestBed.configureTestingModule({
|
|
365
|
+
imports: [TestHostComponent, CopilotChatTextareaComponent],
|
|
366
|
+
providers: [
|
|
367
|
+
provideCopilotChatConfiguration({
|
|
368
|
+
labels: {
|
|
369
|
+
chatInputPlaceholder: 'Test placeholder'
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
]
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Create a new fixture with autoFocus set to true
|
|
376
|
+
const newFixture = TestBed.createComponent(TestHostComponent);
|
|
377
|
+
const newHostComponent = newFixture.componentInstance;
|
|
378
|
+
newHostComponent.autoFocus = true;
|
|
379
|
+
newFixture.detectChanges();
|
|
380
|
+
|
|
381
|
+
// Get the new textarea element
|
|
382
|
+
const newTextareaElement = newFixture.nativeElement.querySelector('textarea');
|
|
383
|
+
|
|
384
|
+
// Wait for async focus to complete
|
|
385
|
+
await newFixture.whenStable();
|
|
386
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
387
|
+
|
|
388
|
+
expect(document.activeElement).toBe(newTextareaElement);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should emit keyDown events', () => {
|
|
392
|
+
const event = new KeyboardEvent('keydown', { key: 'Enter' });
|
|
393
|
+
textareaElement.dispatchEvent(event);
|
|
394
|
+
|
|
395
|
+
expect(hostComponent.onKeyDown).toHaveBeenCalled();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('Public Methods', () => {
|
|
400
|
+
it('should focus textarea programmatically', () => {
|
|
401
|
+
component.focus();
|
|
402
|
+
|
|
403
|
+
expect(document.activeElement).toBe(textareaElement);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should get current value', () => {
|
|
407
|
+
component.value.set('Test value');
|
|
408
|
+
|
|
409
|
+
expect(component.getValue()).toBe('Test value');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should set value programmatically', () => {
|
|
413
|
+
component.setValue('New value');
|
|
414
|
+
|
|
415
|
+
expect(component.getValue()).toBe('New value');
|
|
416
|
+
expect(hostComponent.onValueChange).toHaveBeenCalledWith('New value');
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|