acrylblends 0.6.3 → 0.6.4

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 (269) hide show
  1. package/README.md +3 -2
  2. package/docs/framework/README.md +2 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/guides/jsx.md +24 -0
  5. package/docs/framework/helpers/ssg.md +3 -6
  6. package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
  7. package/docs/framework/upstream.json +2 -2
  8. package/lib/accepts.d.ts +1 -1
  9. package/lib/acryl-base.d.ts +1 -1
  10. package/lib/adapter.d.ts +1 -1
  11. package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
  12. package/lib/basic-auth.d.ts +1 -1
  13. package/lib/bearer-auth.d.ts +1 -1
  14. package/lib/body-limit.d.ts +1 -1
  15. package/lib/cache.d.ts +1 -1
  16. package/lib/cache.js +4 -4
  17. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  18. package/lib/client.d.ts +2 -2
  19. package/lib/client.js +2 -1
  20. package/lib/combine.d.ts +1 -1
  21. package/lib/{components-Ce5FRyHU.js → components-0o0CBYNv.js} +2 -2
  22. package/lib/{components-DYZdxJIJ.js → components-DdI7-ns7.js} +1 -1
  23. package/lib/compress.d.ts +1 -1
  24. package/lib/conninfo.d.ts +2 -2
  25. package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
  26. package/lib/context-storage.d.ts +1 -1
  27. package/lib/cookie.d.ts +1 -1
  28. package/lib/cors.d.ts +1 -1
  29. package/lib/csrf.d.ts +1 -1
  30. package/lib/dev.d.ts +2 -2
  31. package/lib/etag.d.ts +1 -1
  32. package/lib/factory.d.ts +2 -2
  33. package/lib/{hooks-Bq5q9IMl.js → hooks-BMw6BAyP.js} +5 -5
  34. package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
  35. package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
  36. package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
  37. package/lib/index.d.ts +4 -4
  38. package/lib/ip-restriction.d.ts +2 -2
  39. package/lib/jsx/dom/client.js +1 -1
  40. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  41. package/lib/jsx/dom/jsx-runtime.js +1 -1
  42. package/lib/jsx/dom/server.js +1 -1
  43. package/lib/jsx/dom.js +3 -3
  44. package/lib/jsx/streaming.js +1 -1
  45. package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-D18Y35TI.js} +1 -1
  46. package/lib/jsx-renderer.d.ts +75 -0
  47. package/lib/jsx-renderer.js +99 -0
  48. package/lib/jsx.js +3 -3
  49. package/lib/jwk.d.ts +1 -1
  50. package/lib/jwt.d.ts +1 -1
  51. package/lib/language.d.ts +1 -1
  52. package/lib/logger.d.ts +1 -1
  53. package/lib/loopback-only.d.ts +1 -1
  54. package/lib/method-not-allowed.d.ts +2 -2
  55. package/lib/method-override.d.ts +2 -2
  56. package/lib/mount.d.ts +1 -1
  57. package/lib/node-server/conninfo.d.ts +1 -1
  58. package/lib/node-server/serve-static.d.ts +1 -1
  59. package/lib/node-server/serve-static.js +1 -1
  60. package/lib/node-server.d.ts +1 -1
  61. package/lib/node-server.js +1 -1
  62. package/lib/powered-by.d.ts +1 -1
  63. package/lib/pretty-json.d.ts +1 -1
  64. package/lib/quick.d.ts +1 -1
  65. package/lib/request-id.d.ts +1 -1
  66. package/lib/request.d.ts +1 -1
  67. package/lib/route.d.ts +1 -1
  68. package/lib/schema-validator.d.ts +1 -1
  69. package/lib/secure-headers.d.ts +1 -1
  70. package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
  71. package/lib/serve-static.d.ts +1 -1
  72. package/lib/ssg-BwjcA-Ge.js +389 -0
  73. package/lib/ssg.d.ts +156 -0
  74. package/lib/ssg.js +2 -0
  75. package/lib/streaming.d.ts +1 -1
  76. package/lib/testing.d.ts +3 -3
  77. package/lib/testing.js +1 -1
  78. package/lib/timeout.d.ts +1 -1
  79. package/lib/timing.d.ts +1 -1
  80. package/lib/tiny.d.ts +1 -1
  81. package/lib/trailing-slash.d.ts +1 -1
  82. package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  83. package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
  84. package/lib/types.d.ts +1 -1
  85. package/lib/utils/body.d.ts +1 -1
  86. package/lib/utils/ipaddr.d.ts +1 -1
  87. package/lib/utils-Cx61GFMC.js +125 -0
  88. package/lib/validator.d.ts +1 -1
  89. package/lib/ws.d.ts +1 -1
  90. package/package.json +9 -1
  91. package/plugins/acryl-inspect/upstream.json +1 -1
  92. package/plugins/acryl-node-server/upstream.json +1 -1
  93. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  94. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  95. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  96. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  97. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  98. package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
  99. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  100. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  101. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  102. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  103. package/plugins/acryl-router/upstream.json +2 -2
  104. package/plugins/acryl-router-http/upstream.json +1 -1
  105. package/plugins/acryl-state/upstream.json +1 -1
  106. package/plugins/desktop/upstream.json +1 -1
  107. package/plugins/desktop-web/upstream.json +1 -1
  108. package/plugins/extensions/resolve.ts +4 -0
  109. package/shared/AGENTS.core.md +4 -0
  110. package/templates/agent/tsconfig.json +7 -1
  111. package/templates/todo/upstream.json +1 -1
  112. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  113. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  114. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  115. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  116. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  117. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  118. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  119. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  120. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  121. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  122. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  123. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  124. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  125. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  126. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  127. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  128. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  129. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  130. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  131. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  132. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  133. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  134. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  135. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  136. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  137. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  138. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  139. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  140. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  141. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  142. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  143. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  144. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  145. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  146. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  147. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  148. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  149. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  150. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  151. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  152. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  153. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  154. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  155. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  156. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  157. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  158. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  159. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  160. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  161. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
  162. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
  163. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  164. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  165. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  166. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  167. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  168. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
  169. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  170. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  171. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  172. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  173. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  174. package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
  175. package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
  176. package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
  177. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
  178. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
  179. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
  180. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
  181. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
  182. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
  183. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
  184. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
  185. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
  186. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
  187. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
  188. package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
  189. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
  190. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
  191. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
  192. package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
  193. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  194. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  195. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  196. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  197. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  198. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  199. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  200. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  201. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  202. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  203. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  204. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  205. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  206. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  207. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  208. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  209. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  210. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  211. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  212. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  213. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  214. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  215. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  216. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  217. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  218. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  219. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  220. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  221. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  222. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  223. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  224. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  225. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  226. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  227. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  228. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  229. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  230. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  231. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  232. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  233. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  234. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  235. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  236. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  237. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  238. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  239. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  240. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  241. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  242. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  243. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  244. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  245. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  246. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  247. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  248. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  249. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  250. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  251. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  252. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  253. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  254. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  255. package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
  256. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  257. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  258. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  259. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  260. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  261. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  262. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  263. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  264. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  265. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  266. package/plugins/acryl-state/src/react.ts +0 -11
  267. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  268. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  269. package/plugins/acryl-state/tests/service.spec.ts +0 -132
@@ -1,936 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /** @jsxImportSource ./ */
3
- import { JSDOM } from 'jsdom'
4
- import { raw } from '../helper/html/index.ts'
5
- import type { HtmlEscapedString } from '../utils/html.ts'
6
- import { HtmlEscapedCallbackPhase, resolveCallback as rawResolveCallback } from '../utils/html.ts'
7
- import { ErrorBoundary } from './components.ts'
8
- import { Suspense, renderToReadableStream, StreamingContext } from './streaming.ts'
9
-
10
- const unsafeHtml = '<img src=x onerror=alert(1)>'
11
- const escapedUnsafeHtml = '&lt;img src=x onerror=alert(1)&gt;'
12
-
13
- function resolveCallback(template: string | HtmlEscapedString) {
14
- return rawResolveCallback(template, HtmlEscapedCallbackPhase.Stream, false, {})
15
- }
16
-
17
- function replacementResult(html: string) {
18
- const document = new JSDOM(html, { runScripts: 'dangerously' }).window.document
19
- document.querySelectorAll('template, script').forEach((e) => e.remove())
20
- return document.body.innerHTML
21
- }
22
-
23
- async function stringify(node: { toString(): string | Promise<string> }): Promise<string> {
24
- return String(
25
- await rawResolveCallback(await node.toString(), HtmlEscapedCallbackPhase.Stringify, false, {})
26
- )
27
- }
28
-
29
- async function drainStream(stream: ReadableStream<Uint8Array>): Promise<string> {
30
- const decoder = new TextDecoder()
31
- let result = ''
32
- for await (const chunk of stream) {
33
- result += decoder.decode(chunk)
34
- }
35
- return result
36
- }
37
-
38
- const Fallback = () => <div>Out Of Service</div>
39
-
40
- describe('ErrorBoundary', () => {
41
- let errorBoundaryCounter = 0
42
- let suspenseCounter = 0
43
- afterEach(() => {
44
- errorBoundaryCounter++
45
- suspenseCounter++
46
- })
47
-
48
- describe('sync', async () => {
49
- const Component = ({ error }: { error?: boolean }) => {
50
- if (error) {
51
- throw new Error('Error')
52
- }
53
- return <div>Hello</div>
54
- }
55
-
56
- it('no error', async () => {
57
- const html = (
58
- <ErrorBoundary fallback={<Fallback />}>
59
- <Component />
60
- </ErrorBoundary>
61
- )
62
-
63
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')
64
-
65
- errorBoundaryCounter--
66
- suspenseCounter--
67
- })
68
-
69
- it('error', async () => {
70
- const html = (
71
- <ErrorBoundary fallback={<Fallback />}>
72
- <Component error={true} />
73
- </ErrorBoundary>
74
- )
75
-
76
- expect((await resolveCallback(await html.toString())).toString()).toEqual(
77
- '<div>Out Of Service</div>'
78
- )
79
-
80
- suspenseCounter--
81
- })
82
-
83
- it('nullish', async () => {
84
- const html = (
85
- <div>
86
- <ErrorBoundary fallback={<Fallback />}>{[null, undefined]}</ErrorBoundary>
87
- </div>
88
- )
89
-
90
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div></div>')
91
-
92
- errorBoundaryCounter--
93
- suspenseCounter--
94
- })
95
-
96
- it('boolean', async () => {
97
- const html = (
98
- <div>
99
- <ErrorBoundary fallback={<Fallback />}>{[true, false]}</ErrorBoundary>
100
- </div>
101
- )
102
-
103
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div></div>')
104
-
105
- errorBoundaryCounter--
106
- suspenseCounter--
107
- })
108
-
109
- it('string content', async () => {
110
- const html = <ErrorBoundary fallback={<Fallback />}>{'< ok >'}</ErrorBoundary>
111
-
112
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; ok &gt;')
113
-
114
- errorBoundaryCounter--
115
- suspenseCounter--
116
- })
117
-
118
- it('error: string content', async () => {
119
- const html = (
120
- <ErrorBoundary fallback={'< error >'}>
121
- <Component error={true} />
122
- </ErrorBoundary>
123
- )
124
-
125
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
126
-
127
- errorBoundaryCounter--
128
- suspenseCounter--
129
- })
130
-
131
- it('error: Promise<string> from fallback', async () => {
132
- const html = (
133
- <ErrorBoundary fallback={Promise.resolve('< error >')}>
134
- <Component error={true} />
135
- </ErrorBoundary>
136
- )
137
-
138
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
139
-
140
- errorBoundaryCounter--
141
- suspenseCounter--
142
- })
143
-
144
- it('error: string content from fallbackRender', async () => {
145
- const html = (
146
- <ErrorBoundary fallbackRender={() => '< error >'}>
147
- <Component error={true} />
148
- </ErrorBoundary>
149
- )
150
-
151
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
152
-
153
- errorBoundaryCounter--
154
- suspenseCounter--
155
- })
156
-
157
- it('error: Promise<string> from fallbackRender', async () => {
158
- const html = (
159
- <ErrorBoundary fallbackRender={() => Promise.resolve('< error >')}>
160
- <Component error={true} />
161
- </ErrorBoundary>
162
- )
163
-
164
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
165
-
166
- errorBoundaryCounter--
167
- suspenseCounter--
168
- })
169
- })
170
-
171
- describe('async', async () => {
172
- const Component = async ({ error }: { error?: boolean }) => {
173
- await new Promise((resolve) => setTimeout(resolve, 10))
174
- if (error) {
175
- throw new Error('Error')
176
- }
177
- return <div>Hello</div>
178
- }
179
-
180
- it('no error', async () => {
181
- const html = (
182
- <ErrorBoundary fallback={<Fallback />}>
183
- <Component />
184
- </ErrorBoundary>
185
- )
186
-
187
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')
188
-
189
- errorBoundaryCounter--
190
- suspenseCounter--
191
- })
192
-
193
- it('error', async () => {
194
- const html = (
195
- <ErrorBoundary fallback={<Fallback />}>
196
- <Component error={true} />
197
- </ErrorBoundary>
198
- )
199
-
200
- expect((await resolveCallback(await html.toString())).toString()).toEqual(
201
- '<div>Out Of Service</div>'
202
- )
203
-
204
- suspenseCounter--
205
- })
206
-
207
- it('error: string content', async () => {
208
- const html = (
209
- <ErrorBoundary fallback={'< error >'}>
210
- <Component error={true} />
211
- </ErrorBoundary>
212
- )
213
-
214
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
215
-
216
- suspenseCounter--
217
- })
218
-
219
- it('error: Promise<string> from fallback', async () => {
220
- const html = (
221
- <ErrorBoundary fallback={Promise.resolve('< error >')}>
222
- <Component error={true} />
223
- </ErrorBoundary>
224
- )
225
-
226
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
227
-
228
- suspenseCounter--
229
- })
230
-
231
- it('error: string content from fallbackRender', async () => {
232
- const html = (
233
- <ErrorBoundary fallbackRender={() => '< error >'}>
234
- <Component error={true} />
235
- </ErrorBoundary>
236
- )
237
-
238
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
239
-
240
- suspenseCounter--
241
- })
242
-
243
- it('error: Promise<string> from fallbackRender', async () => {
244
- const html = (
245
- <ErrorBoundary fallbackRender={() => Promise.resolve('< error >')}>
246
- <Component error={true} />
247
- </ErrorBoundary>
248
- )
249
-
250
- expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')
251
-
252
- suspenseCounter--
253
- })
254
- })
255
-
256
- describe('async : nested', async () => {
257
- const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}
258
- const Component = async ({ id }: { id: number }) => {
259
- await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))
260
- return <div>{id}</div>
261
- }
262
-
263
- it('no error', async () => {
264
- const html = (
265
- <ErrorBoundary fallback={<Fallback />}>
266
- <Component id={1} />
267
- <ErrorBoundary fallback={<Fallback />}>
268
- <Component id={2} />
269
- </ErrorBoundary>
270
- </ErrorBoundary>
271
- ).toString()
272
-
273
- Object.values(handlers).forEach(({ resolve }) => resolve(undefined))
274
-
275
- expect((await resolveCallback(await html)).toString()).toEqual('<div>1</div><div>2</div>')
276
-
277
- errorBoundaryCounter++
278
- suspenseCounter--
279
- })
280
-
281
- it('error in parent', async () => {
282
- const html = (
283
- <ErrorBoundary fallback={<Fallback />}>
284
- <Component id={1} />
285
- <ErrorBoundary fallback={<Fallback />}>
286
- <Component id={2} />
287
- </ErrorBoundary>
288
- </ErrorBoundary>
289
- ).toString()
290
-
291
- handlers[2].resolve(undefined)
292
- handlers[1].reject()
293
-
294
- expect((await resolveCallback(await html)).toString()).toEqual('<div>Out Of Service</div>')
295
-
296
- errorBoundaryCounter++
297
- suspenseCounter--
298
- })
299
-
300
- it('error in child', async () => {
301
- const html = (
302
- <ErrorBoundary fallback={<Fallback />}>
303
- <Component id={1} />
304
- <ErrorBoundary fallback={<Fallback />}>
305
- <Component id={2} />
306
- </ErrorBoundary>
307
- </ErrorBoundary>
308
- ).toString()
309
-
310
- handlers[1].resolve(undefined)
311
- handlers[2].reject()
312
-
313
- expect((await resolveCallback(await html)).toString()).toEqual(
314
- '<div>1</div><div>Out Of Service</div>'
315
- )
316
-
317
- errorBoundaryCounter++
318
- suspenseCounter--
319
- })
320
- })
321
-
322
- describe('async : setTimeout', async () => {
323
- const TimeoutSuccessComponent = async () => {
324
- await new Promise((resolve) => setTimeout(resolve, 10))
325
- return <div>OK</div>
326
- }
327
- const TimeoutErrorComponent = async () => {
328
- await new Promise((resolve) => setTimeout(resolve, 0))
329
- throw new Error('Error')
330
- }
331
-
332
- it('fallback', async () => {
333
- const html = (
334
- <>
335
- <TimeoutSuccessComponent />
336
- <ErrorBoundary fallback={<Fallback />}>
337
- <TimeoutErrorComponent />
338
- </ErrorBoundary>
339
- </>
340
- ).toString()
341
-
342
- expect((await resolveCallback(await html)).toString()).toEqual(
343
- '<div>OK</div><div>Out Of Service</div>'
344
- )
345
-
346
- suspenseCounter--
347
- })
348
- })
349
-
350
- describe('streaming', async () => {
351
- const Component = async ({ error }: { error?: boolean }) => {
352
- await new Promise((resolve) => setTimeout(resolve, 10))
353
- if (error) {
354
- throw new Error('Error')
355
- }
356
- return <div>Hello</div>
357
- }
358
-
359
- it('no error', async () => {
360
- const stream = renderToReadableStream(
361
- <ErrorBoundary fallback={<Fallback />}>
362
- <Suspense fallback={<p>Loading...</p>}>
363
- <Component />
364
- </Suspense>
365
- </ErrorBoundary>
366
- )
367
- const chunks = []
368
- const textDecoder = new TextDecoder()
369
- for await (const chunk of stream as any) {
370
- chunks.push(textDecoder.decode(chunk))
371
- }
372
-
373
- expect(chunks).toEqual([
374
- `<template id="E:${errorBoundaryCounter}"></template><!--E:${errorBoundaryCounter}-->`,
375
- `<template data-acryl-target="E:${errorBoundaryCounter}"><template id="H:${suspenseCounter}"></template><p>Loading...</p><!--/$--></template><script>
376
- ((d,c) => {
377
- c=d.currentScript.previousSibling
378
- d=d.getElementById('E:${errorBoundaryCounter}')
379
- if(!d)return
380
- d.parentElement.insertBefore(c.content,d.nextSibling)
381
- })(document)
382
- </script>`,
383
- `<template data-acryl-target="H:${suspenseCounter}"><div>Hello</div></template><script>
384
- ((d,c,n) => {
385
- c=d.currentScript.previousSibling
386
- d=d.getElementById('H:${suspenseCounter}')
387
- if(!d)return
388
- do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
389
- d.replaceWith(c.content)
390
- })(document)
391
- </script><script>
392
- ((d,c,n) => {
393
- d=d.getElementById('E:${errorBoundaryCounter}')
394
- if(!d)return
395
- n=d.nextSibling
396
- while(n.nodeType!=8||n.nodeValue!='E:${errorBoundaryCounter}'){n=n.nextSibling}
397
- n.remove()
398
- d.remove()
399
- })(document)
400
- </script>`,
401
- ])
402
-
403
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
404
- '<div>Hello</div>'
405
- )
406
- })
407
-
408
- it('with StreamingContext', async () => {
409
- const stream = renderToReadableStream(
410
- <StreamingContext value={{ scriptNonce: 'test-nonce' }}>
411
- <ErrorBoundary fallback={<Fallback />}>
412
- <Suspense fallback={<p>Loading...</p>}>
413
- <Component />
414
- </Suspense>
415
- </ErrorBoundary>
416
- </StreamingContext>
417
- )
418
- const chunks = []
419
- const textDecoder = new TextDecoder()
420
- for await (const chunk of stream as any) {
421
- chunks.push(textDecoder.decode(chunk))
422
- }
423
-
424
- expect(chunks).toEqual([
425
- `<template id="E:${errorBoundaryCounter}"></template><!--E:${errorBoundaryCounter}-->`,
426
- `<template data-acryl-target="E:${errorBoundaryCounter}"><template id="H:${suspenseCounter}"></template><p>Loading...</p><!--/$--></template><script nonce="test-nonce">
427
- ((d,c) => {
428
- c=d.currentScript.previousSibling
429
- d=d.getElementById('E:${errorBoundaryCounter}')
430
- if(!d)return
431
- d.parentElement.insertBefore(c.content,d.nextSibling)
432
- })(document)
433
- </script>`,
434
- `<template data-acryl-target="H:${suspenseCounter}"><div>Hello</div></template><script nonce="test-nonce">
435
- ((d,c,n) => {
436
- c=d.currentScript.previousSibling
437
- d=d.getElementById('H:${suspenseCounter}')
438
- if(!d)return
439
- do{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')
440
- d.replaceWith(c.content)
441
- })(document)
442
- </script><script>
443
- ((d,c,n) => {
444
- d=d.getElementById('E:${errorBoundaryCounter}')
445
- if(!d)return
446
- n=d.nextSibling
447
- while(n.nodeType!=8||n.nodeValue!='E:${errorBoundaryCounter}'){n=n.nextSibling}
448
- n.remove()
449
- d.remove()
450
- })(document)
451
- </script>`,
452
- ])
453
-
454
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
455
- '<div>Hello</div>'
456
- )
457
- })
458
-
459
- it('error', async () => {
460
- const html = (
461
- <ErrorBoundary fallback={<Fallback />}>
462
- <Component error={true} />
463
- </ErrorBoundary>
464
- )
465
-
466
- expect((await resolveCallback(await html.toString())).toString()).toEqual(
467
- '<div>Out Of Service</div>'
468
- )
469
- })
470
- })
471
-
472
- describe('streaming : contains multiple suspense', async () => {
473
- const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}
474
- const Component = async ({ id }: { id: number }) => {
475
- await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))
476
- return <div>{id}</div>
477
- }
478
-
479
- it('no error', async () => {
480
- const stream = renderToReadableStream(
481
- <ErrorBoundary fallback={<Fallback />}>
482
- <Suspense fallback={<p>Loading...</p>}>
483
- <Component id={1} />
484
- </Suspense>
485
- <Suspense fallback={<p>Loading...</p>}>
486
- <Component id={2} />
487
- </Suspense>
488
- <Suspense fallback={<p>Loading...</p>}>
489
- <Component id={3} />
490
- </Suspense>
491
- </ErrorBoundary>
492
- )
493
-
494
- Object.values(handlers).forEach(({ resolve }) => resolve(undefined))
495
-
496
- const chunks = []
497
- const textDecoder = new TextDecoder()
498
- for await (const chunk of stream as any) {
499
- chunks.push(textDecoder.decode(chunk))
500
- }
501
-
502
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
503
- '<div>1</div><div>2</div><div>3</div>'
504
- )
505
- })
506
-
507
- it('error', async () => {
508
- const stream = renderToReadableStream(
509
- <ErrorBoundary fallback={<Fallback />}>
510
- <Suspense fallback={<p>Loading...</p>}>
511
- <Component id={1} />
512
- </Suspense>
513
- <Suspense fallback={<p>Loading...</p>}>
514
- <Component id={2} />
515
- </Suspense>
516
- <Suspense fallback={<p>Loading...</p>}>
517
- <Component id={3} />
518
- </Suspense>
519
- </ErrorBoundary>
520
- )
521
-
522
- handlers[1].resolve(undefined)
523
- handlers[2].resolve(undefined)
524
- handlers[3].reject()
525
-
526
- const chunks = []
527
- const textDecoder = new TextDecoder()
528
- for await (const chunk of stream as any) {
529
- chunks.push(textDecoder.decode(chunk))
530
- }
531
-
532
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
533
- '<div>Out Of Service</div>'
534
- )
535
- })
536
-
537
- it('renders fallback and reports error once when multiple children reject', async () => {
538
- let fallbackRenderCount = 0
539
- let onErrorCount = 0
540
- const stream = renderToReadableStream(
541
- <ErrorBoundary
542
- fallbackRender={() => {
543
- fallbackRenderCount++
544
- return <Fallback />
545
- }}
546
- onError={() => {
547
- onErrorCount++
548
- }}
549
- >
550
- <Suspense fallback={<p>Loading...</p>}>
551
- <Component id={1} />
552
- </Suspense>
553
- <Suspense fallback={<p>Loading...</p>}>
554
- <Component id={2} />
555
- </Suspense>
556
- <Suspense fallback={<p>Loading...</p>}>
557
- <Component id={3} />
558
- </Suspense>
559
- </ErrorBoundary>
560
- )
561
-
562
- handlers[1].reject()
563
- handlers[2].reject()
564
- handlers[3].resolve(undefined)
565
-
566
- const chunks = []
567
- const textDecoder = new TextDecoder()
568
- for await (const chunk of stream as any) {
569
- chunks.push(textDecoder.decode(chunk))
570
- }
571
-
572
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
573
- '<div>Out Of Service</div>'
574
- )
575
- expect(fallbackRenderCount).toBe(1)
576
- expect(onErrorCount).toBe(1)
577
- })
578
-
579
- it('preserves callbacks from a thenable fallback', async () => {
580
- const fallback = Promise.resolve(
581
- raw('<span>Recovering</span>', [
582
- ({ phase }) =>
583
- phase === HtmlEscapedCallbackPhase.Stream
584
- ? Promise.resolve('<span>Recovered</span>')
585
- : undefined,
586
- ])
587
- )
588
- const stream = renderToReadableStream(
589
- <ErrorBoundary fallback={fallback}>
590
- <Suspense fallback={<p>Loading...</p>}>
591
- <Component id={1} />
592
- </Suspense>
593
- </ErrorBoundary>
594
- )
595
-
596
- handlers[1].reject()
597
-
598
- const chunks = []
599
- const textDecoder = new TextDecoder()
600
- for await (const chunk of stream as any) {
601
- chunks.push(textDecoder.decode(chunk))
602
- }
603
-
604
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
605
- '<span>Recovering</span><span>Recovered</span>'
606
- )
607
- })
608
-
609
- it('waits for an in-flight async fallback when multiple children reject', async () => {
610
- let resolveFallback!: (value: HtmlEscapedString | PromiseLike<HtmlEscapedString>) => void
611
- const fallback = new Promise<HtmlEscapedString>((resolve) => {
612
- resolveFallback = resolve
613
- })
614
-
615
- const stream = renderToReadableStream(
616
- <ErrorBoundary fallback={fallback}>
617
- <Suspense fallback={<p>Loading...</p>}>
618
- <Component id={1} />
619
- </Suspense>
620
- <Suspense fallback={<p>Loading...</p>}>
621
- <Component id={2} />
622
- </Suspense>
623
- <Suspense fallback={<p>Loading...</p>}>
624
- <Component id={3} />
625
- </Suspense>
626
- </ErrorBoundary>
627
- )
628
-
629
- handlers[1].reject()
630
- handlers[2].reject()
631
- handlers[3].resolve(undefined)
632
- await Promise.resolve()
633
- resolveFallback(<Fallback />)
634
-
635
- const chunks = []
636
- const textDecoder = new TextDecoder()
637
- for await (const chunk of stream as any) {
638
- chunks.push(textDecoder.decode(chunk))
639
- }
640
-
641
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
642
- '<div>Out Of Service</div>'
643
- )
644
- })
645
- })
646
-
647
- describe('streaming : nested', async () => {
648
- const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}
649
- const Component = async ({ id }: { id: number }) => {
650
- await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))
651
- return <div>{id}</div>
652
- }
653
-
654
- it('no error', async () => {
655
- const stream = renderToReadableStream(
656
- <ErrorBoundary fallback={<Fallback />}>
657
- <Suspense fallback={<p>Loading...</p>}>
658
- <Component id={1} />
659
- </Suspense>
660
- <ErrorBoundary fallback={<Fallback />}>
661
- <Suspense fallback={<p>Loading...</p>}>
662
- <Component id={2} />
663
- </Suspense>
664
- </ErrorBoundary>
665
- </ErrorBoundary>
666
- )
667
-
668
- Object.values(handlers).forEach(({ resolve }) => resolve(undefined))
669
-
670
- const chunks = []
671
- const textDecoder = new TextDecoder()
672
- for await (const chunk of stream as any) {
673
- chunks.push(textDecoder.decode(chunk))
674
- }
675
-
676
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
677
- '<div>1</div><div>2</div>'
678
- )
679
- })
680
-
681
- it('error in parent', async () => {
682
- const stream = renderToReadableStream(
683
- <ErrorBoundary fallback={<Fallback />}>
684
- <Suspense fallback={<p>Loading...</p>}>
685
- <Component id={1} />
686
- </Suspense>
687
- <ErrorBoundary fallback={<Fallback />}>
688
- <Suspense fallback={<p>Loading...</p>}>
689
- <Component id={2} />
690
- </Suspense>
691
- </ErrorBoundary>
692
- </ErrorBoundary>
693
- )
694
-
695
- handlers[2].resolve(undefined)
696
- handlers[1].reject()
697
-
698
- const chunks = []
699
- const textDecoder = new TextDecoder()
700
- for await (const chunk of stream as any) {
701
- chunks.push(textDecoder.decode(chunk))
702
- }
703
-
704
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
705
- '<div>Out Of Service</div>'
706
- )
707
- })
708
-
709
- it('error in child', async () => {
710
- const stream = renderToReadableStream(
711
- <ErrorBoundary fallback={<Fallback />}>
712
- <Suspense fallback={<p>Loading...</p>}>
713
- <Component id={1} />
714
- </Suspense>
715
- <ErrorBoundary fallback={<Fallback />}>
716
- <Suspense fallback={<p>Loading...</p>}>
717
- <Component id={2} />
718
- </Suspense>
719
- </ErrorBoundary>
720
- </ErrorBoundary>
721
- )
722
-
723
- handlers[1].resolve(undefined)
724
- handlers[2].reject()
725
-
726
- const chunks = []
727
- const textDecoder = new TextDecoder()
728
- for await (const chunk of stream as any) {
729
- chunks.push(textDecoder.decode(chunk))
730
- }
731
-
732
- expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(
733
- '<div>1</div><div>Out Of Service</div>'
734
- )
735
- })
736
- })
737
-
738
- describe('onError', async () => {
739
- const Component = ({ error }: { error?: boolean }) => {
740
- if (error) {
741
- throw new Error('Error')
742
- }
743
- return <div>Hello</div>
744
- }
745
-
746
- it('no error', async () => {
747
- const errors: Error[] = []
748
- const html = (
749
- <ErrorBoundary fallback={<Fallback />} onError={(err) => errors.push(err)}>
750
- <Component />
751
- </ErrorBoundary>
752
- )
753
-
754
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')
755
-
756
- errorBoundaryCounter--
757
- suspenseCounter--
758
-
759
- expect(errors).toEqual([])
760
- })
761
-
762
- it('error', async () => {
763
- const errors: Error[] = []
764
- const html = (
765
- <ErrorBoundary fallback={<Fallback />} onError={(err) => errors.push(err)}>
766
- <Component error={true} />
767
- </ErrorBoundary>
768
- )
769
-
770
- expect((await resolveCallback(await html.toString())).toString()).toEqual(
771
- '<div>Out Of Service</div>'
772
- )
773
-
774
- suspenseCounter--
775
-
776
- expect(errors[0]).toEqual(new Error('Error'))
777
- })
778
- })
779
-
780
- describe('fallbackRender', async () => {
781
- const fallbackRenderer = (error: Error) => <div data-error>{error.message}</div>
782
- const Component = ({ error }: { error?: boolean }) => {
783
- if (error) {
784
- throw new Error('Error')
785
- }
786
- return <div>Hello</div>
787
- }
788
-
789
- it('no error', async () => {
790
- const errors: Error[] = []
791
- const html = (
792
- <ErrorBoundary fallbackRender={fallbackRenderer}>
793
- <Component />
794
- </ErrorBoundary>
795
- )
796
-
797
- expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')
798
-
799
- errorBoundaryCounter--
800
- suspenseCounter--
801
-
802
- expect(errors).toEqual([])
803
- })
804
-
805
- it('error', async () => {
806
- const html = (
807
- <ErrorBoundary fallbackRender={fallbackRenderer}>
808
- <Component error={true} />
809
- </ErrorBoundary>
810
- )
811
-
812
- expect((await resolveCallback(await html.toString())).toString()).toEqual(
813
- '<div data-error="true">Error</div>'
814
- )
815
-
816
- suspenseCounter--
817
- })
818
- })
819
-
820
- describe('escaping', () => {
821
- const Broken = () => {
822
- throw new Error('boom')
823
- }
824
-
825
- it('escapes strings when an asynchronous sibling is present', async () => {
826
- const Async = async () => <span>done</span>
827
- const node = (
828
- <ErrorBoundary fallback='error'>
829
- {unsafeHtml}
830
- <Async />
831
- </ErrorBoundary>
832
- )
833
-
834
- expect(await stringify(node)).toBe(`${escapedUnsafeHtml}<span>done</span>`)
835
- const streamed = await drainStream(renderToReadableStream(node))
836
- expect(streamed).toContain(escapedUnsafeHtml)
837
- expect(streamed).not.toContain(unsafeHtml)
838
- })
839
-
840
- it('escapes untrusted object fallbacks', async () => {
841
- const object = { toString: () => unsafeHtml }
842
- const asyncObject = { toString: async () => unsafeHtml }
843
- const rawObject = { toString: () => raw(unsafeHtml) }
844
-
845
- for (const fallback of [object, asyncObject, rawObject]) {
846
- expect(
847
- await stringify(
848
- <ErrorBoundary fallback={fallback as never}>
849
- <Broken />
850
- </ErrorBoundary>
851
- )
852
- ).toBe(escapedUnsafeHtml)
853
- }
854
- })
855
-
856
- it('preserves an explicitly trusted fallback', async () => {
857
- expect(
858
- await stringify(
859
- <ErrorBoundary fallback={raw('<strong>trusted</strong>')}>
860
- <Broken />
861
- </ErrorBoundary>
862
- )
863
- ).toBe('<strong>trusted</strong>')
864
- })
865
-
866
- it('does not interpret replacement patterns in content', async () => {
867
- const content = "literal $& $` $'"
868
- const Async = async () => <span>done</span>
869
-
870
- expect(
871
- await stringify(
872
- <ErrorBoundary fallback='error'>
873
- {content}
874
- <Async />
875
- </ErrorBoundary>
876
- )
877
- ).toBe('literal $&amp; $` $&#39;<span>done</span>')
878
- })
879
-
880
- it('escapes untrusted object children', async () => {
881
- const object = { toString: () => unsafeHtml }
882
- const asyncObject = { toString: async () => unsafeHtml }
883
- const rawObject = { toString: () => raw(unsafeHtml) }
884
-
885
- for (const child of [object, asyncObject, rawObject]) {
886
- const node = <ErrorBoundary fallback='error'>{child as never}</ErrorBoundary>
887
- expect(await stringify(node)).toBe(escapedUnsafeHtml)
888
- const streamed = await drainStream(renderToReadableStream(node))
889
- expect(streamed).toContain(escapedUnsafeHtml)
890
- expect(streamed).not.toContain(unsafeHtml)
891
- }
892
- })
893
-
894
- it('preserves explicitly trusted children', async () => {
895
- expect(
896
- await stringify(
897
- <ErrorBoundary fallback='error'>{raw('<strong>trusted</strong>')}</ErrorBoundary>
898
- )
899
- ).toBe('<strong>trusted</strong>')
900
- })
901
-
902
- it('keeps benign object stringification visible', async () => {
903
- expect(await stringify(<ErrorBoundary fallback='error'>{{} as never}</ErrorBoundary>)).toBe(
904
- '[object Object]'
905
- )
906
- })
907
-
908
- it('escapes nested fallback arrays', async () => {
909
- expect(
910
- await stringify(
911
- <ErrorBoundary fallback={[[unsafeHtml]]}>
912
- <Broken />
913
- </ErrorBoundary>
914
- )
915
- ).toBe(escapedUnsafeHtml)
916
- })
917
-
918
- it('keeps the existing falsy fallbackRender behavior', async () => {
919
- expect(
920
- await stringify(
921
- <ErrorBoundary fallbackRender={() => 0}>
922
- <Broken />
923
- </ErrorBoundary>
924
- )
925
- ).toBe('')
926
- })
927
-
928
- it('keeps a direct Promise child unchanged', async () => {
929
- expect(
930
- await stringify(
931
- <ErrorBoundary fallback='error'>{Promise.resolve('resolved')}</ErrorBoundary>
932
- )
933
- ).toBe('[object Promise]')
934
- })
935
- })
936
- })