@server/next 0.44.1 → 0.45.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.
@@ -1,694 +0,0 @@
1
- /** @jsxImportSource . */
2
-
3
- expect.extend({
4
- toRender(fn, rendered) {
5
- const html = fn();
6
- if (this.isNot) {
7
- const msg = `Expected "${html}" to render differently`;
8
- return { pass: html === rendered, message: () => msg };
9
- }
10
- const msg = `Expected "${html}" to be rendered as "${rendered}"`;
11
- return { pass: html === rendered, message: () => msg };
12
- },
13
- });
14
-
15
- describe("jsx", () => {
16
- it("can render a div", () => {
17
- expect(<div>Hello</div>).toRender("<div>Hello</div>");
18
- });
19
-
20
- it("can render an input with attributes", () => {
21
- expect(<input name="hello" />).toRender(`<input name="hello" />`);
22
- });
23
-
24
- it("will stringify an attribute", () => {
25
- expect(<input value={5.2} />).toRender('<input value="5.2" />');
26
- });
27
-
28
- it("will ignore a boolean attribute", () => {
29
- expect(<input disabled />).toRender("<input disabled />");
30
- expect(<input disabled={true} />).toRender("<input disabled />");
31
- expect(<input disabled={false} />).toRender("<input />");
32
- });
33
-
34
- it("will remove events", () => {
35
- expect(<input onChange={() => console.log("hello")} />).toRender(
36
- "<input />",
37
- );
38
- });
39
-
40
- it("will inject the doctype for html", () => {
41
- // biome-ignore lint/a11y/useHtmlLang: This is an example, not user code
42
- expect(<html>Hello</html>).toRender("<!DOCTYPE html><html>Hello</html>");
43
- expect(<html lang="en">Hello</html>).toRender(
44
- `<!DOCTYPE html><html lang="en">Hello</html>`,
45
- );
46
- });
47
-
48
- it("can render a pre with text", () => {
49
- expect(<pre>hello</pre>).toRender("<pre>hello</pre>");
50
- });
51
-
52
- it("can render a pre with newlines", () => {
53
- expect(<pre>line1{"\n"}line2</pre>).toRender("<pre>line1\nline2</pre>");
54
- });
55
-
56
- it("can render a pre with indented text", () => {
57
- expect(<pre>{` indented`}</pre>).toRender("<pre> indented</pre>");
58
- });
59
-
60
- it("can render a pre inside a div", () => {
61
- expect(
62
- <div>
63
- <pre>line1{"\n"}line2</pre>
64
- </div>,
65
- ).toRender("<div><pre>line1\nline2</pre></div>");
66
- });
67
-
68
- it("does NOT render the <pre>", () => {
69
- expect(<code>{"<pre>"}</code>).toRender("<code>&lt;pre&gt;</code>");
70
- });
71
- });
72
-
73
- describe("text encoding", () => {
74
- it("encodes < and > to prevent XSS", () => {
75
- expect(<div>{"<script>alert(1)</script>"}</div>).toRender(
76
- "<div>&lt;script&gt;alert(1)&lt;/script&gt;</div>",
77
- );
78
- });
79
-
80
- it("encodes & in text content", () => {
81
- expect(<div>{"a & b"}</div>).toRender("<div>a &amp; b</div>");
82
- });
83
-
84
- it('encodes " in text content', () => {
85
- expect(<div>{'say "hi"'}</div>).toRender("<div>say &quot;hi&quot;</div>");
86
- });
87
-
88
- it("encodes ' in text content", () => {
89
- expect(<div>{"it's"}</div>).toRender("<div>it&#39;s</div>");
90
- });
91
-
92
- it("renders 0 as a child", () => {
93
- expect(<div>{0}</div>).toRender("<div>0</div>");
94
- });
95
-
96
- it("renders positive integers", () => {
97
- expect(<div>{42}</div>).toRender("<div>42</div>");
98
- });
99
-
100
- it("renders floats", () => {
101
- expect(<div>{3.14}</div>).toRender("<div>3.14</div>");
102
- });
103
-
104
- it("renders negative numbers", () => {
105
- expect(<div>{-1}</div>).toRender("<div>-1</div>");
106
- });
107
-
108
- it("renders nothing for null", () => {
109
- expect(<div>{null}</div>).toRender("<div></div>");
110
- });
111
-
112
- it("renders nothing for undefined", () => {
113
- expect(<div>{undefined}</div>).toRender("<div></div>");
114
- });
115
-
116
- it("renders nothing for false", () => {
117
- expect(<div>{false}</div>).toRender("<div></div>");
118
- });
119
-
120
- it("renders nothing for true", () => {
121
- expect(<div>{true}</div>).toRender("<div></div>");
122
- });
123
-
124
- it("renders conditional content", () => {
125
- const show = true;
126
- expect(<div>{show && <span>yes</span>}</div>).toRender(
127
- "<div><span>yes</span></div>",
128
- );
129
- const hide = false;
130
- expect(<div>{hide && <span>no</span>}</div>).toRender("<div></div>");
131
- });
132
-
133
- it("renders multiple children in order", () => {
134
- expect(
135
- <ul>
136
- <li>a</li>
137
- <li>b</li>
138
- <li>c</li>
139
- </ul>,
140
- ).toRender("<ul><li>a</li><li>b</li><li>c</li></ul>");
141
- });
142
-
143
- it("renders array children", () => {
144
- const items = ["x", "y", "z"];
145
- expect(
146
- <span>
147
- {items.map((i) => (
148
- <b key={i}>{i}</b>
149
- ))}
150
- </span>,
151
- ).toRender("<span><b>x</b><b>y</b><b>z</b></span>");
152
- });
153
-
154
- it("treats string returned from a function child as raw HTML", () => {
155
- const getHtml = () => "<b>bold</b>";
156
- expect(<div>{getHtml}</div>).toRender("<div><b>bold</b></div>");
157
- });
158
- });
159
-
160
- describe("attribute encoding", () => {
161
- it("encodes < and > in attribute values", () => {
162
- expect(<div title="<b>hi</b>"></div>).toRender(
163
- '<div title="&lt;b&gt;hi&lt;/b&gt;"></div>',
164
- );
165
- });
166
-
167
- it("encodes & in attribute values", () => {
168
- expect(<div title="a & b"></div>).toRender('<div title="a &amp; b"></div>');
169
- });
170
-
171
- it('encodes " in attribute values', () => {
172
- expect(<div title={`say "hi"`}></div>).toRender(
173
- '<div title="say &quot;hi&quot;"></div>',
174
- );
175
- });
176
-
177
- it("encodes ' in attribute values", () => {
178
- expect(<div title={"it's"}></div>).toRender('<div title="it&#39;s"></div>');
179
- });
180
-
181
- it("maps className to class", () => {
182
- expect(<div className="foo bar"></div>).toRender(
183
- '<div class="foo bar"></div>',
184
- );
185
- });
186
-
187
- it("maps htmlFor to for", () => {
188
- expect(<label htmlFor="email"></label>).toRender(
189
- '<label for="email"></label>',
190
- );
191
- });
192
-
193
- it("supports data-* attributes", () => {
194
- expect(<div data-id="42" data-name="test"></div>).toRender(
195
- '<div data-id="42" data-name="test"></div>',
196
- );
197
- });
198
-
199
- it("supports aria-* attributes", () => {
200
- expect(<button aria-label="close" aria-expanded={false}></button>).toRender(
201
- '<button aria-label="close"></button>',
202
- );
203
- });
204
-
205
- it("supports numeric attribute values", () => {
206
- expect(<input tabIndex={3} />).toRender('<input tabIndex="3" />');
207
- });
208
-
209
- it("strips function-valued attributes", () => {
210
- expect(<div onFoo={() => {}} ref={() => {}}></div>).toRender("<div></div>");
211
- });
212
- });
213
-
214
- describe("self-closing tags", () => {
215
- it("renders br", () => {
216
- expect(<br />).toRender("<br />");
217
- });
218
-
219
- it("renders hr", () => {
220
- expect(<hr />).toRender("<hr />");
221
- });
222
-
223
- it("renders img with attributes", () => {
224
- expect(<img src="cat.png" alt="A cat" />).toRender(
225
- '<img src="cat.png" alt="A cat" />',
226
- );
227
- });
228
-
229
- it("renders input with type and name", () => {
230
- expect(<input type="text" name="q" />).toRender(
231
- '<input type="text" name="q" />',
232
- );
233
- });
234
-
235
- it("renders link tag", () => {
236
- expect(<link rel="stylesheet" href="style.css" />).toRender(
237
- '<link rel="stylesheet" href="style.css" />',
238
- );
239
- });
240
-
241
- it("renders meta tag", () => {
242
- expect(<meta name="description" content="hello" />).toRender(
243
- '<meta name="description" content="hello" />',
244
- );
245
- });
246
-
247
- it("renders source tag", () => {
248
- expect(<source src="video.mp4" type="video/mp4" />).toRender(
249
- '<source src="video.mp4" type="video/mp4" />',
250
- );
251
- });
252
- });
253
-
254
- describe("fragments", () => {
255
- it("does not crash on symbol-based element types (React internals)", () => {
256
- expect(
257
- <>
258
- <span>Hello</span> world
259
- </>,
260
- ).toRender("<span>Hello</span> world");
261
- });
262
-
263
- it("renders nested fragments", () => {
264
- expect(
265
- <>
266
- <>
267
- <span>A</span>
268
- </>
269
- B
270
- </>,
271
- ).toRender("<span>A</span>B");
272
- });
273
-
274
- it("handles empty fragments", () => {
275
- expect(<></>).toRender("");
276
- });
277
-
278
- it("encodes text inside a fragment", () => {
279
- expect(<>{"<b>"}</>).toRender("&lt;b&gt;");
280
- });
281
- });
282
-
283
- describe("script tag", () => {
284
- it("renders script content without encoding", () => {
285
- expect(<script>{"const x = 1 < 2 && 3 > 0;"}</script>).toRender(
286
- "<script>const x = 1 < 2 && 3 > 0;</script>",
287
- );
288
- });
289
-
290
- it("renders script with src attribute and no children", () => {
291
- expect(<script src="app.js"></script>).toRender(
292
- '<script src="app.js"></script>',
293
- );
294
- });
295
-
296
- it("renders empty script tag", () => {
297
- expect(<script></script>).toRender("<script></script>");
298
- });
299
- });
300
-
301
- describe("style tag", () => {
302
- it("renders the CSS as written", () => {
303
- expect(<style>{"body { color: red; }"}</style>).toRender(
304
- "<style>body { color: red; }</style>",
305
- );
306
- });
307
-
308
- it("keeps comments and whitespace without `minify`", () => {
309
- expect(<style>{"/* reset */ body {\n margin: 0;\n}"}</style>).toRender(
310
- "<style>/* reset */ body {\n margin: 0;\n}</style>",
311
- );
312
- });
313
-
314
- it("sends the content raw, like <script>", () => {
315
- // Encoding it would break the CSS, so user input must never reach here
316
- expect(<style>{"a::after { content: '>'; }"}</style>).toRender(
317
- "<style>a::after { content: '>'; }</style>",
318
- );
319
- });
320
-
321
- it("escapes </style> either way, so it can't close the tag early", () => {
322
- expect(<style>{"a { content: '</style>'; }"}</style>).toRender(
323
- "<style>a { content: '<\\/style>'; }</style>",
324
- );
325
- expect(<style minify>{"a { content: '</style>'; }"}</style>).toRender(
326
- "<style>a{content:'<\\/style>'}</style>",
327
- );
328
- });
329
-
330
- it("never renders `minify` as an attribute", () => {
331
- expect(<style minify>{"a { color: red; }"}</style>).toRender(
332
- "<style>a{color:red}</style>",
333
- );
334
- });
335
-
336
- describe("with `minify`", () => {
337
- it("minifies whitespace in style content", () => {
338
- expect(
339
- <style minify>{"body {\n color: red;\n margin: 0;\n}"}</style>,
340
- ).toRender("<style>body{color:red;margin:0}</style>");
341
- });
342
-
343
- it("strips CSS comments", () => {
344
- expect(
345
- <style minify>{"/* reset */ body { margin: 0; } /* end */"}</style>,
346
- ).toRender("<style>body{margin:0}</style>");
347
- });
348
-
349
- it("preserves child combinator in selectors", () => {
350
- expect(
351
- <style minify>{":not(pre) > code { background: none; }"}</style>,
352
- ).toRender("<style>:not(pre)>code{background:none}</style>");
353
- });
354
-
355
- it("preserves sibling combinators in selectors", () => {
356
- // `+` keeps a single space, since removing it breaks calc() below
357
- expect(
358
- <style minify>
359
- {"h2 + p { margin: 0; } h2 ~ p { color: red; }"}
360
- </style>,
361
- ).toRender("<style>h2 + p{margin:0}h2~p{color:red}</style>");
362
- });
363
-
364
- it("keeps the spaces calc() needs around + and -", () => {
365
- // `calc(100%+10px)` is invalid CSS: the operators need their whitespace
366
- expect(<style minify>{"a { top: calc(100% + 10px); }"}</style>).toRender(
367
- "<style>a{top:calc(100% + 10px)}</style>",
368
- );
369
- expect(<style minify>{"a { top: calc(100% - 10px); }"}</style>).toRender(
370
- "<style>a{top:calc(100% - 10px)}</style>",
371
- );
372
- });
373
-
374
- it("removes spaces around braces, colons, and semicolons", () => {
375
- expect(
376
- <style minify>{"a { color : red ; font-size : 1em ; }"}</style>,
377
- ).toRender("<style>a{color:red;font-size:1em}</style>");
378
- });
379
-
380
- it("removes trailing semicolon before closing brace", () => {
381
- expect(<style minify>{"p { margin: 0; padding: 0; }"}</style>).toRender(
382
- "<style>p{margin:0;padding:0}</style>",
383
- );
384
- });
385
-
386
- it("leaves quoted values alone", () => {
387
- // The spaces and delimiters in here are content, not formatting
388
- expect(<style minify>{'a::after { content: ", "; }'}</style>).toRender(
389
- '<style>a::after{content:", "}</style>',
390
- );
391
- expect(
392
- <style minify>{'a::after { content: "a; b: c"; }'}</style>,
393
- ).toRender('<style>a::after{content:"a; b: c"}</style>');
394
- expect(
395
- <style minify>{"a::after { content: 'x y'; }"}</style>,
396
- ).toRender("<style>a::after{content:'x y'}</style>");
397
- });
398
-
399
- it("handles an escaped quote inside a value", () => {
400
- expect(
401
- <style minify>{'a::after { content: "say \\" hi"; }'}</style>,
402
- ).toRender('<style>a::after{content:"say \\" hi"}</style>');
403
- });
404
-
405
- it("still strips a comment written inside a value", () => {
406
- // Comments are stripped first, unconditionally
407
- expect(
408
- <style minify>{'a::after { content: "/* hi */"; }'}</style>,
409
- ).toRender('<style>a::after{content:""}</style>');
410
- });
411
- });
412
- });
413
-
414
- describe("dangerouslySetInnerHTML", () => {
415
- it("renders raw HTML without encoding", () => {
416
- expect(
417
- <div dangerouslySetInnerHTML={{ __html: "<b>bold</b>" }}></div>,
418
- ).toRender("<div><b>bold</b></div>");
419
- });
420
-
421
- it("does not double-encode HTML entities", () => {
422
- expect(
423
- <div dangerouslySetInnerHTML={{ __html: "&amp; &lt;" }}></div>,
424
- ).toRender("<div>&amp; &lt;</div>");
425
- });
426
-
427
- it("preserves script tags in raw HTML", () => {
428
- expect(
429
- <div
430
- dangerouslySetInnerHTML={{ __html: "<script>alert(1)</script>" }}
431
- ></div>,
432
- ).toRender("<div><script>alert(1)</script></div>");
433
- });
434
-
435
- it("does not render dangerouslySetInnerHTML as an attribute", () => {
436
- expect(<div dangerouslySetInnerHTML={{ __html: "hi" }}></div>).toRender(
437
- "<div>hi</div>",
438
- );
439
- });
440
- });
441
-
442
- describe("function components", () => {
443
- it("renders a simple function component", () => {
444
- const Greeting = ({ name }) => <p>Hello, {name}!</p>;
445
- expect(<Greeting name="world" />).toRender("<p>Hello, world!</p>");
446
- });
447
-
448
- it("renders a component with children", () => {
449
- const Box = ({ children }) => <div className="box">{children}</div>;
450
- expect(
451
- <Box>
452
- <span>inside</span>
453
- </Box>,
454
- ).toRender('<div class="box"><span>inside</span></div>');
455
- });
456
-
457
- it("renders nested components", () => {
458
- const Inner = () => <em>inner</em>;
459
- const Outer = () => (
460
- <div>
461
- <Inner />
462
- </div>
463
- );
464
- expect(<Outer />).toRender("<div><em>inner</em></div>");
465
- });
466
-
467
- it("renders a component returning null", () => {
468
- const Empty = () => null;
469
- expect(<Empty />).toRender("");
470
- });
471
-
472
- it("renders a component returning a string", () => {
473
- const Text = () => "hello";
474
- expect(<Text />).toRender("hello");
475
- });
476
-
477
- it("encodes text returned by a component", () => {
478
- const Unsafe = () => "<script>alert(1)</script>";
479
- expect(<Unsafe />).toRender("&lt;script&gt;alert(1)&lt;/script&gt;");
480
- });
481
- });
482
-
483
- describe("forwardRef-like components", () => {
484
- it("renders an object with a render function", () => {
485
- const Button = { render: ({ children }) => <button>{children}</button> };
486
- expect(<Button>click me</Button>).toRender("<button>click me</button>");
487
- });
488
-
489
- it("passes props to render function", () => {
490
- const Input = {
491
- render: ({ type, placeholder }) => (
492
- <input type={type} placeholder={placeholder} />
493
- ),
494
- };
495
- expect(<Input type="email" placeholder="you@example.com" />).toRender(
496
- '<input type="email" placeholder="you@example.com" />',
497
- );
498
- });
499
- });
500
-
501
- describe("React element interop", () => {
502
- // Simulate a component built with React's jsx runtime, which returns plain
503
- // objects { type, props } instead of @server/next functions
504
- const reactEl = (type, props = {}) => ({ type, props });
505
-
506
- it("renders a React element returned from a function component", () => {
507
- const Greeting = () => reactEl("h1", { children: "Hello" });
508
- expect(<Greeting />).toRender("<h1>Hello</h1>");
509
- });
510
-
511
- it("renders nested React elements", () => {
512
- const Card = () =>
513
- reactEl("div", {
514
- children: [
515
- reactEl("h2", { children: "Title" }),
516
- reactEl("p", { children: "Body" }),
517
- ],
518
- });
519
- expect(<Card />).toRender("<div><h2>Title</h2><p>Body</p></div>");
520
- });
521
-
522
- it("renders React elements as children of a native element", () => {
523
- const child = reactEl("strong", { children: "bold" });
524
- expect(<p>{child}</p>).toRender("<p><strong>bold</strong></p>");
525
- });
526
-
527
- it("renders a mix of React elements and plain strings as children", () => {
528
- const em = reactEl("em", { children: "world" });
529
- expect(<p>Hello {em}!</p>).toRender("<p>Hello <em>world</em>!</p>");
530
- });
531
-
532
- it("renders a React element with attributes", () => {
533
- const Link = () =>
534
- reactEl("a", { href: "https://example.com", children: "click" });
535
- expect(<Link />).toRender(`<a href="https://example.com">click</a>`);
536
- });
537
-
538
- it("renders deeply nested React elements", () => {
539
- const Deep = () =>
540
- reactEl("div", {
541
- children: reactEl("ul", {
542
- children: [
543
- reactEl("li", { children: "one" }),
544
- reactEl("li", { children: "two" }),
545
- ],
546
- }),
547
- });
548
- expect(<Deep />).toRender("<div><ul><li>one</li><li>two</li></ul></div>");
549
- });
550
- });
551
- describe("styles", () => {
552
- it("stringifies style object into CSS", () => {
553
- expect(
554
- <div
555
- style={{
556
- display: "inline-block",
557
- padding: "2px 8px",
558
- borderRadius: "4px",
559
- }}
560
- />,
561
- ).toRender(
562
- '<div style="display:inline-block;padding:2px 8px;border-radius:4px"></div>',
563
- );
564
- });
565
-
566
- it("handles numbers and mixed style values", () => {
567
- expect(
568
- <div
569
- style={{
570
- fontSize: 12,
571
- lineHeight: 1.5,
572
- fontWeight: "600",
573
- }}
574
- />,
575
- ).toRender(
576
- '<div style="font-size:12;line-height:1.5;font-weight:600"></div>',
577
- );
578
- });
579
-
580
- it("converts camelCase CSS properties", () => {
581
- expect(
582
- <div
583
- style={{
584
- backgroundColor: "#fff",
585
- marginTop: "10px",
586
- }}
587
- />,
588
- ).toRender('<div style="background-color:#fff;margin-top:10px"></div>');
589
- });
590
-
591
- it("ignores null and undefined style values", () => {
592
- expect(
593
- <div
594
- style={{
595
- color: "red",
596
- padding: null,
597
- margin: undefined,
598
- }}
599
- />,
600
- ).toRender('<div style="color:red"></div>');
601
- });
602
- });
603
-
604
- describe("svg attributes", () => {
605
- it("converts strokeWidth to stroke-width", () => {
606
- expect(
607
- <svg>
608
- <circle strokeWidth="2" />
609
- </svg>,
610
- ).toRender('<svg><circle stroke-width="2"></circle></svg>');
611
- });
612
-
613
- it("converts fillOpacity to fill-opacity", () => {
614
- expect(
615
- <svg>
616
- <rect fillOpacity="0.5" />
617
- </svg>,
618
- ).toRender('<svg><rect fill-opacity="0.5"></rect></svg>');
619
- });
620
-
621
- it("converts strokeLinecap to stroke-linecap", () => {
622
- expect(
623
- <svg>
624
- <line strokeLinecap="round" />
625
- </svg>,
626
- ).toRender('<svg><line stroke-linecap="round"></line></svg>');
627
- });
628
-
629
- it("does NOT break viewBox casing", () => {
630
- expect(
631
- <svg viewBox="0 0 100 100">
632
- <rect />
633
- </svg>,
634
- ).toRender('<svg viewBox="0 0 100 100"><rect></rect></svg>');
635
- });
636
-
637
- it("handles mixed SVG attributes correctly", () => {
638
- expect(
639
- <svg viewBox="0 0 10 10">
640
- <circle strokeWidth="1" fillOpacity="0.2" />
641
- </svg>,
642
- ).toRender(
643
- '<svg viewBox="0 0 10 10"><circle stroke-width="1" fill-opacity="0.2"></circle></svg>',
644
- );
645
- });
646
-
647
- it("keeps viewBox unchanged", () => {
648
- expect(
649
- <svg viewBox="0 0 100 100">
650
- <rect />
651
- </svg>,
652
- ).toRender('<svg viewBox="0 0 100 100"><rect></rect></svg>');
653
- });
654
-
655
- it("does NOT convert viewBox to view-box", () => {
656
- expect(<svg viewBox="0 0 10 10"></svg>).toRender(
657
- '<svg viewBox="0 0 10 10"></svg>',
658
- );
659
- });
660
-
661
- it("converts strokeWidth to stroke-width", () => {
662
- expect(
663
- <svg>
664
- <circle strokeWidth="2" />
665
- </svg>,
666
- ).toRender('<svg><circle stroke-width="2"></circle></svg>');
667
- });
668
-
669
- it("converts fillOpacity to fill-opacity", () => {
670
- expect(
671
- <svg>
672
- <rect fillOpacity="0.5" />
673
- </svg>,
674
- ).toRender('<svg><rect fill-opacity="0.5"></rect></svg>');
675
- });
676
-
677
- it("converts strokeLinecap to stroke-linecap", () => {
678
- expect(
679
- <svg>
680
- <line strokeLinecap="round" />
681
- </svg>,
682
- ).toRender('<svg><line stroke-linecap="round"></line></svg>');
683
- });
684
-
685
- it("handles mixed SVG attributes correctly", () => {
686
- expect(
687
- <svg viewBox="0 0 10 10">
688
- <circle strokeWidth="1" fillOpacity="0.2" />
689
- </svg>,
690
- ).toRender(
691
- '<svg viewBox="0 0 10 10"><circle stroke-width="1" fill-opacity="0.2"></circle></svg>',
692
- );
693
- });
694
- });