@mpen/rerouter 0.1.9 → 0.3.0
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 +76 -18
- package/dist/bin.d.ts +29 -0
- package/dist/bin.js +228 -0
- package/dist/hooks-Dlwcb0sV.js +20 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +2 -0
- package/dist/index-BYXpNitc.d.ts +5 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +139 -0
- package/dist/routes-Hpf6cwcZ.js +135 -0
- package/examples/App.tsx +111 -0
- package/examples/index.html +67 -0
- package/examples/pages/BlogPost.tsx +17 -0
- package/examples/pages/FetchLoading.tsx +53 -0
- package/examples/pages/FetchLoadingItem.tsx +45 -0
- package/examples/pages/Home.tsx +3 -0
- package/examples/pages/KitchenSink.tsx +23 -0
- package/examples/pages/Login.tsx +3 -0
- package/examples/pages/Match.tsx +5 -0
- package/examples/pages/NotFound.tsx +3 -0
- package/examples/pages/SlowLoading.tsx +8 -0
- package/examples/routes.gen.ts +125 -0
- package/examples/routes.ts +40 -0
- package/package.json +37 -32
- package/src/bin.test.ts +199 -0
- package/src/bin.ts +333 -0
- package/src/components/Link.test.tsx +139 -0
- package/src/components/Link.tsx +87 -0
- package/src/components/NavLink.test.tsx +119 -0
- package/src/components/NavLink.tsx +71 -0
- package/src/components/Router.tsx +75 -0
- package/src/fixtures/bin/kitchen-sink.tsx +15 -0
- package/src/fixtures/bin/optional.tsx +3 -0
- package/src/fixtures/bin/pages/Home.tsx +3 -0
- package/src/fixtures/bin/pages/KitchenSink.tsx +3 -0
- package/src/fixtures/bin/pages/Login.tsx +3 -0
- package/src/fixtures/bin/pages/Match.tsx +3 -0
- package/src/fixtures/bin/pages/NotFound.tsx +3 -0
- package/src/fixtures/bin/pages/Optional.tsx +3 -0
- package/src/fixtures/bin/regexp-groups.tsx +11 -0
- package/src/fixtures/bin/simple.tsx +1 -0
- package/src/fixtures/bin/unnamed.tsx +4 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useUrl.ts +22 -0
- package/src/index.ts +6 -0
- package/src/lib/mergeSearch.test.ts +37 -0
- package/src/lib/mergeSearch.ts +21 -0
- package/src/lib/routes.test.ts +67 -0
- package/src/lib/routes.ts +245 -0
- package/src/lib/url.ts +9 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +22 -0
- package/LICENSE +0 -21
- package/dist/bundle.cjs +0 -422
- package/dist/bundle.d.ts +0 -2
- package/dist/bundle.mjs +0 -420
- package/dist/dev.d.ts +0 -1
- package/dist/log.d.ts +0 -1
- package/dist/uri-template.d.ts +0 -56
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Do not modify this file. It was auto-generated with the following command:
|
|
2
|
+
// $ rerouter ./examples/routes.ts -w
|
|
3
|
+
|
|
4
|
+
type AllOrNone<T> =
|
|
5
|
+
| Required<T>
|
|
6
|
+
| { [K in keyof T]?: never }
|
|
7
|
+
|
|
8
|
+
type ParamType = string | number | boolean
|
|
9
|
+
type WildcardType = Iterable<ParamType>
|
|
10
|
+
|
|
11
|
+
export function home(): string {
|
|
12
|
+
let sb = ""
|
|
13
|
+
|
|
14
|
+
sb += "/"
|
|
15
|
+
|
|
16
|
+
return sb
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function kitchenSink(
|
|
20
|
+
params: {
|
|
21
|
+
"foo": ParamType
|
|
22
|
+
"baz": ParamType
|
|
23
|
+
"splat": WildcardType
|
|
24
|
+
} & AllOrNone<{
|
|
25
|
+
"optional": ParamType
|
|
26
|
+
"two": ParamType
|
|
27
|
+
}>
|
|
28
|
+
): string {
|
|
29
|
+
let sb = ""
|
|
30
|
+
|
|
31
|
+
if (params["foo"] == null) throw new Error("Missing param: foo")
|
|
32
|
+
if (params["baz"] == null) throw new Error("Missing param: baz")
|
|
33
|
+
if (params["splat"] == null) throw new Error("Missing param: splat")
|
|
34
|
+
sb += "/hello/"
|
|
35
|
+
sb += (encodeURIComponent)(String(params["foo"]))
|
|
36
|
+
sb += "/bar/"
|
|
37
|
+
sb += (encodeURIComponent)(String(params["baz"]))
|
|
38
|
+
sb += "/"
|
|
39
|
+
sb += Array.from(params["splat"], v => (encodeURIComponent)(String(v))).join("/")
|
|
40
|
+
sb += "/xxx"
|
|
41
|
+
if (params["optional"] != null && params["two"] != null) {
|
|
42
|
+
sb += "/"
|
|
43
|
+
sb += (encodeURIComponent)(String(params["optional"]))
|
|
44
|
+
sb += "/lol/"
|
|
45
|
+
sb += (encodeURIComponent)(String(params["two"]))
|
|
46
|
+
} else if (!(params["optional"] == null && params["two"] == null)) {
|
|
47
|
+
throw new Error("Group requires all-or-none: \"optional\", \"two\"")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return sb
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function blogPost(
|
|
54
|
+
params: {
|
|
55
|
+
"id": ParamType
|
|
56
|
+
} & AllOrNone<{
|
|
57
|
+
"title": ParamType
|
|
58
|
+
}>
|
|
59
|
+
): string {
|
|
60
|
+
let sb = ""
|
|
61
|
+
|
|
62
|
+
if (params["id"] == null) throw new Error("Missing param: id")
|
|
63
|
+
sb += "/blog/"
|
|
64
|
+
sb += (encodeURIComponent)(String(params["id"]))
|
|
65
|
+
if (params["title"] != null) {
|
|
66
|
+
sb += "-"
|
|
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
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function slowLoading(): string {
|
|
76
|
+
let sb = ""
|
|
77
|
+
|
|
78
|
+
sb += "/slow-loading"
|
|
79
|
+
|
|
80
|
+
return sb
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function fetchLoading(): string {
|
|
84
|
+
let sb = ""
|
|
85
|
+
|
|
86
|
+
sb += "/fetch-loading"
|
|
87
|
+
|
|
88
|
+
return sb
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function fetchLoadingItem(
|
|
92
|
+
params: {
|
|
93
|
+
"id": ParamType
|
|
94
|
+
}
|
|
95
|
+
): string {
|
|
96
|
+
let sb = ""
|
|
97
|
+
|
|
98
|
+
if (params["id"] == null) throw new Error("Missing param: id")
|
|
99
|
+
sb += "/fetch-loading/"
|
|
100
|
+
sb += (encodeURIComponent)(String(params["id"]))
|
|
101
|
+
|
|
102
|
+
return sb
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function login(): string {
|
|
106
|
+
let sb = ""
|
|
107
|
+
|
|
108
|
+
sb += "/login"
|
|
109
|
+
|
|
110
|
+
return sb
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function match(
|
|
114
|
+
params: {
|
|
115
|
+
"id": ParamType
|
|
116
|
+
}
|
|
117
|
+
): string {
|
|
118
|
+
let sb = ""
|
|
119
|
+
|
|
120
|
+
if (params["id"] == null) throw new Error("Missing param: id")
|
|
121
|
+
sb += "/matches/"
|
|
122
|
+
sb += (encodeURIComponent)(String(params["id"]))
|
|
123
|
+
|
|
124
|
+
return sb
|
|
125
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RouteObject } from '../src'
|
|
2
|
+
|
|
3
|
+
const loadFetchLoading = () => import('./pages/FetchLoading')
|
|
4
|
+
|
|
5
|
+
const ROUTES: readonly RouteObject[] = [
|
|
6
|
+
{ name: 'home', pattern: '/', component: () => import('./pages/Home') },
|
|
7
|
+
{
|
|
8
|
+
name: 'kitchenSink',
|
|
9
|
+
pattern: '/hello/:foo/bar/:baz/*splat/xxx{/:optional/lol/:two}',
|
|
10
|
+
component: () => import('./pages/KitchenSink'),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'blogPost',
|
|
14
|
+
pattern: '/blog/:id(\\d+){-:title}?',
|
|
15
|
+
component: () => import('./pages/BlogPost'),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'slowLoading',
|
|
19
|
+
pattern: '/slow-loading',
|
|
20
|
+
component: () =>
|
|
21
|
+
new Promise((resolve) => {
|
|
22
|
+
setTimeout(resolve, 2000)
|
|
23
|
+
}).then(() => import('./pages/SlowLoading')),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'fetchLoading',
|
|
27
|
+
pattern: '/fetch-loading',
|
|
28
|
+
component: loadFetchLoading,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'fetchLoadingItem',
|
|
32
|
+
pattern: '/fetch-loading/:id',
|
|
33
|
+
component: loadFetchLoading,
|
|
34
|
+
},
|
|
35
|
+
{ name: 'login', pattern: '/login', component: () => import('./pages/Login') },
|
|
36
|
+
{ name: 'match', pattern: '/matches/:id', component: () => import('./pages/Match') },
|
|
37
|
+
{ name: 'notFound', pattern: '*', component: () => import('./pages/NotFound') },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export default ROUTES
|
package/package.json
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpen/rerouter",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": "./dist/index.js",
|
|
6
|
+
"./bin": "./dist/bin.js",
|
|
7
|
+
"./hooks": "./dist/hooks.js",
|
|
8
|
+
"./package.json": "./package.json"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"rerouter": "./dist/bin.js"
|
|
12
|
+
},
|
|
11
13
|
"type": "module",
|
|
12
|
-
"
|
|
13
|
-
"@
|
|
14
|
-
"@
|
|
15
|
-
"
|
|
16
|
-
"@rollup/plugin-terser": "^0.4.3",
|
|
17
|
-
"@rollup/plugin-typescript": "^11.1.3",
|
|
18
|
-
"bun-types": "^1.0.2",
|
|
19
|
-
"chalk": "^5.3.0",
|
|
20
|
-
"npm-run-all": "^4.1.5",
|
|
21
|
-
"rimraf": "^5.0.1",
|
|
22
|
-
"rollup": "^3.29.2",
|
|
23
|
-
"tslib": "^2.6.2",
|
|
24
|
-
"typescript": "^5.2.2"
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@mpen/classcat": "0.1.3",
|
|
16
|
+
"@mpen/ts-types": "0.1.1",
|
|
17
|
+
"path-to-regexp": "^8.3.0"
|
|
25
18
|
},
|
|
26
|
-
"
|
|
27
|
-
"
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": "^19",
|
|
21
|
+
"typescript": "^5"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@testing-library/react": "^16.3.2",
|
|
25
|
+
"@types/node": "^22",
|
|
26
|
+
"@types/react": "^19",
|
|
27
|
+
"@types/react-dom": "^19.2.3",
|
|
28
|
+
"happy-dom": "^20.9.0",
|
|
29
|
+
"react-dom": "^19.2.3",
|
|
30
|
+
"tsdown": "^0.21"
|
|
28
31
|
},
|
|
29
|
-
"license": "MIT",
|
|
30
32
|
"scripts": {
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
"build": "tsdown",
|
|
34
|
+
"gen": "bun src/bin.ts ./examples/routes.ts -w",
|
|
35
|
+
"serve": "bun --hot examples/index.html",
|
|
36
|
+
"dev": "bun run --sequential gen serve"
|
|
37
|
+
},
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"main": "./dist/index.js",
|
|
43
|
+
"module": "./dist/index.js"
|
|
39
44
|
}
|
package/src/bin.test.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bun test
|
|
2
|
+
import { expect, test, describe, it, afterAll, beforeAll } from 'bun:test'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import fs from 'node:fs/promises'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import process from 'node:process'
|
|
7
|
+
import { pathToFileURL } from 'node:url'
|
|
8
|
+
import { runRerouterBin } from './bin'
|
|
9
|
+
|
|
10
|
+
const TEMP_DIR = path.resolve(import.meta.dirname, 'temp_bin_test')
|
|
11
|
+
const FIXTURES_DIR = path.resolve(import.meta.dirname, 'fixtures/bin')
|
|
12
|
+
const BIN_PATH = path.resolve(import.meta.dirname, 'bin.ts')
|
|
13
|
+
const BUN_PATH = process.execPath
|
|
14
|
+
|
|
15
|
+
async function ensureDir(dir: string) {
|
|
16
|
+
await fs.mkdir(dir, { recursive: true })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('rerouter bin', () => {
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
await ensureDir(TEMP_DIR)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('writes to stdout by default', async () => {
|
|
25
|
+
const routesFile = path.join(FIXTURES_DIR, 'simple.tsx')
|
|
26
|
+
|
|
27
|
+
const result = await runRerouterBin([routesFile])
|
|
28
|
+
expect(result.stdout).toContain('export function home()')
|
|
29
|
+
expect(result.stderr).toBe('')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('writes to file with -o', async () => {
|
|
33
|
+
const routesFile = path.join(FIXTURES_DIR, 'simple.tsx')
|
|
34
|
+
const outputFile = path.join(TEMP_DIR, 'explicit-output.ts')
|
|
35
|
+
|
|
36
|
+
const result = await runRerouterBin([routesFile, '-o', outputFile])
|
|
37
|
+
|
|
38
|
+
const outputContent = await fs.readFile(outputFile, 'utf8')
|
|
39
|
+
expect(outputContent).toContain('export function home()')
|
|
40
|
+
expect(result.stderr).toContain(`Wrote ${outputFile}`)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('writes to adjacent file with -w', async () => {
|
|
44
|
+
const routesFile = path.join(TEMP_DIR, 'write-adjacent.tsx')
|
|
45
|
+
const expectedOutputFile = path.join(TEMP_DIR, 'write-adjacent.gen.ts')
|
|
46
|
+
await fs.copyFile(path.join(FIXTURES_DIR, 'simple.tsx'), routesFile)
|
|
47
|
+
|
|
48
|
+
await runRerouterBin([routesFile, '-w'])
|
|
49
|
+
|
|
50
|
+
const outputContent = await fs.readFile(expectedOutputFile, 'utf8')
|
|
51
|
+
expect(outputContent).toContain('export function home()')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('handles optional groups', async () => {
|
|
55
|
+
const routesFile = path.join(FIXTURES_DIR, 'optional.tsx')
|
|
56
|
+
|
|
57
|
+
const { stdout: outputContent } = await runRerouterBin([routesFile])
|
|
58
|
+
|
|
59
|
+
expect(outputContent).toContain('export function optional(')
|
|
60
|
+
expect(outputContent).toContain('AllOrNone<')
|
|
61
|
+
expect(outputContent).toContain('"bar": ParamType')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('skips unnamed routes', async () => {
|
|
65
|
+
const routesFile = path.join(FIXTURES_DIR, 'unnamed.tsx')
|
|
66
|
+
|
|
67
|
+
const { stdout: outputContent } = await runRerouterBin([routesFile])
|
|
68
|
+
|
|
69
|
+
expect(outputContent).toContain('export function home()')
|
|
70
|
+
expect(outputContent).not.toContain('layout')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('handles regexp params with optional groups', async () => {
|
|
74
|
+
const routesFile = path.join(FIXTURES_DIR, 'regexp-groups.tsx')
|
|
75
|
+
const outputFile = path.join(TEMP_DIR, 'regexp-groups.gen.ts')
|
|
76
|
+
|
|
77
|
+
await runRerouterBin([routesFile, '-o', outputFile])
|
|
78
|
+
|
|
79
|
+
const outputContent = await fs.readFile(outputFile, 'utf8')
|
|
80
|
+
expect(outputContent).toContain('export function blogPost(')
|
|
81
|
+
expect(outputContent).toContain('"id": ParamType')
|
|
82
|
+
expect(outputContent).toContain('"title": ParamType')
|
|
83
|
+
|
|
84
|
+
const { blogPost } = await import(pathToFileURL(outputFile).href)
|
|
85
|
+
expect(blogPost({ id: 123 })).toBe('/blog/123')
|
|
86
|
+
expect(blogPost({ id: 123, title: 'hello world' })).toBe('/blog/123-hello%20world')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('generates importable path helpers', async () => {
|
|
90
|
+
const routesFile = path.join(FIXTURES_DIR, 'kitchen-sink.tsx')
|
|
91
|
+
const outputFile = path.join(TEMP_DIR, 'importable.gen.ts')
|
|
92
|
+
|
|
93
|
+
await runRerouterBin([
|
|
94
|
+
routesFile,
|
|
95
|
+
'-o',
|
|
96
|
+
outputFile,
|
|
97
|
+
'--wildcard-delimiter',
|
|
98
|
+
',',
|
|
99
|
+
'--encode-function',
|
|
100
|
+
'encodeURI',
|
|
101
|
+
])
|
|
102
|
+
|
|
103
|
+
const { home, kitchenSink, login, match } = await import(pathToFileURL(outputFile).href)
|
|
104
|
+
|
|
105
|
+
expect(home()).toBe('/')
|
|
106
|
+
expect(login()).toBe('/login')
|
|
107
|
+
expect(match({ id: 'a/b' })).toBe('/matches/a/b')
|
|
108
|
+
expect(kitchenSink({ foo: 'a/b', baz: 'c', splat: ['x', 'y'] })).toBe(
|
|
109
|
+
'/hello/a/b/bar/c/x,y/xxx',
|
|
110
|
+
)
|
|
111
|
+
expect(
|
|
112
|
+
kitchenSink({
|
|
113
|
+
foo: 'a/b',
|
|
114
|
+
baz: 'c',
|
|
115
|
+
splat: ['x', 'y'],
|
|
116
|
+
optional: 'opt',
|
|
117
|
+
two: 'two',
|
|
118
|
+
}),
|
|
119
|
+
).toBe('/hello/a/b/bar/c/x,y/xxx/opt/lol/two')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('generated path helpers with default options', () => {
|
|
123
|
+
let home: () => string
|
|
124
|
+
let login: () => string
|
|
125
|
+
let match: (params: { id: string }) => string
|
|
126
|
+
let kitchenSink: (params: {
|
|
127
|
+
foo: string
|
|
128
|
+
baz: string
|
|
129
|
+
splat: string[]
|
|
130
|
+
optional?: string
|
|
131
|
+
two?: string
|
|
132
|
+
}) => string
|
|
133
|
+
|
|
134
|
+
beforeAll(async () => {
|
|
135
|
+
const routesFile = path.join(FIXTURES_DIR, 'kitchen-sink.tsx')
|
|
136
|
+
const outputFile = path.join(TEMP_DIR, 'importable-defaults.gen.ts')
|
|
137
|
+
|
|
138
|
+
const result = await runRerouterBin([routesFile])
|
|
139
|
+
await fs.writeFile(outputFile, result.stdout, 'utf8')
|
|
140
|
+
|
|
141
|
+
const generated = await import(pathToFileURL(outputFile).href)
|
|
142
|
+
home = generated.home
|
|
143
|
+
login = generated.login
|
|
144
|
+
match = generated.match
|
|
145
|
+
kitchenSink = generated.kitchenSink
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('home()', () => {
|
|
149
|
+
expect(home()).toBe('/')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('login()', () => {
|
|
153
|
+
expect(login()).toBe('/login')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('match()', () => {
|
|
157
|
+
expect(match({ id: '123' })).toBe('/matches/123')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('match() uses encodeURIComponent', () => {
|
|
161
|
+
expect(match({ id: 'a/b' })).toBe('/matches/a%2Fb')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('kitchenSink() without optional group', () => {
|
|
165
|
+
expect(kitchenSink({ foo: 'a', baz: 'b', splat: ['x', 'y'] })).toBe(
|
|
166
|
+
'/hello/a/bar/b/x/y/xxx',
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('kitchenSink() with optional group', () => {
|
|
171
|
+
expect(
|
|
172
|
+
kitchenSink({
|
|
173
|
+
foo: 'a',
|
|
174
|
+
baz: 'b',
|
|
175
|
+
splat: ['x', 'y'],
|
|
176
|
+
optional: 'opt',
|
|
177
|
+
two: 'two',
|
|
178
|
+
}),
|
|
179
|
+
).toBe('/hello/a/bar/b/x/y/xxx/opt/lol/two')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('kitchenSink() requires all-or-none optional group', () => {
|
|
183
|
+
expect(() =>
|
|
184
|
+
kitchenSink({ foo: 'a', baz: 'b', splat: ['x', 'y'], optional: 'opt' } as any),
|
|
185
|
+
).toThrow('Group requires all-or-none: "optional", "two"')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('can be invoked as a CLI script', async () => {
|
|
190
|
+
const routesFile = path.join(FIXTURES_DIR, 'simple.tsx')
|
|
191
|
+
|
|
192
|
+
const result = await $`${BUN_PATH} ${BIN_PATH} ${routesFile}`.text()
|
|
193
|
+
expect(result).toContain('export function home()')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
afterAll(async () => {
|
|
197
|
+
await fs.rm(TEMP_DIR, { recursive: true, force: true })
|
|
198
|
+
})
|
|
199
|
+
})
|