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,301 +1,178 @@
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(`Map Resource [${host}]`, () => {
8
- test.describe('Light Mode', () => {
9
- test('should render map container with correct styles', async ({ page }) => {
10
- await page.goto(createInspectorUrl({ simulation: 'show-map', theme: 'light', host }));
11
-
12
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
13
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
14
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
15
-
16
- const styles = await mapContainer.evaluate((el) => {
17
- const computed = window.getComputedStyle(el);
18
- return {
19
- overflow: computed.overflow,
20
- };
21
- });
22
-
23
- expect(styles.overflow).toBe('hidden');
24
- });
25
-
26
- test('should have rounded border in inline mode', async ({ page }) => {
27
- await page.goto(
28
- createInspectorUrl({
29
- simulation: 'show-map',
30
- theme: 'light',
31
- displayMode: 'inline',
32
- host,
33
- })
34
- );
35
-
36
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
37
- const innerContainer = iframe.locator('.border.rounded-2xl').first();
38
- await expect(innerContainer).toBeVisible({ timeout: 10000 });
39
-
40
- const styles = await innerContainer.evaluate((el) => {
41
- const computed = window.getComputedStyle(el);
42
- return {
43
- borderRadius: computed.borderRadius,
44
- borderWidth: computed.borderWidth,
45
- };
46
- });
47
-
48
- // Should have rounded corners (rounded-2xl = 16px)
49
- expect(parseInt(styles.borderRadius)).toBeGreaterThanOrEqual(16);
50
- });
51
-
52
- test('should have fullscreen expand button in inline mode', async ({ page }) => {
53
- await page.goto(
54
- createInspectorUrl({
55
- simulation: 'show-map',
56
- theme: 'light',
57
- displayMode: 'inline',
58
- host,
59
- })
60
- );
61
-
62
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
63
- const expandButton = iframe.locator('button[aria-label="Enter fullscreen"]');
64
- await expect(expandButton).toBeVisible({ timeout: 10000 });
65
-
66
- const styles = await expandButton.evaluate((el) => {
67
- const computed = window.getComputedStyle(el);
68
- return {
69
- cursor: computed.cursor,
70
- position: computed.position,
71
- };
72
- });
73
-
74
- expect(styles.cursor).toBe('pointer');
75
- expect(styles.position).toBe('absolute');
76
- });
77
-
78
- test('should load without console errors', async ({ page }) => {
79
- const errors: string[] = [];
80
- page.on('console', (msg) => {
81
- if (msg.type() === 'error') {
82
- errors.push(msg.text());
83
- }
84
- });
85
-
86
- await page.goto(createInspectorUrl({ simulation: 'show-map', theme: 'light', host }));
87
-
88
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
89
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
90
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
91
-
92
- // Filter out expected iframe/MCP handshake errors
93
- const unexpectedErrors = errors.filter(
94
- (e) =>
95
- !e.includes('[IframeResource]') &&
96
- !e.includes('mcp') &&
97
- !e.includes('PostMessage') &&
98
- !e.includes('connect')
99
- );
100
- expect(unexpectedErrors).toHaveLength(0);
101
- });
102
- });
103
-
104
- test.describe('Prod Tools Mode', () => {
105
- test('should show empty state with Run button', async ({ page }) => {
106
- await page.goto(createInspectorUrl({ tool: 'show-map', theme: 'dark', host }));
107
-
108
- const emptyState = page.locator('text=Press Run to call the tool');
109
- await expect(emptyState).toBeVisible();
110
-
111
- const runButton = page.locator('button:has-text("Run")');
112
- await expect(runButton).toBeVisible();
113
-
114
- const iframe = page.locator('iframe');
115
- await expect(iframe).not.toBeAttached();
116
- });
117
-
118
- test('should have themed empty state colors in light mode', async ({ page }) => {
119
- await page.goto(createInspectorUrl({ tool: 'show-map', theme: 'light', host }));
120
-
121
- const emptyState = page.locator('text=Press Run to call the tool');
122
- await expect(emptyState).toBeVisible();
123
-
124
- const color = await emptyState.evaluate((el) => {
125
- return window.getComputedStyle(el).color;
126
- });
127
-
128
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
129
- expect(r + g + b).toBeLessThan(600);
130
- });
131
-
132
- test('should have themed empty state colors in dark mode', async ({ page }) => {
133
- await page.goto(createInspectorUrl({ tool: 'show-map', theme: 'dark', host }));
134
-
135
- const emptyState = page.locator('text=Press Run to call the tool');
136
- await expect(emptyState).toBeVisible();
137
-
138
- const color = await emptyState.evaluate((el) => {
139
- return window.getComputedStyle(el).color;
140
- });
141
-
142
- const [r, g, b] = color.match(/\d+/g)!.map(Number);
143
- expect(r + g + b).toBeGreaterThan(200);
144
- });
145
- });
146
-
147
- test.describe('Prod Resources Mode', () => {
148
- test('should activate without errors', async ({ page }) => {
149
- await page.goto(
150
- createInspectorUrl({ simulation: 'show-map', theme: 'dark', host, prodResources: true })
151
- );
152
-
153
- const root = page.locator('#root');
154
- await expect(root).not.toBeEmpty();
155
- });
156
- });
157
-
158
- test.describe('Dark Mode', () => {
159
- test('should render map container with correct styles', async ({ page }) => {
160
- await page.goto(createInspectorUrl({ simulation: 'show-map', theme: 'dark', host }));
161
-
162
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
163
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
164
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
165
- });
166
-
167
- test('should have appropriate border color for dark mode', async ({ page }) => {
168
- await page.goto(
169
- createInspectorUrl({
170
- simulation: 'show-map',
171
- theme: 'dark',
172
- displayMode: 'inline',
173
- host,
174
- })
175
- );
176
-
177
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
178
- const innerContainer = iframe.locator('.border.rounded-2xl').first();
179
- await expect(innerContainer).toBeVisible({ timeout: 10000 });
180
-
181
- const styles = await innerContainer.evaluate((el) => {
182
- const computed = window.getComputedStyle(el);
183
- return {
184
- borderColor: computed.borderColor,
185
- };
186
- });
187
-
188
- // Border color should be set
189
- expect(styles.borderColor).toBeTruthy();
190
- });
191
-
192
- test('should load without console errors', async ({ page }) => {
193
- const errors: string[] = [];
194
- page.on('console', (msg) => {
195
- if (msg.type() === 'error') {
196
- errors.push(msg.text());
197
- }
198
- });
199
-
200
- await page.goto(createInspectorUrl({ simulation: 'show-map', theme: 'dark', host }));
201
-
202
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
203
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
204
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
205
-
206
- // Filter out expected iframe/MCP handshake errors
207
- const unexpectedErrors = errors.filter(
208
- (e) =>
209
- !e.includes('[IframeResource]') &&
210
- !e.includes('mcp') &&
211
- !e.includes('PostMessage') &&
212
- !e.includes('connect')
213
- );
214
- expect(unexpectedErrors).toHaveLength(0);
215
- });
216
- });
217
-
218
- test.describe('Fullscreen Mode', () => {
219
- test('should not have rounded border in fullscreen mode', async ({ page }) => {
220
- await page.goto(
221
- createInspectorUrl({
222
- simulation: 'show-map',
223
- theme: 'light',
224
- displayMode: 'fullscreen',
225
- host,
226
- })
227
- );
228
-
229
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
230
- const innerContainer = iframe.locator('.rounded-none.border-0').first();
231
- await expect(innerContainer).toBeVisible({ timeout: 10000 });
232
-
233
- const styles = await innerContainer.evaluate((el) => {
234
- const computed = window.getComputedStyle(el);
235
- return {
236
- borderRadius: computed.borderRadius,
237
- };
238
- });
239
-
240
- expect(styles.borderRadius).toBe('0px');
241
- });
242
-
243
- test('should not show fullscreen button when already in fullscreen', async ({ page }) => {
244
- await page.goto(
245
- createInspectorUrl({
246
- simulation: 'show-map',
247
- theme: 'light',
248
- displayMode: 'fullscreen',
249
- host,
250
- })
251
- );
252
-
253
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
254
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
255
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
256
-
257
- // The expand button should not be visible in fullscreen mode
258
- const expandButton = iframe.locator('button[aria-label="Enter fullscreen"]');
259
- await expect(expandButton).not.toBeVisible();
260
- });
261
-
262
- test('should show place list sidebar in fullscreen', async ({ page }) => {
263
- await page.goto(
264
- createInspectorUrl({
265
- simulation: 'show-map',
266
- theme: 'dark',
267
- displayMode: 'fullscreen',
268
- host,
269
- })
270
- );
271
-
272
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
273
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
274
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
275
- });
276
-
277
- test('should show suggestion chips in fullscreen on desktop', async ({ page }) => {
278
- // Set viewport to desktop size
279
- await page.setViewportSize({ width: 1024, height: 768 });
280
- await page.goto(
281
- createInspectorUrl({
282
- simulation: 'show-map',
283
- theme: 'light',
284
- displayMode: 'fullscreen',
285
- host,
286
- })
287
- );
288
-
289
- const iframe = page.frameLocator('iframe').frameLocator('iframe');
290
- const mapContainer = iframe.locator('.antialiased.w-full.overflow-hidden').first();
291
- await expect(mapContainer).toBeVisible({ timeout: 10000 });
292
-
293
- // Suggestion chips should be present (contains "Open now", "Top rated", etc.)
294
- // Use toBeAttached — Playwright's toBeVisible can false-negative inside
295
- // double-iframes with overflow:hidden ancestors.
296
- const openNowChip = iframe.locator('button:has-text("Open now")');
297
- await expect(openNowChip).toBeAttached({ timeout: 5000 });
298
- });
299
- });
1
+ import { test, expect } from 'sunpeak/test';
2
+
3
+ test('should render map container with correct styles', async ({ mcp }) => {
4
+ const result = await mcp.callTool('show-map');
5
+ const app = result.app();
6
+
7
+ const mapContainer = app.locator('.antialiased.w-full.overflow-hidden').first();
8
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
9
+
10
+ const styles = await mapContainer.evaluate((el) => ({
11
+ overflow: window.getComputedStyle(el).overflow,
12
+ }));
13
+ expect(styles.overflow).toBe('hidden');
14
+ });
15
+
16
+ test('should have rounded border in inline mode', async ({ mcp }) => {
17
+ const result = await mcp.callTool('show-map', {}, { displayMode: 'inline' });
18
+ const app = result.app();
19
+
20
+ const innerContainer = app.locator('.border.rounded-2xl').first();
21
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
22
+
23
+ const styles = await innerContainer.evaluate((el) => ({
24
+ borderRadius: window.getComputedStyle(el).borderRadius,
25
+ }));
26
+ expect(parseInt(styles.borderRadius)).toBeGreaterThanOrEqual(16);
27
+ });
28
+
29
+ test('should have fullscreen expand button in inline mode', async ({ mcp }) => {
30
+ const result = await mcp.callTool('show-map', {}, { displayMode: 'inline' });
31
+ const app = result.app();
32
+
33
+ const expandButton = app.locator('button[aria-label="Enter fullscreen"]');
34
+ await expect(expandButton).toBeVisible({ timeout: 10000 });
35
+
36
+ const styles = await expandButton.evaluate((el) => ({
37
+ cursor: window.getComputedStyle(el).cursor,
38
+ position: window.getComputedStyle(el).position,
39
+ }));
40
+ expect(styles.cursor).toBe('pointer');
41
+ expect(styles.position).toBe('absolute');
42
+ });
43
+
44
+ test('should load without console errors in light mode', async ({ mcp }) => {
45
+ const errors: string[] = [];
46
+ mcp.page.on('console', (msg) => {
47
+ if (msg.type() === 'error') errors.push(msg.text());
300
48
  });
301
- }
49
+
50
+ const result = await mcp.callTool('show-map');
51
+ const app = result.app();
52
+ await expect(app.locator('.antialiased.w-full.overflow-hidden').first()).toBeVisible({
53
+ timeout: 10000,
54
+ });
55
+
56
+ const unexpectedErrors = errors.filter(
57
+ (e) =>
58
+ !e.includes('[IframeResource]') &&
59
+ !e.includes('mcp') &&
60
+ !e.includes('PostMessage') &&
61
+ !e.includes('connect')
62
+ );
63
+ expect(unexpectedErrors).toHaveLength(0);
64
+ });
65
+
66
+ test('should show empty state with Run button in prod tools mode', async ({ mcp }) => {
67
+ await mcp.openTool('show-map', { theme: 'dark' });
68
+
69
+ await expect(mcp.page.locator('text=Press Run to call the tool')).toBeVisible();
70
+ await expect(mcp.page.locator('button:has-text("Run")')).toBeVisible();
71
+ await expect(mcp.page.locator('iframe')).not.toBeAttached();
72
+ });
73
+
74
+ test('should have themed empty state colors in light mode', async ({ mcp }) => {
75
+ await mcp.openTool('show-map', { theme: 'light' });
76
+
77
+ const emptyState = mcp.page.locator('text=Press Run to call the tool');
78
+ await expect(emptyState).toBeVisible();
79
+
80
+ const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
81
+ const [r, g, b] = color.match(/\d+/g)!.map(Number);
82
+ expect(r + g + b).toBeLessThan(600);
83
+ });
84
+
85
+ test('should have themed empty state colors in dark mode', async ({ mcp }) => {
86
+ await mcp.openTool('show-map', { theme: 'dark' });
87
+
88
+ const emptyState = mcp.page.locator('text=Press Run to call the tool');
89
+ await expect(emptyState).toBeVisible();
90
+
91
+ const color = await emptyState.evaluate((el) => window.getComputedStyle(el).color);
92
+ const [r, g, b] = color.match(/\d+/g)!.map(Number);
93
+ expect(r + g + b).toBeGreaterThan(200);
94
+ });
95
+
96
+ test('should activate prod resources mode without errors', async ({ mcp }) => {
97
+ await mcp.callTool('show-map', {}, { theme: 'dark', prodResources: true });
98
+ const root = mcp.page.locator('#root');
99
+ await expect(root).not.toBeEmpty();
100
+ });
101
+
102
+ test('should render map in dark mode', async ({ mcp }) => {
103
+ const result = await mcp.callTool('show-map', {}, { theme: 'dark' });
104
+ const app = result.app();
105
+ await expect(app.locator('.antialiased.w-full.overflow-hidden').first()).toBeVisible({
106
+ timeout: 10000,
107
+ });
108
+ });
109
+
110
+ test('should have appropriate border color in dark mode', async ({ mcp }) => {
111
+ const result = await mcp.callTool('show-map', {}, { theme: 'dark', displayMode: 'inline' });
112
+ const app = result.app();
113
+
114
+ const innerContainer = app.locator('.border.rounded-2xl').first();
115
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
116
+
117
+ const styles = await innerContainer.evaluate((el) => ({
118
+ borderColor: window.getComputedStyle(el).borderColor,
119
+ }));
120
+ expect(styles.borderColor).toBeTruthy();
121
+ });
122
+
123
+ test('should load without console errors in dark mode', async ({ mcp }) => {
124
+ const errors: string[] = [];
125
+ mcp.page.on('console', (msg) => {
126
+ if (msg.type() === 'error') errors.push(msg.text());
127
+ });
128
+
129
+ const result = await mcp.callTool('show-map', {}, { theme: 'dark' });
130
+ const app = result.app();
131
+ await expect(app.locator('.antialiased.w-full.overflow-hidden').first()).toBeVisible({
132
+ timeout: 10000,
133
+ });
134
+
135
+ const unexpectedErrors = errors.filter(
136
+ (e) =>
137
+ !e.includes('[IframeResource]') &&
138
+ !e.includes('mcp') &&
139
+ !e.includes('PostMessage') &&
140
+ !e.includes('connect')
141
+ );
142
+ expect(unexpectedErrors).toHaveLength(0);
143
+ });
144
+
145
+ test('should not have rounded border in fullscreen mode', async ({ mcp }) => {
146
+ const result = await mcp.callTool('show-map', {}, { displayMode: 'fullscreen' });
147
+ const app = result.app();
148
+
149
+ const innerContainer = app.locator('.rounded-none.border-0').first();
150
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
151
+
152
+ const styles = await innerContainer.evaluate((el) => ({
153
+ borderRadius: window.getComputedStyle(el).borderRadius,
154
+ }));
155
+ expect(styles.borderRadius).toBe('0px');
156
+ });
157
+
158
+ test('should not show fullscreen button in fullscreen mode', async ({ mcp }) => {
159
+ const result = await mcp.callTool('show-map', {}, { displayMode: 'fullscreen' });
160
+ const app = result.app();
161
+
162
+ await expect(app.locator('.antialiased.w-full.overflow-hidden').first()).toBeVisible({
163
+ timeout: 10000,
164
+ });
165
+ await expect(app.locator('button[aria-label="Enter fullscreen"]')).not.toBeVisible();
166
+ });
167
+
168
+ test('should show suggestion chips in fullscreen on desktop', async ({ mcp }) => {
169
+ await mcp.page.setViewportSize({ width: 1024, height: 768 });
170
+
171
+ const result = await mcp.callTool('show-map', {}, { displayMode: 'fullscreen' });
172
+ const app = result.app();
173
+
174
+ await expect(app.locator('.antialiased.w-full.overflow-hidden').first()).toBeVisible({
175
+ timeout: 10000,
176
+ });
177
+ await expect(app.locator('button:has-text("Open now")')).toBeAttached({ timeout: 5000 });
178
+ });