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,315 +1,191 @@
1
- import { test, expect } from '@playwright/test';
2
- import { createInspectorUrl } from './helpers';
1
+ import { test, expect } from 'sunpeak/test';
3
2
 
4
- const hosts = ['chatgpt', 'claude'] as const;
5
-
6
- for (const host of hosts) {
7
- test.describe(`Carousel Resource [${host}]`, () => {
8
- test.describe('Light Mode', () => {
9
- test('should render carousel cards with correct styles', async ({ page }) => {
10
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'light', host }));
11
-
12
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
13
- const card = iframe.locator('.rounded-2xl').first();
14
- await expect(card).toBeVisible();
15
-
16
- const styles = await card.evaluate((el) => {
17
- const computed = window.getComputedStyle(el);
18
- return {
19
- borderRadius: computed.borderRadius,
20
- cursor: computed.cursor,
21
- };
22
- });
23
-
24
- expect(styles.borderRadius).toBe('16px'); // rounded-2xl
25
- expect(styles.cursor).toBe('pointer');
26
- });
27
-
28
- test('should have card with border styling', async ({ page }) => {
29
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'light', host }));
30
-
31
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
32
- const card = iframe.locator('.rounded-2xl.border').first();
33
- await expect(card).toBeVisible();
34
-
35
- const styles = await card.evaluate((el) => {
36
- const computed = window.getComputedStyle(el);
37
- return {
38
- borderWidth: computed.borderWidth,
39
- borderStyle: computed.borderStyle,
40
- };
41
- });
42
-
43
- expect(styles.borderWidth).toBe('1px');
44
- expect(styles.borderStyle).toBe('solid');
45
- });
46
-
47
- test('should have interactive buttons', async ({ page }) => {
48
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'light', host }));
49
-
50
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
51
- const visitButton = iframe.locator('button:has-text("Visit")').first();
52
- await expect(visitButton).toBeAttached();
53
-
54
- const styles = await visitButton.evaluate((el) => {
55
- const computed = window.getComputedStyle(el);
56
- return {
57
- cursor: computed.cursor,
58
- };
59
- });
60
-
61
- expect(styles.cursor).toBe('pointer');
62
- });
63
- });
64
-
65
- test.describe('Prod Tools Mode', () => {
66
- test('should show empty state with Run button', async ({ page }) => {
67
- await page.goto(createInspectorUrl({ tool: 'show-carousel', theme: 'dark', host }));
68
-
69
- const emptyState = page.locator('text=Press Run to call the tool');
70
- await expect(emptyState).toBeVisible();
71
-
72
- const runButton = page.locator('button:has-text("Run")');
73
- await expect(runButton).toBeVisible();
74
-
75
- const iframe = page.locator('iframe');
76
- await expect(iframe).not.toBeAttached();
77
- });
78
-
79
- test('should have themed empty state colors in light mode', async ({ page }) => {
80
- await page.goto(createInspectorUrl({ tool: 'show-carousel', theme: 'light', host }));
81
-
82
- const emptyState = page.locator('text=Press Run to call the tool');
83
- await expect(emptyState).toBeVisible();
84
-
85
- const color = await emptyState.evaluate((el) => {
86
- return window.getComputedStyle(el).color;
87
- });
88
-
89
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
90
- expect(r + g + b).toBeLessThan(600);
91
- });
92
-
93
- test('should have themed empty state colors in dark mode', async ({ page }) => {
94
- await page.goto(createInspectorUrl({ tool: 'show-carousel', theme: 'dark', host }));
95
-
96
- const emptyState = page.locator('text=Press Run to call the tool');
97
- await expect(emptyState).toBeVisible();
98
-
99
- const color = await emptyState.evaluate((el) => {
100
- return window.getComputedStyle(el).color;
101
- });
102
-
103
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
104
- expect(r + g + b).toBeGreaterThan(200);
105
- });
106
- });
107
-
108
- test.describe('Prod Resources Mode', () => {
109
- test('should activate without errors', async ({ page }) => {
110
- await page.goto(
111
- createInspectorUrl({
112
- simulation: 'show-carousel',
113
- theme: 'dark',
114
- host,
115
- prodResources: true,
116
- })
117
- );
118
-
119
- const root = page.locator('#root');
120
- await expect(root).not.toBeEmpty();
121
- });
122
- });
123
-
124
- test.describe('Fullscreen Mode', () => {
125
- test('should render correctly in fullscreen displayMode', async ({ page }) => {
126
- await page.goto(
127
- createInspectorUrl({
128
- simulation: 'show-carousel',
129
- theme: 'light',
130
- displayMode: 'fullscreen',
131
- host,
132
- })
133
- );
134
-
135
- await page.waitForLoadState('networkidle');
136
- const root = page.locator('#root');
137
- await expect(root).not.toBeEmpty();
138
- });
139
-
140
- test('should show detail view with place info in fullscreen', async ({ page }) => {
141
- await page.goto(
142
- createInspectorUrl({
143
- simulation: 'show-carousel',
144
- theme: 'light',
145
- displayMode: 'fullscreen',
146
- host,
147
- })
148
- );
149
-
150
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
151
-
152
- // Click the first card to open fullscreen detail
153
- const card = iframe.locator('.rounded-2xl').first();
154
- await expect(card).toBeVisible();
155
- await card.click();
156
-
157
- // Should show detail view with place name, description, and detail fields
158
- await expect(iframe.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({
159
- timeout: 5000,
160
- });
161
- await expect(iframe.locator('text=Highlights')).toBeVisible();
162
- await expect(iframe.locator('text=Tips')).toBeVisible();
163
- });
164
-
165
- test('should show detail view when Learn More is clicked', async ({ page }) => {
166
- await page.goto(
167
- createInspectorUrl({
168
- simulation: 'show-carousel',
169
- theme: 'light',
170
- displayMode: 'fullscreen',
171
- host,
172
- })
173
- );
174
-
175
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
176
-
177
- // Click "Learn More" on the first card
178
- const learnMore = iframe.locator('button:has-text("Learn More")').first();
179
- await expect(learnMore).toBeVisible();
180
- await learnMore.click();
181
-
182
- // Should show detail view
183
- await expect(iframe.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({
184
- timeout: 5000,
185
- });
186
- await expect(iframe.locator('text=Address')).toBeVisible();
187
- });
188
-
189
- test('should not have a back button in detail view', async ({ page }) => {
190
- await page.goto(
191
- createInspectorUrl({
192
- simulation: 'show-carousel',
193
- theme: 'light',
194
- displayMode: 'fullscreen',
195
- host,
196
- })
197
- );
198
-
199
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
200
-
201
- // Click the first card to open detail
202
- const card = iframe.locator('.rounded-2xl').first();
203
- await expect(card).toBeVisible();
204
- await card.click();
205
-
206
- await expect(iframe.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({
207
- timeout: 5000,
208
- });
209
-
210
- // Back button should not exist
211
- const backButton = iframe.locator('button[aria-label="Back to carousel"]');
212
- await expect(backButton).not.toBeAttached();
213
- });
214
-
215
- test('should center the hero image without stretching', async ({ page }) => {
216
- await page.goto(
217
- createInspectorUrl({
218
- simulation: 'show-carousel',
219
- theme: 'light',
220
- displayMode: 'fullscreen',
221
- host,
222
- })
223
- );
224
-
225
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
226
-
227
- // Click the first card to open detail
228
- const card = iframe.locator('.rounded-2xl').first();
229
- await expect(card).toBeVisible();
230
- await card.click();
231
-
232
- await expect(iframe.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({
233
- timeout: 5000,
234
- });
235
-
236
- // Check the image container is centered
237
- const imageContainer = iframe.locator('img').first().locator('..');
238
- const styles = await imageContainer.evaluate((el) => {
239
- const computed = window.getComputedStyle(el);
240
- return {
241
- justifyContent: computed.justifyContent,
242
- };
243
- });
244
-
245
- expect(styles.justifyContent).toBe('center');
246
- });
247
- });
248
-
249
- test.describe('Dark Mode', () => {
250
- test('should render carousel cards with correct styles', async ({ page }) => {
251
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
252
-
253
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
254
- const card = iframe.locator('.rounded-2xl').first();
255
- await expect(card).toBeVisible();
256
-
257
- const styles = await card.evaluate((el) => {
258
- const computed = window.getComputedStyle(el);
259
- return {
260
- borderRadius: computed.borderRadius,
261
- cursor: computed.cursor,
262
- };
263
- });
264
-
265
- expect(styles.borderRadius).toBe('16px'); // rounded-2xl
266
- expect(styles.cursor).toBe('pointer');
267
- });
268
-
269
- test('should have appropriate styling for dark mode', async ({ page }) => {
270
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
271
-
272
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
273
- // Select card by its border + rounded combo
274
- const card = iframe.locator('.rounded-2xl.border').first();
275
- await expect(card).toBeVisible();
276
-
277
- const styles = await card.evaluate((el) => {
278
- const computed = window.getComputedStyle(el);
279
- return {
280
- borderWidth: computed.borderWidth,
281
- borderStyle: computed.borderStyle,
282
- };
283
- });
284
-
285
- expect(styles.borderWidth).toBe('1px');
286
- expect(styles.borderStyle).toBe('solid');
287
- });
288
-
289
- test('should load without console errors', async ({ page }) => {
290
- const errors: string[] = [];
291
- page.on('console', (msg) => {
292
- if (msg.type() === 'error') {
293
- errors.push(msg.text());
294
- }
295
- });
296
-
297
- await page.goto(createInspectorUrl({ simulation: 'show-carousel', theme: 'dark', host }));
298
-
299
- // Wait for iframe content to render
300
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
301
- await expect(iframe.locator('.rounded-2xl').first()).toBeVisible();
302
-
303
- // Filter out expected iframe/MCP handshake errors
304
- const unexpectedErrors = errors.filter(
305
- (e) =>
306
- !e.includes('[IframeResource]') &&
307
- !e.includes('mcp') &&
308
- !e.includes('PostMessage') &&
309
- !e.includes('connect')
310
- );
311
- expect(unexpectedErrors).toHaveLength(0);
312
- });
313
- });
3
+ test('should render carousel cards with correct styles', async ({ mcp }) => {
4
+ const result = await mcp.callTool('show-carousel');
5
+ const app = result.app();
6
+
7
+ const card = app.locator('.rounded-2xl').first();
8
+ await expect(card).toBeVisible();
9
+
10
+ const styles = await card.evaluate((el) => {
11
+ const computed = window.getComputedStyle(el);
12
+ return { borderRadius: computed.borderRadius, cursor: computed.cursor };
13
+ });
14
+ expect(styles.borderRadius).toBe('16px');
15
+ expect(styles.cursor).toBe('pointer');
16
+ });
17
+
18
+ test('should have card with border styling', async ({ mcp }) => {
19
+ const result = await mcp.callTool('show-carousel');
20
+ const app = result.app();
21
+
22
+ const card = app.locator('.rounded-2xl.border').first();
23
+ await expect(card).toBeVisible();
24
+
25
+ const styles = await card.evaluate((el) => {
26
+ const computed = window.getComputedStyle(el);
27
+ return { borderWidth: computed.borderWidth, borderStyle: computed.borderStyle };
314
28
  });
315
- }
29
+ expect(styles.borderWidth).toBe('1px');
30
+ expect(styles.borderStyle).toBe('solid');
31
+ });
32
+
33
+ test('should have interactive buttons', async ({ mcp }) => {
34
+ const result = await mcp.callTool('show-carousel');
35
+ const app = result.app();
36
+
37
+ const visitButton = app.locator('button:has-text("Visit")').first();
38
+ await expect(visitButton).toBeAttached();
39
+
40
+ const styles = await visitButton.evaluate((el) => ({
41
+ cursor: window.getComputedStyle(el).cursor,
42
+ }));
43
+ expect(styles.cursor).toBe('pointer');
44
+ });
45
+
46
+ test('should show empty state with Run button in prod tools mode', async ({ mcp }) => {
47
+ await mcp.openTool('show-carousel', { 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-carousel', { 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-carousel', { 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-carousel', {}, { 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
+ await mcp.callTool('show-carousel', {}, { displayMode: 'fullscreen' });
84
+ await mcp.page.waitForLoadState('networkidle');
85
+ const root = mcp.page.locator('#root');
86
+ await expect(root).not.toBeEmpty();
87
+ });
88
+
89
+ test('should show detail view with place info in fullscreen', async ({ mcp }) => {
90
+ const result = await mcp.callTool('show-carousel', {}, { displayMode: 'fullscreen' });
91
+ const app = result.app();
92
+
93
+ const card = app.locator('.rounded-2xl').first();
94
+ await expect(card).toBeVisible();
95
+ await card.click();
96
+
97
+ await expect(app.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({ timeout: 5000 });
98
+ await expect(app.locator('text=Highlights')).toBeVisible();
99
+ await expect(app.locator('text=Tips')).toBeVisible();
100
+ });
101
+
102
+ test('should show detail view when Learn More is clicked', async ({ mcp }) => {
103
+ const result = await mcp.callTool('show-carousel', {}, { displayMode: 'fullscreen' });
104
+ const app = result.app();
105
+
106
+ const learnMore = app.locator('button:has-text("Learn More")').first();
107
+ await expect(learnMore).toBeVisible();
108
+ await learnMore.click();
109
+
110
+ await expect(app.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({ timeout: 5000 });
111
+ await expect(app.locator('text=Address')).toBeVisible();
112
+ });
113
+
114
+ test('should not have a back button in detail view', async ({ mcp }) => {
115
+ const result = await mcp.callTool('show-carousel', {}, { displayMode: 'fullscreen' });
116
+ const app = result.app();
117
+
118
+ const card = app.locator('.rounded-2xl').first();
119
+ await expect(card).toBeVisible();
120
+ await card.click();
121
+
122
+ await expect(app.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({ timeout: 5000 });
123
+ const backButton = app.locator('button[aria-label="Back to carousel"]');
124
+ await expect(backButton).not.toBeAttached();
125
+ });
126
+
127
+ test('should center the hero image without stretching', async ({ mcp }) => {
128
+ const result = await mcp.callTool('show-carousel', {}, { displayMode: 'fullscreen' });
129
+ const app = result.app();
130
+
131
+ const card = app.locator('.rounded-2xl').first();
132
+ await expect(card).toBeVisible();
133
+ await card.click();
134
+
135
+ await expect(app.locator('h1:has-text("Lady Bird Lake")')).toBeVisible({ timeout: 5000 });
136
+ const imageContainer = app.locator('img').first().locator('..');
137
+ const styles = await imageContainer.evaluate((el) => ({
138
+ justifyContent: window.getComputedStyle(el).justifyContent,
139
+ }));
140
+ expect(styles.justifyContent).toBe('center');
141
+ });
142
+
143
+ test('should render carousel in dark mode with correct styles', async ({ mcp }) => {
144
+ const result = await mcp.callTool('show-carousel', {}, { theme: 'dark' });
145
+ const app = result.app();
146
+
147
+ const card = app.locator('.rounded-2xl').first();
148
+ await expect(card).toBeVisible();
149
+
150
+ const styles = await card.evaluate((el) => ({
151
+ borderRadius: window.getComputedStyle(el).borderRadius,
152
+ cursor: window.getComputedStyle(el).cursor,
153
+ }));
154
+ expect(styles.borderRadius).toBe('16px');
155
+ expect(styles.cursor).toBe('pointer');
156
+ });
157
+
158
+ test('should have appropriate dark mode styling', async ({ mcp }) => {
159
+ const result = await mcp.callTool('show-carousel', {}, { theme: 'dark' });
160
+ const app = result.app();
161
+
162
+ const card = app.locator('.rounded-2xl.border').first();
163
+ await expect(card).toBeVisible();
164
+
165
+ const styles = await card.evaluate((el) => ({
166
+ borderWidth: window.getComputedStyle(el).borderWidth,
167
+ borderStyle: window.getComputedStyle(el).borderStyle,
168
+ }));
169
+ expect(styles.borderWidth).toBe('1px');
170
+ expect(styles.borderStyle).toBe('solid');
171
+ });
172
+
173
+ test('should load without console errors in dark mode', async ({ mcp }) => {
174
+ const errors: string[] = [];
175
+ mcp.page.on('console', (msg) => {
176
+ if (msg.type() === 'error') errors.push(msg.text());
177
+ });
178
+
179
+ const result = await mcp.callTool('show-carousel', {}, { theme: 'dark' });
180
+ const app = result.app();
181
+ await expect(app.locator('.rounded-2xl').first()).toBeVisible();
182
+
183
+ const unexpectedErrors = errors.filter(
184
+ (e) =>
185
+ !e.includes('[IframeResource]') &&
186
+ !e.includes('mcp') &&
187
+ !e.includes('PostMessage') &&
188
+ !e.includes('connect')
189
+ );
190
+ expect(unexpectedErrors).toHaveLength(0);
191
+ });