sunpeak 0.8.6 → 0.9.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 (77) hide show
  1. package/bin/commands/build.mjs +3 -3
  2. package/bin/commands/pull.mjs +2 -2
  3. package/bin/sunpeak.js +8 -6
  4. package/dist/chatgpt/chatgpt-simulator.d.ts +1 -2
  5. package/dist/chatgpt/index.cjs +9 -0
  6. package/dist/chatgpt/index.cjs.map +1 -0
  7. package/dist/chatgpt/index.d.ts +3 -1
  8. package/dist/chatgpt/index.js +9 -0
  9. package/dist/chatgpt/index.js.map +1 -0
  10. package/dist/chatgpt/simulator-url.d.ts +127 -0
  11. package/dist/index.cjs +39 -8448
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.js +58 -8466
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/entry.cjs +2 -1
  16. package/dist/mcp/entry.cjs.map +1 -1
  17. package/dist/mcp/entry.js +2 -1
  18. package/dist/mcp/entry.js.map +1 -1
  19. package/dist/mcp/index.cjs +1 -1
  20. package/dist/mcp/index.d.ts +1 -1
  21. package/dist/mcp/index.js +1 -1
  22. package/dist/mcp/server.d.ts +1 -1
  23. package/dist/mcp/types.d.ts +3 -9
  24. package/dist/{server-DVmTC-SF.js → server-310A1k9o.js} +2 -2
  25. package/dist/{server-DVmTC-SF.js.map → server-310A1k9o.js.map} +1 -1
  26. package/dist/{server-B9YgCQdS.cjs → server-CSybLAYo.cjs} +2 -2
  27. package/dist/{server-B9YgCQdS.cjs.map → server-CSybLAYo.cjs.map} +1 -1
  28. package/dist/simulator-url-CG8lAAC3.cjs +8545 -0
  29. package/dist/simulator-url-CG8lAAC3.cjs.map +1 -0
  30. package/dist/simulator-url-CexnaL-e.js +8529 -0
  31. package/dist/simulator-url-CexnaL-e.js.map +1 -0
  32. package/dist/style.css +5872 -5894
  33. package/dist/types/simulation.d.ts +6 -32
  34. package/package.json +7 -3
  35. package/template/.sunpeak/dev.tsx +8 -7
  36. package/template/README.md +5 -5
  37. package/template/dist/albums.js +1 -1
  38. package/template/dist/albums.json +1 -1
  39. package/template/dist/carousel.js +1 -1
  40. package/template/dist/carousel.json +1 -1
  41. package/template/dist/map.js +1 -1
  42. package/template/dist/map.json +1 -1
  43. package/template/dist/review.js +2 -2
  44. package/template/dist/review.json +1 -1
  45. package/template/node_modules/.bin/playwright +21 -0
  46. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
  47. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +4 -4
  48. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +20 -20
  49. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
  50. package/template/node_modules/.vite/deps/_metadata.json +38 -38
  51. package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js → chunk-N6DVYEXK.js} +8 -8
  52. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  53. package/template/package.json +3 -1
  54. package/template/playwright.config.ts +26 -0
  55. package/template/src/resources/index.ts +4 -4
  56. package/template/src/resources/map-resource.test.tsx +95 -0
  57. package/template/src/resources/review-resource.test.tsx +538 -0
  58. package/template/src/resources/review-resource.tsx +6 -1
  59. package/template/src/simulations/albums-show-simulation.json +1 -1
  60. package/template/src/simulations/carousel-show-simulation.json +1 -1
  61. package/template/src/simulations/map-show-simulation.json +1 -1
  62. package/template/src/simulations/review-diff-simulation.json +1 -1
  63. package/template/src/simulations/review-post-simulation.json +1 -1
  64. package/template/src/simulations/review-purchase-simulation.json +1 -1
  65. package/template/test-results/.last-run.json +4 -0
  66. package/template/tests/e2e/albums.spec.ts +120 -0
  67. package/template/tests/e2e/carousel.spec.ts +127 -0
  68. package/template/tests/e2e/map.spec.ts +188 -0
  69. package/template/tests/e2e/review.spec.ts +245 -0
  70. package/template/vitest.config.ts +1 -0
  71. package/template/dist/counter.js +0 -49
  72. package/template/dist/counter.json +0 -15
  73. package/template/src/resources/counter-resource.json +0 -12
  74. package/template/src/resources/counter-resource.test.tsx +0 -116
  75. package/template/src/resources/counter-resource.tsx +0 -101
  76. package/template/src/simulations/counter-show-simulation.json +0 -20
  77. /package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js.map → chunk-N6DVYEXK.js.map} +0 -0
@@ -0,0 +1,120 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { createSimulatorUrl } from 'sunpeak/chatgpt';
3
+
4
+ test.describe('Albums Resource', () => {
5
+ test.describe('Light Mode', () => {
6
+ test('should render album cards with correct styles', async ({ page }) => {
7
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
8
+
9
+ const albumCard = page.locator('button:has-text("Summer Slice")');
10
+ await expect(albumCard).toBeVisible();
11
+
12
+ // Verify album card unique styles
13
+ const styles = await albumCard.evaluate((el) => {
14
+ const computed = window.getComputedStyle(el);
15
+ return {
16
+ cursor: computed.cursor,
17
+ borderRadius: computed.borderRadius,
18
+ };
19
+ });
20
+
21
+ expect(styles.cursor).toBe('pointer');
22
+ expect(styles.borderRadius).toBe('12px'); // rounded-xl
23
+ });
24
+
25
+ test('should have album image with correct aspect ratio', async ({ page }) => {
26
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'light' }));
27
+
28
+ const albumImage = page.locator('button:has-text("Summer Slice") img').first();
29
+ await expect(albumImage).toBeVisible();
30
+
31
+ // Verify aspect-[4/3] container
32
+ const imageContainer = page.locator('button:has-text("Summer Slice") .aspect-\\[4\\/3\\]');
33
+ await expect(imageContainer).toBeVisible();
34
+
35
+ const containerStyles = await imageContainer.evaluate((el) => {
36
+ const computed = window.getComputedStyle(el);
37
+ return {
38
+ borderRadius: computed.borderRadius,
39
+ overflow: computed.overflow,
40
+ };
41
+ });
42
+
43
+ expect(containerStyles.borderRadius).toBe('12px'); // rounded-xl
44
+ expect(containerStyles.overflow).toBe('hidden');
45
+ });
46
+ });
47
+
48
+ test.describe('Dark Mode', () => {
49
+ test('should render album cards with correct styles', async ({ page }) => {
50
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
51
+
52
+ const albumCard = page.locator('button:has-text("Summer Slice")');
53
+ await expect(albumCard).toBeVisible();
54
+
55
+ const styles = await albumCard.evaluate((el) => {
56
+ const computed = window.getComputedStyle(el);
57
+ return {
58
+ cursor: computed.cursor,
59
+ borderRadius: computed.borderRadius,
60
+ };
61
+ });
62
+
63
+ expect(styles.cursor).toBe('pointer');
64
+ expect(styles.borderRadius).toBe('12px'); // rounded-xl
65
+ });
66
+
67
+ test('should have text with appropriate contrast', async ({ page }) => {
68
+ await page.goto(createSimulatorUrl({ simulation: 'albums-show', theme: 'dark' }));
69
+
70
+ const albumTitle = page.locator('button:has-text("Summer Slice") .text-primary').first();
71
+ await expect(albumTitle).toBeVisible();
72
+
73
+ // In dark mode, text-primary should be light colored for contrast
74
+ const titleStyles = await albumTitle.evaluate((el) => {
75
+ const computed = window.getComputedStyle(el);
76
+ return {
77
+ color: computed.color,
78
+ };
79
+ });
80
+
81
+ // Verify the text color exists (should be a light color in dark mode)
82
+ expect(titleStyles.color).toBeTruthy();
83
+ });
84
+ });
85
+
86
+ test.describe('Fullscreen Mode', () => {
87
+ test('should render correctly in fullscreen displayMode', async ({ page }) => {
88
+ await page.goto(
89
+ createSimulatorUrl({ simulation: 'albums-show', theme: 'light', displayMode: 'fullscreen' })
90
+ );
91
+
92
+ // Wait for content to load
93
+ await page.waitForLoadState('networkidle');
94
+
95
+ // The root container should be present
96
+ const root = page.locator('#root');
97
+ await expect(root).not.toBeEmpty();
98
+ });
99
+
100
+ test('should maintain album card styles in fullscreen', async ({ page }) => {
101
+ await page.goto(
102
+ createSimulatorUrl({ simulation: 'albums-show', theme: 'dark', displayMode: 'fullscreen' })
103
+ );
104
+
105
+ const albumCard = page.locator('button:has-text("Summer Slice")');
106
+ await expect(albumCard).toBeVisible();
107
+
108
+ const styles = await albumCard.evaluate((el) => {
109
+ const computed = window.getComputedStyle(el);
110
+ return {
111
+ cursor: computed.cursor,
112
+ borderRadius: computed.borderRadius,
113
+ };
114
+ });
115
+
116
+ expect(styles.cursor).toBe('pointer');
117
+ expect(styles.borderRadius).toBe('12px');
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,127 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { createSimulatorUrl } from 'sunpeak/chatgpt';
3
+
4
+ test.describe('Carousel Resource', () => {
5
+ test.describe('Light Mode', () => {
6
+ test('should render carousel cards with correct styles', async ({ page }) => {
7
+ await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
8
+
9
+ // Wait for carousel to load
10
+ await page.waitForLoadState('networkidle');
11
+
12
+ // Find a card in the carousel
13
+ const card = page.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(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
30
+
31
+ await page.waitForLoadState('networkidle');
32
+
33
+ // Cards have border-subtle styling
34
+ const card = page.locator('.rounded-2xl.border').first();
35
+ await expect(card).toBeVisible();
36
+
37
+ const styles = await card.evaluate((el) => {
38
+ const computed = window.getComputedStyle(el);
39
+ return {
40
+ borderWidth: computed.borderWidth,
41
+ borderStyle: computed.borderStyle,
42
+ };
43
+ });
44
+
45
+ expect(styles.borderWidth).toBe('1px');
46
+ expect(styles.borderStyle).toBe('solid');
47
+ });
48
+
49
+ test('should have interactive buttons', async ({ page }) => {
50
+ await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'light' }));
51
+
52
+ await page.waitForLoadState('networkidle');
53
+
54
+ // Find the Visit button (primary button)
55
+ const visitButton = page.locator('button:has-text("Visit")').first();
56
+ await expect(visitButton).toBeAttached();
57
+
58
+ const styles = await visitButton.evaluate((el) => {
59
+ const computed = window.getComputedStyle(el);
60
+ return {
61
+ cursor: computed.cursor,
62
+ };
63
+ });
64
+
65
+ expect(styles.cursor).toBe('pointer');
66
+ });
67
+ });
68
+
69
+ test.describe('Dark Mode', () => {
70
+ test('should render carousel cards with correct styles', async ({ page }) => {
71
+ await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
72
+
73
+ await page.waitForLoadState('networkidle');
74
+
75
+ const card = page.locator('.rounded-2xl').first();
76
+ await expect(card).toBeVisible();
77
+
78
+ const styles = await card.evaluate((el) => {
79
+ const computed = window.getComputedStyle(el);
80
+ return {
81
+ borderRadius: computed.borderRadius,
82
+ cursor: computed.cursor,
83
+ };
84
+ });
85
+
86
+ expect(styles.borderRadius).toBe('16px'); // rounded-2xl
87
+ expect(styles.cursor).toBe('pointer');
88
+ });
89
+
90
+ test('should have appropriate background color for dark mode', async ({ page }) => {
91
+ await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
92
+
93
+ await page.waitForLoadState('networkidle');
94
+
95
+ // Verify the card has a background color set
96
+ const card = page.locator('.rounded-2xl.bg-surface').first();
97
+ await expect(card).toBeVisible();
98
+
99
+ const styles = await card.evaluate((el) => {
100
+ const computed = window.getComputedStyle(el);
101
+ return {
102
+ backgroundColor: computed.backgroundColor,
103
+ };
104
+ });
105
+
106
+ // Background color should be set (not transparent)
107
+ expect(styles.backgroundColor).toBeTruthy();
108
+ expect(styles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
109
+ });
110
+
111
+ test('should load without console errors', async ({ page }) => {
112
+ const errors: string[] = [];
113
+ page.on('console', (msg) => {
114
+ if (msg.type() === 'error') {
115
+ errors.push(msg.text());
116
+ }
117
+ });
118
+
119
+ await page.goto(createSimulatorUrl({ simulation: 'carousel-show', theme: 'dark' }));
120
+ await page.waitForLoadState('networkidle');
121
+
122
+ expect(errors).toHaveLength(0);
123
+ });
124
+ });
125
+
126
+ // Note: No fullscreen test for carousel as per requirements
127
+ });
@@ -0,0 +1,188 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { createSimulatorUrl } from 'sunpeak/chatgpt';
3
+
4
+ test.describe('Map Resource', () => {
5
+ test.describe('Light Mode', () => {
6
+ test('should render map container with correct styles', async ({ page }) => {
7
+ await page.goto(createSimulatorUrl({ simulation: 'map-show', theme: 'light' }));
8
+
9
+ // Wait for the map component to render
10
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
11
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
12
+
13
+ const styles = await mapContainer.evaluate((el) => {
14
+ const computed = window.getComputedStyle(el);
15
+ return {
16
+ overflow: computed.overflow,
17
+ };
18
+ });
19
+
20
+ expect(styles.overflow).toBe('hidden');
21
+ });
22
+
23
+ test('should have rounded border in inline mode', async ({ page }) => {
24
+ await page.goto(
25
+ createSimulatorUrl({ simulation: 'map-show', theme: 'light', displayMode: 'inline' })
26
+ );
27
+
28
+ // Wait for the inner container with rounded borders
29
+ const innerContainer = page.locator('.border.rounded-2xl').first();
30
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
31
+
32
+ const styles = await innerContainer.evaluate((el) => {
33
+ const computed = window.getComputedStyle(el);
34
+ return {
35
+ borderRadius: computed.borderRadius,
36
+ borderWidth: computed.borderWidth,
37
+ };
38
+ });
39
+
40
+ // Should have rounded corners (rounded-2xl = 16px)
41
+ expect(parseInt(styles.borderRadius)).toBeGreaterThanOrEqual(16);
42
+ });
43
+
44
+ test('should have fullscreen expand button in inline mode', async ({ page }) => {
45
+ await page.goto(
46
+ createSimulatorUrl({ simulation: 'map-show', theme: 'light', displayMode: 'inline' })
47
+ );
48
+
49
+ // Wait for the expand button
50
+ const expandButton = page.locator('button[aria-label="Enter fullscreen"]');
51
+ await expect(expandButton).toBeVisible({ timeout: 10000 });
52
+
53
+ const styles = await expandButton.evaluate((el) => {
54
+ const computed = window.getComputedStyle(el);
55
+ return {
56
+ cursor: computed.cursor,
57
+ position: computed.position,
58
+ };
59
+ });
60
+
61
+ expect(styles.cursor).toBe('pointer');
62
+ expect(styles.position).toBe('absolute');
63
+ });
64
+
65
+ test('should load without console errors', async ({ page }) => {
66
+ const errors: string[] = [];
67
+ page.on('console', (msg) => {
68
+ if (msg.type() === 'error') {
69
+ errors.push(msg.text());
70
+ }
71
+ });
72
+
73
+ await page.goto(createSimulatorUrl({ simulation: 'map-show', theme: 'light' }));
74
+
75
+ // Wait for map to render
76
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
77
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
78
+
79
+ expect(errors).toHaveLength(0);
80
+ });
81
+ });
82
+
83
+ test.describe('Dark Mode', () => {
84
+ test('should render map container with correct styles', async ({ page }) => {
85
+ await page.goto(createSimulatorUrl({ simulation: 'map-show', theme: 'dark' }));
86
+
87
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
88
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
89
+ });
90
+
91
+ test('should have appropriate border color for dark mode', async ({ page }) => {
92
+ await page.goto(
93
+ createSimulatorUrl({ simulation: 'map-show', theme: 'dark', displayMode: 'inline' })
94
+ );
95
+
96
+ // In dark mode, the border uses dark:border-white/10
97
+ const innerContainer = page.locator('.border.rounded-2xl').first();
98
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
99
+
100
+ const styles = await innerContainer.evaluate((el) => {
101
+ const computed = window.getComputedStyle(el);
102
+ return {
103
+ borderColor: computed.borderColor,
104
+ };
105
+ });
106
+
107
+ // Border color should be set
108
+ expect(styles.borderColor).toBeTruthy();
109
+ });
110
+
111
+ test('should load without console errors', async ({ page }) => {
112
+ const errors: string[] = [];
113
+ page.on('console', (msg) => {
114
+ if (msg.type() === 'error') {
115
+ errors.push(msg.text());
116
+ }
117
+ });
118
+
119
+ await page.goto(createSimulatorUrl({ simulation: 'map-show', theme: 'dark' }));
120
+
121
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
122
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
123
+
124
+ expect(errors).toHaveLength(0);
125
+ });
126
+ });
127
+
128
+ test.describe('Fullscreen Mode', () => {
129
+ test('should not have rounded border in fullscreen mode', async ({ page }) => {
130
+ await page.goto(
131
+ createSimulatorUrl({ simulation: 'map-show', theme: 'light', displayMode: 'fullscreen' })
132
+ );
133
+
134
+ // In fullscreen, the container uses rounded-none and border-0
135
+ const innerContainer = page.locator('.rounded-none.border-0').first();
136
+ await expect(innerContainer).toBeVisible({ timeout: 10000 });
137
+
138
+ const styles = await innerContainer.evaluate((el) => {
139
+ const computed = window.getComputedStyle(el);
140
+ return {
141
+ borderRadius: computed.borderRadius,
142
+ };
143
+ });
144
+
145
+ expect(styles.borderRadius).toBe('0px');
146
+ });
147
+
148
+ test('should not show fullscreen button when already in fullscreen', async ({ page }) => {
149
+ await page.goto(
150
+ createSimulatorUrl({ simulation: 'map-show', theme: 'light', displayMode: 'fullscreen' })
151
+ );
152
+
153
+ // Wait for content to render
154
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
155
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
156
+
157
+ // The expand button should not be visible in fullscreen mode
158
+ const expandButton = page.locator('button[aria-label="Enter fullscreen"]');
159
+ await expect(expandButton).not.toBeVisible();
160
+ });
161
+
162
+ test('should show place list sidebar in fullscreen', async ({ page }) => {
163
+ await page.goto(
164
+ createSimulatorUrl({ simulation: 'map-show', theme: 'dark', displayMode: 'fullscreen' })
165
+ );
166
+
167
+ // The map container should be present
168
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
169
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
170
+ });
171
+
172
+ test('should show suggestion chips in fullscreen on desktop', async ({ page }) => {
173
+ // Set viewport to desktop size
174
+ await page.setViewportSize({ width: 1024, height: 768 });
175
+ await page.goto(
176
+ createSimulatorUrl({ simulation: 'map-show', theme: 'light', displayMode: 'fullscreen' })
177
+ );
178
+
179
+ // Wait for map to render
180
+ const mapContainer = page.locator('.antialiased.w-full.overflow-hidden').first();
181
+ await expect(mapContainer).toBeVisible({ timeout: 10000 });
182
+
183
+ // Suggestion chips should be visible (contains "Open now", "Top rated", etc.)
184
+ const openNowChip = page.locator('button:has-text("Open now")');
185
+ await expect(openNowChip).toBeVisible({ timeout: 5000 });
186
+ });
187
+ });
188
+ });
@@ -0,0 +1,245 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { createSimulatorUrl } from 'sunpeak/chatgpt';
3
+
4
+ test.describe('Review Resource', () => {
5
+ test.describe('Light Mode', () => {
6
+ test('should render review title with correct styles', async ({ page }) => {
7
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'light' }));
8
+
9
+ await page.waitForLoadState('networkidle');
10
+
11
+ // Review has a title
12
+ const title = page.locator('h1:has-text("Refactor Authentication Module")');
13
+ await expect(title).toBeVisible();
14
+
15
+ const styles = await title.evaluate((el) => {
16
+ const computed = window.getComputedStyle(el);
17
+ return {
18
+ fontWeight: computed.fontWeight,
19
+ };
20
+ });
21
+
22
+ // Should be semibold (600)
23
+ expect(parseInt(styles.fontWeight)).toBeGreaterThanOrEqual(600);
24
+ });
25
+
26
+ test('should render change items with type-specific styling', async ({ page }) => {
27
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'light' }));
28
+
29
+ await page.waitForLoadState('networkidle');
30
+
31
+ // Review diff shows changes - find the list items
32
+ const changeItem = page.locator('li').first();
33
+ await expect(changeItem).toBeVisible();
34
+
35
+ const styles = await changeItem.evaluate((el) => {
36
+ const computed = window.getComputedStyle(el);
37
+ return {
38
+ borderRadius: computed.borderRadius,
39
+ backgroundColor: computed.backgroundColor,
40
+ };
41
+ });
42
+
43
+ // Background should be set (one of the type colors)
44
+ expect(styles.backgroundColor).toBeTruthy();
45
+ expect(styles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)');
46
+ });
47
+
48
+ test('should have interactive apply and cancel buttons', async ({ page }) => {
49
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'light' }));
50
+
51
+ await page.waitForLoadState('networkidle');
52
+
53
+ // Find the Apply Changes button (based on simulation data)
54
+ const applyButton = page.locator('button:has-text("Apply Changes")');
55
+ await expect(applyButton).toBeVisible();
56
+
57
+ const applyStyles = await applyButton.evaluate((el) => {
58
+ const computed = window.getComputedStyle(el);
59
+ return {
60
+ cursor: computed.cursor,
61
+ };
62
+ });
63
+ expect(applyStyles.cursor).toBe('pointer');
64
+
65
+ // Find the Cancel button
66
+ const cancelButton = page.locator('button:has-text("Cancel")');
67
+ await expect(cancelButton).toBeVisible();
68
+
69
+ const cancelStyles = await cancelButton.evaluate((el) => {
70
+ const computed = window.getComputedStyle(el);
71
+ return {
72
+ cursor: computed.cursor,
73
+ };
74
+ });
75
+ expect(cancelStyles.cursor).toBe('pointer');
76
+ });
77
+
78
+ test('should have expand fullscreen button in inline mode', async ({ page }) => {
79
+ await page.goto(
80
+ createSimulatorUrl({ simulation: 'review-diff', theme: 'light', displayMode: 'inline' })
81
+ );
82
+
83
+ await page.waitForLoadState('networkidle');
84
+
85
+ // Find the expand button
86
+ const expandButton = page.locator('button[aria-label="Enter fullscreen"]');
87
+ await expect(expandButton).toBeVisible();
88
+
89
+ const styles = await expandButton.evaluate((el) => {
90
+ const computed = window.getComputedStyle(el);
91
+ return {
92
+ cursor: computed.cursor,
93
+ };
94
+ });
95
+
96
+ expect(styles.cursor).toBe('pointer');
97
+ });
98
+ });
99
+
100
+ test.describe('Dark Mode', () => {
101
+ test('should render review title with correct styles', async ({ page }) => {
102
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'dark' }));
103
+
104
+ await page.waitForLoadState('networkidle');
105
+
106
+ const title = page.locator('h1:has-text("Refactor Authentication Module")');
107
+ await expect(title).toBeVisible();
108
+ });
109
+
110
+ test('should have appropriate text colors for dark mode', async ({ page }) => {
111
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'dark' }));
112
+
113
+ await page.waitForLoadState('networkidle');
114
+
115
+ // The title should have text-primary class
116
+ const title = page.locator('h1.text-primary').first();
117
+ await expect(title).toBeVisible();
118
+
119
+ const styles = await title.evaluate((el) => {
120
+ const computed = window.getComputedStyle(el);
121
+ return {
122
+ color: computed.color,
123
+ };
124
+ });
125
+
126
+ // In dark mode, text color should be light
127
+ expect(styles.color).toBeTruthy();
128
+ });
129
+
130
+ test('should render change items in dark mode', async ({ page }) => {
131
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'dark' }));
132
+
133
+ await page.waitForLoadState('networkidle');
134
+
135
+ const changeItem = page.locator('li').first();
136
+ await expect(changeItem).toBeVisible();
137
+ });
138
+
139
+ test('should load without console errors', async ({ page }) => {
140
+ const errors: string[] = [];
141
+ page.on('console', (msg) => {
142
+ if (msg.type() === 'error') {
143
+ errors.push(msg.text());
144
+ }
145
+ });
146
+
147
+ await page.goto(createSimulatorUrl({ simulation: 'review-diff', theme: 'dark' }));
148
+ await page.waitForLoadState('networkidle');
149
+
150
+ expect(errors).toHaveLength(0);
151
+ });
152
+ });
153
+
154
+ test.describe('Fullscreen Mode', () => {
155
+ test('should not show fullscreen button when already in fullscreen', async ({ page }) => {
156
+ await page.goto(
157
+ createSimulatorUrl({ simulation: 'review-diff', theme: 'light', displayMode: 'fullscreen' })
158
+ );
159
+
160
+ await page.waitForLoadState('networkidle');
161
+
162
+ // The expand button should not be visible in fullscreen mode
163
+ const expandButton = page.locator('button[aria-label="Enter fullscreen"]');
164
+ await expect(expandButton).not.toBeVisible();
165
+ });
166
+
167
+ test('should render content correctly in fullscreen', async ({ page }) => {
168
+ await page.goto(
169
+ createSimulatorUrl({ simulation: 'review-diff', theme: 'dark', displayMode: 'fullscreen' })
170
+ );
171
+
172
+ await page.waitForLoadState('networkidle');
173
+
174
+ // The root content should be present
175
+ const root = page.locator('#root');
176
+ await expect(root).not.toBeEmpty();
177
+
178
+ // Title should be visible
179
+ const title = page.locator('h1');
180
+ await expect(title).toBeVisible();
181
+ });
182
+
183
+ test('should have scrollable content area in fullscreen', async ({ page }) => {
184
+ await page.goto(
185
+ createSimulatorUrl({ simulation: 'review-diff', theme: 'light', displayMode: 'fullscreen' })
186
+ );
187
+
188
+ await page.waitForLoadState('networkidle');
189
+
190
+ // The content area should have overflow-y-auto
191
+ const contentArea = page.locator('.overflow-y-auto').first();
192
+ await expect(contentArea).toBeVisible();
193
+
194
+ const styles = await contentArea.evaluate((el) => {
195
+ const computed = window.getComputedStyle(el);
196
+ return {
197
+ overflowY: computed.overflowY,
198
+ };
199
+ });
200
+
201
+ expect(styles.overflowY).toBe('auto');
202
+ });
203
+ });
204
+
205
+ test.describe('Review Post Simulation', () => {
206
+ test('should render post review in light mode', async ({ page }) => {
207
+ await page.goto(createSimulatorUrl({ simulation: 'review-post', theme: 'light' }));
208
+
209
+ await page.waitForLoadState('networkidle');
210
+
211
+ // Should render the review content
212
+ const root = page.locator('#root');
213
+ await expect(root).not.toBeEmpty();
214
+ });
215
+
216
+ test('should render post review in dark mode', async ({ page }) => {
217
+ await page.goto(createSimulatorUrl({ simulation: 'review-post', theme: 'dark' }));
218
+
219
+ await page.waitForLoadState('networkidle');
220
+
221
+ const root = page.locator('#root');
222
+ await expect(root).not.toBeEmpty();
223
+ });
224
+ });
225
+
226
+ test.describe('Review Purchase Simulation', () => {
227
+ test('should render purchase review in light mode', async ({ page }) => {
228
+ await page.goto(createSimulatorUrl({ simulation: 'review-purchase', theme: 'light' }));
229
+
230
+ await page.waitForLoadState('networkidle');
231
+
232
+ const root = page.locator('#root');
233
+ await expect(root).not.toBeEmpty();
234
+ });
235
+
236
+ test('should render purchase review in dark mode', async ({ page }) => {
237
+ await page.goto(createSimulatorUrl({ simulation: 'review-purchase', theme: 'dark' }));
238
+
239
+ await page.waitForLoadState('networkidle');
240
+
241
+ const root = page.locator('#root');
242
+ await expect(root).not.toBeEmpty();
243
+ });
244
+ });
245
+ });
@@ -10,6 +10,7 @@ export default defineConfig({
10
10
  globals: true,
11
11
  environment: 'jsdom',
12
12
  setupFiles: './src/test/setup.ts',
13
+ exclude: ['**/node_modules/**', '**/tests/e2e/**'],
13
14
  },
14
15
  resolve: {
15
16
  alias: {