sunpeak 0.18.14 → 0.19.2
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/README.md +37 -134
- package/bin/commands/new.mjs +3 -1
- package/bin/commands/test-init.mjs +305 -0
- package/bin/commands/test.mjs +144 -0
- package/bin/lib/inspect/inspect-config.d.mts +4 -0
- package/bin/lib/inspect/inspect-config.mjs +18 -24
- package/bin/lib/test/base-config.mjs +75 -0
- package/bin/lib/test/matchers.mjs +99 -0
- package/bin/lib/test/test-config.d.mts +66 -0
- package/bin/lib/test/test-config.mjs +125 -0
- package/bin/lib/test/test-fixtures.d.mts +129 -0
- package/bin/lib/test/test-fixtures.mjs +232 -0
- package/bin/sunpeak.js +18 -5
- package/package.json +22 -10
- package/template/README.md +18 -8
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.html +468 -280
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.bin/playwright +2 -2
- package/template/node_modules/.bin/vite +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/node_modules/.vite/deps/_metadata.json +4 -4
- package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
- package/template/node_modules/.vite-mcp/deps/mapbox-gl.js +15924 -14588
- package/template/node_modules/.vite-mcp/deps/mapbox-gl.js.map +1 -1
- package/template/node_modules/.vite-mcp/deps/vitest.js +8 -8
- package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
- package/template/package.json +9 -7
- package/template/playwright.config.ts +2 -40
- package/template/test-results/.last-run.json +4 -0
- package/template/tests/e2e/albums.spec.ts +114 -245
- package/template/tests/e2e/carousel.spec.ts +189 -313
- package/template/tests/e2e/map.spec.ts +177 -300
- package/template/tests/e2e/review.spec.ts +232 -423
- package/template/tests/e2e/visual.spec.ts +36 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-claude-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-claude-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-chatgpt-linux.png +0 -0
- package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-claude-linux.png +0 -0
- package/template/tests/live/albums.spec.ts +1 -1
- package/template/tests/live/carousel.spec.ts +1 -1
- package/template/tests/live/map.spec.ts +1 -1
- package/template/tests/live/playwright.config.ts +1 -1
- package/template/tests/live/review.spec.ts +1 -1
- package/template/vitest.config.ts +1 -1
- package/template/tests/e2e/global-setup.ts +0 -10
- package/template/tests/e2e/helpers.ts +0 -13
package/template/package.json
CHANGED
|
@@ -7,30 +7,32 @@
|
|
|
7
7
|
"dev": "sunpeak dev",
|
|
8
8
|
"build": "sunpeak build",
|
|
9
9
|
"start": "sunpeak start",
|
|
10
|
-
"test": "
|
|
11
|
-
"test:
|
|
12
|
-
"test:
|
|
10
|
+
"test": "sunpeak test",
|
|
11
|
+
"test:unit": "sunpeak test --unit",
|
|
12
|
+
"test:e2e": "sunpeak test --e2e",
|
|
13
|
+
"test:visual": "sunpeak test --visual",
|
|
14
|
+
"test:live": "sunpeak test --live"
|
|
13
15
|
},
|
|
14
16
|
"dependencies": {
|
|
15
17
|
"clsx": "^2.1.1",
|
|
16
18
|
"embla-carousel-react": "^8.6.0",
|
|
17
19
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
18
|
-
"mapbox-gl": "^3.
|
|
20
|
+
"mapbox-gl": "^3.21.0",
|
|
19
21
|
"sunpeak": "workspace:*",
|
|
20
22
|
"tailwind-merge": "^3.5.0",
|
|
21
23
|
"zod": "^4.3.6"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@playwright/test": "^1.
|
|
26
|
+
"@playwright/test": "^1.59.1",
|
|
25
27
|
"@tailwindcss/vite": "^4.2.2",
|
|
26
28
|
"@testing-library/jest-dom": "^6.9.1",
|
|
27
29
|
"@testing-library/react": "^16.3.2",
|
|
28
30
|
"@testing-library/user-event": "^14.6.1",
|
|
29
|
-
"@types/node": "^25.5.
|
|
31
|
+
"@types/node": "^25.5.2",
|
|
30
32
|
"@types/react": "^19.2.14",
|
|
31
33
|
"@types/react-dom": "^19.2.3",
|
|
32
34
|
"@vitejs/plugin-react": "^6.0.1",
|
|
33
|
-
"
|
|
35
|
+
"happy-dom": "^18.0.1",
|
|
34
36
|
"react": "^19.2.4",
|
|
35
37
|
"react-dom": "^19.2.4",
|
|
36
38
|
"tailwindcss": "^4.2.2",
|
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
import { defineConfig
|
|
1
|
+
import { defineConfig } from 'sunpeak/test/config';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
// the running server. In CI, validate.mjs assigns unique ports via env vars.
|
|
5
|
-
const port = Number(process.env.SUNPEAK_TEST_PORT) || 6776;
|
|
6
|
-
const sandboxPort = Number(process.env.SUNPEAK_SANDBOX_PORT) || 24680;
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
globalSetup: './tests/e2e/global-setup.ts',
|
|
10
|
-
testDir: './tests/e2e',
|
|
11
|
-
fullyParallel: true,
|
|
12
|
-
forbidOnly: !!process.env.CI,
|
|
13
|
-
retries: process.env.CI ? 2 : 1,
|
|
14
|
-
// Limit parallel workers. Each test loads a full inspector page with
|
|
15
|
-
// iframe → cross-origin sandbox proxy → inner iframe. Too many concurrent
|
|
16
|
-
// pages overwhelm the sandbox proxy server and cause PostMessage relay
|
|
17
|
-
// timeouts. 2 workers balances speed vs reliability.
|
|
18
|
-
workers: process.env.CI ? 1 : 2,
|
|
19
|
-
reporter: 'list',
|
|
20
|
-
use: {
|
|
21
|
-
baseURL: `http://localhost:${port}`,
|
|
22
|
-
trace: 'on-first-retry',
|
|
23
|
-
},
|
|
24
|
-
projects: [
|
|
25
|
-
{
|
|
26
|
-
name: 'chromium',
|
|
27
|
-
use: {
|
|
28
|
-
...devices['Desktop Chrome'],
|
|
29
|
-
launchOptions: {
|
|
30
|
-
args: ['--use-gl=angle'],
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
],
|
|
35
|
-
webServer: {
|
|
36
|
-
command: `PORT=${port} SUNPEAK_SANDBOX_PORT=${sandboxPort} pnpm dev`,
|
|
37
|
-
url: `http://localhost:${port}/health`,
|
|
38
|
-
reuseExistingServer: !process.env.CI,
|
|
39
|
-
timeout: 60000,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
3
|
+
export default defineConfig();
|
|
@@ -1,246 +1,115 @@
|
|
|
1
|
-
import { test, expect } from '
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
test('should have album image with correct aspect ratio', async ({ page }) => {
|
|
30
|
-
await page.goto(createInspectorUrl({ simulation: 'show-albums', theme: 'light', host }));
|
|
31
|
-
|
|
32
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
33
|
-
const albumImage = iframe.locator('button:has-text("Summer Slice") img').first();
|
|
34
|
-
await expect(albumImage).toBeVisible();
|
|
35
|
-
|
|
36
|
-
// Verify aspect-[4/3] container
|
|
37
|
-
const imageContainer = iframe.locator(
|
|
38
|
-
'button:has-text("Summer Slice") .aspect-\\[4\\/3\\]'
|
|
39
|
-
);
|
|
40
|
-
await expect(imageContainer).toBeVisible();
|
|
41
|
-
|
|
42
|
-
const containerStyles = await imageContainer.evaluate((el) => {
|
|
43
|
-
const computed = window.getComputedStyle(el);
|
|
44
|
-
return {
|
|
45
|
-
borderRadius: computed.borderRadius,
|
|
46
|
-
overflow: computed.overflow,
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
expect(containerStyles.borderRadius).toBe('12px'); // rounded-xl
|
|
51
|
-
expect(containerStyles.overflow).toBe('hidden');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test.describe('Dark Mode', () => {
|
|
56
|
-
test('should render album cards with correct styles', async ({ page }) => {
|
|
57
|
-
await page.goto(createInspectorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
58
|
-
|
|
59
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
60
|
-
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
61
|
-
await expect(albumCard).toBeVisible();
|
|
62
|
-
|
|
63
|
-
const styles = await albumCard.evaluate((el) => {
|
|
64
|
-
const computed = window.getComputedStyle(el);
|
|
65
|
-
return {
|
|
66
|
-
cursor: computed.cursor,
|
|
67
|
-
borderRadius: computed.borderRadius,
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(styles.cursor).toBe('pointer');
|
|
72
|
-
expect(styles.borderRadius).toBe('12px'); // rounded-xl
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should have text with appropriate contrast', async ({ page }) => {
|
|
76
|
-
await page.goto(createInspectorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
77
|
-
|
|
78
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
79
|
-
const albumTitle = iframe.locator('button:has-text("Summer Slice") div').first();
|
|
80
|
-
await expect(albumTitle).toBeVisible();
|
|
81
|
-
|
|
82
|
-
// In dark mode, text should be light colored for contrast
|
|
83
|
-
const titleStyles = await albumTitle.evaluate((el) => {
|
|
84
|
-
const computed = window.getComputedStyle(el);
|
|
85
|
-
return {
|
|
86
|
-
color: computed.color,
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Verify the text color exists (should be a light color in dark mode)
|
|
91
|
-
expect(titleStyles.color).toBeTruthy();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test.describe('Prod Tools Mode', () => {
|
|
96
|
-
test('should show empty state with Run button', async ({ page }) => {
|
|
97
|
-
await page.goto(createInspectorUrl({ tool: 'show-albums', theme: 'dark', host }));
|
|
98
|
-
|
|
99
|
-
// Should show the "Press Run to call the tool" empty state
|
|
100
|
-
const emptyState = page.locator('text=Press Run to call the tool');
|
|
101
|
-
await expect(emptyState).toBeVisible();
|
|
102
|
-
|
|
103
|
-
// Run button should be visible in the conversation header
|
|
104
|
-
const runButton = page.locator('button:has-text("Run")');
|
|
105
|
-
await expect(runButton).toBeVisible();
|
|
106
|
-
|
|
107
|
-
// Iframe should NOT be present (no resource loaded yet)
|
|
108
|
-
const iframe = page.locator('iframe');
|
|
109
|
-
await expect(iframe).not.toBeAttached();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('should have themed empty state colors in light mode', async ({ page }) => {
|
|
113
|
-
await page.goto(createInspectorUrl({ tool: 'show-albums', theme: 'light', host }));
|
|
114
|
-
|
|
115
|
-
const emptyState = page.locator('text=Press Run to call the tool');
|
|
116
|
-
await expect(emptyState).toBeVisible();
|
|
117
|
-
|
|
118
|
-
const color = await emptyState.evaluate((el) => {
|
|
119
|
-
return window.getComputedStyle(el).color;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Light mode text-secondary should be a dark-ish color (not white/very light)
|
|
123
|
-
const [r, g, b] = color.match(/\d+/g)!.map(Number);
|
|
124
|
-
// In light mode, secondary text should have a reasonable luminance (not too bright)
|
|
125
|
-
expect(r + g + b).toBeLessThan(600);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('should have themed empty state colors in dark mode', async ({ page }) => {
|
|
129
|
-
await page.goto(createInspectorUrl({ tool: 'show-albums', theme: 'dark', host }));
|
|
130
|
-
|
|
131
|
-
const emptyState = page.locator('text=Press Run to call the tool');
|
|
132
|
-
await expect(emptyState).toBeVisible();
|
|
133
|
-
|
|
134
|
-
const color = await emptyState.evaluate((el) => {
|
|
135
|
-
return window.getComputedStyle(el).color;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Dark mode text-secondary should be a light-ish color (not black/very dark)
|
|
139
|
-
const [r, g, b] = color.match(/\d+/g)!.map(Number);
|
|
140
|
-
expect(r + g + b).toBeGreaterThan(200);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test.describe('Prod Resources Mode', () => {
|
|
145
|
-
test('should render resource normally when dist is available', async ({ page }) => {
|
|
146
|
-
// With prodResources=true but no dist/ files, shows "Building..."
|
|
147
|
-
// With dist/ files available, renders the resource from dist/
|
|
148
|
-
// This test verifies the mode activates without errors
|
|
149
|
-
await page.goto(
|
|
150
|
-
createInspectorUrl({
|
|
151
|
-
simulation: 'show-albums',
|
|
152
|
-
theme: 'dark',
|
|
153
|
-
host,
|
|
154
|
-
prodResources: true,
|
|
155
|
-
})
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
// Should either show "Building..." or the resource (depending on dist availability)
|
|
159
|
-
const root = page.locator('#root');
|
|
160
|
-
await expect(root).not.toBeEmpty();
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test.describe('Fullscreen Mode', () => {
|
|
165
|
-
test('should render correctly in fullscreen displayMode', async ({ page }) => {
|
|
166
|
-
await page.goto(
|
|
167
|
-
createInspectorUrl({
|
|
168
|
-
simulation: 'show-albums',
|
|
169
|
-
theme: 'light',
|
|
170
|
-
displayMode: 'fullscreen',
|
|
171
|
-
host,
|
|
172
|
-
})
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// Wait for content to load
|
|
176
|
-
await page.waitForLoadState('networkidle');
|
|
177
|
-
|
|
178
|
-
// The root container should be present
|
|
179
|
-
const root = page.locator('#root');
|
|
180
|
-
await expect(root).not.toBeEmpty();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('should maintain album card styles in fullscreen', async ({ page }) => {
|
|
184
|
-
await page.goto(
|
|
185
|
-
createInspectorUrl({
|
|
186
|
-
simulation: 'show-albums',
|
|
187
|
-
theme: 'dark',
|
|
188
|
-
displayMode: 'fullscreen',
|
|
189
|
-
host,
|
|
190
|
-
})
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
194
|
-
const albumCard = iframe.locator('button:has-text("Summer Slice")');
|
|
195
|
-
await expect(albumCard).toBeVisible();
|
|
196
|
-
|
|
197
|
-
const styles = await albumCard.evaluate((el) => {
|
|
198
|
-
const computed = window.getComputedStyle(el);
|
|
199
|
-
return {
|
|
200
|
-
cursor: computed.cursor,
|
|
201
|
-
borderRadius: computed.borderRadius,
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
expect(styles.cursor).toBe('pointer');
|
|
206
|
-
expect(styles.borderRadius).toBe('12px');
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('should render content after switching from inline to fullscreen', async ({ page }) => {
|
|
210
|
-
// Start in inline mode
|
|
211
|
-
await page.goto(createInspectorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
212
|
-
|
|
213
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
214
|
-
await expect(iframe.locator('button:has-text("Summer Slice")')).toBeVisible();
|
|
215
|
-
|
|
216
|
-
// Switch to fullscreen via sidebar
|
|
217
|
-
await page.locator('button:has-text("Full")').click();
|
|
218
|
-
|
|
219
|
-
// Content should still be visible after the mode transition
|
|
220
|
-
await expect(iframe.locator('button:has-text("Summer Slice")')).toBeVisible({
|
|
221
|
-
timeout: 5000,
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Claude doesn't support PiP — only run this test for hosts that have the button.
|
|
226
|
-
(host === 'claude' ? test.skip : test)(
|
|
227
|
-
'should render content after switching from inline to pip',
|
|
228
|
-
async ({ page }) => {
|
|
229
|
-
// Start in inline mode
|
|
230
|
-
await page.goto(createInspectorUrl({ simulation: 'show-albums', theme: 'dark', host }));
|
|
231
|
-
|
|
232
|
-
const iframe = page.frameLocator('iframe').frameLocator('iframe');
|
|
233
|
-
await expect(iframe.locator('button:has-text("Summer Slice")')).toBeVisible();
|
|
234
|
-
|
|
235
|
-
// Switch to PiP via sidebar
|
|
236
|
-
await page.locator('button:has-text("PiP")').click();
|
|
237
|
-
|
|
238
|
-
// Content should still be visible after the mode transition
|
|
239
|
-
await expect(iframe.locator('button:has-text("Summer Slice")')).toBeVisible({
|
|
240
|
-
timeout: 5000,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
);
|
|
244
|
-
});
|
|
1
|
+
import { test, expect } from 'sunpeak/test';
|
|
2
|
+
|
|
3
|
+
test('should render album cards with correct styles', async ({ mcp }) => {
|
|
4
|
+
const result = await mcp.callTool('show-albums');
|
|
5
|
+
const app = result.app();
|
|
6
|
+
|
|
7
|
+
const albumCard = app.locator('button:has-text("Summer Slice")');
|
|
8
|
+
await expect(albumCard).toBeVisible();
|
|
9
|
+
|
|
10
|
+
const styles = await albumCard.evaluate((el) => {
|
|
11
|
+
const computed = window.getComputedStyle(el);
|
|
12
|
+
return { cursor: computed.cursor, borderRadius: computed.borderRadius };
|
|
13
|
+
});
|
|
14
|
+
expect(styles.cursor).toBe('pointer');
|
|
15
|
+
expect(styles.borderRadius).toBe('12px');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should have album image with correct aspect ratio', async ({ mcp }) => {
|
|
19
|
+
const result = await mcp.callTool('show-albums');
|
|
20
|
+
const app = result.app();
|
|
21
|
+
|
|
22
|
+
const imageContainer = app.locator('button:has-text("Summer Slice") .aspect-\\[4\\/3\\]');
|
|
23
|
+
await expect(imageContainer).toBeVisible();
|
|
24
|
+
|
|
25
|
+
const styles = await imageContainer.evaluate((el) => {
|
|
26
|
+
const computed = window.getComputedStyle(el);
|
|
27
|
+
return { borderRadius: computed.borderRadius, overflow: computed.overflow };
|
|
245
28
|
});
|
|
246
|
-
|
|
29
|
+
expect(styles.borderRadius).toBe('12px');
|
|
30
|
+
expect(styles.overflow).toBe('hidden');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should render album cards in dark mode', async ({ mcp }) => {
|
|
34
|
+
const result = await mcp.callTool('show-albums', {}, { theme: 'dark' });
|
|
35
|
+
const app = result.app();
|
|
36
|
+
|
|
37
|
+
const albumTitle = app.locator('button:has-text("Summer Slice") div').first();
|
|
38
|
+
await expect(albumTitle).toBeVisible();
|
|
39
|
+
|
|
40
|
+
const titleStyles = await albumTitle.evaluate((el) => ({
|
|
41
|
+
color: window.getComputedStyle(el).color,
|
|
42
|
+
}));
|
|
43
|
+
expect(titleStyles.color).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should show empty state with Run button in prod tools mode', async ({ mcp }) => {
|
|
47
|
+
await mcp.openTool('show-albums', { theme: 'dark' });
|
|
48
|
+
|
|
49
|
+
await expect(mcp.page.locator('text=Press Run to call the tool')).toBeVisible();
|
|
50
|
+
await expect(mcp.page.locator('button:has-text("Run")')).toBeVisible();
|
|
51
|
+
await expect(mcp.page.locator('iframe')).not.toBeAttached();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should have themed empty state colors in light mode', async ({ mcp }) => {
|
|
55
|
+
await mcp.openTool('show-albums', { theme: 'light' });
|
|
56
|
+
|
|
57
|
+
const emptyState = mcp.page.locator('text=Press Run to call the tool');
|
|
58
|
+
await expect(emptyState).toBeVisible();
|
|
59
|
+
|
|
60
|
+
const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
|
|
61
|
+
const [r, g, b] = color.match(/\d+/g)!.map(Number);
|
|
62
|
+
expect(r + g + b).toBeLessThan(600);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should have themed empty state colors in dark mode', async ({ mcp }) => {
|
|
66
|
+
await mcp.openTool('show-albums', { theme: 'dark' });
|
|
67
|
+
|
|
68
|
+
const emptyState = mcp.page.locator('text=Press Run to call the tool');
|
|
69
|
+
await expect(emptyState).toBeVisible();
|
|
70
|
+
|
|
71
|
+
const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
|
|
72
|
+
const [r, g, b] = color.match(/\d+/g)!.map(Number);
|
|
73
|
+
expect(r + g + b).toBeGreaterThan(200);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should activate prod resources mode without errors', async ({ mcp }) => {
|
|
77
|
+
await mcp.callTool('show-albums', {}, { theme: 'dark', prodResources: true });
|
|
78
|
+
const root = mcp.page.locator('#root');
|
|
79
|
+
await expect(root).not.toBeEmpty();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should render correctly in fullscreen', async ({ mcp }) => {
|
|
83
|
+
const result = await mcp.callTool('show-albums', {}, { displayMode: 'fullscreen' });
|
|
84
|
+
const app = result.app();
|
|
85
|
+
|
|
86
|
+
const albumCard = app.locator('button:has-text("Summer Slice")');
|
|
87
|
+
await expect(albumCard).toBeVisible();
|
|
88
|
+
|
|
89
|
+
const styles = await albumCard.evaluate((el) => ({
|
|
90
|
+
cursor: window.getComputedStyle(el).cursor,
|
|
91
|
+
borderRadius: window.getComputedStyle(el).borderRadius,
|
|
92
|
+
}));
|
|
93
|
+
expect(styles.cursor).toBe('pointer');
|
|
94
|
+
expect(styles.borderRadius).toBe('12px');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should preserve content when switching to fullscreen', async ({ mcp }) => {
|
|
98
|
+
const result = await mcp.callTool('show-albums', {}, { theme: 'dark' });
|
|
99
|
+
const app = result.app();
|
|
100
|
+
await expect(app.locator('button:has-text("Summer Slice")')).toBeVisible();
|
|
101
|
+
|
|
102
|
+
await mcp.setDisplayMode('fullscreen');
|
|
103
|
+
await expect(app.locator('button:has-text("Summer Slice")')).toBeVisible({ timeout: 5000 });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should preserve content when switching to PiP', async ({ mcp }) => {
|
|
107
|
+
test.skip(mcp.host === 'claude', 'Claude does not support PiP');
|
|
108
|
+
|
|
109
|
+
const result = await mcp.callTool('show-albums', {}, { theme: 'dark' });
|
|
110
|
+
const app = result.app();
|
|
111
|
+
await expect(app.locator('button:has-text("Summer Slice")')).toBeVisible();
|
|
112
|
+
|
|
113
|
+
await mcp.setDisplayMode('pip');
|
|
114
|
+
await expect(app.locator('button:has-text("Summer Slice")')).toBeVisible({ timeout: 5000 });
|
|
115
|
+
});
|