Skip to content

Commit 0a8b8b6

Browse files
committed
fix: Flaky test and auto-scroll behavior
1 parent 210d391 commit 0a8b8b6

File tree

3 files changed

+45
-48
lines changed

3 files changed

+45
-48
lines changed

src/components/chat/chat.spec.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { elementUpdated, expect, fixture, waitUntil } from '@open-wc/testing';
1+
import { elementUpdated, expect, fixture } from '@open-wc/testing';
22
import { html, nothing } from 'lit';
3-
import { spy, stub } from 'sinon';
3+
import { spy, stub, useFakeTimers } from 'sinon';
44
import type IgcIconButtonComponent from '../button/icon-button.js';
55
import IgcChipComponent from '../chip/chip.js';
66
import { enterKey } from '../common/controllers/key-bindings.js';
@@ -882,22 +882,25 @@ describe('Chat', () => {
882882
});
883883

884884
it('emits igcTypingChange', async () => {
885+
const clock = useFakeTimers({ now: 0, toFake: ['Date', 'setTimeout'] });
886+
885887
const eventSpy = spy(chat, 'emitEvent');
886888
const textArea = getChatDOM(chat).input.textarea;
887889

888-
chat.options = { stopTypingDelay: 100 };
890+
chat.options = { stopTypingDelay: 2500 };
889891
simulateKeyboard(textArea, 'a');
890892
await elementUpdated(chat);
891893

892894
expect(eventSpy).calledWith('igcTypingChange');
893895
expect(eventSpy.firstCall.args[1]?.detail).to.eql({ isTyping: true });
894896

895-
eventSpy.resetHistory();
896-
897-
await waitUntil(() => eventSpy.calledWith('igcTypingChange'));
897+
clock.setSystemTime(2501);
898+
await clock.runAllAsync();
898899

899900
expect(eventSpy).calledWith('igcTypingChange');
900-
expect(eventSpy.firstCall.args[1]?.detail).to.eql({ isTyping: false });
901+
expect(eventSpy.lastCall.args[1]?.detail).to.eql({ isTyping: false });
902+
903+
clock.restore();
901904
});
902905

903906
it('emits igcInputFocus', async () => {

src/components/chat/chat.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ export default class IgcChatComponent extends EventEmitterMixin<
237237
@query('[part="suggestions-container"]')
238238
private readonly _suggestionsContainer?: HTMLElement;
239239

240+
@query('[part="message-area-container"]', true)
241+
private readonly _scrollContainer!: HTMLElement;
242+
240243
private _updateContext(): void {
241244
this._context.setValue(this._state, true);
242245
}
@@ -347,15 +350,15 @@ export default class IgcChatComponent extends EventEmitterMixin<
347350
return;
348351
}
349352

350-
const lastMessage = this.renderRoot
351-
.querySelectorAll(IgcChatMessageComponent.tagName)
352-
.item(this.messages.length - 1);
353-
354-
const scrollTarget =
355-
this._typingIndicator ?? this._suggestionsContainer ?? lastMessage;
353+
const current = this._scrollContainer.scrollTop;
356354

357355
requestAnimationFrame(() => {
358-
scrollTarget?.scrollIntoView({ block: 'end', inline: 'end' });
356+
const scrollHeight = this._scrollContainer.scrollHeight;
357+
if (current < scrollHeight) {
358+
this._scrollContainer.scrollBy({
359+
top: Math.abs(scrollHeight - current),
360+
});
361+
}
359362
});
360363
}
361364

stories/chat.stories.ts

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ And some sample html:
116116

117117
const userMessages: any[] = [];
118118

119-
let isResponseSent: boolean;
120-
121119
const _messageAuthorTemplate = ({ message }: ChatMessageRenderContext) => {
122120
return message.sender !== 'user'
123121
? html`
@@ -134,7 +132,7 @@ const _messageAuthorTemplate = ({ message }: ChatMessageRenderContext) => {
134132
: nothing;
135133
};
136134
const _messageActionsTemplate = ({ message }: ChatMessageRenderContext) => {
137-
return message.sender !== 'user' && message.text.trim() && isResponseSent
135+
return message.sender !== 'user' && message.text.trim()
138136
? html`
139137
<div>
140138
<igc-icon-button
@@ -185,7 +183,7 @@ function handleCustomSendClick(chat: IgcChatComponent) {
185183
chat.draftMessage = { text: '', attachments: [] };
186184
}
187185

188-
function handleMessageSend(event: CustomEvent<IgcChatMessage>): void {
186+
async function handleMessageSend(event: CustomEvent<IgcChatMessage>) {
189187
const chat = event.target as IgcChatComponent;
190188
const message = event.detail;
191189

@@ -205,29 +203,31 @@ function handleMessageSend(event: CustomEvent<IgcChatMessage>): void {
205203
]
206204
: [];
207205

208-
isResponseSent = false;
209-
setTimeout(async () => {
210-
chat.messages = [
211-
...chat.messages,
212-
{ id: crypto.randomUUID(), text: '', sender: 'bot', attachments },
213-
];
214-
215-
await showResponse(chat, generateAIResponse(message.text).split(' '));
216-
217-
messages = chat.messages;
218-
isResponseSent = true;
219-
chat.options = { ...chat.options, suggestions: [], isTyping: false };
220-
// TODO: add attachments (if any) to the response message
221-
}, 1000);
222-
}
206+
await new Promise((resolve) => setTimeout(resolve, 1000));
207+
208+
chat.options = { ...chat.options, isTyping: false };
209+
210+
const responseText = generateAIResponse(message.text).split(' ');
223211

224-
async function showResponse(chat: IgcChatComponent, responseParts: string[]) {
225-
const lastMessage = chat.messages[chat.messages.length - 1];
212+
const aiResponse = {
213+
id: Date.now().toString(),
214+
sender: 'bot',
215+
text: '',
216+
attachments,
217+
};
218+
219+
chat.messages = [...chat.messages, aiResponse];
226220

227-
for (const part of responseParts) {
228-
await new Promise((resolve) => requestAnimationFrame(resolve));
229-
lastMessage.text = `${lastMessage.text} ${part}`;
230-
chat.messages = [...chat.messages];
221+
for (const part of responseText) {
222+
await new Promise((resolve) => setTimeout(resolve, 30));
223+
const updated = [...chat.messages];
224+
const currentMessage = updated[updated.length - 1];
225+
const updatedMessage = {
226+
...aiResponse,
227+
text: `${currentMessage.text} ${part}`,
228+
};
229+
updated[updated.length - 1] = updatedMessage;
230+
chat.messages = updated;
231231
}
232232
}
233233

@@ -441,15 +441,6 @@ export const Basic: Story = {
441441
render: () => {
442442
messages = initialMessages;
443443
return html`
444-
<style>
445-
igc-chat::part(typing-dot) {
446-
background: var(--igc-background, #f00);
447-
border: solid 1px var(--igc-background, #f00);
448-
border-radius: 8px;
449-
color: var(--igc-color, #932424);
450-
width: 12px;
451-
}
452-
</style>
453444
<igc-chat
454445
style="--igc-chat-height: calc(100vh - 32px);"
455446
.messages=${messages}

0 commit comments

Comments
 (0)