8
import {expect, test} from '@playwright/test';
9
import {encodeStore, type Store} from '../../lib/stores';
10
11
-const STORE: Store = {
12
- source: `export default function TestComponent({ x }) {
13
- return <Button>{x}</Button>;
14
-}
15
-`,
16
-};
17
-const HASH = encodeStore(STORE);
11
+test.describe.configure({mode: 'parallel'});
12
13
function concat(data: Array<string>): string {
14
return data.join('');
15
}
16
+const DIRECTIVE_TEST_CASES = [
17
+ {
18
+ name: 'module-scope-use-memo',
19
+ input: `'use memo';
20
+
21
+const Header = () => {
22
+ const handleClick = () => {
23
+ console.log('Header clicked');
24
+ };
25
+
26
+ return <h1 onClick={handleClick}>Welcome to the App!</h1>;
27
+};`,
28
+ },
29
+ {
30
+ name: 'module-scope-use-no-memo',
31
+ input: `'use no memo';
32
+
33
+const Footer = () => {
34
+ const handleMouseOver = () => {
35
+ console.log('Footer hovered');
36
+ };
37
+
38
+ return <footer onMouseOver={handleMouseOver}>Footer Information</footer>;
39
+};
40
+`,
41
+ },
42
+ {
43
+ name: 'function-scope-use-memo-function-declaration',
44
+ input: `function App() {
45
+ 'use memo';
46
+
47
+ function Sidebar() {
48
+ const handleToggle = () => {
49
+ console.log('Sidebar toggled');
50
+ };
51
+
52
+ return <aside onClick={handleToggle}>Sidebar Content</aside>;
53
+ }
54
+
55
+ const MemoizedSidebar = React.memo(Sidebar);
56
+
57
+ function Content() {
58
+ return <main>Main Content</main>;
59
+ }
60
+
61
+ const MemoizedContent = React.memo(Content);
62
+
63
+ return (
64
+ <div>
65
+ <MemoizedSidebar />
66
+ <MemoizedContent />
67
+ </div>
68
+ );
69
+}`,
70
+ },
71
+ {
72
+ name: 'function-scope-use-no-memo-function-expression',
73
+ input: `const Dashboard = function() {
74
+ 'use no memo';
75
+ const Widget = function() {
76
+ const handleExpand = () => {
77
+ console.log('Widget expanded');
78
+ };
79
+
80
+ return <div onClick={handleExpand}>Widget Content</div>;
81
+ };
82
+
83
+ const Panel = function() {
84
+ return <section>Panel Information</section>;
85
+ };
86
+
87
+ return (
88
+ <div>
89
+ <Widget />
90
+ <Panel />
91
+ </div>
92
+ );
93
+};`,
94
+ },
95
+ {
96
+ name: 'function-scope-use-memo-arrow-function-expression',
97
+ input: `const Analytics = () => {
98
+ 'use memo';
99
+
100
+ const Chart = () => {
101
+ const handleRefresh = () => {
102
+ console.log('Chart refreshed');
103
+ };
104
23
-test('editor should compile successfully', async ({page}) => {
24
- await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
105
+ return <div onClick={handleRefresh}>Chart Content</div>;
106
+ };
107
+
108
+ const MemoizedChart = React.memo(Chart);
109
+
110
+ const Graph = () => {
111
+ return <div>Graph Content</div>;
112
+ };
113
+
114
+ const MemoizedGraph = React.memo(Graph);
115
+
116
+ return (
117
+ <div>
118
+ <MemoizedChart />
119
+ <MemoizedGraph />
120
+ </div>
121
+ );
122
+};`,
123
+ },
124
+ {
125
+ name: 'module-scope-use-no-memo-function-expression',
126
+ input: `'use no memo';
127
+
128
+const Sidebar = function() {
129
+ return <aside>Sidebar Information</aside>;
130
+};`,
131
+ },
132
+ {
133
+ name: 'function-scope-no-directive-arrow-function-expression',
134
+ input: `
135
+const Profile = () => {
136
+'use no memo';
137
+ const Avatar = () => {
138
+ return <div>Avatar Content</div>;
139
+ };
140
+
141
+ const MemoizedAvatar = React.memo(Avatar);
142
+
143
+ const Bio = () => {
144
+ const handleBioUpdate = () => {
145
+ console.log('Bio updated');
146
+ };
147
+
148
+ return <div onClick={handleBioUpdate}>Bio Content</div>;
149
+ };
150
+
151
+ const MemoizedBio = React.memo(Bio);
152
+
153
+ return (
154
+ <div>
155
+ <MemoizedAvatar />
156
+ <MemoizedBio />
157
+ </div>
158
+ );
159
+};`,
160
+ },
161
+ {
162
+ name: 'function-scope-use-no-memo-function-declaration',
163
+ input: `'use no memo';
164
+
165
+function Settings() {
166
+ 'use memo';
167
+
168
+ function Preferences() {
169
+ const handleSave = () => {
170
+ console.log('Preferences saved');
171
+ };
172
+
173
+ return <div onClick={handleSave}>Preferences Content</div>;
174
+ }
175
+
176
+ function Notifications() {
177
+ return <div>Notifications Settings</div>;
178
+ }
179
+
180
+ return (
181
+ <div>
182
+ <Preferences />
183
+ <Notifications />
184
+ </div>
185
+ );
186
+}`,
187
+ },
188
+];
189
+test('editor should open successfully', async ({page}) => {
190
+ await page.goto(`/`, {waitUntil: 'networkidle'});
191
await page.screenshot({
192
fullPage: true,
27
- path: 'test-results/00-on-networkidle.png',
193
+ path: 'test-results/00-fresh-page.png',
194
});
195
+});
196
+test('editor should compile from hash successfully', async ({page}) => {
197
+ const store: Store = {
198
+ source: `export default function TestComponent({ x }) {
199
+ return <Button>{x}</Button>;
200
+ }
201
+ `,
202
+ };
203
+ const hash = encodeStore(store);
204
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
205
206
// User input from hash compiles
207
await page.screenshot({
208
fullPage: true,
33
- path: 'test-results/01-show-js-before.png',
209
+ path: 'test-results/01-compiles-from-hash.png',
210
});
211
const userInput =
212
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
37
- expect(concat(userInput)).toMatchSnapshot('user-input.txt');
213
+ expect(concat(userInput)).toMatchSnapshot('01-user-output.txt');
214
+});
215
+test('reset button works', async ({page}) => {
216
+ const store: Store = {
217
+ source: `export default function TestComponent({ x }) {
218
+ return <Button>{x}</Button>;
219
+ }
220
+ `,
221
+ };
222
+ const hash = encodeStore(store);
223
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
224
225
// Reset button works
226
page.on('dialog', dialog => dialog.accept());
227
await page.getByRole('button', {name: 'Reset'}).click();
228
await page.screenshot({
229
fullPage: true,
44
- path: 'test-results/02-show-js-after.png',
230
+ path: 'test-results/02-reset-button-works.png',
231
});
232
const defaultInput =
233
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
48
- expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
234
+ expect(concat(defaultInput)).toMatchSnapshot('02-default-output.txt');
235
});
236
+DIRECTIVE_TEST_CASES.forEach((t, idx) =>
237
+ test(`directives work: ${t.name}`, async ({page}) => {
238
+ const store: Store = {
239
+ source: t.input,
240
+ };
241
+ const hash = encodeStore(store);
242
+ await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
243
+ await page.screenshot({
244
+ fullPage: true,
245
+ path: `test-results/03-0${idx}-${t.name}.png`,
246
+ });
247
+
248
+ const useMemoOutput =
249
+ (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
250
+ expect(concat(useMemoOutput)).toMatchSnapshot(`${t.name}-output.txt`);
251
+ }),
252
+);