metaowl 0.4.1 → 0.5.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +12 -0
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +141 -0
  10. package/build/runtime/modules/app-mounter.js +65 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/error-boundary.js +116 -0
  15. package/build/runtime/modules/fetch.js +31 -0
  16. package/build/runtime/modules/file-router.js +205 -0
  17. package/build/runtime/modules/forms.js +193 -0
  18. package/build/runtime/modules/i18n.js +167 -0
  19. package/build/runtime/modules/layouts.js +163 -0
  20. package/build/runtime/modules/link.js +141 -0
  21. package/build/runtime/modules/meta.js +117 -0
  22. package/build/runtime/modules/odoo-rpc.js +264 -0
  23. package/build/runtime/modules/pwa.js +262 -0
  24. package/build/runtime/modules/router.js +389 -0
  25. package/build/runtime/modules/seo.js +186 -0
  26. package/build/runtime/modules/store.js +196 -0
  27. package/build/runtime/modules/templates-manager.js +52 -0
  28. package/build/runtime/modules/test-utils.js +238 -0
  29. package/build/runtime/vite/plugin.js +183 -0
  30. package/eslint.js +29 -0
  31. package/package.json +28 -10
  32. package/CONTRIBUTING.md +0 -49
  33. package/bin/metaowl-build.js +0 -12
  34. package/bin/metaowl-dev.js +0 -12
  35. package/bin/metaowl-generate.js +0 -339
  36. package/bin/metaowl-lint.js +0 -71
  37. package/bin/utils.js +0 -82
  38. package/eslint.config.js +0 -3
  39. package/index.js +0 -328
  40. package/modules/app-mounter.js +0 -104
  41. package/modules/auto-import.js +0 -225
  42. package/modules/cache.js +0 -59
  43. package/modules/composables.js +0 -600
  44. package/modules/error-boundary.js +0 -228
  45. package/modules/fetch.js +0 -51
  46. package/modules/file-router.js +0 -478
  47. package/modules/forms.js +0 -353
  48. package/modules/i18n.js +0 -333
  49. package/modules/layouts.js +0 -431
  50. package/modules/link.js +0 -255
  51. package/modules/meta.js +0 -119
  52. package/modules/odoo-rpc.js +0 -511
  53. package/modules/pwa.js +0 -515
  54. package/modules/router.js +0 -769
  55. package/modules/seo.js +0 -501
  56. package/modules/store.js +0 -409
  57. package/modules/templates-manager.js +0 -89
  58. package/modules/test-utils.js +0 -532
  59. package/test/auto-import.test.js +0 -110
  60. package/test/cache.test.js +0 -55
  61. package/test/composables.test.js +0 -103
  62. package/test/dynamic-routes.test.js +0 -469
  63. package/test/error-boundary.test.js +0 -126
  64. package/test/fetch.test.js +0 -100
  65. package/test/file-router.test.js +0 -55
  66. package/test/forms.test.js +0 -203
  67. package/test/i18n.test.js +0 -188
  68. package/test/layouts.test.js +0 -395
  69. package/test/link.test.js +0 -189
  70. package/test/meta.test.js +0 -146
  71. package/test/odoo-rpc.test.js +0 -547
  72. package/test/pwa.test.js +0 -154
  73. package/test/router-guards.test.js +0 -229
  74. package/test/router.test.js +0 -77
  75. package/test/seo.test.js +0 -353
  76. package/test/store.test.js +0 -476
  77. package/test/templates-manager.test.js +0 -83
  78. package/test/test-utils.test.js +0 -314
  79. package/vite/plugin.js +0 -290
  80. package/vitest.config.js +0 -8
@@ -1,103 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- useAuth,
4
- useLocalStorage,
5
- useFetch,
6
- useDebounce,
7
- useThrottle,
8
- useWindowSize,
9
- useOnlineStatus,
10
- useAsyncState,
11
- useCache,
12
- Composables
13
- } from '../modules/composables.js'
14
-
15
- describe('Composables', () => {
16
- describe('Exports', () => {
17
- it('should export useAuth function', () => {
18
- expect(typeof useAuth).toBe('function')
19
- })
20
-
21
- it('should export useLocalStorage function', () => {
22
- expect(typeof useLocalStorage).toBe('function')
23
- })
24
-
25
- it('should export useFetch function', () => {
26
- expect(typeof useFetch).toBe('function')
27
- })
28
-
29
- it('should export useDebounce function', () => {
30
- expect(typeof useDebounce).toBe('function')
31
- })
32
-
33
- it('should export useThrottle function', () => {
34
- expect(typeof useThrottle).toBe('function')
35
- })
36
-
37
- it('should export useWindowSize function', () => {
38
- expect(typeof useWindowSize).toBe('function')
39
- })
40
-
41
- it('should export useOnlineStatus function', () => {
42
- expect(typeof useOnlineStatus).toBe('function')
43
- })
44
-
45
- it('should export useAsyncState function', () => {
46
- expect(typeof useAsyncState).toBe('function')
47
- })
48
-
49
- it('should export useCache function', () => {
50
- expect(typeof useCache).toBe('function')
51
- })
52
- })
53
-
54
- describe('Composables namespace', () => {
55
- it('should export all composables via namespace', () => {
56
- expect(Composables.useAuth).toBe(useAuth)
57
- expect(Composables.useLocalStorage).toBe(useLocalStorage)
58
- expect(Composables.useFetch).toBe(useFetch)
59
- expect(Composables.useDebounce).toBe(useDebounce)
60
- expect(Composables.useThrottle).toBe(useThrottle)
61
- expect(Composables.useWindowSize).toBe(useWindowSize)
62
- expect(Composables.useOnlineStatus).toBe(useOnlineStatus)
63
- expect(Composables.useAsyncState).toBe(useAsyncState)
64
- expect(Composables.useCache).toBe(useCache)
65
- })
66
-
67
- it('should have correct function signatures', () => {
68
- // Check that all functions accept parameters
69
- // Note: function.length only counts required parameters before first default
70
- expect(useAuth.length).toBeGreaterThanOrEqual(0)
71
- expect(useLocalStorage.length).toBeGreaterThanOrEqual(0)
72
- expect(useFetch.length).toBeGreaterThanOrEqual(0)
73
- expect(useDebounce.length).toBeGreaterThanOrEqual(0)
74
- expect(useThrottle.length).toBeGreaterThanOrEqual(0)
75
- expect(useWindowSize.length).toBeGreaterThanOrEqual(0)
76
- expect(useOnlineStatus.length).toBeGreaterThanOrEqual(0)
77
- expect(useAsyncState.length).toBeGreaterThanOrEqual(0)
78
- expect(useCache.length).toBeGreaterThanOrEqual(0)
79
- })
80
- })
81
-
82
- describe('Documentation', () => {
83
- it('should have JSDoc comments', () => {
84
- // Check that functions are documented by checking they're exported
85
- const exportedFunctions = [
86
- useAuth,
87
- useLocalStorage,
88
- useFetch,
89
- useDebounce,
90
- useThrottle,
91
- useWindowSize,
92
- useOnlineStatus,
93
- useAsyncState,
94
- useCache
95
- ]
96
-
97
- for (const fn of exportedFunctions) {
98
- expect(typeof fn).toBe('function')
99
- expect(fn.name).toBeTruthy()
100
- }
101
- })
102
- })
103
- })
@@ -1,469 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import {
3
- pathFromKey,
4
- matchRoute,
5
- isDynamicRoute,
6
- findRoute,
7
- generateUrl,
8
- validateRouteParams,
9
- createCatchAllRoute,
10
- createRedirectRoute,
11
- buildRoutes,
12
- defineRoute,
13
- route
14
- } from '../modules/file-router.js'
15
-
16
- // Mock Component class
17
- class MockComponent {
18
- static template = '<div>Mock</div>'
19
- }
20
-
21
- class TestPage extends MockComponent {}
22
- class UserPage extends MockComponent {}
23
- class ProductPage extends MockComponent {}
24
- class CatchAllPage extends MockComponent {}
25
-
26
- describe('Dynamic Routes', () => {
27
- describe('pathFromKey', () => {
28
- it('converts static routes', () => {
29
- expect(pathFromKey('./pages/about/About.js')).toBe('/about')
30
- expect(pathFromKey('./pages/blog/post/Post.js')).toBe('/blog/post')
31
- })
32
-
33
- it('converts index route', () => {
34
- expect(pathFromKey('./pages/index/Index.js')).toBe('/')
35
- })
36
-
37
- it('converts [param] routes', () => {
38
- expect(pathFromKey('./pages/user/[id]/User.js')).toBe('/user/:id')
39
- })
40
-
41
- it('converts nested [params]', () => {
42
- expect(pathFromKey('./pages/product/[category]/[slug]/Product.js'))
43
- .toBe('/product/:category/:slug')
44
- })
45
-
46
- it('converts optional params [param?]', () => {
47
- expect(pathFromKey('./pages/blog/[id]/[slug?]/Blog.js'))
48
- .toBe('/blog/:id/:slug?')
49
- })
50
-
51
- it('converts catch-all [...path]', () => {
52
- expect(pathFromKey('./pages/docs/[...path]/Docs.js'))
53
- .toBe('/docs/:path(.*)')
54
- })
55
-
56
- it('handles root level params', () => {
57
- expect(pathFromKey('./pages/[id]/Page.js')).toBe('/:id')
58
- })
59
- })
60
-
61
- describe('matchRoute', () => {
62
- it('matches static routes', () => {
63
- const match = matchRoute('/about', '/about')
64
- expect(match).toEqual({ params: {}, pattern: '/about' })
65
- })
66
-
67
- it('extracts single param', () => {
68
- const match = matchRoute('/user/:id', '/user/123')
69
- expect(match.params).toEqual({ id: '123' })
70
- })
71
-
72
- it('extracts multiple params', () => {
73
- const match = matchRoute('/product/:category/:slug', '/product/tech/hello-world')
74
- expect(match.params).toEqual({ category: 'tech', slug: 'hello-world' })
75
- })
76
-
77
- it('returns null for non-matching route', () => {
78
- const match = matchRoute('/user/:id', '/about')
79
- expect(match).toBeNull()
80
- })
81
-
82
- it('matches optional params when provided', () => {
83
- const match = matchRoute('/blog/:id/:slug?', '/blog/123/my-post')
84
- expect(match.params).toEqual({ id: '123', slug: 'my-post' })
85
- })
86
-
87
- it('matches optional params when omitted', () => {
88
- const match = matchRoute('/blog/:id/:slug?', '/blog/123')
89
- expect(match.params).toEqual({ id: '123' })
90
- })
91
-
92
- it('matches catch-all routes', () => {
93
- const match = matchRoute('/docs/:path(.*)', '/docs/getting-started/install')
94
- expect(match.params).toEqual({ path: 'getting-started/install' })
95
- })
96
-
97
- it('handles root catch-all', () => {
98
- const match = matchRoute('/:path(.*)', '/anything/here')
99
- expect(match.params).toEqual({ path: 'anything/here' })
100
- })
101
-
102
- it('handles routes with special characters in params', () => {
103
- const match = matchRoute('/user/:id', '/user/user%40example.com')
104
- expect(match.params.id).toBe('user%40example.com')
105
- })
106
-
107
- it('does not match when required param is missing', () => {
108
- const match = matchRoute('/user/:id/profile', '/user/')
109
- expect(match).toBeNull()
110
- })
111
- })
112
-
113
- describe('isDynamicRoute', () => {
114
- it('returns true for routes with params', () => {
115
- expect(isDynamicRoute('/user/:id')).toBe(true)
116
- expect(isDynamicRoute('/product/:category/:id')).toBe(true)
117
- })
118
-
119
- it('returns false for static routes', () => {
120
- expect(isDynamicRoute('/about')).toBe(false)
121
- expect(isDynamicRoute('/blog/post')).toBe(false)
122
- })
123
-
124
- it('returns true for optional params', () => {
125
- expect(isDynamicRoute('/blog/:id?')).toBe(true)
126
- })
127
-
128
- it('returns true for catch-all', () => {
129
- expect(isDynamicRoute('/:path(.*)')).toBe(true)
130
- })
131
- })
132
-
133
- describe('findRoute', () => {
134
- const routes = [
135
- { name: 'index', path: ['/'], component: MockComponent },
136
- { name: 'about', path: ['/about'], component: MockComponent },
137
- { name: 'user', path: ['/user/:id'], component: UserPage, params: ['id'] },
138
- { name: 'product', path: ['/product/:category/:slug'], component: ProductPage, params: ['category', 'slug'] },
139
- { name: 'catch-all', path: ['/:path(.*)'], component: CatchAllPage, params: ['path'] }
140
- ]
141
-
142
- it('finds static route', () => {
143
- const route = findRoute(routes, '/about')
144
- expect(route.name).toBe('about')
145
- })
146
-
147
- it('finds dynamic route with params', () => {
148
- const route = findRoute(routes, '/user/123')
149
- expect(route.name).toBe('user')
150
- expect(route.params).toEqual({ id: '123' })
151
- })
152
-
153
- it('finds route with multiple params', () => {
154
- const route = findRoute(routes, '/product/tech/hello')
155
- expect(route.name).toBe('product')
156
- expect(route.params).toEqual({ category: 'tech', slug: 'hello' })
157
- })
158
-
159
- it('finds catch-all route', () => {
160
- const route = findRoute(routes, '/not/a/known/path')
161
- expect(route.name).toBe('catch-all')
162
- expect(route.params.path).toBe('not/a/known/path')
163
- })
164
-
165
- it('returns null for unknown route', () => {
166
- const route = findRoute(routes, '/unknown')
167
- // Should find catch-all instead
168
- expect(route?.name).toBe('catch-all')
169
- })
170
-
171
- it('prefers exact match over dynamic', () => {
172
- const specificRoutes = [
173
- { name: 'specific', path: ['/user/me'], component: MockComponent },
174
- { name: 'dynamic', path: ['/user/:id'], component: MockComponent, params: ['id'] }
175
- ]
176
-
177
- const route = findRoute(specificRoutes, '/user/me')
178
- expect(route.name).toBe('specific')
179
- })
180
- })
181
-
182
- describe('generateUrl', () => {
183
- const routes = [
184
- { name: 'index', path: ['/'] },
185
- { name: 'about', path: ['/about'] },
186
- { name: 'user', path: ['/user/:id'] },
187
- { name: 'product', path: ['/product/:category/:slug'] },
188
- { name: 'optional', path: ['/blog/:id/:slug?'] }
189
- ]
190
-
191
- it('generates static URL', () => {
192
- expect(generateUrl(routes, 'about')).toBe('/about')
193
- })
194
-
195
- it('generates URL with single param', () => {
196
- expect(generateUrl(routes, 'user', { id: '123' })).toBe('/user/123')
197
- })
198
-
199
- it('generates URL with multiple params', () => {
200
- expect(generateUrl(routes, 'product', { category: 'tech', slug: 'hello' }))
201
- .toBe('/product/tech/hello')
202
- })
203
-
204
- it('throws for unknown route', () => {
205
- expect(() => generateUrl(routes, 'unknown')).toThrow('Route "unknown" not found')
206
- })
207
-
208
- it('handles optional param when provided', () => {
209
- expect(generateUrl(routes, 'optional', { id: '123', slug: 'my-post' }))
210
- .toBe('/blog/123/my-post')
211
- })
212
-
213
- it('handles optional param when omitted', () => {
214
- expect(generateUrl(routes, 'optional', { id: '123' }))
215
- .toBe('/blog/123')
216
- })
217
- })
218
-
219
- describe('validateRouteParams', () => {
220
- const route = {
221
- name: 'user',
222
- params: ['id', 'name']
223
- }
224
-
225
- it('returns valid when all params provided', () => {
226
- const result = validateRouteParams(route, { id: '123', name: 'John' })
227
- expect(result.valid).toBe(true)
228
- expect(result.missing).toEqual([])
229
- expect(result.extra).toEqual([])
230
- })
231
-
232
- it('returns missing params', () => {
233
- const result = validateRouteParams(route, { id: '123' })
234
- expect(result.valid).toBe(false)
235
- expect(result.missing).toEqual(['name'])
236
- expect(result.extra).toEqual([])
237
- })
238
-
239
- it('returns extra params', () => {
240
- const result = validateRouteParams(route, { id: '123', name: 'John', extra: 'value' })
241
- expect(result.valid).toBe(true)
242
- expect(result.missing).toEqual([])
243
- expect(result.extra).toEqual(['extra'])
244
- })
245
-
246
- it('handles route without params', () => {
247
- const staticRoute = { name: 'about' }
248
- const result = validateRouteParams(staticRoute, {})
249
- expect(result.valid).toBe(true)
250
- })
251
- })
252
-
253
- describe('createCatchAllRoute', () => {
254
- it('creates catch-all route', () => {
255
- const route = createCatchAllRoute(CatchAllPage)
256
-
257
- expect(route.name).toBe('404')
258
- expect(route.path).toEqual(['/:path(.*)'])
259
- expect(route.component).toBe(CatchAllPage)
260
- expect(route.params).toEqual(['path'])
261
- expect(route.meta.catchAll).toBe(true)
262
- })
263
-
264
- it('accepts custom name', () => {
265
- const route = createCatchAllRoute(CatchAllPage, { name: 'not-found' })
266
- expect(route.name).toBe('not-found')
267
- })
268
-
269
- it('accepts custom meta', () => {
270
- const route = createCatchAllRoute(CatchAllPage, { meta: { requiresAuth: true } })
271
- expect(route.meta.requiresAuth).toBe(true)
272
- expect(route.meta.catchAll).toBe(true)
273
- })
274
- })
275
-
276
- describe('createRedirectRoute', () => {
277
- it('creates redirect route', () => {
278
- const route = createRedirectRoute('/old-path', '/new-path')
279
-
280
- expect(route.name).toBe('redirect-old-path')
281
- expect(route.path).toEqual(['/old-path'])
282
- expect(route.redirect).toBe('/new-path')
283
- expect(route.component).toBeNull()
284
- })
285
-
286
- it('sanitizes name from path', () => {
287
- const route = createRedirectRoute('/path/with/many/slashes', '/new')
288
- expect(route.name).toBe('redirect-path-with-many-slashes')
289
- })
290
- })
291
-
292
- describe('buildRoutes', () => {
293
- it('extracts component from module', () => {
294
- const modules = {
295
- './pages/Test.js': { default: TestPage }
296
- }
297
-
298
- const routes = buildRoutes(modules)
299
-
300
- expect(routes[0].component).toBe(TestPage)
301
- })
302
-
303
- it('falls back to first named export', () => {
304
- const modules = {
305
- './pages/About.js': { AboutPage: MockComponent }
306
- }
307
-
308
- const routes = buildRoutes(modules)
309
-
310
- expect(routes[0].component).toBe(MockComponent)
311
- })
312
-
313
- it('copies route config from component', () => {
314
- class ConfiguredPage extends MockComponent {
315
- static route = {
316
- meta: { requiresAuth: true },
317
- beforeEnter: () => {}
318
- }
319
- }
320
-
321
- const modules = {
322
- './pages/Configured.js': { default: ConfiguredPage }
323
- }
324
-
325
- const routes = buildRoutes(modules)
326
-
327
- expect(routes[0].meta).toEqual({ requiresAuth: true })
328
- expect(routes[0].beforeEnter).toBeDefined()
329
- })
330
-
331
- it('throws on missing component export', () => {
332
- const modules = {
333
- './pages/Empty.js': {}
334
- }
335
-
336
- expect(() => buildRoutes(modules)).toThrow('No component export found')
337
- })
338
- })
339
-
340
- describe('defineRoute', () => {
341
- it('returns route config object', () => {
342
- const config = defineRoute({
343
- path: '/custom',
344
- meta: { requiresAuth: true }
345
- })
346
-
347
- expect(config.path).toBe('/custom')
348
- expect(config.meta.requiresAuth).toBe(true)
349
- })
350
-
351
- it('can be assigned to component', () => {
352
- class MyPage extends MockComponent {}
353
- MyPage.route = defineRoute({
354
- path: '/my-page',
355
- meta: { title: 'My Page' }
356
- })
357
-
358
- expect(MyPage.route.path).toBe('/my-page')
359
- })
360
- })
361
-
362
- describe('route decorator', () => {
363
- it('sets route config on component', () => {
364
- const decorator = route({ meta: { requiresAuth: true } })
365
- class TestComponent extends MockComponent {}
366
-
367
- decorator(TestComponent)
368
-
369
- expect(TestComponent.route.meta.requiresAuth).toBe(true)
370
- })
371
-
372
- it('returns the component class', () => {
373
- const decorator = route({})
374
- class TestComponent extends MockComponent {}
375
-
376
- const result = decorator(TestComponent)
377
-
378
- expect(result).toBe(TestComponent)
379
- })
380
- })
381
-
382
- describe('complex routing scenarios', () => {
383
- it('handles blog with categories and posts', () => {
384
- const modules = {
385
- './pages/blog/Blog.js': { default: MockComponent },
386
- './pages/blog/[category]/Category.js': { default: MockComponent },
387
- './pages/blog/[category]/[slug]/Post.js': { default: MockComponent }
388
- }
389
-
390
- const routes = buildRoutes(modules)
391
-
392
- expect(routes).toHaveLength(3)
393
-
394
- // Check that routes are sorted correctly
395
- const blogRoute = routes.find(r => r.name === 'blog')
396
- const categoryRoute = routes.find(r => r.name.includes('category') && !r.name.includes('slug'))
397
- const postRoute = routes.find(r => r.name.includes('slug'))
398
-
399
- expect(blogRoute.path[0]).toBe('/blog')
400
- expect(categoryRoute.path[0]).toBe('/blog/:category')
401
- expect(postRoute.path[0]).toBe('/blog/:category/:slug')
402
- })
403
-
404
- it('handles admin area with nested routes', () => {
405
- const modules = {
406
- './pages/admin/dashboard/Dashboard.js': { default: MockComponent },
407
- './pages/admin/users/[id]/User.js': { default: MockComponent },
408
- './pages/admin/settings/Settings.js': { default: MockComponent }
409
- }
410
-
411
- const routes = buildRoutes(modules)
412
-
413
- expect(routes).toHaveLength(3)
414
- expect(routes.every(r => r.name.startsWith('admin'))).toBe(true)
415
- })
416
-
417
- it('handles catch-all 404 page', () => {
418
- const modules = {
419
- './pages/index/Index.js': { default: MockComponent },
420
- './pages/[...path]/NotFound.js': { default: MockComponent }
421
- }
422
-
423
- const routes = buildRoutes(modules)
424
-
425
- const notFoundRoute = routes.find(r => r.name === 'path')
426
- expect(notFoundRoute.path[0]).toBe('/:path(.*)')
427
- })
428
- })
429
-
430
- describe('edge cases', () => {
431
- it('handles empty modules', () => {
432
- const routes = buildRoutes({})
433
- expect(routes).toEqual([])
434
- })
435
-
436
- it('handles deep nesting', () => {
437
- const modules = {
438
- './pages/a/b/c/d/e/Deep.js': { default: MockComponent }
439
- }
440
-
441
- const routes = buildRoutes(modules)
442
-
443
- expect(routes[0].path[0]).toBe('/a/b/c/d/e')
444
- expect(routes[0].name).toBe('a-b-c-d-e')
445
- })
446
-
447
- it('handles special characters in paths', () => {
448
- // These would be unusual but should work
449
- const modules = {
450
- './pages/[id]/[action]/Dynamic.js': { default: MockComponent }
451
- }
452
-
453
- const routes = buildRoutes(modules)
454
-
455
- expect(routes[0].path[0]).toBe('/:id/:action')
456
- })
457
-
458
- it('handles single letter params', () => {
459
- const modules = {
460
- './pages/[a]/[b]/[c]/Page.js': { default: MockComponent }
461
- }
462
-
463
- const routes = buildRoutes(modules)
464
-
465
- expect(routes[0].path[0]).toBe('/:a/:b/:c')
466
- expect(routes[0].params).toEqual(['a', 'b', 'c'])
467
- })
468
- })
469
- })
@@ -1,126 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import {
3
- onError,
4
- setErrorContext,
5
- getErrorContext,
6
- clearErrorContext,
7
- captureError,
8
- initGlobalErrorHandling
9
- } from '../modules/error-boundary.js'
10
-
11
- describe('Error Boundary', () => {
12
- beforeEach(() => {
13
- clearErrorContext()
14
- // Clear all handlers
15
- vi.clearAllMocks()
16
- })
17
-
18
- describe('onError', () => {
19
- it('registers an error handler', () => {
20
- const handler = vi.fn()
21
- const unsubscribe = onError(handler)
22
-
23
- // Simulate error
24
- const error = new Error('Test error')
25
- captureError(error)
26
-
27
- expect(handler).toHaveBeenCalled()
28
- unsubscribe()
29
- })
30
-
31
- it('returns unsubscribe function', () => {
32
- const handler = vi.fn()
33
- const unsubscribe = onError(handler)
34
-
35
- unsubscribe()
36
-
37
- // Simulate error after unsubscribe
38
- const error = new Error('Test error')
39
- captureError(error)
40
-
41
- expect(handler).not.toHaveBeenCalled()
42
- })
43
-
44
- it('calls multiple handlers', () => {
45
- const handler1 = vi.fn()
46
- const handler2 = vi.fn()
47
-
48
- onError(handler1)
49
- onError(handler2)
50
-
51
- const error = new Error('Test error')
52
- captureError(error)
53
-
54
- expect(handler1).toHaveBeenCalled()
55
- expect(handler2).toHaveBeenCalled()
56
- })
57
-
58
- it('passes error and context to handler', () => {
59
- const handler = vi.fn()
60
- setErrorContext({ route: '/test' })
61
-
62
- onError(handler)
63
-
64
- const error = new Error('Test error')
65
- captureError(error, { component: 'TestComponent' })
66
-
67
- expect(handler).toHaveBeenCalledWith(error, {
68
- route: '/test',
69
- component: 'TestComponent'
70
- })
71
- })
72
- })
73
-
74
- describe('setErrorContext / getErrorContext', () => {
75
- it('sets and gets error context', () => {
76
- setErrorContext({ route: '/home' })
77
-
78
- expect(getErrorContext()).toEqual({ route: '/home' })
79
- })
80
-
81
- it('merges context objects', () => {
82
- setErrorContext({ route: '/home' })
83
- setErrorContext({ user: 'john' })
84
-
85
- expect(getErrorContext()).toEqual({
86
- route: '/home',
87
- user: 'john'
88
- })
89
- })
90
- })
91
-
92
- describe('clearErrorContext', () => {
93
- it('clears all context', () => {
94
- setErrorContext({ route: '/home', user: 'john' })
95
- clearErrorContext()
96
-
97
- expect(getErrorContext()).toEqual({})
98
- })
99
- })
100
-
101
- describe('captureError', () => {
102
- it('captures error and notifies handlers', () => {
103
- const handler = vi.fn()
104
- onError(handler)
105
-
106
- const error = new Error('Captured error')
107
- captureError(error)
108
-
109
- expect(handler).toHaveBeenCalledWith(error, {})
110
- })
111
-
112
- it('includes context in captured error', () => {
113
- const handler = vi.fn()
114
- setErrorContext({ app: 'test' })
115
- onError(handler)
116
-
117
- const error = new Error('Test error')
118
- captureError(error, { component: 'Home' })
119
-
120
- expect(handler).toHaveBeenCalledWith(error, {
121
- app: 'test',
122
- component: 'Home'
123
- })
124
- })
125
- })
126
- })