effect-start 0.14.0 → 0.16.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/package.json +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +603 -0
- package/src/ContentNegotiation.ts +542 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +362 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +5 -5
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +319 -0
- package/src/Http.ts +167 -0
- package/src/HttpAppExtra.test.ts +39 -20
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +485 -0
- package/src/Route.ts +266 -1069
- package/src/RouteBody.test.ts +234 -0
- package/src/RouteBody.ts +193 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +106 -0
- package/src/RouteHttp.test.ts +2906 -0
- package/src/RouteHttp.ts +427 -0
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +481 -0
- package/src/RouteMount.ts +470 -0
- package/src/RouteSchema.test.ts +427 -0
- package/src/RouteSchema.ts +423 -0
- package/src/RouteTree.test.ts +494 -0
- package/src/RouteTree.ts +219 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -3
- package/src/StreamExtra.ts +21 -1
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +76 -0
- package/src/bun/BunBundle.test.ts +36 -42
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +4 -6
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +72 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.test.ts +124 -442
- package/src/bun/BunRoute.ts +146 -286
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
- package/src/experimental/EncryptedCookies.test.ts +125 -64
- package/src/experimental/SseHttpResponse.ts +0 -1
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +3 -4
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/testing/TestHttpClient.test.ts +26 -26
- package/src/testing/TestLogger.test.ts +27 -14
- package/src/testing/TestLogger.ts +15 -9
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import * as Error from "@effect/platform/Error"
|
|
2
2
|
import * as FileSystem from "@effect/platform/FileSystem"
|
|
3
|
-
import * as
|
|
3
|
+
import * as test from "bun:test"
|
|
4
4
|
import * as Effect from "effect/Effect"
|
|
5
5
|
import * as Schema from "effect/Schema"
|
|
6
6
|
import * as Scope from "effect/Scope"
|
|
7
7
|
import * as path from "node:path"
|
|
8
|
+
import * as NodeFileSystem from "../node/FileSystem.ts"
|
|
9
|
+
import * as SchemaExtra from "../SchemaExtra.ts"
|
|
10
|
+
import * as TestLogger from "../testing/TestLogger.ts"
|
|
8
11
|
import * as FileRouter from "./FileRouter.ts"
|
|
9
12
|
import { parseRoute } from "./FileRouter.ts"
|
|
10
13
|
import type { RouteHandle } from "./FileRouter.ts"
|
|
11
14
|
import * as FileRouterCodegen from "./FileRouterCodegen.ts"
|
|
12
|
-
import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
13
|
-
import * as Route from "./Route.ts"
|
|
14
|
-
import * as SchemaExtra from "./SchemaExtra.ts"
|
|
15
|
-
import * as TestLogger from "./testing/TestLogger.ts"
|
|
16
15
|
|
|
17
16
|
function createTempDirWithFiles(
|
|
18
17
|
files: Record<string, string>,
|
|
@@ -37,7 +36,7 @@ function createTempDirWithFiles(
|
|
|
37
36
|
})
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
test.it("generates code for routes only", () => {
|
|
41
40
|
const handles: RouteHandle[] = [
|
|
42
41
|
parseRoute("route.tsx"),
|
|
43
42
|
parseRoute("about/route.tsx"),
|
|
@@ -49,8 +48,6 @@ t.it("generates code for routes only", () => {
|
|
|
49
48
|
* Auto-generated by effect-start.
|
|
50
49
|
*/
|
|
51
50
|
|
|
52
|
-
import type { Router } from "effect-start"
|
|
53
|
-
|
|
54
51
|
export const routes = [
|
|
55
52
|
{
|
|
56
53
|
path: "/",
|
|
@@ -63,10 +60,12 @@ export const routes = [
|
|
|
63
60
|
] as const
|
|
64
61
|
`
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
test
|
|
64
|
+
.expect(code)
|
|
65
|
+
.toBe(expected)
|
|
67
66
|
})
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
test.it("generates code with layers", () => {
|
|
70
69
|
const handles: RouteHandle[] = [
|
|
71
70
|
parseRoute("layer.tsx"),
|
|
72
71
|
parseRoute("route.tsx"),
|
|
@@ -79,8 +78,6 @@ t.it("generates code with layers", () => {
|
|
|
79
78
|
* Auto-generated by effect-start.
|
|
80
79
|
*/
|
|
81
80
|
|
|
82
|
-
import type { Router } from "effect-start"
|
|
83
|
-
|
|
84
81
|
export const routes = [
|
|
85
82
|
{
|
|
86
83
|
path: "/",
|
|
@@ -99,10 +96,12 @@ export const routes = [
|
|
|
99
96
|
] as const
|
|
100
97
|
`
|
|
101
98
|
|
|
102
|
-
|
|
99
|
+
test
|
|
100
|
+
.expect(code)
|
|
101
|
+
.toBe(expected)
|
|
103
102
|
})
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
test.it("generates code with nested layers", () => {
|
|
106
105
|
const handles: RouteHandle[] = [
|
|
107
106
|
parseRoute("layer.tsx"),
|
|
108
107
|
parseRoute("dashboard/layer.tsx"),
|
|
@@ -116,8 +115,6 @@ t.it("generates code with nested layers", () => {
|
|
|
116
115
|
* Auto-generated by effect-start.
|
|
117
116
|
*/
|
|
118
117
|
|
|
119
|
-
import type { Router } from "effect-start"
|
|
120
|
-
|
|
121
118
|
export const routes = [
|
|
122
119
|
{
|
|
123
120
|
path: "/dashboard",
|
|
@@ -138,10 +135,12 @@ export const routes = [
|
|
|
138
135
|
] as const
|
|
139
136
|
`
|
|
140
137
|
|
|
141
|
-
|
|
138
|
+
test
|
|
139
|
+
.expect(code)
|
|
140
|
+
.toBe(expected)
|
|
142
141
|
})
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
test.it("only includes group layers for routes in that group", () => {
|
|
145
144
|
const handles: RouteHandle[] = [
|
|
146
145
|
parseRoute("layer.tsx"),
|
|
147
146
|
parseRoute("(admin)/layer.ts"),
|
|
@@ -151,16 +150,19 @@ t.it("only includes group layers for routes in that group", () => {
|
|
|
151
150
|
|
|
152
151
|
const code = FileRouterCodegen.generateCode(handles)
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
test
|
|
154
|
+
.expect(code)
|
|
155
|
+
.toContain("path: \"/users\"")
|
|
156
|
+
test
|
|
157
|
+
.expect(code)
|
|
158
|
+
.toContain("path: \"/movies\"")
|
|
159
|
+
test
|
|
160
|
+
.expect(code)
|
|
161
|
+
.toContain("() => import(\"./layer.tsx\")")
|
|
162
|
+
test
|
|
163
|
+
.expect(code)
|
|
164
|
+
.toContain("() => import(\"./(admin)/layer.ts\")")
|
|
157
165
|
|
|
158
|
-
// /users should have both root layer and (admin) layer
|
|
159
|
-
t.expect(code).toContain("() => import(\"./layer.tsx\")")
|
|
160
|
-
|
|
161
|
-
t.expect(code).toContain("() => import(\"./(admin)/layer.ts\")")
|
|
162
|
-
|
|
163
|
-
// /movies should only have root layer, not (admin) layer
|
|
164
166
|
const expectedMovies = ` {
|
|
165
167
|
path: "/movies",
|
|
166
168
|
load: () => import("./movies/route.tsx"),
|
|
@@ -169,10 +171,12 @@ t.it("only includes group layers for routes in that group", () => {
|
|
|
169
171
|
],
|
|
170
172
|
},`
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
test
|
|
175
|
+
.expect(code)
|
|
176
|
+
.toContain(expectedMovies)
|
|
173
177
|
})
|
|
174
178
|
|
|
175
|
-
|
|
179
|
+
test.it("handles dynamic routes with params", () => {
|
|
176
180
|
const handles: RouteHandle[] = [
|
|
177
181
|
parseRoute("users/route.tsx"),
|
|
178
182
|
parseRoute("users/[userId]/route.tsx"),
|
|
@@ -181,12 +185,18 @@ t.it("handles dynamic routes with params", () => {
|
|
|
181
185
|
|
|
182
186
|
const code = FileRouterCodegen.generateCode(handles)
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
test
|
|
189
|
+
.expect(code)
|
|
190
|
+
.toContain("path: \"/users\"")
|
|
191
|
+
test
|
|
192
|
+
.expect(code)
|
|
193
|
+
.toContain("path: \"/users/[userId]\"")
|
|
194
|
+
test
|
|
195
|
+
.expect(code)
|
|
196
|
+
.toContain("path: \"/posts/[postId]/comments/[commentId]\"")
|
|
187
197
|
})
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
test.it("handles rest parameters", () => {
|
|
190
200
|
const handles: RouteHandle[] = [
|
|
191
201
|
parseRoute("docs/[[...slug]]/route.tsx"),
|
|
192
202
|
parseRoute("api/[...path]/route.tsx"),
|
|
@@ -194,11 +204,15 @@ t.it("handles rest parameters", () => {
|
|
|
194
204
|
|
|
195
205
|
const code = FileRouterCodegen.generateCode(handles)
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
test
|
|
208
|
+
.expect(code)
|
|
209
|
+
.toContain("path: \"/docs/[[...slug]]\"")
|
|
210
|
+
test
|
|
211
|
+
.expect(code)
|
|
212
|
+
.toContain("path: \"/api/[...path]\"")
|
|
199
213
|
})
|
|
200
214
|
|
|
201
|
-
|
|
215
|
+
test.it("handles groups in path", () => {
|
|
202
216
|
const handles: RouteHandle[] = [
|
|
203
217
|
parseRoute("(admin)/users/route.tsx"),
|
|
204
218
|
parseRoute("(admin)/layer.tsx"),
|
|
@@ -206,23 +220,29 @@ t.it("handles groups in path", () => {
|
|
|
206
220
|
|
|
207
221
|
const code = FileRouterCodegen.generateCode(handles)
|
|
208
222
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"
|
|
212
|
-
|
|
223
|
+
test
|
|
224
|
+
.expect(code)
|
|
225
|
+
.toContain("path: \"/users\"")
|
|
226
|
+
test
|
|
227
|
+
.expect(code)
|
|
228
|
+
.toContain(
|
|
229
|
+
"layers: [\n () => import(\"./(admin)/layer.tsx\"),\n ]",
|
|
230
|
+
)
|
|
213
231
|
})
|
|
214
232
|
|
|
215
|
-
|
|
233
|
+
test.it("generates correct variable names for root routes", () => {
|
|
216
234
|
const handles: RouteHandle[] = [
|
|
217
235
|
parseRoute("route.tsx"),
|
|
218
236
|
]
|
|
219
237
|
|
|
220
238
|
const code = FileRouterCodegen.generateCode(handles)
|
|
221
239
|
|
|
222
|
-
|
|
240
|
+
test
|
|
241
|
+
.expect(code)
|
|
242
|
+
.toContain("path: \"/\"")
|
|
223
243
|
})
|
|
224
244
|
|
|
225
|
-
|
|
245
|
+
test.it("handles routes with dots in path segments", () => {
|
|
226
246
|
const handles: RouteHandle[] = [
|
|
227
247
|
parseRoute("events.json/route.ts"),
|
|
228
248
|
parseRoute("config.yaml.backup/route.ts"),
|
|
@@ -230,29 +250,33 @@ t.it("handles routes with dots in path segments", () => {
|
|
|
230
250
|
|
|
231
251
|
const code = FileRouterCodegen.generateCode(handles)
|
|
232
252
|
|
|
233
|
-
|
|
234
|
-
|
|
253
|
+
test
|
|
254
|
+
.expect(code)
|
|
255
|
+
.toContain("path: \"/events.json\"")
|
|
256
|
+
test
|
|
257
|
+
.expect(code)
|
|
258
|
+
.toContain("path: \"/config.yaml.backup\"")
|
|
235
259
|
})
|
|
236
260
|
|
|
237
|
-
|
|
261
|
+
test.it("uses default module identifier", () => {
|
|
238
262
|
const handles: RouteHandle[] = [
|
|
239
263
|
parseRoute("route.tsx"),
|
|
240
264
|
]
|
|
241
265
|
|
|
242
266
|
const code = FileRouterCodegen.generateCode(handles)
|
|
243
|
-
|
|
244
|
-
t.expect(code).toContain("import type { Router } from \"effect-start\"")
|
|
245
267
|
})
|
|
246
268
|
|
|
247
|
-
|
|
269
|
+
test.it("generates empty routes array when no handles provided", () => {
|
|
248
270
|
const handles: RouteHandle[] = []
|
|
249
271
|
|
|
250
272
|
const code = FileRouterCodegen.generateCode(handles)
|
|
251
273
|
|
|
252
|
-
|
|
274
|
+
test
|
|
275
|
+
.expect(code)
|
|
276
|
+
.toContain("export const routes = [] as const")
|
|
253
277
|
})
|
|
254
278
|
|
|
255
|
-
|
|
279
|
+
test.it("only includes routes, not layers", () => {
|
|
256
280
|
const handles: RouteHandle[] = [
|
|
257
281
|
parseRoute("layer.tsx"),
|
|
258
282
|
parseRoute("users/layer.tsx"),
|
|
@@ -260,10 +284,12 @@ t.it("only includes routes, not layers", () => {
|
|
|
260
284
|
|
|
261
285
|
const code = FileRouterCodegen.generateCode(handles)
|
|
262
286
|
|
|
263
|
-
|
|
287
|
+
test
|
|
288
|
+
.expect(code)
|
|
289
|
+
.toContain("export const routes = [] as const")
|
|
264
290
|
})
|
|
265
291
|
|
|
266
|
-
|
|
292
|
+
test.it("complex nested routes with multiple layers", () => {
|
|
267
293
|
const handles: RouteHandle[] = [
|
|
268
294
|
parseRoute("layer.tsx"),
|
|
269
295
|
parseRoute("(auth)/layer.tsx"),
|
|
@@ -276,18 +302,30 @@ t.it("complex nested routes with multiple layers", () => {
|
|
|
276
302
|
|
|
277
303
|
const code = FileRouterCodegen.generateCode(handles)
|
|
278
304
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
305
|
+
test
|
|
306
|
+
.expect(code)
|
|
307
|
+
.toContain("path: \"/login\"")
|
|
308
|
+
test
|
|
309
|
+
.expect(code)
|
|
310
|
+
.toContain("path: \"/signup\"")
|
|
311
|
+
test
|
|
312
|
+
.expect(code)
|
|
313
|
+
.toContain("path: \"/dashboard\"")
|
|
314
|
+
test
|
|
315
|
+
.expect(code)
|
|
316
|
+
.toContain("path: \"/dashboard/settings\"")
|
|
317
|
+
test
|
|
318
|
+
.expect(code)
|
|
319
|
+
.toContain("() => import(\"./layer.tsx\")")
|
|
320
|
+
test
|
|
321
|
+
.expect(code)
|
|
322
|
+
.toContain("() => import(\"./(auth)/layer.tsx\")")
|
|
323
|
+
test
|
|
324
|
+
.expect(code)
|
|
325
|
+
.toContain("() => import(\"./dashboard/layer.tsx\")")
|
|
288
326
|
})
|
|
289
327
|
|
|
290
|
-
|
|
328
|
+
test.it("handles routes with hyphens and underscores in path segments", () => {
|
|
291
329
|
const handles: RouteHandle[] = [
|
|
292
330
|
parseRoute("api-v1/route.ts"),
|
|
293
331
|
parseRoute("my_resource/route.ts"),
|
|
@@ -295,66 +333,55 @@ t.it("handles routes with hyphens and underscores in path segments", () => {
|
|
|
295
333
|
|
|
296
334
|
const code = FileRouterCodegen.generateCode(handles)
|
|
297
335
|
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
test
|
|
337
|
+
.expect(code)
|
|
338
|
+
.toContain("path: \"/api-v1\"")
|
|
339
|
+
test
|
|
340
|
+
.expect(code)
|
|
341
|
+
.toContain("path: \"/my_resource\"")
|
|
300
342
|
})
|
|
301
343
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
.toBe(true)
|
|
308
|
-
|
|
309
|
-
t
|
|
310
|
-
.expect(FileRouterCodegen.validateRouteModule({
|
|
311
|
-
default: Route.html(Effect.succeed("<div>Hello</div>")),
|
|
312
|
-
}))
|
|
313
|
-
.toBe(true)
|
|
314
|
-
|
|
315
|
-
t
|
|
316
|
-
.expect(FileRouterCodegen.validateRouteModule({
|
|
317
|
-
default: Route.json({ message: "Hello" }),
|
|
318
|
-
}))
|
|
319
|
-
.toBe(true)
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
t.it("validateRouteModule returns false for invalid modules", () => {
|
|
323
|
-
t.expect(FileRouterCodegen.validateRouteModule({})).toBe(false)
|
|
324
|
-
|
|
325
|
-
t
|
|
344
|
+
test.it.skip("validateRouteModule returns false for invalid modules", () => {
|
|
345
|
+
test
|
|
346
|
+
.expect(FileRouterCodegen.validateRouteModule({}))
|
|
347
|
+
.toBe(false)
|
|
348
|
+
test
|
|
326
349
|
.expect(FileRouterCodegen.validateRouteModule({ default: {} }))
|
|
327
350
|
.toBe(false)
|
|
328
|
-
|
|
329
|
-
t
|
|
351
|
+
test
|
|
330
352
|
.expect(FileRouterCodegen.validateRouteModule({ default: "not a route" }))
|
|
331
353
|
.toBe(false)
|
|
332
|
-
|
|
333
|
-
t
|
|
354
|
+
test
|
|
334
355
|
.expect(FileRouterCodegen.validateRouteModule({ foo: "bar" }))
|
|
335
356
|
.toBe(false)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
357
|
+
test
|
|
358
|
+
.expect(FileRouterCodegen.validateRouteModule(null))
|
|
359
|
+
.toBe(false)
|
|
360
|
+
test
|
|
361
|
+
.expect(FileRouterCodegen.validateRouteModule(undefined))
|
|
362
|
+
.toBe(false)
|
|
363
|
+
test
|
|
364
|
+
.expect(FileRouterCodegen.validateRouteModule("string"))
|
|
365
|
+
.toBe(false)
|
|
366
|
+
test
|
|
367
|
+
.expect(FileRouterCodegen.validateRouteModule(42))
|
|
368
|
+
.toBe(false)
|
|
344
369
|
})
|
|
345
370
|
|
|
346
|
-
|
|
371
|
+
test.it("mixed params and rest in same route", () => {
|
|
347
372
|
const handles: RouteHandle[] = [
|
|
348
373
|
parseRoute("users/[userId]/files/[...path]/route.tsx"),
|
|
349
374
|
]
|
|
350
375
|
|
|
351
376
|
const code = FileRouterCodegen.generateCode(handles)
|
|
352
377
|
|
|
353
|
-
|
|
378
|
+
test
|
|
379
|
+
.expect(code)
|
|
380
|
+
.toContain("path: \"/users/[userId]/files/[...path]\"")
|
|
354
381
|
})
|
|
355
382
|
|
|
356
|
-
|
|
357
|
-
|
|
383
|
+
test.describe("layerMatchesRoute", () => {
|
|
384
|
+
test.it("layer in dynamic param dir only applies to routes in that dir", () => {
|
|
358
385
|
const handles: RouteHandle[] = [
|
|
359
386
|
parseRoute("[userId]/layer.tsx"),
|
|
360
387
|
parseRoute("[userId]/posts/route.tsx"),
|
|
@@ -371,17 +398,21 @@ t.describe("layerMatchesRoute", () => {
|
|
|
371
398
|
],
|
|
372
399
|
},`
|
|
373
400
|
|
|
374
|
-
|
|
401
|
+
test
|
|
402
|
+
.expect(code)
|
|
403
|
+
.toContain(expectedUserIdPosts)
|
|
375
404
|
|
|
376
405
|
const expectedOtherId = ` {
|
|
377
406
|
path: "/[otherId]",
|
|
378
407
|
load: () => import("./[otherId]/route.tsx"),
|
|
379
408
|
},`
|
|
380
409
|
|
|
381
|
-
|
|
410
|
+
test
|
|
411
|
+
.expect(code)
|
|
412
|
+
.toContain(expectedOtherId)
|
|
382
413
|
})
|
|
383
414
|
|
|
384
|
-
|
|
415
|
+
test.it("nested groups only apply to routes in those groups", () => {
|
|
385
416
|
const handles: RouteHandle[] = [
|
|
386
417
|
parseRoute("layer.tsx"),
|
|
387
418
|
parseRoute("(admin)/(dashboard)/layer.tsx"),
|
|
@@ -401,7 +432,9 @@ t.describe("layerMatchesRoute", () => {
|
|
|
401
432
|
],
|
|
402
433
|
},`
|
|
403
434
|
|
|
404
|
-
|
|
435
|
+
test
|
|
436
|
+
.expect(code)
|
|
437
|
+
.toContain(expectedAdminDashboardUsers)
|
|
405
438
|
|
|
406
439
|
const expectedAdminSettings = ` {
|
|
407
440
|
path: "/settings",
|
|
@@ -411,7 +444,9 @@ t.describe("layerMatchesRoute", () => {
|
|
|
411
444
|
],
|
|
412
445
|
},`
|
|
413
446
|
|
|
414
|
-
|
|
447
|
+
test
|
|
448
|
+
.expect(code)
|
|
449
|
+
.toContain(expectedAdminSettings)
|
|
415
450
|
|
|
416
451
|
const expectedOtherDashboard = ` {
|
|
417
452
|
path: "/",
|
|
@@ -421,10 +456,12 @@ t.describe("layerMatchesRoute", () => {
|
|
|
421
456
|
],
|
|
422
457
|
},`
|
|
423
458
|
|
|
424
|
-
|
|
459
|
+
test
|
|
460
|
+
.expect(code)
|
|
461
|
+
.toContain(expectedOtherDashboard)
|
|
425
462
|
})
|
|
426
463
|
|
|
427
|
-
|
|
464
|
+
test.it("similar directory names do not match (user vs users)", () => {
|
|
428
465
|
const handles: RouteHandle[] = [
|
|
429
466
|
parseRoute("user/layer.tsx"),
|
|
430
467
|
parseRoute("user/route.tsx"),
|
|
@@ -441,17 +478,21 @@ t.describe("layerMatchesRoute", () => {
|
|
|
441
478
|
],
|
|
442
479
|
},`
|
|
443
480
|
|
|
444
|
-
|
|
481
|
+
test
|
|
482
|
+
.expect(code)
|
|
483
|
+
.toContain(expectedUser)
|
|
445
484
|
|
|
446
485
|
const expectedUsers = ` {
|
|
447
486
|
path: "/users",
|
|
448
487
|
load: () => import("./users/route.tsx"),
|
|
449
488
|
},`
|
|
450
489
|
|
|
451
|
-
|
|
490
|
+
test
|
|
491
|
+
.expect(code)
|
|
492
|
+
.toContain(expectedUsers)
|
|
452
493
|
})
|
|
453
494
|
|
|
454
|
-
|
|
495
|
+
test.it("mixed groups and literals layer matching", () => {
|
|
455
496
|
const handles: RouteHandle[] = [
|
|
456
497
|
parseRoute("(admin)/users/layer.tsx"),
|
|
457
498
|
parseRoute("(admin)/users/[userId]/route.tsx"),
|
|
@@ -469,24 +510,30 @@ t.describe("layerMatchesRoute", () => {
|
|
|
469
510
|
],
|
|
470
511
|
},`
|
|
471
512
|
|
|
472
|
-
|
|
513
|
+
test
|
|
514
|
+
.expect(code)
|
|
515
|
+
.toContain(expectedAdminUsersId)
|
|
473
516
|
|
|
474
517
|
const expectedUsers = ` {
|
|
475
518
|
path: "/users",
|
|
476
519
|
load: () => import("./users/route.tsx"),
|
|
477
520
|
},`
|
|
478
521
|
|
|
479
|
-
|
|
522
|
+
test
|
|
523
|
+
.expect(code)
|
|
524
|
+
.toContain(expectedUsers)
|
|
480
525
|
|
|
481
526
|
const expectedAdminPosts = ` {
|
|
482
527
|
path: "/posts",
|
|
483
528
|
load: () => import("./(admin)/posts/route.tsx"),
|
|
484
529
|
},`
|
|
485
530
|
|
|
486
|
-
|
|
531
|
+
test
|
|
532
|
+
.expect(code)
|
|
533
|
+
.toContain(expectedAdminPosts)
|
|
487
534
|
})
|
|
488
535
|
|
|
489
|
-
|
|
536
|
+
test.it("param directory layer only applies to routes in that dir", () => {
|
|
490
537
|
const handles: RouteHandle[] = [
|
|
491
538
|
parseRoute("[tenantId]/layer.tsx"),
|
|
492
539
|
parseRoute("[tenantId]/settings/route.tsx"),
|
|
@@ -503,17 +550,21 @@ t.describe("layerMatchesRoute", () => {
|
|
|
503
550
|
],
|
|
504
551
|
},`
|
|
505
552
|
|
|
506
|
-
|
|
553
|
+
test
|
|
554
|
+
.expect(code)
|
|
555
|
+
.toContain(expectedTenantSettings)
|
|
507
556
|
|
|
508
557
|
const expectedOther = ` {
|
|
509
558
|
path: "/other",
|
|
510
559
|
load: () => import("./other/route.tsx"),
|
|
511
560
|
},`
|
|
512
561
|
|
|
513
|
-
|
|
562
|
+
test
|
|
563
|
+
.expect(code)
|
|
564
|
+
.toContain(expectedOther)
|
|
514
565
|
})
|
|
515
566
|
|
|
516
|
-
|
|
567
|
+
test.it(
|
|
517
568
|
"optional param directory layer only applies to routes in that dir",
|
|
518
569
|
() => {
|
|
519
570
|
const handles: RouteHandle[] = [
|
|
@@ -532,18 +583,22 @@ t.describe("layerMatchesRoute", () => {
|
|
|
532
583
|
],
|
|
533
584
|
},`
|
|
534
585
|
|
|
535
|
-
|
|
586
|
+
test
|
|
587
|
+
.expect(code)
|
|
588
|
+
.toContain(expectedIdSettings)
|
|
536
589
|
|
|
537
590
|
const expectedOther = ` {
|
|
538
591
|
path: "/other",
|
|
539
592
|
load: () => import("./other/route.tsx"),
|
|
540
593
|
},`
|
|
541
594
|
|
|
542
|
-
|
|
595
|
+
test
|
|
596
|
+
.expect(code)
|
|
597
|
+
.toContain(expectedOther)
|
|
543
598
|
},
|
|
544
599
|
)
|
|
545
600
|
|
|
546
|
-
|
|
601
|
+
test.it("layer and route at same directory level", () => {
|
|
547
602
|
const handles: RouteHandle[] = [
|
|
548
603
|
parseRoute("users/layer.tsx"),
|
|
549
604
|
parseRoute("users/route.tsx"),
|
|
@@ -559,7 +614,9 @@ t.describe("layerMatchesRoute", () => {
|
|
|
559
614
|
],
|
|
560
615
|
},`
|
|
561
616
|
|
|
562
|
-
|
|
617
|
+
test
|
|
618
|
+
.expect(code)
|
|
619
|
+
.toContain(expected)
|
|
563
620
|
})
|
|
564
621
|
})
|
|
565
622
|
|
|
@@ -569,7 +626,7 @@ const simpleRouteContent = `import * as Route from "${
|
|
|
569
626
|
export default Route.text("Hello")
|
|
570
627
|
`
|
|
571
628
|
|
|
572
|
-
|
|
629
|
+
test.it("update() > writes file", () =>
|
|
573
630
|
Effect
|
|
574
631
|
.gen(function*() {
|
|
575
632
|
const fs = yield* FileSystem.FileSystem
|
|
@@ -585,7 +642,9 @@ t.it("update() > writes file", () =>
|
|
|
585
642
|
path.join(routesPath, "manifest.ts"),
|
|
586
643
|
)
|
|
587
644
|
|
|
588
|
-
|
|
645
|
+
test
|
|
646
|
+
.expect(content)
|
|
647
|
+
.toContain("export const routes =")
|
|
589
648
|
})
|
|
590
649
|
.pipe(
|
|
591
650
|
Effect.scoped,
|
|
@@ -593,7 +652,7 @@ t.it("update() > writes file", () =>
|
|
|
593
652
|
Effect.runPromise,
|
|
594
653
|
))
|
|
595
654
|
|
|
596
|
-
|
|
655
|
+
test.it("update() > writes only when it changes", () =>
|
|
597
656
|
Effect
|
|
598
657
|
.gen(function*() {
|
|
599
658
|
const fs = yield* FileSystem.FileSystem
|
|
@@ -615,8 +674,13 @@ t.it("update() > writes only when it changes", () =>
|
|
|
615
674
|
path.join(routesPath, "manifest.ts"),
|
|
616
675
|
)
|
|
617
676
|
|
|
618
|
-
|
|
619
|
-
|
|
677
|
+
test
|
|
678
|
+
.expect(content2)
|
|
679
|
+
.not
|
|
680
|
+
.toBe("")
|
|
681
|
+
test
|
|
682
|
+
.expect(content2)
|
|
683
|
+
.toBe(content)
|
|
620
684
|
})
|
|
621
685
|
.pipe(
|
|
622
686
|
Effect.scoped,
|
|
@@ -624,7 +688,7 @@ t.it("update() > writes only when it changes", () =>
|
|
|
624
688
|
Effect.runPromise,
|
|
625
689
|
))
|
|
626
690
|
|
|
627
|
-
|
|
691
|
+
test.it(
|
|
628
692
|
"update() > removes deleted routes from manifest",
|
|
629
693
|
() =>
|
|
630
694
|
Effect
|
|
@@ -642,8 +706,12 @@ t.it(
|
|
|
642
706
|
path.join(routesPath, "manifest.ts"),
|
|
643
707
|
)
|
|
644
708
|
|
|
645
|
-
|
|
646
|
-
|
|
709
|
+
test
|
|
710
|
+
.expect(content)
|
|
711
|
+
.toContain("path: \"/\"")
|
|
712
|
+
test
|
|
713
|
+
.expect(content)
|
|
714
|
+
.toContain("path: \"/about\"")
|
|
647
715
|
|
|
648
716
|
yield* fs.remove(path.join(routesPath, "about/route.tsx"))
|
|
649
717
|
|
|
@@ -653,8 +721,13 @@ t.it(
|
|
|
653
721
|
path.join(routesPath, "manifest.ts"),
|
|
654
722
|
)
|
|
655
723
|
|
|
656
|
-
|
|
657
|
-
|
|
724
|
+
test
|
|
725
|
+
.expect(content2)
|
|
726
|
+
.toContain("path: \"/\"")
|
|
727
|
+
test
|
|
728
|
+
.expect(content2)
|
|
729
|
+
.not
|
|
730
|
+
.toContain("path: \"/about\"")
|
|
658
731
|
})
|
|
659
732
|
.pipe(
|
|
660
733
|
Effect.scoped,
|
|
@@ -663,7 +736,7 @@ t.it(
|
|
|
663
736
|
),
|
|
664
737
|
)
|
|
665
738
|
|
|
666
|
-
|
|
739
|
+
test.it(
|
|
667
740
|
"update() > removes routes when entire directory is deleted",
|
|
668
741
|
() =>
|
|
669
742
|
Effect
|
|
@@ -682,9 +755,15 @@ t.it(
|
|
|
682
755
|
path.join(routesPath, "manifest.ts"),
|
|
683
756
|
)
|
|
684
757
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
758
|
+
test
|
|
759
|
+
.expect(content)
|
|
760
|
+
.toContain("path: \"/\"")
|
|
761
|
+
test
|
|
762
|
+
.expect(content)
|
|
763
|
+
.toContain("path: \"/about\"")
|
|
764
|
+
test
|
|
765
|
+
.expect(content)
|
|
766
|
+
.toContain("path: \"/users\"")
|
|
688
767
|
|
|
689
768
|
yield* fs.remove(path.join(routesPath, "users"), {
|
|
690
769
|
recursive: true,
|
|
@@ -696,9 +775,16 @@ t.it(
|
|
|
696
775
|
path.join(routesPath, "manifest.ts"),
|
|
697
776
|
)
|
|
698
777
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
778
|
+
test
|
|
779
|
+
.expect(content2)
|
|
780
|
+
.toContain("path: \"/\"")
|
|
781
|
+
test
|
|
782
|
+
.expect(content2)
|
|
783
|
+
.toContain("path: \"/about\"")
|
|
784
|
+
test
|
|
785
|
+
.expect(content2)
|
|
786
|
+
.not
|
|
787
|
+
.toContain("path: \"/users\"")
|
|
702
788
|
})
|
|
703
789
|
.pipe(
|
|
704
790
|
Effect.scoped,
|
|
@@ -707,121 +793,176 @@ t.it(
|
|
|
707
793
|
),
|
|
708
794
|
)
|
|
709
795
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
796
|
+
test.describe("PathParams schema generation and validation", () => {
|
|
797
|
+
test.describe("generatePathParamsSchema", () => {
|
|
798
|
+
test.it("returns null for routes with no params", () => {
|
|
713
799
|
const handle = parseRoute("users/route.tsx")
|
|
714
800
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
715
|
-
|
|
801
|
+
test
|
|
802
|
+
.expect(schema)
|
|
803
|
+
.toBe(null)
|
|
716
804
|
})
|
|
717
805
|
|
|
718
|
-
|
|
806
|
+
test.it("generates schema for single required param", () => {
|
|
719
807
|
const handle = parseRoute("users/[id]/route.tsx")
|
|
720
808
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
721
|
-
|
|
722
|
-
|
|
809
|
+
test
|
|
810
|
+
.expect(schema)
|
|
811
|
+
.not
|
|
812
|
+
.toBe(null)
|
|
813
|
+
test
|
|
814
|
+
.expect(Object.keys(schema!.fields))
|
|
815
|
+
.toEqual(["id"])
|
|
723
816
|
})
|
|
724
817
|
|
|
725
|
-
|
|
818
|
+
test.it("generates schema for single optional param", () => {
|
|
726
819
|
const handle = parseRoute("about/[[section]]/route.tsx")
|
|
727
820
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
728
|
-
|
|
729
|
-
|
|
821
|
+
test
|
|
822
|
+
.expect(schema)
|
|
823
|
+
.not
|
|
824
|
+
.toBe(null)
|
|
825
|
+
test
|
|
826
|
+
.expect(Object.keys(schema!.fields))
|
|
827
|
+
.toEqual(["section"])
|
|
730
828
|
})
|
|
731
829
|
|
|
732
|
-
|
|
830
|
+
test.it("generates schema for rest segment", () => {
|
|
733
831
|
const handle = parseRoute("docs/[...path]/route.tsx")
|
|
734
832
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
735
|
-
|
|
736
|
-
|
|
833
|
+
test
|
|
834
|
+
.expect(schema)
|
|
835
|
+
.not
|
|
836
|
+
.toBe(null)
|
|
837
|
+
test
|
|
838
|
+
.expect(Object.keys(schema!.fields))
|
|
839
|
+
.toEqual(["path"])
|
|
737
840
|
})
|
|
738
841
|
|
|
739
|
-
|
|
842
|
+
test.it("rest segment should capture path starting with /", () => {
|
|
740
843
|
const handle = parseRoute("docs/[...path]/route.tsx")
|
|
741
844
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
742
|
-
|
|
845
|
+
test
|
|
846
|
+
.expect(schema)
|
|
847
|
+
.not
|
|
848
|
+
.toBe(null)
|
|
743
849
|
|
|
744
|
-
// Rest segments capture remaining path as string
|
|
745
|
-
// For route /docs/[...path] matching /docs/guide/getting-started
|
|
746
|
-
// The path param should be: "/guide/getting-started" (with leading /)
|
|
747
850
|
const formatted = SchemaExtra.formatSchemaCode(schema!)
|
|
748
|
-
|
|
851
|
+
test
|
|
852
|
+
.expect(formatted)
|
|
853
|
+
.toBe("{ path: Schema.String }")
|
|
749
854
|
})
|
|
750
855
|
|
|
751
|
-
|
|
856
|
+
test.it("generates schema for optional rest segment", () => {
|
|
752
857
|
const handle = parseRoute("docs/[[...slug]]/route.tsx")
|
|
753
858
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
754
|
-
|
|
755
|
-
|
|
859
|
+
test
|
|
860
|
+
.expect(schema)
|
|
861
|
+
.not
|
|
862
|
+
.toBe(null)
|
|
863
|
+
test
|
|
864
|
+
.expect(Object.keys(schema!.fields))
|
|
865
|
+
.toEqual(["slug"])
|
|
756
866
|
})
|
|
757
867
|
|
|
758
|
-
|
|
868
|
+
test.it("generates schema for multiple params", () => {
|
|
759
869
|
const handle = parseRoute("posts/[postId]/comments/[commentId]/route.tsx")
|
|
760
870
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
871
|
+
test
|
|
872
|
+
.expect(schema)
|
|
873
|
+
.not
|
|
874
|
+
.toBe(null)
|
|
875
|
+
test
|
|
876
|
+
.expect(Object.keys(schema!.fields).sort())
|
|
877
|
+
.toEqual([
|
|
878
|
+
"commentId",
|
|
879
|
+
"postId",
|
|
880
|
+
])
|
|
766
881
|
})
|
|
767
882
|
|
|
768
|
-
|
|
883
|
+
test.it("generates schema for mixed required and optional params", () => {
|
|
769
884
|
const handle = parseRoute("users/[userId]/posts/[[postId]]/route.tsx")
|
|
770
885
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
771
|
-
|
|
772
|
-
|
|
886
|
+
test
|
|
887
|
+
.expect(schema)
|
|
888
|
+
.not
|
|
889
|
+
.toBe(null)
|
|
890
|
+
test
|
|
891
|
+
.expect(Object.keys(schema!.fields).sort())
|
|
892
|
+
.toEqual(["postId", "userId"])
|
|
773
893
|
})
|
|
774
894
|
|
|
775
|
-
|
|
895
|
+
test.it("ignores group segments", () => {
|
|
776
896
|
const handle = parseRoute("(admin)/users/[id]/route.tsx")
|
|
777
897
|
const schema = FileRouterCodegen.generatePathParamsSchema(handle.segments)
|
|
778
|
-
|
|
779
|
-
|
|
898
|
+
test
|
|
899
|
+
.expect(schema)
|
|
900
|
+
.not
|
|
901
|
+
.toBe(null)
|
|
902
|
+
test
|
|
903
|
+
.expect(Object.keys(schema!.fields))
|
|
904
|
+
.toEqual(["id"])
|
|
780
905
|
})
|
|
781
906
|
})
|
|
782
907
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
908
|
+
test.describe("schemaEqual", () => {
|
|
909
|
+
test.it("returns true when both schemas are undefined/null", () => {
|
|
910
|
+
test
|
|
911
|
+
.expect(SchemaExtra.schemaEqual(undefined, null))
|
|
912
|
+
.toBe(true)
|
|
786
913
|
})
|
|
787
914
|
|
|
788
|
-
|
|
915
|
+
test.it("returns false when only one schema is undefined", () => {
|
|
789
916
|
const schema = Schema.Struct({ id: Schema.String })
|
|
790
|
-
|
|
791
|
-
|
|
917
|
+
test
|
|
918
|
+
.expect(SchemaExtra.schemaEqual(undefined, schema))
|
|
919
|
+
.toBe(false)
|
|
920
|
+
test
|
|
921
|
+
.expect(SchemaExtra.schemaEqual(schema, null))
|
|
922
|
+
.toBe(false)
|
|
792
923
|
})
|
|
793
924
|
|
|
794
|
-
|
|
925
|
+
test.it("returns true for exact matches", () => {
|
|
795
926
|
const schema1 = Schema.Struct({ id: Schema.String })
|
|
796
927
|
const schema2 = Schema.Struct({ id: Schema.String })
|
|
797
|
-
|
|
928
|
+
test
|
|
929
|
+
.expect(SchemaExtra.schemaEqual(schema1, schema2))
|
|
930
|
+
.toBe(true)
|
|
798
931
|
})
|
|
799
932
|
|
|
800
|
-
|
|
933
|
+
test.it("returns true for refinement matches (UUID = String)", () => {
|
|
801
934
|
const userSchema = Schema.Struct({ id: Schema.UUID })
|
|
802
935
|
const expectedSchema = Schema.Struct({ id: Schema.String })
|
|
803
|
-
|
|
936
|
+
test
|
|
937
|
+
.expect(SchemaExtra.schemaEqual(userSchema, expectedSchema))
|
|
938
|
+
.toBe(true)
|
|
804
939
|
})
|
|
805
940
|
|
|
806
|
-
|
|
941
|
+
test.it("returns false for type mismatches", () => {
|
|
807
942
|
const schema1 = Schema.Struct({ id: Schema.String })
|
|
808
943
|
const schema2 = Schema.Struct({ id: Schema.Number })
|
|
809
|
-
|
|
944
|
+
test
|
|
945
|
+
.expect(SchemaExtra.schemaEqual(schema1, schema2))
|
|
946
|
+
.toBe(false)
|
|
810
947
|
})
|
|
811
948
|
|
|
812
|
-
|
|
949
|
+
test.it("returns false for field name mismatches", () => {
|
|
813
950
|
const schema1 = Schema.Struct({ id: Schema.String })
|
|
814
951
|
const schema2 = Schema.Struct({ userId: Schema.String })
|
|
815
|
-
|
|
952
|
+
test
|
|
953
|
+
.expect(SchemaExtra.schemaEqual(schema1, schema2))
|
|
954
|
+
.toBe(false)
|
|
816
955
|
})
|
|
817
956
|
|
|
818
|
-
|
|
957
|
+
test.it("returns false for field count mismatches", () => {
|
|
819
958
|
const schema1 = Schema.Struct({ id: Schema.String })
|
|
820
959
|
const schema2 = Schema.Struct({ id: Schema.String, name: Schema.String })
|
|
821
|
-
|
|
960
|
+
test
|
|
961
|
+
.expect(SchemaExtra.schemaEqual(schema1, schema2))
|
|
962
|
+
.toBe(false)
|
|
822
963
|
})
|
|
823
964
|
|
|
824
|
-
|
|
965
|
+
test.it("returns true for multiple fields with UUID refinement", () => {
|
|
825
966
|
const userSchema = Schema.Struct({
|
|
826
967
|
id: Schema.UUID,
|
|
827
968
|
name: Schema.String,
|
|
@@ -830,10 +971,12 @@ t.describe("PathParams schema generation and validation", () => {
|
|
|
830
971
|
id: Schema.String,
|
|
831
972
|
name: Schema.String,
|
|
832
973
|
})
|
|
833
|
-
|
|
974
|
+
test
|
|
975
|
+
.expect(SchemaExtra.schemaEqual(userSchema, expectedSchema))
|
|
976
|
+
.toBe(true)
|
|
834
977
|
})
|
|
835
978
|
|
|
836
|
-
|
|
979
|
+
test.it("handles optional fields correctly", () => {
|
|
837
980
|
const schema1 = Schema.Struct({
|
|
838
981
|
id: Schema.String,
|
|
839
982
|
name: Schema.optional(Schema.String),
|
|
@@ -842,48 +985,62 @@ t.describe("PathParams schema generation and validation", () => {
|
|
|
842
985
|
id: Schema.String,
|
|
843
986
|
name: Schema.optional(Schema.String),
|
|
844
987
|
})
|
|
845
|
-
|
|
988
|
+
test
|
|
989
|
+
.expect(SchemaExtra.schemaEqual(schema1, schema2))
|
|
990
|
+
.toBe(true)
|
|
846
991
|
})
|
|
847
992
|
})
|
|
848
993
|
|
|
849
|
-
|
|
850
|
-
|
|
994
|
+
test.describe("formatSchemaCode", () => {
|
|
995
|
+
test.it("formats single required field", () => {
|
|
851
996
|
const schema = Schema.Struct({ id: Schema.String })
|
|
852
997
|
const formatted = SchemaExtra.formatSchemaCode(schema)
|
|
853
|
-
|
|
998
|
+
test
|
|
999
|
+
.expect(formatted)
|
|
1000
|
+
.toBe("{ id: Schema.String }")
|
|
854
1001
|
})
|
|
855
1002
|
|
|
856
|
-
|
|
1003
|
+
test.it("formats multiple fields", () => {
|
|
857
1004
|
const schema = Schema.Struct({
|
|
858
1005
|
id: Schema.String,
|
|
859
1006
|
count: Schema.Number,
|
|
860
1007
|
})
|
|
861
1008
|
const formatted = SchemaExtra.formatSchemaCode(schema)
|
|
862
|
-
|
|
863
|
-
|
|
1009
|
+
test
|
|
1010
|
+
.expect(formatted)
|
|
1011
|
+
.toContain("id: Schema.String")
|
|
1012
|
+
test
|
|
1013
|
+
.expect(formatted)
|
|
1014
|
+
.toContain("count: Schema.Number")
|
|
864
1015
|
})
|
|
865
1016
|
|
|
866
|
-
|
|
1017
|
+
test.it("formats optional fields with ? marker", () => {
|
|
867
1018
|
const schema = Schema.Struct({
|
|
868
1019
|
id: Schema.String,
|
|
869
1020
|
name: Schema.optional(Schema.String),
|
|
870
1021
|
})
|
|
871
1022
|
const formatted = SchemaExtra.formatSchemaCode(schema)
|
|
872
|
-
|
|
873
|
-
|
|
1023
|
+
test
|
|
1024
|
+
.expect(formatted)
|
|
1025
|
+
.toContain("id: Schema.String")
|
|
1026
|
+
test
|
|
1027
|
+
.expect(formatted)
|
|
1028
|
+
.toContain("name")
|
|
874
1029
|
})
|
|
875
1030
|
|
|
876
|
-
|
|
1031
|
+
test.it("formats boolean fields", () => {
|
|
877
1032
|
const schema = Schema.Struct({
|
|
878
1033
|
active: Schema.Boolean,
|
|
879
1034
|
})
|
|
880
1035
|
const formatted = SchemaExtra.formatSchemaCode(schema)
|
|
881
|
-
|
|
1036
|
+
test
|
|
1037
|
+
.expect(formatted)
|
|
1038
|
+
.toBe("{ active: Schema.Boolean }")
|
|
882
1039
|
})
|
|
883
1040
|
})
|
|
884
1041
|
|
|
885
|
-
|
|
886
|
-
|
|
1042
|
+
test.describe("validateRouteModules", () => {
|
|
1043
|
+
test.it(
|
|
887
1044
|
"does not log when PathParams schema is missing",
|
|
888
1045
|
() =>
|
|
889
1046
|
Effect
|
|
@@ -906,9 +1063,10 @@ export default Route.text("User")
|
|
|
906
1063
|
|
|
907
1064
|
yield* FileRouterCodegen.validateRouteModules(routesPath, handles)
|
|
908
1065
|
|
|
909
|
-
// Verify no logs were created
|
|
910
1066
|
const messages = yield* TestLogger.messages
|
|
911
|
-
|
|
1067
|
+
test
|
|
1068
|
+
.expect(messages)
|
|
1069
|
+
.toHaveLength(0)
|
|
912
1070
|
})
|
|
913
1071
|
.pipe(
|
|
914
1072
|
Effect.scoped,
|
|
@@ -920,7 +1078,7 @@ export default Route.text("User")
|
|
|
920
1078
|
),
|
|
921
1079
|
)
|
|
922
1080
|
|
|
923
|
-
|
|
1081
|
+
test.it(
|
|
924
1082
|
"logs error when PathParams schema is incorrect",
|
|
925
1083
|
() =>
|
|
926
1084
|
Effect
|
|
@@ -928,7 +1086,7 @@ export default Route.text("User")
|
|
|
928
1086
|
const fs = yield* FileSystem.FileSystem
|
|
929
1087
|
const schemaPath = path.resolve(
|
|
930
1088
|
import.meta.dirname,
|
|
931
|
-
"
|
|
1089
|
+
"../../node_modules/effect/Schema",
|
|
932
1090
|
)
|
|
933
1091
|
const routeContent = `import * as Route from "${
|
|
934
1092
|
path.resolve(import.meta.dirname, "./Route.ts")
|
|
@@ -948,12 +1106,19 @@ export default Route.text("User").schemaPathParams({ userId: Schema.String })
|
|
|
948
1106
|
|
|
949
1107
|
yield* FileRouterCodegen.validateRouteModules(routesPath, handles)
|
|
950
1108
|
|
|
951
|
-
// Verify error was logged
|
|
952
1109
|
const messages = yield* TestLogger.messages
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1110
|
+
test
|
|
1111
|
+
.expect(messages)
|
|
1112
|
+
.toHaveLength(1)
|
|
1113
|
+
test
|
|
1114
|
+
.expect(messages[0])
|
|
1115
|
+
.toContain("[Error]")
|
|
1116
|
+
test
|
|
1117
|
+
.expect(messages[0])
|
|
1118
|
+
.toContain("incorrect PathParams schema")
|
|
1119
|
+
test
|
|
1120
|
+
.expect(messages[0])
|
|
1121
|
+
.toContain("expected schemaPathParams")
|
|
957
1122
|
})
|
|
958
1123
|
.pipe(
|
|
959
1124
|
Effect.scoped,
|