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.
- package/README.md +3 -2
- package/docs/framework/README.md +2 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/guides/jsx.md +24 -0
- package/docs/framework/helpers/ssg.md +3 -6
- package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
- package/docs/framework/upstream.json +2 -2
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/cache.js +4 -4
- package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
- package/lib/client.d.ts +2 -2
- package/lib/client.js +2 -1
- package/lib/combine.d.ts +1 -1
- package/lib/{components-Ce5FRyHU.js → components-0o0CBYNv.js} +2 -2
- package/lib/{components-DYZdxJIJ.js → components-DdI7-ns7.js} +1 -1
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
- package/lib/context-storage.d.ts +1 -1
- package/lib/cookie.d.ts +1 -1
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/{hooks-Bq5q9IMl.js → hooks-BMw6BAyP.js} +5 -5
- package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
- package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
- package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +1 -1
- package/lib/jsx/dom/server.js +1 -1
- package/lib/jsx/dom.js +3 -3
- package/lib/jsx/streaming.js +1 -1
- package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-D18Y35TI.js} +1 -1
- package/lib/jsx-renderer.d.ts +75 -0
- package/lib/jsx-renderer.js +99 -0
- package/lib/jsx.js +3 -3
- package/lib/jwk.d.ts +1 -1
- package/lib/jwt.d.ts +1 -1
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/ssg-BwjcA-Ge.js +389 -0
- package/lib/ssg.d.ts +156 -0
- package/lib/ssg.js +2 -0
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +3 -3
- package/lib/testing.js +1 -1
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
- package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils-Cx61GFMC.js +125 -0
- package/lib/validator.d.ts +1 -1
- package/lib/ws.d.ts +1 -1
- package/package.json +9 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/upstream.json +1 -1
- package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -0
- package/plugins/acryl-router/src/middleware/index.ts +1 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/resolve.ts +4 -0
- package/shared/AGENTS.core.md +4 -0
- package/templates/agent/tsconfig.json +7 -1
- package/templates/todo/upstream.json +1 -1
- package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
- package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
- package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
- package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
- package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
- package/plugins/acryl-router/src/core/app.test.ts +0 -3833
- package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
- package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
- package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
- package/plugins/acryl-router/src/core/compose.test.ts +0 -751
- package/plugins/acryl-router/src/core/context.test.ts +0 -629
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
- package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
- package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
- package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
- package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
- package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
- package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
- package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
- package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
- package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
- package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
- package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
- package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
- package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
- package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
- package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
- package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
- package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
- package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
- package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
- package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
- package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
- package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
- package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
- package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
- package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
- package/plugins/acryl-router/src/core/request.test.ts +0 -751
- package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
- package/plugins/acryl-router/src/core/types.test.ts +0 -4134
- package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
- package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
- package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
- package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
- package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
- package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
- package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
- package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
- package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
- package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
- package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
- package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
- package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
- package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
- package/plugins/acryl-router/tests/loader.spec.ts +0 -72
- package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
- package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
- package/plugins/acryl-router/tests/service.spec.ts +0 -426
- package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
- package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
- package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
- package/plugins/acryl-state/src/react.ts +0 -11
- package/plugins/acryl-state/tests/cell.spec.ts +0 -71
- package/plugins/acryl-state/tests/react.spec.ts +0 -39
- 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 = '<img src=x onerror=alert(1)>'
|
|
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('< ok >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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('< error >')
|
|
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 $& $` $'<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
|
-
})
|