+ + +
+
", + "static": "
<script>alert("x") & 'y'</script>link
", + "prerender": "
<script>alert("x") & 'y'</script>link
" +} diff --git a/tests/Jsx/react/escaping.jsx b/tests/Jsx/react/escaping.jsx new file mode 100644 index 00000000..2ec8cb54 --- /dev/null +++ b/tests/Jsx/react/escaping.jsx @@ -0,0 +1,11 @@ +export default function App() { + const s = ``; + return ( +
+ {s} + link +
\n\nleading
\n\nraw
", + "static": "
\n\nleading
\n\nraw
", + "prerender": "
\n\nleading
\n\nraw
" +} diff --git a/tests/Jsx/react/forms.jsx b/tests/Jsx/react/forms.jsx new file mode 100644 index 00000000..a5b42ee9 --- /dev/null +++ b/tests/Jsx/react/forms.jsx @@ -0,0 +1,30 @@ +export default function App() { + return ( +
+ + + + + + + + + + +
{'\nleading'}
+
+    
+  );
+}
diff --git a/tests/Jsx/react/fragments-keys.expected.txt b/tests/Jsx/react/fragments-keys.expected.txt
new file mode 100644
index 00000000..2c5d47bc
--- /dev/null
+++ b/tests/Jsx/react/fragments-keys.expected.txt
@@ -0,0 +1,5 @@
+{
+  "string": "
  • first
  • a
  • b
  • c
  • a1
  • a2
  • b1
  • b2
  • c1
  • c2
  • 12345s1s2g1g2
", + "static": "
  • first
  • a
  • b
  • c
  • a1
  • a2
  • b1
  • b2
  • c1
  • c2
  • 12345s1s2g1g2
", + "prerender": "
  • first
  • a
  • b
  • c
  • a1
  • a2
  • b1
  • b2
  • c1
  • c2
  • 12345s1s2g1g2
" +} diff --git a/tests/Jsx/react/fragments-keys.jsx b/tests/Jsx/react/fragments-keys.jsx new file mode 100644 index 00000000..bb8f5e09 --- /dev/null +++ b/tests/Jsx/react/fragments-keys.jsx @@ -0,0 +1,18 @@ +import { Fragment } from 'react'; +export default function App() { + const items = ['a', 'b', 'c']; + return ( +
    + <> +
  • first
  • + {items.map((x) =>
  • {x}
  • )} + + {items.map((x) => ( +
  • {x}1
  • {x}2
  • + ))} + {[[1, 2], [3, [4, 5]]]} + {new Set(['s1', 's2'])} + {(function* () { yield 'g1'; yield g2; })()} +
+ ); +} diff --git a/tests/Jsx/react/head-only.expected.txt b/tests/Jsx/react/head-only.expected.txt new file mode 100644 index 00000000..75128055 --- /dev/null +++ b/tests/Jsx/react/head-only.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "t

no html element

", + "static": "t

no html element

", + "prerender": "t

no html element

" +} diff --git a/tests/Jsx/react/head-only.jsx b/tests/Jsx/react/head-only.jsx new file mode 100644 index 00000000..23b1177a --- /dev/null +++ b/tests/Jsx/react/head-only.jsx @@ -0,0 +1,8 @@ +export default function App() { + return ( + <> + t +

no html element

+ + ); +} diff --git a/tests/Jsx/react/hoisting-fragment.expected.txt b/tests/Jsx/react/hoisting-fragment.expected.txt new file mode 100644 index 00000000..423752e4 --- /dev/null +++ b/tests/Jsx/react/hoisting-fragment.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "Hoisted title

text

svg title
", + "static": "Hoisted title

text

svg title
", + "prerender": "Hoisted title

text

svg title
" +} diff --git a/tests/Jsx/react/hoisting-fragment.jsx b/tests/Jsx/react/hoisting-fragment.jsx new file mode 100644 index 00000000..979aeebd --- /dev/null +++ b/tests/Jsx/react/hoisting-fragment.jsx @@ -0,0 +1,14 @@ +export default function App() { + return ( +
+ Hoisted title + + + +
", + "static": "
bold & it
", + "prerender": "
bold & it
" +} diff --git a/tests/Jsx/react/inner-html.jsx b/tests/Jsx/react/inner-html.jsx new file mode 100644 index 00000000..5f4de096 --- /dev/null +++ b/tests/Jsx/react/inner-html.jsx @@ -0,0 +1,13 @@ +export default function App() { + return ( +
+
bold & it' }} /> + + + + +
+ ); +} diff --git a/tests/Jsx/react/invalid-child-throws.expected.txt b/tests/Jsx/react/invalid-child-throws.expected.txt new file mode 100644 index 00000000..1cc70116 --- /dev/null +++ b/tests/Jsx/react/invalid-child-throws.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: Objects are not valid as a React child (found: object with keys {a, b}). If you meant to render a collection of children, use an array instead.", + "static": "ERROR: Objects are not valid as a React child (found: object with keys {a, b}). If you meant to render a collection of children, use an array instead.", + "prerender": "ERROR: Objects are not valid as a React child (found: object with keys {a, b}). If you meant to render a collection of children, use an array instead." +} diff --git a/tests/Jsx/react/invalid-child-throws.jsx b/tests/Jsx/react/invalid-child-throws.jsx new file mode 100644 index 00000000..dcf5967a --- /dev/null +++ b/tests/Jsx/react/invalid-child-throws.jsx @@ -0,0 +1 @@ +export default function App() { return
{{ a: 1, b: 2 }}
; } diff --git a/tests/Jsx/react/invalid-type-throws.expected.txt b/tests/Jsx/react/invalid-type-throws.expected.txt new file mode 100644 index 00000000..6528bd1c --- /dev/null +++ b/tests/Jsx/react/invalid-type-throws.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.", + "static": "ERROR: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.", + "prerender": "ERROR: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined." +} diff --git a/tests/Jsx/react/invalid-type-throws.jsx b/tests/Jsx/react/invalid-type-throws.jsx new file mode 100644 index 00000000..9c858ba8 --- /dev/null +++ b/tests/Jsx/react/invalid-type-throws.jsx @@ -0,0 +1,2 @@ +const Missing = undefined; +export default function App() { return
; } diff --git a/tests/Jsx/react/large-boundary.expected.txt b/tests/Jsx/react/large-boundary.expected.txt new file mode 100644 index 00000000..2f61467a --- /dev/null +++ b/tests/Jsx/react/large-boundary.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
  • Row number 0 with some text to make it long enough
  • Row number 1 with some text to make it long enough
  • Row number 2 with some text to make it long enough
  • Row number 3 with some text to make it long enough
  • Row number 4 with some text to make it long enough
  • Row number 5 with some text to make it long enough
  • Row number 6 with some text to make it long enough
  • Row number 7 with some text to make it long enough
  • Row number 8 with some text to make it long enough
  • Row number 9 with some text to make it long enough
  • Row number 10 with some text to make it long enough
  • Row number 11 with some text to make it long enough
  • Row number 12 with some text to make it long enough
  • Row number 13 with some text to make it long enough
  • Row number 14 with some text to make it long enough
  • Row number 15 with some text to make it long enough
  • Row number 16 with some text to make it long enough
  • Row number 17 with some text to make it long enough
  • Row number 18 with some text to make it long enough
  • Row number 19 with some text to make it long enough
  • Row number 20 with some text to make it long enough
  • Row number 21 with some text to make it long enough
  • Row number 22 with some text to make it long enough
  • Row number 23 with some text to make it long enough
  • Row number 24 with some text to make it long enough
  • Row number 25 with some text to make it long enough
  • Row number 26 with some text to make it long enough
  • Row number 27 with some text to make it long enough
  • Row number 28 with some text to make it long enough
  • Row number 29 with some text to make it long enough
  • Row number 30 with some text to make it long enough
  • Row number 31 with some text to make it long enough
  • Row number 32 with some text to make it long enough
  • Row number 33 with some text to make it long enough
  • Row number 34 with some text to make it long enough
  • Row number 35 with some text to make it long enough
  • Row number 36 with some text to make it long enough
  • Row number 37 with some text to make it long enough
  • Row number 38 with some text to make it long enough
  • Row number 39 with some text to make it long enough
  • Row number 40 with some text to make it long enough
  • Row number 41 with some text to make it long enough
  • Row number 42 with some text to make it long enough
  • Row number 43 with some text to make it long enough
  • Row number 44 with some text to make it long enough
  • Row number 45 with some text to make it long enough
  • Row number 46 with some text to make it long enough
  • Row number 47 with some text to make it long enough
  • Row number 48 with some text to make it long enough
  • Row number 49 with some text to make it long enough
  • Row number 50 with some text to make it long enough
  • Row number 51 with some text to make it long enough
  • Row number 52 with some text to make it long enough
  • Row number 53 with some text to make it long enough
  • Row number 54 with some text to make it long enough
  • Row number 55 with some text to make it long enough
  • Row number 56 with some text to make it long enough
  • Row number 57 with some text to make it long enough
  • Row number 58 with some text to make it long enough
  • Row number 59 with some text to make it long enough

small

", + "static": "
  • Row number 0 with some text to make it long enough
  • Row number 1 with some text to make it long enough
  • Row number 2 with some text to make it long enough
  • Row number 3 with some text to make it long enough
  • Row number 4 with some text to make it long enough
  • Row number 5 with some text to make it long enough
  • Row number 6 with some text to make it long enough
  • Row number 7 with some text to make it long enough
  • Row number 8 with some text to make it long enough
  • Row number 9 with some text to make it long enough
  • Row number 10 with some text to make it long enough
  • Row number 11 with some text to make it long enough
  • Row number 12 with some text to make it long enough
  • Row number 13 with some text to make it long enough
  • Row number 14 with some text to make it long enough
  • Row number 15 with some text to make it long enough
  • Row number 16 with some text to make it long enough
  • Row number 17 with some text to make it long enough
  • Row number 18 with some text to make it long enough
  • Row number 19 with some text to make it long enough
  • Row number 20 with some text to make it long enough
  • Row number 21 with some text to make it long enough
  • Row number 22 with some text to make it long enough
  • Row number 23 with some text to make it long enough
  • Row number 24 with some text to make it long enough
  • Row number 25 with some text to make it long enough
  • Row number 26 with some text to make it long enough
  • Row number 27 with some text to make it long enough
  • Row number 28 with some text to make it long enough
  • Row number 29 with some text to make it long enough
  • Row number 30 with some text to make it long enough
  • Row number 31 with some text to make it long enough
  • Row number 32 with some text to make it long enough
  • Row number 33 with some text to make it long enough
  • Row number 34 with some text to make it long enough
  • Row number 35 with some text to make it long enough
  • Row number 36 with some text to make it long enough
  • Row number 37 with some text to make it long enough
  • Row number 38 with some text to make it long enough
  • Row number 39 with some text to make it long enough
  • Row number 40 with some text to make it long enough
  • Row number 41 with some text to make it long enough
  • Row number 42 with some text to make it long enough
  • Row number 43 with some text to make it long enough
  • Row number 44 with some text to make it long enough
  • Row number 45 with some text to make it long enough
  • Row number 46 with some text to make it long enough
  • Row number 47 with some text to make it long enough
  • Row number 48 with some text to make it long enough
  • Row number 49 with some text to make it long enough
  • Row number 50 with some text to make it long enough
  • Row number 51 with some text to make it long enough
  • Row number 52 with some text to make it long enough
  • Row number 53 with some text to make it long enough
  • Row number 54 with some text to make it long enough
  • Row number 55 with some text to make it long enough
  • Row number 56 with some text to make it long enough
  • Row number 57 with some text to make it long enough
  • Row number 58 with some text to make it long enough
  • Row number 59 with some text to make it long enough

small

", + "prerender": "
  • Row number 0 with some text to make it long enough
  • Row number 1 with some text to make it long enough
  • Row number 2 with some text to make it long enough
  • Row number 3 with some text to make it long enough
  • Row number 4 with some text to make it long enough
  • Row number 5 with some text to make it long enough
  • Row number 6 with some text to make it long enough
  • Row number 7 with some text to make it long enough
  • Row number 8 with some text to make it long enough
  • Row number 9 with some text to make it long enough
  • Row number 10 with some text to make it long enough
  • Row number 11 with some text to make it long enough
  • Row number 12 with some text to make it long enough
  • Row number 13 with some text to make it long enough
  • Row number 14 with some text to make it long enough
  • Row number 15 with some text to make it long enough
  • Row number 16 with some text to make it long enough
  • Row number 17 with some text to make it long enough
  • Row number 18 with some text to make it long enough
  • Row number 19 with some text to make it long enough
  • Row number 20 with some text to make it long enough
  • Row number 21 with some text to make it long enough
  • Row number 22 with some text to make it long enough
  • Row number 23 with some text to make it long enough
  • Row number 24 with some text to make it long enough
  • Row number 25 with some text to make it long enough
  • Row number 26 with some text to make it long enough
  • Row number 27 with some text to make it long enough
  • Row number 28 with some text to make it long enough
  • Row number 29 with some text to make it long enough
  • Row number 30 with some text to make it long enough
  • Row number 31 with some text to make it long enough
  • Row number 32 with some text to make it long enough
  • Row number 33 with some text to make it long enough
  • Row number 34 with some text to make it long enough
  • Row number 35 with some text to make it long enough
  • Row number 36 with some text to make it long enough
  • Row number 37 with some text to make it long enough
  • Row number 38 with some text to make it long enough
  • Row number 39 with some text to make it long enough
  • Row number 40 with some text to make it long enough
  • Row number 41 with some text to make it long enough
  • Row number 42 with some text to make it long enough
  • Row number 43 with some text to make it long enough
  • Row number 44 with some text to make it long enough
  • Row number 45 with some text to make it long enough
  • Row number 46 with some text to make it long enough
  • Row number 47 with some text to make it long enough
  • Row number 48 with some text to make it long enough
  • Row number 49 with some text to make it long enough
  • Row number 50 with some text to make it long enough
  • Row number 51 with some text to make it long enough
  • Row number 52 with some text to make it long enough
  • Row number 53 with some text to make it long enough
  • Row number 54 with some text to make it long enough
  • Row number 55 with some text to make it long enough
  • Row number 56 with some text to make it long enough
  • Row number 57 with some text to make it long enough
  • Row number 58 with some text to make it long enough
  • Row number 59 with some text to make it long enough

small

" +} diff --git a/tests/Jsx/react/large-boundary.jsx b/tests/Jsx/react/large-boundary.jsx new file mode 100644 index 00000000..d9cbbbec --- /dev/null +++ b/tests/Jsx/react/large-boundary.jsx @@ -0,0 +1,12 @@ +import { Suspense } from 'react'; +const rows = Array.from({ length: 60 }, (_, i) => i); +export default function App() { + return ( +
+ loading

}> +
    {rows.map((i) =>
  • Row number {i} with some text to make it long enough
  • )}
+
+ small

}>

small

+
+ ); +} diff --git a/tests/Jsx/react/lazy.expected.txt b/tests/Jsx/react/lazy.expected.txt new file mode 100644 index 00000000..ee170545 --- /dev/null +++ b/tests/Jsx/react/lazy.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
waiting
", + "static": "
waiting
", + "prerender": "
lazy x
" +} diff --git a/tests/Jsx/react/lazy.jsx b/tests/Jsx/react/lazy.jsx new file mode 100644 index 00000000..157d26ad --- /dev/null +++ b/tests/Jsx/react/lazy.jsx @@ -0,0 +1,5 @@ +import { Suspense, lazy } from 'react'; +const Lazy = lazy(() => new Promise((r) => setTimeout(() => r({ default: ({ t }) => lazy {t} }), 2))); +export default function App() { + return
; +} diff --git a/tests/Jsx/react/nested-suspense-async.expected.txt b/tests/Jsx/react/nested-suspense-async.expected.txt new file mode 100644 index 00000000..2c839091 --- /dev/null +++ b/tests/Jsx/react/nested-suspense-async.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "

outer loading

", + "static": "

outer loading

", + "prerender": "
AB
" +} diff --git a/tests/Jsx/react/nested-suspense-async.jsx b/tests/Jsx/react/nested-suspense-async.jsx new file mode 100644 index 00000000..7132153a --- /dev/null +++ b/tests/Jsx/react/nested-suspense-async.jsx @@ -0,0 +1,10 @@ +import { Suspense } from 'react'; +const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms)); +async function A() { const v = await wait(3, 'A'); return
{v}
; } +async function B() { const v = await wait(2, 'B'); return {v}; } +export default function App() { + return outer loading

}>
; +} + +// React's development build reports async components on the client. +export const hydrateWarnings = ['is an async Client Component']; diff --git a/tests/Jsx/react/obsolete-elements.expected.txt b/tests/Jsx/react/obsolete-elements.expected.txt new file mode 100644 index 00000000..16b1554c --- /dev/null +++ b/tests/Jsx/react/obsolete-elements.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
cell outside a table
", + "static": "
cell outside a table
", + "prerender": "
cell outside a table
" +} diff --git a/tests/Jsx/react/obsolete-elements.jsx b/tests/Jsx/react/obsolete-elements.jsx new file mode 100644 index 00000000..9bcb9296 --- /dev/null +++ b/tests/Jsx/react/obsolete-elements.jsx @@ -0,0 +1,12 @@ +// Elements the HTML parser drops or browsers no longer know: React's server +// renderer still serializes them, so the bytes are pinned, but there is no +// valid DOM for the client to hydrate. +export default function App() { + return ( +
+ + cell outside a table +
+ ); +} +export const hydrate = false; diff --git a/tests/Jsx/react/portal-throws.expected.txt b/tests/Jsx/react/portal-throws.expected.txt new file mode 100644 index 00000000..d097cd52 --- /dev/null +++ b/tests/Jsx/react/portal-throws.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render.", + "static": "ERROR: Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render.", + "prerender": "ERROR: Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render." +} diff --git a/tests/Jsx/react/portal-throws.jsx b/tests/Jsx/react/portal-throws.jsx new file mode 100644 index 00000000..1dfc6b77 --- /dev/null +++ b/tests/Jsx/react/portal-throws.jsx @@ -0,0 +1,2 @@ +import { createPortal } from 'react-dom'; +export default function App() { return
{createPortal(

x

, { nodeType: 1 })}
; } diff --git a/tests/Jsx/react/precompile-shapes.expected.txt b/tests/Jsx/react/precompile-shapes.expected.txt new file mode 100644 index 00000000..61022d8a --- /dev/null +++ b/tests/Jsx/react/precompile-shapes.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
", + "static": "

Hello Ada!

3 items and 6 halves

textinlinemore

  • one
  • two bold Ada xy
  • tail
empty hrefstatic empty href
ab

loading fallback

© 2026
", + "prerender": "

Hello Ada!

3 items and 6 halves

textinlinemore

  • one
  • two bold Ada xy
  • tail
empty hrefstatic empty href
ab

before late value after

© 2026
" +} diff --git a/tests/Jsx/react/precompile-shapes.jsx b/tests/Jsx/react/precompile-shapes.jsx new file mode 100644 index 00000000..124c593b --- /dev/null +++ b/tests/Jsx/react/precompile-shapes.jsx @@ -0,0 +1,35 @@ +// Shapes that precompile turns into templates: nested static HTML with +// holes at different tree positions, text next to holes, attribute holes, +// event handlers, a suspending hole, and useId inside holes. +import { useId, Suspense, use } from 'react'; +function Id({ label }) { const id = useId(); return {label}; } +function Twice() { return [, ]; } +const late = new Promise((r) => setTimeout(() => r('late value'), 2)); +function Late() { return {use(late)}; } +export default function App({ name, n, href, cls }) { + return ( +
+

Hello {name}!

+

{n} items and {n * 2} halves

+

textmore

+
{}} hidden={n > 10} id="s"> +
    +
  • +
  • two bold {name}
  • +
  • {null}{false}tail
  • +
+ empty href + static empty href +
+ {'a'}{'b'} +
+
+ loading

}> +

before after

+
+
+
© {2026}
+
+ ); +} +export const props = { name: 'Ada', n: 3, href: '', cls: 'box' }; diff --git a/tests/Jsx/react/raw-text-escaping.expected.txt b/tests/Jsx/react/raw-text-escaping.expected.txt new file mode 100644 index 00000000..b3d98408 --- /dev/null +++ b/tests/Jsx/react/raw-text-escaping.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
", + "static": "
", + "prerender": "
" +} diff --git a/tests/Jsx/react/raw-text-escaping.jsx b/tests/Jsx/react/raw-text-escaping.jsx new file mode 100644 index 00000000..528c8855 --- /dev/null +++ b/tests/Jsx/react/raw-text-escaping.jsx @@ -0,0 +1,12 @@ +// React escapes ` + ", " + +
+ ); +} +export const hydrate = false; diff --git a/tests/Jsx/react/render-phase-update.expected.txt b/tests/Jsx/react/render-phase-update.expected.txt new file mode 100644 index 00000000..a0d35233 --- /dev/null +++ b/tests/Jsx/react/render-phase-update.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "

3

10,7

", + "static": "

3

10,7

", + "prerender": "

3

10,7

" +} diff --git a/tests/Jsx/react/render-phase-update.jsx b/tests/Jsx/react/render-phase-update.jsx new file mode 100644 index 00000000..11cefff8 --- /dev/null +++ b/tests/Jsx/react/render-phase-update.jsx @@ -0,0 +1,13 @@ +import { useState, useReducer } from 'react'; +function Counter() { + const [n, setN] = useState(0); + if (n < 3) setN(n + 1); + return

{n}

; +} +function Batched() { + const [n, setN] = useState(0); + const [r, dispatch] = useReducer((s, a) => s + a, 0); + if (n === 0) { setN((x) => x + 5); setN((x) => x * 2); dispatch(3); dispatch(4); } + return

{n},{r}

; +} +export default function App() { return
; } diff --git a/tests/Jsx/react/resource-apis.expected.txt b/tests/Jsx/react/resource-apis.expected.txt new file mode 100644 index 00000000..fd7892b1 --- /dev/null +++ b/tests/Jsx/react/resource-apis.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
resources
", + "static": "
resources
", + "prerender": "
resources
" +} diff --git a/tests/Jsx/react/resource-apis.jsx b/tests/Jsx/react/resource-apis.jsx new file mode 100644 index 00000000..30bc03de --- /dev/null +++ b/tests/Jsx/react/resource-apis.jsx @@ -0,0 +1,14 @@ +import { preload, preinit, preconnect, prefetchDNS, preloadModule, preinitModule } from 'react-dom'; +export default function App() { + prefetchDNS('https://dns.example'); + preconnect('https://cdn.example', { crossOrigin: 'anonymous' }); + preload('/font.woff2', { as: 'font', type: 'font/woff2' }); + preload('/img.png', { as: 'image', fetchPriority: 'high' }); + preload('/style.css', { as: 'style' }); + preload('/script.js', { as: 'script', integrity: 'sha256-x' }); + preloadModule('/mod.js'); + preinit('/styles.css', { as: 'style', precedence: 'medium' }); + preinit('/init.js', { as: 'script' }); + preinitModule('/init-mod.js'); + return
resources
; +} diff --git a/tests/Jsx/react/style-string-throws.expected.txt b/tests/Jsx/react/style-string-throws.expected.txt new file mode 100644 index 00000000..cde10a2e --- /dev/null +++ b/tests/Jsx/react/style-string-throws.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.", + "static": "ERROR: The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.", + "prerender": "ERROR: The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX." +} diff --git a/tests/Jsx/react/style-string-throws.jsx b/tests/Jsx/react/style-string-throws.jsx new file mode 100644 index 00000000..6b2093b5 --- /dev/null +++ b/tests/Jsx/react/style-string-throws.jsx @@ -0,0 +1,3 @@ +export default function App() { + return
x
; +} diff --git a/tests/Jsx/react/style.expected.txt b/tests/Jsx/react/style.expected.txt new file mode 100644 index 00000000..f7963ce0 --- /dev/null +++ b/tests/Jsx/react/style.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
s
", + "static": "
s
", + "prerender": "
s
" +} diff --git a/tests/Jsx/react/style.jsx b/tests/Jsx/react/style.jsx new file mode 100644 index 00000000..4b841a41 --- /dev/null +++ b/tests/Jsx/react/style.jsx @@ -0,0 +1,9 @@ +export default function App() { + return ( +
+
+
+ .png")' }}>s +
+ ); +} diff --git a/tests/Jsx/react/suspense-error.expected.txt b/tests/Jsx/react/suspense-error.expected.txt new file mode 100644 index 00000000..4ba3165c --- /dev/null +++ b/tests/Jsx/react/suspense-error.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "

fallback shown

after

", + "static": "

fallback shown

after

", + "prerender": "

fallback shown

after

" +} diff --git a/tests/Jsx/react/suspense-error.jsx b/tests/Jsx/react/suspense-error.jsx new file mode 100644 index 00000000..3e086852 --- /dev/null +++ b/tests/Jsx/react/suspense-error.jsx @@ -0,0 +1,13 @@ +import { Suspense } from 'react'; +function Boom() { throw new Error('boom'); } +export default function App() { + return ( +
+ fallback shown

}>before
+

after

+
+ ); +} + +// Throws on the client too, so there is nothing to hydrate cleanly. +export const hydrate = false; diff --git a/tests/Jsx/react/suspense-outside-boundary.expected.txt b/tests/Jsx/react/suspense-outside-boundary.expected.txt new file mode 100644 index 00000000..da449d4b --- /dev/null +++ b/tests/Jsx/react/suspense-outside-boundary.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.", + "static": "ERROR: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.", + "prerender": "

late

" +} diff --git a/tests/Jsx/react/suspense-outside-boundary.jsx b/tests/Jsx/react/suspense-outside-boundary.jsx new file mode 100644 index 00000000..18537627 --- /dev/null +++ b/tests/Jsx/react/suspense-outside-boundary.jsx @@ -0,0 +1,4 @@ +import { use } from 'react'; +const p = new Promise((r) => setTimeout(() => r('late'), 2)); +function Data() { return

{use(p)}

; } +export default function App() { return
; } diff --git a/tests/Jsx/react/suspense-resolved-promise.expected.txt b/tests/Jsx/react/suspense-resolved-promise.expected.txt new file mode 100644 index 00000000..34ac8dc2 --- /dev/null +++ b/tests/Jsx/react/suspense-resolved-promise.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "fallback", + "static": "fallback", + "prerender": "

already resolved

" +} diff --git a/tests/Jsx/react/suspense-resolved-promise.jsx b/tests/Jsx/react/suspense-resolved-promise.jsx new file mode 100644 index 00000000..c8b420bb --- /dev/null +++ b/tests/Jsx/react/suspense-resolved-promise.jsx @@ -0,0 +1,6 @@ +import { Suspense, use } from 'react'; +const ready = Promise.resolve('already resolved'); +function Data() { return

{use(ready)}

; } +export default function App() { + return fallback}>; +} diff --git a/tests/Jsx/react/suspense-resolved.expected.txt b/tests/Jsx/react/suspense-resolved.expected.txt new file mode 100644 index 00000000..ffe0fc01 --- /dev/null +++ b/tests/Jsx/react/suspense-resolved.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "

content

textinner texttailnested
", + "static": "

content

textinner texttailnested
", + "prerender": "

content

textinner texttailnested
" +} diff --git a/tests/Jsx/react/suspense-resolved.jsx b/tests/Jsx/react/suspense-resolved.jsx new file mode 100644 index 00000000..6df65b18 --- /dev/null +++ b/tests/Jsx/react/suspense-resolved.jsx @@ -0,0 +1,10 @@ +import { Suspense } from 'react'; +export default function App() { + return ( +
+ loading

}>

content

+ textinner texttail + nested +
+ ); +} diff --git a/tests/Jsx/react/suspense-use-promise.expected.txt b/tests/Jsx/react/suspense-use-promise.expected.txt new file mode 100644 index 00000000..9834b673 --- /dev/null +++ b/tests/Jsx/react/suspense-use-promise.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
loading aloading b
", + "static": "
loading aloading b
", + "prerender": "

data:a

data:b

data:c

" +} diff --git a/tests/Jsx/react/suspense-use-promise.jsx b/tests/Jsx/react/suspense-use-promise.jsx new file mode 100644 index 00000000..e981d45a --- /dev/null +++ b/tests/Jsx/react/suspense-use-promise.jsx @@ -0,0 +1,15 @@ +import { Suspense, use } from 'react'; +const cache = new Map(); +function load(key, ms) { + if (!cache.has(key)) cache.set(key, new Promise((r) => setTimeout(() => r('data:' + key), ms))); + return cache.get(key); +} +function Data({ k, ms }) { return

{use(load(k, ms))}

; } +export default function App() { + return ( +
+ loading a}> + loading b}> +
+ ); +} diff --git a/tests/Jsx/react/svg-math.expected.txt b/tests/Jsx/react/svg-math.expected.txt new file mode 100644 index 00000000..d89d4fc6 --- /dev/null +++ b/tests/Jsx/react/svg-math.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
Icon title
html inside
x=2
", + "static": "
Icon title
html inside
x=2
", + "prerender": "
Icon title
html inside
x=2
" +} diff --git a/tests/Jsx/react/svg-math.jsx b/tests/Jsx/react/svg-math.jsx new file mode 100644 index 00000000..0dfe78a2 --- /dev/null +++ b/tests/Jsx/react/svg-math.jsx @@ -0,0 +1,14 @@ +export default function App() { + return ( +
+ + Icon title + + +
html inside
+ +
+ x=2 +
+ ); +} diff --git a/tests/Jsx/react/tables.expected.txt b/tests/Jsx/react/tables.expected.txt new file mode 100644 index 00000000..38e4cdc7 --- /dev/null +++ b/tests/Jsx/react/tables.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "
h
1…
", + "static": "
h
1…
", + "prerender": "
h
1cell
" +} diff --git a/tests/Jsx/react/tables.jsx b/tests/Jsx/react/tables.jsx new file mode 100644 index 00000000..0f1c87a9 --- /dev/null +++ b/tests/Jsx/react/tables.jsx @@ -0,0 +1,14 @@ +import { Suspense, use } from 'react'; +const p = new Promise((r) => setTimeout(() => r('cell'), 2)); +function Cell() { return {use(p)}; } +export default function App() { + return ( + + + + + …}> + +
h
1
+ ); +} diff --git a/tests/Jsx/react/text-markers.expected.txt b/tests/Jsx/react/text-markers.expected.txt new file mode 100644 index 00000000..c76fdb23 --- /dev/null +++ b/tests/Jsx/react/text-markers.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "

Hello Dennis

3 items, 6 halves and done

abc001e+210.3000000000000000412345678901234567890root text more
", + "static": "

Hello Dennis

3 items, 6 halves and done

abc001e+210.3000000000000000412345678901234567890root text more
", + "prerender": "

Hello Dennis

3 items, 6 halves and done

abc001e+210.3000000000000000412345678901234567890root text more
" +} diff --git a/tests/Jsx/react/text-markers.jsx b/tests/Jsx/react/text-markers.jsx new file mode 100644 index 00000000..c52316a5 --- /dev/null +++ b/tests/Jsx/react/text-markers.jsx @@ -0,0 +1,12 @@ +export default function App({ name, count }) { + return ( +
+

Hello {name}

+

{count} items, {count * 2} halves {'and'} {null}{false}{undefined}{true} done

+ {'a'}{'b'}{''}{'c'} + {0}{-0}{1e21}{0.1 + 0.2}{12345678901234567890n} + {'root text'}{' more'} +
+ ); +} +export const props = { name: 'Dennis', count: 3 }; diff --git a/tests/Jsx/react/too-many-rerenders-throws.expected.txt b/tests/Jsx/react/too-many-rerenders-throws.expected.txt new file mode 100644 index 00000000..fe9ae52f --- /dev/null +++ b/tests/Jsx/react/too-many-rerenders-throws.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "ERROR: Too many re-renders. React limits the number of renders to prevent an infinite loop.", + "static": "ERROR: Too many re-renders. React limits the number of renders to prevent an infinite loop.", + "prerender": "ERROR: Too many re-renders. React limits the number of renders to prevent an infinite loop." +} diff --git a/tests/Jsx/react/too-many-rerenders-throws.jsx b/tests/Jsx/react/too-many-rerenders-throws.jsx new file mode 100644 index 00000000..79b2c117 --- /dev/null +++ b/tests/Jsx/react/too-many-rerenders-throws.jsx @@ -0,0 +1,6 @@ +import { useState } from 'react'; +export default function App() { + const [n, setN] = useState(0); + setN(n + 1); + return

{n}

; +} diff --git a/tests/Jsx/react/urls.expected.txt b/tests/Jsx/react/urls.expected.txt new file mode 100644 index 00000000..b5f966ca --- /dev/null +++ b/tests/Jsx/react/urls.expected.txt @@ -0,0 +1,5 @@ +{ + "string": "", + "static": "", + "prerender": "" +} diff --git a/tests/Jsx/react/urls.jsx b/tests/Jsx/react/urls.jsx new file mode 100644 index 00000000..8271258b --- /dev/null +++ b/tests/Jsx/react/urls.jsx @@ -0,0 +1,13 @@ +export default function App() { + return ( +
+ a + b +
+