Skip to content

Commit 2d243ae

Browse files
committed
test: stabilize chat example e2e controls
1 parent b34244b commit 2d243ae

7 files changed

Lines changed: 72 additions & 15 deletions

File tree

examples/chat/angular/e2e/lifecycle.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,21 @@ test('lifecycle: reload reconnects to the active conversation', async ({
2424
).toContainText(/hi/i);
2525
});
2626

27-
test('lifecycle: new conversation clears local thread and restores welcome state', async ({
27+
test('lifecycle: new chat switches thread and restores welcome state', async ({
2828
page,
2929
}) => {
3030
await openDemo(page, '/embed');
3131
await messageInput(page).fill('say hi briefly');
3232
await sendButton(page).click();
3333
await waitForFinalAssistant(page);
34+
const previousThreadId = await page.evaluate(() => {
35+
const raw = localStorage.getItem('ngaf-chat-demo:palette');
36+
return raw
37+
? (JSON.parse(raw) as { threadId?: string | null }).threadId
38+
: undefined;
39+
});
3440

35-
await page.getByRole('button', { name: 'New conversation' }).click();
41+
await page.getByRole('button', { name: 'New chat' }).click();
3642

3743
await expect(
3844
page.getByRole('heading', { name: 'How can I help?' })
@@ -44,7 +50,8 @@ test('lifecycle: new conversation clears local thread and restores welcome state
4450
? (JSON.parse(raw) as { threadId?: string | null }).threadId
4551
: undefined;
4652
});
47-
expect(threadId ?? null).toBeNull();
53+
expect(threadId).toBeTruthy();
54+
expect(threadId).not.toBe(previousThreadId);
4855
});
4956

5057
test('lifecycle: selecting a welcome suggestion submits and clears welcome state', async ({

examples/chat/angular/e2e/model-picker.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ test('model picker: configured models render, persist, and reach backend state',
1717
const modelTrigger = toolbarSelect(page, 'Model');
1818
// Open the chat-select menu and assert the three model options are listed.
1919
await modelTrigger.click();
20-
const modelMenu = page
21-
.locator('.demo-shell__field')
22-
.filter({ hasText: 'Model' })
23-
.locator('chat-select .chat-select__menu');
20+
const modelMenu = page.getByRole('listbox', { name: 'Model' });
2421
await expect(modelMenu.locator('.chat-select__option')).toHaveText([
2522
'gpt-5',
2623
'gpt-5-mini',

examples/chat/angular/e2e/test-helpers.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ export function sendButton(page: Page): Locator {
5353
*/
5454
export function toolbarSelect(page: Page, label: string): Locator {
5555
return page
56-
.locator('.demo-shell__field')
57-
.filter({ hasText: label })
58-
.locator('chat-select .chat-select__trigger');
56+
.getByRole('toolbar', { name: 'Demo controls' })
57+
.getByRole('button', { name: new RegExp(`^${escapeRegExp(label)}:`) });
5958
}
6059

6160
/**
@@ -73,10 +72,7 @@ export async function selectToolbarOption(
7372
): Promise<void> {
7473
const trigger = toolbarSelect(page, label);
7574
await trigger.click();
76-
const menu = page
77-
.locator('.demo-shell__field')
78-
.filter({ hasText: label })
79-
.locator('chat-select .chat-select__menu');
75+
const menu = page.getByRole('listbox', { name: label });
8076
await menu.waitFor({ state: 'visible' });
8177
const optionButton = menu
8278
.locator('.chat-select__option')

examples/chat/angular/src/app/shell/demo-shell.component.css

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
display: flex;
4141
flex-direction: column;
4242
min-width: 0;
43+
box-sizing: border-box;
4344
}
4445
.demo-shell__main[data-sidenav-mode="expanded"] {
4546
padding-left: var(--ngaf-chat-sidenav-width-expanded, 280px);
@@ -132,8 +133,50 @@
132133

133134
@media (max-width: 767px) {
134135
.demo-shell__toolbar {
136+
flex-wrap: wrap;
137+
align-items: stretch;
138+
gap: 8px;
135139
padding-left: 60px;
136140
}
141+
142+
.demo-shell__field--first {
143+
margin-left: 0;
144+
}
145+
146+
.demo-shell__segmented,
147+
.demo-shell__field {
148+
flex: 1 1 calc(50% - 4px);
149+
min-width: 0;
150+
}
151+
152+
.demo-shell__field ::ng-deep chat-select,
153+
.demo-shell__field ::ng-deep chat-select .chat-select__trigger {
154+
width: 100%;
155+
min-width: 0;
156+
}
157+
158+
.demo-shell__field ::ng-deep chat-select .chat-select__label {
159+
min-width: 0;
160+
overflow: hidden;
161+
text-overflow: ellipsis;
162+
white-space: nowrap;
163+
}
164+
}
165+
166+
@media (min-width: 768px) and (max-width: 1199px) {
167+
.demo-shell__toolbar {
168+
flex-wrap: wrap;
169+
align-items: stretch;
170+
}
171+
172+
.demo-shell__field--first {
173+
margin-left: 0;
174+
}
175+
176+
.demo-shell__field {
177+
flex: 1 1 120px;
178+
min-width: 0;
179+
}
137180
}
138181

139182
.demo-shell__interrupt-panel {

libs/chat/src/lib/compositions/chat-sidebar/chat-sidebar.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { CHAT_HOST_TOKENS, ensureChatRootStyles } from '../../styles/chat-tokens
4343
box-shadow: -8px 0 32px rgba(0,0,0,.08);
4444
transform: translateX(100%);
4545
transition: transform 200ms ease-out, bottom 200ms ease-out;
46-
z-index: 30;
46+
z-index: 60;
4747
display: flex;
4848
flex-direction: column;
4949
}

libs/chat/src/lib/primitives/chat-select/chat-select.component.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ describe('ChatSelectComponent', () => {
4747
expect(host.querySelector('.chat-select__label')?.textContent).toContain('Alpha');
4848
});
4949

50+
it('labels the trigger with the control name and current value', () => {
51+
setSignalInput(fixture, 'menuLabel', 'Model');
52+
fixture.detectChanges();
53+
expect(
54+
host.querySelector<HTMLButtonElement>('.chat-select__trigger')?.getAttribute('aria-label'),
55+
).toBe('Model: Alpha');
56+
});
57+
5058
it('falls back to placeholder when value not in options', () => {
5159
setSignalInput(fixture, 'value', '');
5260
setSignalInput(fixture, 'placeholder', 'Pick one');

libs/chat/src/lib/primitives/chat-select/chat-select.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface ChatSelectOption {
4444
class="chat-select__trigger"
4545
[class.is-open]="open()"
4646
[disabled]="disabled()"
47+
[attr.aria-label]="triggerLabel()"
4748
[attr.aria-haspopup]="'listbox'"
4849
[attr.aria-expanded]="open()"
4950
(click)="toggle()"
@@ -94,6 +95,11 @@ export class ChatSelectComponent {
9495
return match?.label ?? this.placeholder();
9596
});
9697

98+
protected readonly triggerLabel = computed(() => {
99+
const label = this.menuLabel() ?? this.placeholder();
100+
return `${label}: ${this.currentLabel()}`;
101+
});
102+
97103
private readonly hostEl = inject(ElementRef).nativeElement as HTMLElement;
98104
private readonly document = inject(DOCUMENT);
99105
private readonly destroyRef = inject(DestroyRef);

0 commit comments

Comments
 (0)