@mpen/rerouter 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/{src → cli}/bin.test.ts +24 -2
- package/{src → cli}/bin.ts +27 -18
- package/cli/tsconfig.json +9 -0
- package/dist/acorn-k7ED_tOl.js +4968 -0
- package/dist/angular--Iqdw9UJ.js +4057 -0
- package/dist/babel-hfWAujRY.js +9878 -0
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +28 -23
- package/dist/estree-C1Zjnvlw.js +7266 -0
- package/dist/flow-BaD9LyIP.js +52912 -0
- package/dist/glimmer-CvCjW_1V.js +7541 -0
- package/dist/graphql-BdtzBuWh.js +1945 -0
- package/dist/html-DkZtUVbo.js +7137 -0
- package/dist/index.d.ts +19 -6
- package/dist/index.js +135 -27
- package/dist/markdown-Z8Vrc69e.js +6876 -0
- package/dist/meriyah-DeO4stuH.js +7590 -0
- package/dist/postcss-BmgGJ0E5.js +6777 -0
- package/dist/prettier-BT_F8kIx.js +15629 -0
- package/dist/typescript-DtIxStjy.js +22936 -0
- package/dist/yaml-CWOPBY0q.js +5281 -0
- package/examples/App.tsx +18 -49
- package/examples/dist/BlogPost-c10d9w2p.js +1 -0
- package/examples/dist/FetchLoading-534mdrgz.js +1 -0
- package/examples/dist/FetchLoading-sbxbdkre.js +1 -0
- package/examples/dist/Home-a1258p25.js +1 -0
- package/examples/dist/KitchenSink-821mjg0h.js +1 -0
- package/examples/dist/Login-wywx6bp7.js +1 -0
- package/examples/dist/Match-1e72jm5w.js +1 -0
- package/examples/dist/NotFound-smxj24jw.js +1 -0
- package/examples/dist/SlowLoading-59xxmbfk.js +1 -0
- package/examples/dist/index-0d4kj0rv.js +2 -0
- package/examples/dist/index-3x197sbt.js +9 -0
- package/examples/dist/index-a2hkfx1n.js +9 -0
- package/examples/dist/index-d21me1mc.js +9 -0
- package/examples/dist/index-ktqdknsn.js +2 -0
- package/examples/dist/index-p53qxxzd.js +2 -0
- package/examples/dist/index.html +67 -0
- package/examples/routes.gen.ts +66 -86
- package/examples/routes.ts +2 -2
- package/examples/server/serve-dist.ts +33 -0
- package/examples/server/tsconfig.json +9 -0
- package/package.json +11 -6
- package/src/components/Link.tsx +8 -6
- package/src/components/Router.test.tsx +183 -0
- package/src/components/Router.tsx +161 -29
- package/src/lib/routes.ts +2 -0
- package/tsconfig.json +3 -2
- package/tsdown.config.ts +3 -4
- package/dist/hooks-Dlwcb0sV.js +0 -20
- package/dist/hooks.d.ts +0 -2
- package/dist/hooks.js +0 -2
- package/dist/index-BYXpNitc.d.ts +0 -5
- /package/{src → cli}/fixtures/bin/kitchen-sink.tsx +0 -0
- /package/{src → cli}/fixtures/bin/optional.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Home.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/KitchenSink.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Login.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Match.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/NotFound.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Optional.tsx +0 -0
- /package/{src → cli}/fixtures/bin/regexp-groups.tsx +0 -0
- /package/{src → cli}/fixtures/bin/simple.tsx +0 -0
- /package/{src → cli}/fixtures/bin/unnamed.tsx +0 -0
- /package/dist/{routes-Hpf6cwcZ.js → routes-PW-bNm8e.js} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>@mpen/rerouter examples</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
margin: 0;
|
|
10
|
+
font-family:
|
|
11
|
+
ui-sans-serif,
|
|
12
|
+
system-ui,
|
|
13
|
+
-apple-system,
|
|
14
|
+
Segoe UI,
|
|
15
|
+
Roboto,
|
|
16
|
+
Helvetica,
|
|
17
|
+
Arial,
|
|
18
|
+
'Apple Color Emoji',
|
|
19
|
+
'Segoe UI Emoji';
|
|
20
|
+
background: #0b1020;
|
|
21
|
+
color: #e8eefc;
|
|
22
|
+
}
|
|
23
|
+
a {
|
|
24
|
+
color: inherit;
|
|
25
|
+
}
|
|
26
|
+
.app {
|
|
27
|
+
max-width: 900px;
|
|
28
|
+
margin: 0 auto;
|
|
29
|
+
padding: 24px;
|
|
30
|
+
}
|
|
31
|
+
.card {
|
|
32
|
+
background: rgba(255, 255, 255, 0.06);
|
|
33
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
34
|
+
border-radius: 12px;
|
|
35
|
+
padding: 16px;
|
|
36
|
+
margin-bottom: 16px;
|
|
37
|
+
}
|
|
38
|
+
.nav {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
gap: 10px;
|
|
42
|
+
margin-top: 8px;
|
|
43
|
+
}
|
|
44
|
+
.pill {
|
|
45
|
+
padding: 6px 10px;
|
|
46
|
+
border-radius: 999px;
|
|
47
|
+
border: 1px solid rgba(255, 255, 255, 0.16);
|
|
48
|
+
background: rgba(255, 255, 255, 0.06);
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
}
|
|
51
|
+
.pill.active {
|
|
52
|
+
border-color: rgba(90, 200, 250, 0.7);
|
|
53
|
+
background: rgba(90, 200, 250, 0.18);
|
|
54
|
+
}
|
|
55
|
+
code {
|
|
56
|
+
font-family:
|
|
57
|
+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
|
58
|
+
'Courier New', monospace;
|
|
59
|
+
font-size: 0.95em;
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
62
|
+
<script type="module" crossorigin src="./index-d21me1mc.js"></script></head>
|
|
63
|
+
<body>
|
|
64
|
+
<div id="root"></div>
|
|
65
|
+
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
package/examples/routes.gen.ts
CHANGED
|
@@ -1,125 +1,105 @@
|
|
|
1
1
|
// Do not modify this file. It was auto-generated with the following command:
|
|
2
|
-
// $ rerouter ./examples/routes.ts -w
|
|
2
|
+
// $ rerouter ./examples/routes.ts -w -p
|
|
3
3
|
|
|
4
|
-
type AllOrNone<T> =
|
|
5
|
-
| Required<T>
|
|
6
|
-
| { [K in keyof T]?: never }
|
|
4
|
+
type AllOrNone<T> = Required<T> | { [K in keyof T]?: never }
|
|
7
5
|
|
|
8
6
|
type ParamType = string | number | boolean
|
|
9
7
|
type WildcardType = Iterable<ParamType>
|
|
10
8
|
|
|
11
9
|
export function home(): string {
|
|
12
|
-
|
|
10
|
+
let sb = ''
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
sb += '/'
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
return sb
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
export function kitchenSink(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} & AllOrNone<{
|
|
25
|
-
"optional": ParamType
|
|
26
|
-
"two": ParamType
|
|
27
|
-
}>
|
|
18
|
+
params: { foo: ParamType; baz: ParamType; splat: WildcardType } & AllOrNone<{
|
|
19
|
+
optional: ParamType
|
|
20
|
+
two: ParamType
|
|
21
|
+
}>,
|
|
28
22
|
): string {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
let sb = ''
|
|
24
|
+
|
|
25
|
+
if (params['foo'] == null) throw new Error('Missing param: foo')
|
|
26
|
+
if (params['baz'] == null) throw new Error('Missing param: baz')
|
|
27
|
+
if (params['splat'] == null) throw new Error('Missing param: splat')
|
|
28
|
+
sb += '/hello/'
|
|
29
|
+
sb += encodeURIComponent(String(params['foo']))
|
|
30
|
+
sb += '/bar/'
|
|
31
|
+
sb += encodeURIComponent(String(params['baz']))
|
|
32
|
+
sb += '/'
|
|
33
|
+
sb += Array.from(params['splat'], (v) => encodeURIComponent(String(v))).join('/')
|
|
34
|
+
sb += '/xxx'
|
|
35
|
+
if (params['optional'] != null && params['two'] != null) {
|
|
36
|
+
sb += '/'
|
|
37
|
+
sb += encodeURIComponent(String(params['optional']))
|
|
38
|
+
sb += '/lol/'
|
|
39
|
+
sb += encodeURIComponent(String(params['two']))
|
|
40
|
+
} else if (!(params['optional'] == null && params['two'] == null)) {
|
|
41
|
+
throw new Error('Group requires all-or-none: "optional", "two"')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return sb
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
export function blogPost(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
sb += (encodeURIComponent)(String(params["title"]))
|
|
68
|
-
} else if (!(params["title"] == null)) {
|
|
69
|
-
throw new Error("Group requires all-or-none: \"title\"")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return sb
|
|
47
|
+
export function blogPost(params: { id: ParamType } & AllOrNone<{ title: ParamType }>): string {
|
|
48
|
+
let sb = ''
|
|
49
|
+
|
|
50
|
+
if (params['id'] == null) throw new Error('Missing param: id')
|
|
51
|
+
sb += '/blog/'
|
|
52
|
+
sb += encodeURIComponent(String(params['id']))
|
|
53
|
+
if (params['title'] != null) {
|
|
54
|
+
sb += '-'
|
|
55
|
+
sb += encodeURIComponent(String(params['title']))
|
|
56
|
+
} else if (!(params['title'] == null)) {
|
|
57
|
+
throw new Error('Group requires all-or-none: "title"')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return sb
|
|
73
61
|
}
|
|
74
62
|
|
|
75
63
|
export function slowLoading(): string {
|
|
76
|
-
|
|
64
|
+
let sb = ''
|
|
77
65
|
|
|
78
|
-
|
|
66
|
+
sb += '/slow-loading'
|
|
79
67
|
|
|
80
|
-
|
|
68
|
+
return sb
|
|
81
69
|
}
|
|
82
70
|
|
|
83
71
|
export function fetchLoading(): string {
|
|
84
|
-
|
|
72
|
+
let sb = ''
|
|
85
73
|
|
|
86
|
-
|
|
74
|
+
sb += '/fetch-loading'
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
return sb
|
|
89
77
|
}
|
|
90
78
|
|
|
91
|
-
export function fetchLoadingItem(
|
|
92
|
-
|
|
93
|
-
"id": ParamType
|
|
94
|
-
}
|
|
95
|
-
): string {
|
|
96
|
-
let sb = ""
|
|
79
|
+
export function fetchLoadingItem(params: { id: ParamType }): string {
|
|
80
|
+
let sb = ''
|
|
97
81
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
if (params['id'] == null) throw new Error('Missing param: id')
|
|
83
|
+
sb += '/fetch-loading/'
|
|
84
|
+
sb += encodeURIComponent(String(params['id']))
|
|
101
85
|
|
|
102
|
-
|
|
86
|
+
return sb
|
|
103
87
|
}
|
|
104
88
|
|
|
105
89
|
export function login(): string {
|
|
106
|
-
|
|
90
|
+
let sb = ''
|
|
107
91
|
|
|
108
|
-
|
|
92
|
+
sb += '/login'
|
|
109
93
|
|
|
110
|
-
|
|
94
|
+
return sb
|
|
111
95
|
}
|
|
112
96
|
|
|
113
|
-
export function match(
|
|
114
|
-
|
|
115
|
-
"id": ParamType
|
|
116
|
-
}
|
|
117
|
-
): string {
|
|
118
|
-
let sb = ""
|
|
97
|
+
export function match(params: { id: ParamType }): string {
|
|
98
|
+
let sb = ''
|
|
119
99
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
if (params['id'] == null) throw new Error('Missing param: id')
|
|
101
|
+
sb += '/matches/'
|
|
102
|
+
sb += encodeURIComponent(String(params['id']))
|
|
123
103
|
|
|
124
|
-
|
|
104
|
+
return sb
|
|
125
105
|
}
|
package/examples/routes.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Routes } from '@mpen/rerouter'
|
|
2
2
|
|
|
3
3
|
const loadFetchLoading = () => import('./pages/FetchLoading')
|
|
4
4
|
|
|
5
|
-
const ROUTES:
|
|
5
|
+
const ROUTES: Routes = [
|
|
6
6
|
{ name: 'home', pattern: '/', component: () => import('./pages/Home') },
|
|
7
7
|
{
|
|
8
8
|
name: 'kitchenSink',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const distPath = fileURLToPath(new URL('../dist/', import.meta.url))
|
|
5
|
+
const index = Bun.file(join(distPath, 'index.html'))
|
|
6
|
+
|
|
7
|
+
async function fileForPath(pathname: string) {
|
|
8
|
+
const requestedPath = pathname === '/' ? 'index.html' : pathname.slice(1)
|
|
9
|
+
const file = Bun.file(join(distPath, requestedPath))
|
|
10
|
+
|
|
11
|
+
if (await file.exists()) return file
|
|
12
|
+
if (pathname.startsWith('/_bun/') || requestedPath.split('/').at(-1)?.includes('.'))
|
|
13
|
+
return undefined
|
|
14
|
+
|
|
15
|
+
return index
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const server = Bun.serve({
|
|
19
|
+
async fetch(request) {
|
|
20
|
+
const url = new URL(request.url)
|
|
21
|
+
const file = await fileForPath(url.pathname)
|
|
22
|
+
|
|
23
|
+
if (!file) return new Response('Not found', { status: 404 })
|
|
24
|
+
|
|
25
|
+
return new Response(file, {
|
|
26
|
+
headers: {
|
|
27
|
+
'Cache-Control': 'no-store',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
console.log(`Serving production build at ${server.url}`)
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpen/rerouter",
|
|
3
|
-
"
|
|
3
|
+
"description": "Lightweight type-safe router for React with URL helper generation.",
|
|
4
|
+
"version": "0.3.1",
|
|
4
5
|
"exports": {
|
|
5
6
|
".": "./dist/index.js",
|
|
6
7
|
"./bin": "./dist/bin.js",
|
|
7
|
-
"./hooks": "./dist/hooks.js",
|
|
8
8
|
"./package.json": "./package.json"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@mpen/classcat": "0.1.3",
|
|
16
|
-
"@mpen/ts-types": "0.1.
|
|
16
|
+
"@mpen/ts-types": "0.1.2",
|
|
17
17
|
"path-to-regexp": "^8.3.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
@@ -31,14 +31,19 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsdown",
|
|
34
|
-
"gen": "bun
|
|
34
|
+
"gen": "bun cli/bin.ts ./examples/routes.ts -w -p",
|
|
35
35
|
"serve": "bun --hot examples/index.html",
|
|
36
|
-
"dev": "bun run --sequential gen serve"
|
|
36
|
+
"dev": "bun run --sequential gen serve",
|
|
37
|
+
"prod": "bun run gen && bun build --production --splitting examples/index.html --outdir examples/dist && bun examples/server/serve-dist.ts"
|
|
37
38
|
},
|
|
38
39
|
"types": "./dist/index.d.ts",
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"access": "public"
|
|
41
42
|
},
|
|
42
43
|
"main": "./dist/index.js",
|
|
43
|
-
"module": "./dist/index.js"
|
|
44
|
+
"module": "./dist/index.js",
|
|
45
|
+
"inlinedDependencies": {
|
|
46
|
+
"prettier": "3.8.3",
|
|
47
|
+
"process": "0.11.10"
|
|
48
|
+
}
|
|
44
49
|
}
|
package/src/components/Link.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cc, type ClassValue } from '@mpen/classcat'
|
|
2
2
|
import type { OverrideProps } from '@mpen/ts-types/react'
|
|
3
|
-
import type
|
|
3
|
+
import { startTransition, type MouseEvent } from 'react'
|
|
4
4
|
import { pushUrl, replaceUrl } from '../lib/url'
|
|
5
5
|
import { mergeSearch } from '../lib/mergeSearch'
|
|
6
6
|
|
|
@@ -72,11 +72,13 @@ export function Link({ to, search, children, className, replace, ...rest }: Link
|
|
|
72
72
|
if (ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return
|
|
73
73
|
if (ev.button !== 0) return
|
|
74
74
|
ev.preventDefault()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
startTransition(() => {
|
|
76
|
+
if (replace) {
|
|
77
|
+
replaceUrl(href)
|
|
78
|
+
} else {
|
|
79
|
+
pushUrl(href)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
return (
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
+
import { Window } from 'happy-dom'
|
|
3
|
+
import type { RouteObject } from '../lib/routes'
|
|
4
|
+
|
|
5
|
+
const testWindow = new Window({ url: 'http://localhost/start' })
|
|
6
|
+
|
|
7
|
+
testWindow.SyntaxError = SyntaxError
|
|
8
|
+
|
|
9
|
+
Object.assign(globalThis, {
|
|
10
|
+
window: testWindow,
|
|
11
|
+
document: testWindow.document,
|
|
12
|
+
navigator: testWindow.navigator,
|
|
13
|
+
location: testWindow.location,
|
|
14
|
+
history: testWindow.history,
|
|
15
|
+
Event: testWindow.Event,
|
|
16
|
+
HTMLElement: testWindow.HTMLElement,
|
|
17
|
+
MouseEvent: testWindow.MouseEvent,
|
|
18
|
+
Node: testWindow.Node,
|
|
19
|
+
PopStateEvent: testWindow.PopStateEvent,
|
|
20
|
+
SyntaxError,
|
|
21
|
+
getComputedStyle: testWindow.getComputedStyle.bind(testWindow),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const { act, cleanup, render, waitFor } = await import('@testing-library/react')
|
|
25
|
+
const { Router } = await import('./Router')
|
|
26
|
+
const { pushUrl } = await import('../lib/url')
|
|
27
|
+
|
|
28
|
+
function wait(ms: number): Promise<void> {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
setTimeout(resolve, ms)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe(Router.name, () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
window.history.replaceState(null, '', '/start')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
cleanup()
|
|
41
|
+
window.history.replaceState(null, '', '/start')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('delays the loading fallback while a route component is loading', async () => {
|
|
45
|
+
window.history.replaceState(null, '', '/slow')
|
|
46
|
+
|
|
47
|
+
const routes: readonly RouteObject[] = [
|
|
48
|
+
{
|
|
49
|
+
pattern: '/slow',
|
|
50
|
+
component: () =>
|
|
51
|
+
new Promise(() => {
|
|
52
|
+
// Keep the route pending so the fallback delay is observable.
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
const view = render(
|
|
58
|
+
<Router loading={<div>Loading route...</div>} loadingDelayMs={25} routes={routes} />,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
62
|
+
|
|
63
|
+
await act(async () => {
|
|
64
|
+
await wait(30)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(view.getByText('Loading route...')).toBeTruthy()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('does not show the loading fallback when the route loads before the delay', async () => {
|
|
71
|
+
window.history.replaceState(null, '', '/quick')
|
|
72
|
+
|
|
73
|
+
const routes: readonly RouteObject[] = [
|
|
74
|
+
{
|
|
75
|
+
pattern: '/quick',
|
|
76
|
+
component: () =>
|
|
77
|
+
wait(5).then(() => ({
|
|
78
|
+
default: function QuickRoute() {
|
|
79
|
+
return <div>Quick route</div>
|
|
80
|
+
},
|
|
81
|
+
})),
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const view = render(
|
|
86
|
+
<Router loading={<div>Loading route...</div>} loadingDelayMs={50} routes={routes} />,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(view.getByText('Quick route')).toBeTruthy()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('keeps the current route visible until a slow next route reaches the delay', async () => {
|
|
99
|
+
const routes: readonly RouteObject[] = [
|
|
100
|
+
{
|
|
101
|
+
pattern: '/start',
|
|
102
|
+
component: async () => ({
|
|
103
|
+
default: function StartRoute() {
|
|
104
|
+
return <div>Start route</div>
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
pattern: '/slow',
|
|
110
|
+
component: () =>
|
|
111
|
+
new Promise(() => {
|
|
112
|
+
// Keep the next route pending so the delayed loading state is observable.
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
const view = render(
|
|
118
|
+
<Router loading={<div>Loading route...</div>} loadingDelayMs={25} routes={routes} />,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
await waitFor(() => {
|
|
122
|
+
expect(view.getByText('Start route')).toBeTruthy()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
act(() => {
|
|
126
|
+
pushUrl('/slow')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(view.getByText('Start route')).toBeTruthy()
|
|
130
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
131
|
+
|
|
132
|
+
await act(async () => {
|
|
133
|
+
await wait(30)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
expect(view.queryByText('Start route')).toBeNull()
|
|
137
|
+
expect(view.getByText('Loading route...')).toBeTruthy()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('keeps the current route visible until a quick next route is ready', async () => {
|
|
141
|
+
const routes: readonly RouteObject[] = [
|
|
142
|
+
{
|
|
143
|
+
pattern: '/start',
|
|
144
|
+
component: async () => ({
|
|
145
|
+
default: function StartRoute() {
|
|
146
|
+
return <div>Start route</div>
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
pattern: '/quick',
|
|
152
|
+
component: () =>
|
|
153
|
+
wait(5).then(() => ({
|
|
154
|
+
default: function QuickRoute() {
|
|
155
|
+
return <div>Quick route</div>
|
|
156
|
+
},
|
|
157
|
+
})),
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
const view = render(
|
|
162
|
+
<Router loading={<div>Loading route...</div>} loadingDelayMs={50} routes={routes} />,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(view.getByText('Start route')).toBeTruthy()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
act(() => {
|
|
170
|
+
pushUrl('/quick')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
expect(view.getByText('Start route')).toBeTruthy()
|
|
174
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(view.getByText('Quick route')).toBeTruthy()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
expect(view.queryByText('Start route')).toBeNull()
|
|
181
|
+
expect(view.queryByText('Loading route...')).toBeNull()
|
|
182
|
+
})
|
|
183
|
+
})
|