Skip to content

Commit 53de2c7

Browse files
committed
test(examples-chat): add url-routing e2e for knob query params
Four scenarios: 1. Deep-link /embed/<thread-id> loads the thread without resending. 2. /embed?model=gpt-5-nano sets the model picker via URL hydration. 3. Mode switch preserves both /embed/<id> path and ?model= query. 4. Ephemeral hydration: /embed?theme=material-dark does NOT write theme to localStorage (URL hydrates signal but not storage).
1 parent 5fdabc5 commit 53de2c7

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: MIT
2+
import { test, expect } from '@playwright/test';
3+
import {
4+
activeThreadIdFromUrl,
5+
messageInput,
6+
openDemo,
7+
sendButton,
8+
waitForFinalAssistant,
9+
} from './test-helpers';
10+
11+
test('url routing: deep-link with thread id loads that thread', async ({ page }) => {
12+
// Bootstrap: create a thread by sending one message.
13+
await openDemo(page, '/embed');
14+
await messageInput(page).fill('say hi briefly');
15+
await sendButton(page).click();
16+
await waitForFinalAssistant(page);
17+
18+
await expect(page).toHaveURL(/\/embed\/[A-Za-z0-9-]+$/);
19+
const threadId = await activeThreadIdFromUrl(page);
20+
expect(threadId).toBeTruthy();
21+
22+
// Reload via direct navigation to /embed/<id> — assert the existing
23+
// assistant message renders without resending the prompt.
24+
await page.goto(`/embed/${threadId}`);
25+
await expect(page.locator('chat-message[data-role="assistant"]')).toContainText(/hi/i, {
26+
timeout: 30_000,
27+
});
28+
});
29+
30+
test('url routing: deep-link with knob param sets the picker', async ({ page }) => {
31+
await openDemo(page, '/embed?model=gpt-5-nano');
32+
33+
// The model toolbar trigger surfaces the current model. Confirm the URL
34+
// value won, not the default.
35+
const modelTrigger = page.locator('.demo-shell__field[data-field="model"] .chat-select__trigger');
36+
await expect(modelTrigger).toContainText('gpt-5-nano');
37+
});
38+
39+
test('url routing: mode switch preserves thread + knob params', async ({ page }) => {
40+
// Bootstrap: thread + non-default knob.
41+
await openDemo(page, '/embed');
42+
await messageInput(page).fill('say hi briefly');
43+
await sendButton(page).click();
44+
await waitForFinalAssistant(page);
45+
const threadId = await activeThreadIdFromUrl(page);
46+
expect(threadId).toBeTruthy();
47+
48+
// Set a non-default model via the toolbar.
49+
const modelTrigger = page.locator('.demo-shell__field[data-field="model"] .chat-select__trigger');
50+
await modelTrigger.click();
51+
await page.locator('.chat-select__option', { hasText: 'gpt-5-nano' }).first().click();
52+
await expect(page).toHaveURL(/[?&]model=gpt-5-nano/);
53+
54+
// Click Popup mode in the segmented control.
55+
await page.locator('.demo-shell__segmented-button', { hasText: 'Popup' }).click();
56+
57+
// URL holds both thread + knob param.
58+
await expect(page).toHaveURL(new RegExp(`/popup/${threadId}(\\?|\\?.*&)model=gpt-5-nano`));
59+
});
60+
61+
test('url routing: ephemeral hydration does not write to localStorage', async ({ page }) => {
62+
// Visit with a non-default theme in the URL.
63+
await openDemo(page, '/embed?theme=material-dark');
64+
65+
// openDemo clears localStorage before the test starts; assert it's
66+
// still clean (no `theme: 'material-dark'` written by hydration).
67+
const stored = await page.evaluate(() => {
68+
const raw = localStorage.getItem('ngaf-chat-demo:palette');
69+
return raw ? (JSON.parse(raw) as { theme?: string }).theme : null;
70+
});
71+
expect(stored).toBeNull();
72+
});

0 commit comments

Comments
 (0)