sunpeak 0.18.13 → 0.19.1

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.
Files changed (45) hide show
  1. package/README.md +34 -134
  2. package/bin/commands/test-init.mjs +305 -0
  3. package/bin/commands/test.mjs +83 -0
  4. package/bin/lib/inspect/inspect-config.mjs +16 -24
  5. package/bin/lib/test/base-config.mjs +60 -0
  6. package/bin/lib/test/matchers.mjs +99 -0
  7. package/bin/lib/test/test-config.d.mts +44 -0
  8. package/bin/lib/test/test-config.mjs +123 -0
  9. package/bin/lib/test/test-fixtures.d.mts +96 -0
  10. package/bin/lib/test/test-fixtures.mjs +189 -0
  11. package/bin/sunpeak.js +18 -5
  12. package/dist/mcp/index.cjs +58 -16
  13. package/dist/mcp/index.cjs.map +1 -1
  14. package/dist/mcp/index.js +58 -16
  15. package/dist/mcp/index.js.map +1 -1
  16. package/package.json +22 -10
  17. package/template/README.md +15 -8
  18. package/template/dist/albums/albums.json +1 -1
  19. package/template/dist/carousel/carousel.json +1 -1
  20. package/template/dist/map/map.html +468 -280
  21. package/template/dist/map/map.json +1 -1
  22. package/template/dist/review/review.json +1 -1
  23. package/template/node_modules/.bin/playwright +2 -2
  24. package/template/node_modules/.bin/vite +2 -2
  25. package/template/node_modules/.bin/vitest +2 -2
  26. package/template/node_modules/.vite/deps/_metadata.json +4 -4
  27. package/template/node_modules/.vite-mcp/deps/_metadata.json +22 -22
  28. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js +15924 -14588
  29. package/template/node_modules/.vite-mcp/deps/mapbox-gl.js.map +1 -1
  30. package/template/node_modules/.vite-mcp/deps/vitest.js +8 -8
  31. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  32. package/template/package.json +4 -4
  33. package/template/playwright.config.ts +2 -40
  34. package/template/tests/e2e/albums.spec.ts +114 -245
  35. package/template/tests/e2e/carousel.spec.ts +189 -313
  36. package/template/tests/e2e/map.spec.ts +177 -300
  37. package/template/tests/e2e/review.spec.ts +232 -423
  38. package/template/tests/live/albums.spec.ts +1 -1
  39. package/template/tests/live/carousel.spec.ts +1 -1
  40. package/template/tests/live/map.spec.ts +1 -1
  41. package/template/tests/live/playwright.config.ts +1 -1
  42. package/template/tests/live/review.spec.ts +1 -1
  43. package/template/vitest.config.ts +1 -1
  44. package/template/tests/e2e/global-setup.ts +0 -10
  45. package/template/tests/e2e/helpers.ts +0 -13
@@ -1,424 +1,233 @@
1
- import { test, expect } from '@playwright/test';
2
- import { createInspectorUrl } from './helpers';
3
-
4
- const hosts = ['chatgpt', 'claude'] as const;
5
-
6
- for (const host of hosts) {
7
- test.describe(`Review Resource [${host}]`, () => {
8
- test.describe('Light Mode', () => {
9
- test('should render review title with correct styles', async ({ page }) => {
10
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'light', host }));
11
-
12
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
13
- const title = iframe.locator('h1:has-text("Refactor Authentication Module")');
14
- await expect(title).toBeVisible();
15
-
16
- const styles = await title.evaluate((el) => {
17
- const computed = window.getComputedStyle(el);
18
- return {
19
- fontWeight: computed.fontWeight,
20
- };
21
- });
22
-
23
- // Should render semibold (600)
24
- expect(parseInt(styles.fontWeight)).toBeGreaterThanOrEqual(600);
25
- });
26
-
27
- test('should render change items with type-specific styling', async ({ page }) => {
28
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'light', host }));
29
-
30
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
31
- const changeItem = iframe.locator('li').first();
32
- await expect(changeItem).toBeVisible();
33
-
34
- const styles = await changeItem.evaluate((el) => {
35
- const computed = window.getComputedStyle(el);
36
- return {
37
- borderRadius: computed.borderRadius,
38
- backgroundColor: computed.backgroundColor,
39
- };
40
- });
41
-
42
- // Background should be set (one of the type colors)
43
- expect(styles.backgroundColor).toBeTruthy();
44
- expect(styles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
45
- });
46
-
47
- test('should have interactive apply and cancel buttons', async ({ page }) => {
48
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'light', host }));
49
-
50
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
51
-
52
- // Find the Apply Changes button (based on simulation data)
53
- const applyButton = iframe.locator('button:has-text("Apply Changes")');
54
- await expect(applyButton).toBeVisible();
55
-
56
- const applyStyles = await applyButton.evaluate((el) => {
57
- const computed = window.getComputedStyle(el);
58
- return {
59
- cursor: computed.cursor,
60
- };
61
- });
62
- expect(applyStyles.cursor).toBe('pointer');
63
-
64
- // Find the Cancel button
65
- const cancelButton = iframe.locator('button:has-text("Cancel")');
66
- await expect(cancelButton).toBeVisible();
67
-
68
- const cancelStyles = await cancelButton.evaluate((el) => {
69
- const computed = window.getComputedStyle(el);
70
- return {
71
- cursor: computed.cursor,
72
- };
73
- });
74
- expect(cancelStyles.cursor).toBe('pointer');
75
- });
76
-
77
- test('should have expand fullscreen button in inline mode', async ({ page }) => {
78
- await page.goto(
79
- createInspectorUrl({
80
- simulation: 'review-diff',
81
- theme: 'light',
82
- displayMode: 'inline',
83
- host,
84
- })
85
- );
86
-
87
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
88
- const expandButton = iframe.locator('button[aria-label="Enter fullscreen"]');
89
- await expect(expandButton).toBeVisible();
90
-
91
- const styles = await expandButton.evaluate((el) => {
92
- const computed = window.getComputedStyle(el);
93
- return {
94
- cursor: computed.cursor,
95
- };
96
- });
97
-
98
- expect(styles.cursor).toBe('pointer');
99
- });
100
- });
101
-
102
- test.describe('Prod Tools Mode', () => {
103
- test('should show empty state with Run button', async ({ page }) => {
104
- await page.goto(createInspectorUrl({ tool: 'review-diff', theme: 'dark', host }));
105
-
106
- const emptyState = page.locator('text=Press Run to call the tool');
107
- await expect(emptyState).toBeVisible();
108
-
109
- const runButton = page.locator('button:has-text("Run")');
110
- await expect(runButton).toBeVisible();
111
-
112
- const iframe = page.locator('iframe');
113
- await expect(iframe).not.toBeAttached();
114
- });
115
-
116
- test('should have themed empty state colors in light mode', async ({ page }) => {
117
- await page.goto(createInspectorUrl({ tool: 'review-diff', theme: 'light', host }));
118
-
119
- const emptyState = page.locator('text=Press Run to call the tool');
120
- await expect(emptyState).toBeVisible();
121
-
122
- const color = await emptyState.evaluate((el) => {
123
- return window.getComputedStyle(el).color;
124
- });
125
-
126
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
127
- expect(r + g + b).toBeLessThan(600);
128
- });
129
-
130
- test('should have themed empty state colors in dark mode', async ({ page }) => {
131
- await page.goto(createInspectorUrl({ tool: 'review-diff', theme: 'dark', host }));
132
-
133
- const emptyState = page.locator('text=Press Run to call the tool');
134
- await expect(emptyState).toBeVisible();
135
-
136
- const color = await emptyState.evaluate((el) => {
137
- return window.getComputedStyle(el).color;
138
- });
139
-
140
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
141
- expect(r + g + b).toBeGreaterThan(200);
142
- });
143
- });
144
-
145
- test.describe('Prod Resources Mode', () => {
146
- test('should activate without errors', async ({ page }) => {
147
- await page.goto(
148
- createInspectorUrl({
149
- simulation: 'review-diff',
150
- theme: 'dark',
151
- host,
152
- prodResources: true,
153
- })
154
- );
155
-
156
- const root = page.locator('#root');
157
- await expect(root).not.toBeEmpty();
158
- });
159
- });
160
-
161
- test.describe('Dark Mode', () => {
162
- test('should render review title with correct styles', async ({ page }) => {
163
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'dark', host }));
164
-
165
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
166
- const title = iframe.locator('h1:has-text("Refactor Authentication Module")');
167
- await expect(title).toBeVisible();
168
- });
169
-
170
- test('should have appropriate text colors for dark mode', async ({ page }) => {
171
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'dark', host }));
172
-
173
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
174
- const title = iframe.locator('h1').first();
175
- await expect(title).toBeVisible();
176
-
177
- const styles = await title.evaluate((el) => {
178
- const computed = window.getComputedStyle(el);
179
- return {
180
- color: computed.color,
181
- };
182
- });
183
-
184
- // In dark mode, text color should be light
185
- expect(styles.color).toBeTruthy();
186
- });
187
-
188
- test('should render change items in dark mode', async ({ page }) => {
189
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'dark', host }));
190
-
191
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
192
- const changeItem = iframe.locator('li').first();
193
- await expect(changeItem).toBeVisible();
194
- });
195
-
196
- test('should load without console errors', async ({ page }) => {
197
- const errors: string[] = [];
198
- page.on('console', (msg) => {
199
- if (msg.type() === 'error') {
200
- errors.push(msg.text());
201
- }
202
- });
203
-
204
- await page.goto(createInspectorUrl({ simulation: 'review-diff', theme: 'dark', host }));
205
-
206
- // Wait for iframe content to render
207
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
208
- await expect(iframe.locator('h1').first()).toBeVisible();
209
-
210
- // Filter out expected iframe/MCP handshake errors
211
- const unexpectedErrors = errors.filter(
212
- (e) =>
213
- !e.includes('[IframeResource]') &&
214
- !e.includes('mcp') &&
215
- !e.includes('PostMessage') &&
216
- !e.includes('connect')
217
- );
218
- expect(unexpectedErrors).toHaveLength(0);
219
- });
220
- });
221
-
222
- test.describe('Fullscreen Mode', () => {
223
- test('should not show fullscreen button when already in fullscreen', async ({ page }) => {
224
- await page.goto(
225
- createInspectorUrl({
226
- simulation: 'review-diff',
227
- theme: 'light',
228
- displayMode: 'fullscreen',
229
- host,
230
- })
231
- );
232
-
233
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
234
- // Wait for content to render first
235
- await expect(iframe.locator('h1').first()).toBeVisible();
236
-
237
- // The expand button should not be visible in fullscreen mode
238
- const expandButton = iframe.locator('button[aria-label="Enter fullscreen"]');
239
- await expect(expandButton).not.toBeVisible();
240
- });
241
-
242
- test('should render content correctly in fullscreen', async ({ page }) => {
243
- await page.goto(
244
- createInspectorUrl({
245
- simulation: 'review-diff',
246
- theme: 'dark',
247
- displayMode: 'fullscreen',
248
- host,
249
- })
250
- );
251
-
252
- // The root container should be present
253
- const root = page.locator('#root');
254
- await expect(root).not.toBeEmpty();
255
-
256
- // Title should be visible inside the iframe
257
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
258
- const title = iframe.locator('h1');
259
- await expect(title).toBeVisible();
260
- });
261
-
262
- test('should render content in fullscreen mode', async ({ page }) => {
263
- await page.goto(
264
- createInspectorUrl({
265
- simulation: 'review-diff',
266
- theme: 'light',
267
- displayMode: 'fullscreen',
268
- host,
269
- })
270
- );
271
-
272
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
273
- // Content sections should be visible in fullscreen
274
- const title = iframe.locator('h1');
275
- await expect(title).toBeVisible();
276
-
277
- // Fullscreen expand button should NOT be visible (already in fullscreen)
278
- const expandButton = iframe.locator('button[aria-label="Enter fullscreen"]');
279
- await expect(expandButton).toHaveCount(0);
280
- });
281
- });
282
-
283
- test.describe('Review Post Simulation', () => {
284
- test('should render post review in light mode', async ({ page }) => {
285
- await page.goto(createInspectorUrl({ simulation: 'review-post', theme: 'light', host }));
286
-
287
- await page.waitForLoadState('networkidle');
288
-
289
- // Should render the review content
290
- const root = page.locator('#root');
291
- await expect(root).not.toBeEmpty();
292
- });
293
-
294
- test('should render post review in dark mode', async ({ page }) => {
295
- await page.goto(createInspectorUrl({ simulation: 'review-post', theme: 'dark', host }));
296
-
297
- await page.waitForLoadState('networkidle');
298
-
299
- const root = page.locator('#root');
300
- await expect(root).not.toBeEmpty();
301
- });
302
-
303
- test('should show server success message when confirming', async ({ page }) => {
304
- await page.goto(
305
- createInspectorUrl({
306
- simulation: 'review-post',
307
- theme: 'dark',
308
- host,
309
- })
310
- );
311
-
312
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
313
- const publishButton = iframe.locator('button:has-text("Publish")');
314
- await expect(publishButton).toBeVisible();
315
- // Use evaluate to dispatch click directly — Playwright's coordinate-based
316
- // click can miss the target inside the double cross-origin iframe.
317
- await publishButton.evaluate((el) => (el as HTMLElement).click());
318
-
319
- // Should show the server's success message from serverTools mock
320
- await expect(iframe.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
321
- // Should also show what button was pressed
322
- await expect(iframe.locator('text=Publishing post...')).toBeVisible({ timeout: 10000 });
323
- });
324
-
325
- test('should show server cancel message when rejecting', async ({ page }) => {
326
- await page.goto(
327
- createInspectorUrl({
328
- simulation: 'review-post',
329
- theme: 'dark',
330
- host,
331
- })
332
- );
333
-
334
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
335
- const cancelButton = iframe.locator('button:has-text("Cancel")');
336
- await expect(cancelButton).toBeVisible();
337
- await cancelButton.evaluate((el) => (el as HTMLElement).click());
338
-
339
- // Server returned cancelled status via serverTools when condition
340
- await expect(iframe.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });
341
- });
342
- });
343
-
344
- test.describe('Review Purchase Simulation', () => {
345
- test('should render purchase review in light mode', async ({ page }) => {
346
- await page.goto(
347
- createInspectorUrl({ simulation: 'review-purchase', theme: 'light', host })
348
- );
349
-
350
- await page.waitForLoadState('networkidle');
351
-
352
- const root = page.locator('#root');
353
- await expect(root).not.toBeEmpty();
354
- });
355
-
356
- test('should render purchase review in dark mode', async ({ page }) => {
357
- await page.goto(createInspectorUrl({ simulation: 'review-purchase', theme: 'dark', host }));
358
-
359
- await page.waitForLoadState('networkidle');
360
-
361
- const root = page.locator('#root');
362
- await expect(root).not.toBeEmpty();
363
- });
364
-
365
- test('should show loading then result when placing order', async ({ page }) => {
366
- await page.goto(
367
- createInspectorUrl({
368
- simulation: 'review-purchase',
369
- theme: 'light',
370
- host,
371
- })
372
- );
373
-
374
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
375
- const placeOrderButton = iframe.locator('button:has-text("Place Order")');
376
- await expect(placeOrderButton).toBeVisible();
377
- await placeOrderButton.evaluate((el) => (el as HTMLElement).click());
378
-
379
- // After server responds, should show what the user clicked and the server result
380
- await expect(iframe.locator('text=Placing order...')).toBeVisible({ timeout: 10000 });
381
- await expect(iframe.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
382
- });
383
- });
384
-
385
- test.describe('Server Tool Simulation via serverTools field', () => {
386
- test('should confirm review-diff and show server success', async ({ page }) => {
387
- await page.goto(
388
- createInspectorUrl({
389
- simulation: 'review-diff',
390
- theme: 'dark',
391
- host,
392
- })
393
- );
394
-
395
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
396
- const applyButton = iframe.locator('button:has-text("Apply Changes")');
397
- await expect(applyButton).toBeVisible();
398
- await applyButton.evaluate((el) => (el as HTMLElement).click());
399
-
400
- // Should show the decision label and server response
401
- await expect(iframe.locator('text=Applying changes...')).toBeVisible({ timeout: 10000 });
402
- await expect(iframe.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
403
- });
404
-
405
- test('should cancel review-diff and show server cancelled', async ({ page }) => {
406
- await page.goto(
407
- createInspectorUrl({
408
- simulation: 'review-diff',
409
- theme: 'dark',
410
- host,
411
- })
412
- );
413
-
414
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
415
- const cancelButton = iframe.locator('button:has-text("Cancel")');
416
- await expect(cancelButton).toBeVisible();
417
- await cancelButton.evaluate((el) => (el as HTMLElement).click());
418
-
419
- // Server returned cancelled status via when condition matching
420
- await expect(iframe.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });
421
- });
422
- });
1
+ import { test, expect } from 'sunpeak/test';
2
+
3
+ test('should render review title with correct styles', async ({ mcp }) => {
4
+ const result = await mcp.callTool('review-diff');
5
+ const app = result.app();
6
+
7
+ const title = app.locator('h1:has-text("Refactor Authentication Module")');
8
+ await expect(title).toBeVisible();
9
+
10
+ const styles = await title.evaluate((el) => ({
11
+ fontWeight: window.getComputedStyle(el).fontWeight,
12
+ }));
13
+ expect(parseInt(styles.fontWeight)).toBeGreaterThanOrEqual(600);
14
+ });
15
+
16
+ test('should render change items with type-specific styling', async ({ mcp }) => {
17
+ const result = await mcp.callTool('review-diff');
18
+ const app = result.app();
19
+
20
+ const changeItem = app.locator('li').first();
21
+ await expect(changeItem).toBeVisible();
22
+
23
+ const styles = await changeItem.evaluate((el) => ({
24
+ backgroundColor: window.getComputedStyle(el).backgroundColor,
25
+ }));
26
+ expect(styles.backgroundColor).toBeTruthy();
27
+ expect(styles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
28
+ });
29
+
30
+ test('should have interactive apply and cancel buttons', async ({ mcp }) => {
31
+ const result = await mcp.callTool('review-diff');
32
+ const app = result.app();
33
+
34
+ const applyButton = app.locator('button:has-text("Apply Changes")');
35
+ await expect(applyButton).toBeVisible();
36
+ expect(await applyButton.evaluate((el) => window.getComputedStyle(el).cursor)).toBe('pointer');
37
+
38
+ const cancelButton = app.locator('button:has-text("Cancel")');
39
+ await expect(cancelButton).toBeVisible();
40
+ expect(await cancelButton.evaluate((el) => window.getComputedStyle(el).cursor)).toBe('pointer');
41
+ });
42
+
43
+ test('should have expand fullscreen button in inline mode', async ({ mcp }) => {
44
+ const result = await mcp.callTool('review-diff', {}, { displayMode: 'inline' });
45
+ const app = result.app();
46
+
47
+ const expandButton = app.locator('button[aria-label="Enter fullscreen"]');
48
+ await expect(expandButton).toBeVisible();
49
+ expect(await expandButton.evaluate((el) => window.getComputedStyle(el).cursor)).toBe('pointer');
50
+ });
51
+
52
+ test('should show empty state with Run button in prod tools mode', async ({ mcp }) => {
53
+ await mcp.openTool('review-diff', { theme: 'dark' });
54
+
55
+ await expect(mcp.page.locator('text=Press Run to call the tool')).toBeVisible();
56
+ await expect(mcp.page.locator('button:has-text("Run")')).toBeVisible();
57
+ await expect(mcp.page.locator('iframe')).not.toBeAttached();
58
+ });
59
+
60
+ test('should have themed empty state colors in light mode', async ({ mcp }) => {
61
+ await mcp.openTool('review-diff', { theme: 'light' });
62
+
63
+ const emptyState = mcp.page.locator('text=Press Run to call the tool');
64
+ await expect(emptyState).toBeVisible();
65
+
66
+ const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
67
+ const [r, g, b] = color.match(/\d+/g)!.map(Number);
68
+ expect(r + g + b).toBeLessThan(600);
69
+ });
70
+
71
+ test('should have themed empty state colors in dark mode', async ({ mcp }) => {
72
+ await mcp.openTool('review-diff', { theme: 'dark' });
73
+
74
+ const emptyState = mcp.page.locator('text=Press Run to call the tool');
75
+ await expect(emptyState).toBeVisible();
76
+
77
+ const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
78
+ const [r, g, b] = color.match(/\d+/g)!.map(Number);
79
+ expect(r + g + b).toBeGreaterThan(200);
80
+ });
81
+
82
+ test('should activate prod resources mode without errors', async ({ mcp }) => {
83
+ await mcp.callTool('review-diff', {}, { theme: 'dark', prodResources: true });
84
+ const root = mcp.page.locator('#root');
85
+ await expect(root).not.toBeEmpty();
86
+ });
87
+
88
+ test('should render review title in dark mode', async ({ mcp }) => {
89
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
90
+ const app = result.app();
91
+ await expect(app.locator('h1:has-text("Refactor Authentication Module")')).toBeVisible();
92
+ });
93
+
94
+ test('should have appropriate text colors in dark mode', async ({ mcp }) => {
95
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
96
+ const app = result.app();
97
+
98
+ const title = app.locator('h1').first();
99
+ await expect(title).toBeVisible();
100
+
101
+ const styles = await title.evaluate((el) => ({
102
+ color: window.getComputedStyle(el).color,
103
+ }));
104
+ expect(styles.color).toBeTruthy();
105
+ });
106
+
107
+ test('should render change items in dark mode', async ({ mcp }) => {
108
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
109
+ const app = result.app();
110
+ await expect(app.locator('li').first()).toBeVisible();
111
+ });
112
+
113
+ test('should load without console errors in dark mode', async ({ mcp }) => {
114
+ const errors: string[] = [];
115
+ mcp.page.on('console', (msg) => {
116
+ if (msg.type() === 'error') errors.push(msg.text());
423
117
  });
424
- }
118
+
119
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
120
+ const app = result.app();
121
+ await expect(app.locator('h1').first()).toBeVisible();
122
+
123
+ const unexpectedErrors = errors.filter(
124
+ (e) =>
125
+ !e.includes('[IframeResource]') &&
126
+ !e.includes('mcp') &&
127
+ !e.includes('PostMessage') &&
128
+ !e.includes('connect')
129
+ );
130
+ expect(unexpectedErrors).toHaveLength(0);
131
+ });
132
+
133
+ test('should not show fullscreen button in fullscreen mode', async ({ mcp }) => {
134
+ const result = await mcp.callTool('review-diff', {}, { displayMode: 'fullscreen' });
135
+ const app = result.app();
136
+
137
+ await expect(app.locator('h1').first()).toBeVisible();
138
+ await expect(app.locator('button[aria-label="Enter fullscreen"]')).not.toBeVisible();
139
+ });
140
+
141
+ test('should render content in fullscreen mode', async ({ mcp }) => {
142
+ const result = await mcp.callTool(
143
+ 'review-diff',
144
+ {},
145
+ { theme: 'dark', displayMode: 'fullscreen' }
146
+ );
147
+ const app = result.app();
148
+
149
+ await expect(mcp.page.locator('#root')).not.toBeEmpty();
150
+ await expect(app.locator('h1')).toBeVisible();
151
+ });
152
+
153
+ test('should render post review in light mode', async ({ mcp }) => {
154
+ await mcp.callTool('review-post');
155
+ await mcp.page.waitForLoadState('networkidle');
156
+ await expect(mcp.page.locator('#root')).not.toBeEmpty();
157
+ });
158
+
159
+ test('should render post review in dark mode', async ({ mcp }) => {
160
+ await mcp.callTool('review-post', {}, { theme: 'dark' });
161
+ await mcp.page.waitForLoadState('networkidle');
162
+ await expect(mcp.page.locator('#root')).not.toBeEmpty();
163
+ });
164
+
165
+ test('should show server success message when confirming post', async ({ mcp }) => {
166
+ const result = await mcp.callTool('review-post', {}, { theme: 'dark' });
167
+ const app = result.app();
168
+
169
+ const publishButton = app.locator('button:has-text("Publish")');
170
+ await expect(publishButton).toBeVisible();
171
+ await publishButton.evaluate((el) => (el as HTMLElement).click());
172
+
173
+ await expect(app.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
174
+ await expect(app.locator('text=Publishing post...')).toBeVisible({ timeout: 10000 });
175
+ });
176
+
177
+ test('should show server cancel message when rejecting post', async ({ mcp }) => {
178
+ const result = await mcp.callTool('review-post', {}, { theme: 'dark' });
179
+ const app = result.app();
180
+
181
+ const cancelButton = app.locator('button:has-text("Cancel")');
182
+ await expect(cancelButton).toBeVisible();
183
+ await cancelButton.evaluate((el) => (el as HTMLElement).click());
184
+
185
+ await expect(app.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });
186
+ });
187
+
188
+ test('should render purchase review in light mode', async ({ mcp }) => {
189
+ await mcp.callTool('review-purchase');
190
+ await mcp.page.waitForLoadState('networkidle');
191
+ await expect(mcp.page.locator('#root')).not.toBeEmpty();
192
+ });
193
+
194
+ test('should render purchase review in dark mode', async ({ mcp }) => {
195
+ await mcp.callTool('review-purchase', {}, { theme: 'dark' });
196
+ await mcp.page.waitForLoadState('networkidle');
197
+ await expect(mcp.page.locator('#root')).not.toBeEmpty();
198
+ });
199
+
200
+ test('should show loading then result when placing order', async ({ mcp }) => {
201
+ const result = await mcp.callTool('review-purchase');
202
+ const app = result.app();
203
+
204
+ const placeOrderButton = app.locator('button:has-text("Place Order")');
205
+ await expect(placeOrderButton).toBeVisible();
206
+ await placeOrderButton.evaluate((el) => (el as HTMLElement).click());
207
+
208
+ await expect(app.locator('text=Placing order...')).toBeVisible({ timeout: 10000 });
209
+ await expect(app.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
210
+ });
211
+
212
+ test('should confirm review-diff and show server success', async ({ mcp }) => {
213
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
214
+ const app = result.app();
215
+
216
+ const applyButton = app.locator('button:has-text("Apply Changes")');
217
+ await expect(applyButton).toBeVisible();
218
+ await applyButton.evaluate((el) => (el as HTMLElement).click());
219
+
220
+ await expect(app.locator('text=Applying changes...')).toBeVisible({ timeout: 10000 });
221
+ await expect(app.locator('text=Completed.')).toBeVisible({ timeout: 10000 });
222
+ });
223
+
224
+ test('should cancel review-diff and show server cancelled', async ({ mcp }) => {
225
+ const result = await mcp.callTool('review-diff', {}, { theme: 'dark' });
226
+ const app = result.app();
227
+
228
+ const cancelButton = app.locator('button:has-text("Cancel")');
229
+ await expect(cancelButton).toBeVisible();
230
+ await cancelButton.evaluate((el) => (el as HTMLElement).click());
231
+
232
+ await expect(app.locator('text=Cancelled.')).toBeVisible({ timeout: 10000 });
233
+ });
@@ -1,4 +1,4 @@
1
- import { test, expect } from 'sunpeak/test';
1
+ import { test, expect } from 'sunpeak/test/live';
2
2
 
3
3
  test('albums tool renders photo grid with correct styles', async ({ live }) => {
4
4
  const app = await live.invoke('show-albums');