Skip to content

Commit 718ac66

Browse files
committed
Add e2e testing utility for URL params
1 parent e32b315 commit 718ac66

8 files changed

Lines changed: 264 additions & 38 deletions

File tree

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
"default": "./dist/index.cjs"
1818
}
1919
},
20+
"./chatgpt": {
21+
"import": {
22+
"types": "./dist/chatgpt/index.d.ts",
23+
"default": "./dist/chatgpt/index.js"
24+
}
25+
},
2026
"./style.css": "./dist/style.css",
2127
"./chatgpt/globals.css": "./dist/chatgpt/globals.css",
2228
"./mcp": {

src/chatgpt/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export { ChatGPTSimulator } from './chatgpt-simulator';
22
export type { Simulation } from '../types/simulation';
33
export { initMockOpenAI } from './mock-openai';
44
export * from './theme-provider';
5+
export { createSimulatorUrl } from './simulator-url';
6+
export type { SimulatorUrlParams } from './simulator-url';

src/chatgpt/simulator-url.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import type { Theme, DisplayMode, DeviceType, ViewMode } from '../types/runtime';
2+
3+
/**
4+
* Strongly-typed URL parameters for the ChatGPT Simulator.
5+
*
6+
* Use with `createSimulatorUrl()` to generate type-safe URL paths for e2e tests.
7+
*
8+
* @example
9+
* ```ts
10+
* import { createSimulatorUrl } from 'sunpeak/chatgpt';
11+
*
12+
* // In e2e tests:
13+
* await page.goto(createSimulatorUrl({
14+
* simulation: 'albums-show',
15+
* theme: 'dark',
16+
* displayMode: 'fullscreen',
17+
* }));
18+
* ```
19+
*/
20+
export interface SimulatorUrlParams {
21+
/**
22+
* The simulation name to load (e.g., 'albums-show', 'review-diff').
23+
* Corresponds to the simulation JSON filename without the '-simulation.json' suffix.
24+
*/
25+
simulation?: string;
26+
27+
/**
28+
* The color theme for the simulator.
29+
* @default 'dark'
30+
*/
31+
theme?: Theme;
32+
33+
/**
34+
* The display mode for the widget.
35+
* - 'inline': Embedded in the conversation
36+
* - 'pip': Picture-in-picture mode with max height
37+
* - 'fullscreen': Full screen overlay
38+
* @default 'inline'
39+
*/
40+
displayMode?: DisplayMode;
41+
42+
/**
43+
* The locale for the simulator (e.g., 'en-US', 'ja-JP').
44+
* @default 'en-US'
45+
*/
46+
locale?: string;
47+
48+
/**
49+
* Maximum height in pixels for PiP mode.
50+
* Only applicable when displayMode is 'pip'.
51+
*/
52+
maxHeight?: number;
53+
54+
/**
55+
* The device type to simulate.
56+
* Affects default hover/touch capabilities.
57+
*/
58+
deviceType?: DeviceType;
59+
60+
/**
61+
* Whether the device supports hover interactions.
62+
* @default true for desktop, false for mobile/tablet
63+
*/
64+
hover?: boolean;
65+
66+
/**
67+
* Whether the device supports touch interactions.
68+
* @default false for desktop, true for mobile/tablet
69+
*/
70+
touch?: boolean;
71+
72+
/**
73+
* Safe area inset from the top of the screen (in pixels).
74+
* Used for devices with notches or status bars.
75+
*/
76+
safeAreaTop?: number;
77+
78+
/**
79+
* Safe area inset from the bottom of the screen (in pixels).
80+
* Used for devices with home indicators.
81+
*/
82+
safeAreaBottom?: number;
83+
84+
/**
85+
* Safe area inset from the left of the screen (in pixels).
86+
*/
87+
safeAreaLeft?: number;
88+
89+
/**
90+
* Safe area inset from the right of the screen (in pixels).
91+
*/
92+
safeAreaRight?: number;
93+
94+
/**
95+
* The view mode for the widget.
96+
* - 'modal': Display as a modal dialog
97+
* - 'default': Normal display
98+
*/
99+
viewMode?: ViewMode;
100+
}
101+
102+
/**
103+
* Creates a URL path with query parameters for the ChatGPT Simulator.
104+
*
105+
* @param params - The simulator parameters to encode
106+
* @param basePath - The base path for the URL (default: '/')
107+
* @returns A URL path string with encoded query parameters
108+
*
109+
* @example
110+
* ```ts
111+
* // Basic usage
112+
* createSimulatorUrl({ simulation: 'albums-show', theme: 'light' })
113+
* // Returns: '/?simulation=albums-show&theme=light'
114+
*
115+
* // With display mode
116+
* createSimulatorUrl({
117+
* simulation: 'review-diff',
118+
* theme: 'dark',
119+
* displayMode: 'fullscreen',
120+
* })
121+
* // Returns: '/?simulation=review-diff&theme=dark&displayMode=fullscreen'
122+
*
123+
* // With device simulation
124+
* createSimulatorUrl({
125+
* simulation: 'map-show',
126+
* deviceType: 'mobile',
127+
* touch: true,
128+
* hover: false,
129+
* })
130+
* // Returns: '/?simulation=map-show&deviceType=mobile&touch=true&hover=false'
131+
*
132+
* // With safe area insets (for notch simulation)
133+
* createSimulatorUrl({
134+
* simulation: 'carousel-show',
135+
* safeAreaTop: 44,
136+
* safeAreaBottom: 34,
137+
* })
138+
* // Returns: '/?simulation=carousel-show&safeAreaTop=44&safeAreaBottom=34'
139+
* ```
140+
*/
141+
export function createSimulatorUrl(params: SimulatorUrlParams, basePath = '/'): string {
142+
const searchParams = new URLSearchParams();
143+
144+
// Add each defined parameter
145+
if (params.simulation !== undefined) {
146+
searchParams.set('simulation', params.simulation);
147+
}
148+
if (params.theme !== undefined) {
149+
searchParams.set('theme', params.theme);
150+
}
151+
if (params.displayMode !== undefined) {
152+
searchParams.set('displayMode', params.displayMode);
153+
}
154+
if (params.locale !== undefined) {
155+
searchParams.set('locale', params.locale);
156+
}
157+
if (params.maxHeight !== undefined) {
158+
searchParams.set('maxHeight', String(params.maxHeight));
159+
}
160+
if (params.deviceType !== undefined) {
161+
searchParams.set('deviceType', params.deviceType);
162+
}
163+
if (params.hover !== undefined) {
164+
searchParams.set('hover', String(params.hover));
165+
}
166+
if (params.touch !== undefined) {
167+
searchParams.set('touch', String(params.touch));
168+
}
169+
if (params.safeAreaTop !== undefined) {
170+
searchParams.set('safeAreaTop', String(params.safeAreaTop));
171+
}
172+
if (params.safeAreaBottom !== undefined) {
173+
searchParams.set('safeAreaBottom', String(params.safeAreaBottom));
174+
}
175+
if (params.safeAreaLeft !== undefined) {
176+
searchParams.set('safeAreaLeft', String(params.safeAreaLeft));
177+
}
178+
if (params.safeAreaRight !== undefined) {
179+
searchParams.set('safeAreaRight', String(params.safeAreaRight));
180+
}
181+
if (params.viewMode !== undefined) {
182+
searchParams.set('viewMode', params.viewMode);
183+
}
184+
185+
const queryString = searchParams.toString();
186+
return queryString ? `${basePath}?${queryString}` : basePath;
187+
}

template/tests/e2e/albums.spec.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { test, expect } from '@playwright/test';
2+
import { createSimulatorUrl } from 'sunpeak/chatgpt';
23

34
test.describe('Albums Resource', () => {
45
test.describe('Light Mode', () => {
56
test('should render album cards with correct styles', async ({ page }) => {
6-
await page.goto('/?simulation=albums-show&theme=light');
7+
await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
78

89
const albumCard = page.locator('button:has-text("Summer Slice")');
910
await expect(albumCard).toBeVisible();
@@ -22,7 +23,7 @@ test.describe('Albums Resource', () => {
2223
});
2324

2425
test('should have album image with correct aspect ratio', async ({ page }) => {
25-
await page.goto('/?simulation=albums-show&theme=light');
26+
await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
2627

2728
const albumImage = page.locator('button:has-text("Summer Slice") img').first();
2829
await expect(albumImage).toBeVisible();
@@ -46,7 +47,7 @@ test.describe('Albums Resource', () => {
4647

4748
test.describe('Dark Mode', () => {
4849
test('should render album cards with correct styles', async ({ page }) => {
49-
await page.goto('/?simulation=albums-show&theme=dark');
50+
await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
5051

5152
const albumCard = page.locator('button:has-text("Summer Slice")');
5253
await expect(albumCard).toBeVisible();
@@ -64,7 +65,7 @@ test.describe('Albums Resource', () => {
6465
});
6566

6667
test('should have text with appropriate contrast', async ({ page }) => {
67-
await page.goto('/?simulation=albums-show&theme=dark');
68+
await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
6869

6970
const albumTitle = page.locator('button:has-text("Summer Slice") .text-primary').first();
7071
await expect(albumTitle).toBeVisible();
@@ -84,7 +85,9 @@ test.describe('Albums Resource', () => {
8485

8586
test.describe('Fullscreen Mode', () => {
8687
test('should render correctly in fullscreen displayMode', async ({ page }) => {
87-
await page.goto('/?simulation=albums-show&theme=light&displayMode=fullscreen');
88+
await page.goto(
89+
createSimulatorUrl({ simulation: 'albums-show', theme: 'light', displayMode: 'fullscreen' })
90+
);
8891

8992
// Wait for content to load
9093
await page.waitForLoadState('networkidle');
@@ -95,7 +98,9 @@ test.describe('Albums Resource', () => {
9598
});
9699

97100
test('should maintain album card styles in fullscreen', async ({ page }) => {
98-
await page.goto('/?simulation=albums-show&theme=dark&displayMode=fullscreen');
101+
await page.goto(
102+
createSimulatorUrl({ simulation: 'albums-show', theme: 'dark', displayMode: 'fullscreen' })
103+
);
99104

100105
const albumCard = page.locator('button:has-text("Summer Slice")');
101106
await expect(albumCard).toBeVisible();

template/tests/e2e/carousel.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { test, expect } from '@playwright/test';
2+
import { createSimulatorUrl } from 'sunpeak/chatgpt';
23

34
test.describe('Carousel Resource', () => {
45
test.describe('Light Mode', () => {
56
test('should render carousel cards with correct styles', async ({ page }) => {
6-
await page.goto('/?simulation=carousel-show&theme=light');
7+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
78

89
// Wait for carousel to load
910
await page.waitForLoadState('networkidle');
@@ -25,7 +26,7 @@ test.describe('Carousel Resource', () => {
2526
});
2627

2728
test('should have card with border styling', async ({ page }) => {
28-
await page.goto('/?simulation=carousel-show&theme=light');
29+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
2930

3031
await page.waitForLoadState('networkidle');
3132

@@ -46,7 +47,7 @@ test.describe('Carousel Resource', () => {
4647
});
4748

4849
test('should have interactive buttons', async ({ page }) => {
49-
await page.goto('/?simulation=carousel-show&theme=light');
50+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
5051

5152
await page.waitForLoadState('networkidle');
5253

@@ -67,7 +68,7 @@ test.describe('Carousel Resource', () => {
6768

6869
test.describe('Dark Mode', () => {
6970
test('should render carousel cards with correct styles', async ({ page }) => {
70-
await page.goto('/?simulation=carousel-show&theme=dark');
71+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
7172

7273
await page.waitForLoadState('networkidle');
7374

@@ -87,7 +88,7 @@ test.describe('Carousel Resource', () => {
8788
});
8889

8990
test('should have appropriate background color for dark mode', async ({ page }) => {
90-
await page.goto('/?simulation=carousel-show&theme=dark');
91+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
9192

9293
await page.waitForLoadState('networkidle');
9394

@@ -115,7 +116,7 @@ test.describe('Carousel Resource', () => {
115116
}
116117
});
117118

118-
await page.goto('/?simulation=carousel-show&theme=dark');
119+
await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
119120
await page.waitForLoadState('networkidle');
120121

121122
expect(errors).toHaveLength(0);

0 commit comments

Comments
 (0)