metaowl 0.4.1 → 0.6.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 (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +267 -2
  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 +144 -0
  10. package/build/runtime/modules/app-mounter.js +73 -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/constants.js +38 -0
  15. package/build/runtime/modules/error-boundary.js +116 -0
  16. package/build/runtime/modules/fetch.js +31 -0
  17. package/build/runtime/modules/file-router.js +207 -0
  18. package/build/runtime/modules/fonts.js +172 -0
  19. package/build/runtime/modules/forms.js +193 -0
  20. package/build/runtime/modules/i18n.js +180 -0
  21. package/build/runtime/modules/image.js +175 -0
  22. package/build/runtime/modules/layouts.js +214 -0
  23. package/build/runtime/modules/link.js +141 -0
  24. package/build/runtime/modules/meta.js +117 -0
  25. package/build/runtime/modules/odoo-rpc.js +265 -0
  26. package/build/runtime/modules/pwa.js +272 -0
  27. package/build/runtime/modules/router.js +384 -0
  28. package/build/runtime/modules/seo.js +186 -0
  29. package/build/runtime/modules/store.js +198 -0
  30. package/build/runtime/modules/templates-manager.js +52 -0
  31. package/build/runtime/modules/test-utils.js +238 -0
  32. package/build/runtime/vite/plugin.js +197 -0
  33. package/eslint.js +29 -0
  34. package/package.json +45 -27
  35. package/CONTRIBUTING.md +0 -49
  36. package/bin/metaowl-build.js +0 -12
  37. package/bin/metaowl-dev.js +0 -12
  38. package/bin/metaowl-generate.js +0 -339
  39. package/bin/metaowl-lint.js +0 -71
  40. package/bin/utils.js +0 -82
  41. package/eslint.config.js +0 -3
  42. package/index.js +0 -328
  43. package/modules/app-mounter.js +0 -104
  44. package/modules/auto-import.js +0 -225
  45. package/modules/cache.js +0 -59
  46. package/modules/composables.js +0 -600
  47. package/modules/error-boundary.js +0 -228
  48. package/modules/fetch.js +0 -51
  49. package/modules/file-router.js +0 -478
  50. package/modules/forms.js +0 -353
  51. package/modules/i18n.js +0 -333
  52. package/modules/layouts.js +0 -431
  53. package/modules/link.js +0 -255
  54. package/modules/meta.js +0 -119
  55. package/modules/odoo-rpc.js +0 -511
  56. package/modules/pwa.js +0 -515
  57. package/modules/router.js +0 -769
  58. package/modules/seo.js +0 -501
  59. package/modules/store.js +0 -409
  60. package/modules/templates-manager.js +0 -89
  61. package/modules/test-utils.js +0 -532
  62. package/test/auto-import.test.js +0 -110
  63. package/test/cache.test.js +0 -55
  64. package/test/composables.test.js +0 -103
  65. package/test/dynamic-routes.test.js +0 -469
  66. package/test/error-boundary.test.js +0 -126
  67. package/test/fetch.test.js +0 -100
  68. package/test/file-router.test.js +0 -55
  69. package/test/forms.test.js +0 -203
  70. package/test/i18n.test.js +0 -188
  71. package/test/layouts.test.js +0 -395
  72. package/test/link.test.js +0 -189
  73. package/test/meta.test.js +0 -146
  74. package/test/odoo-rpc.test.js +0 -547
  75. package/test/pwa.test.js +0 -154
  76. package/test/router-guards.test.js +0 -229
  77. package/test/router.test.js +0 -77
  78. package/test/seo.test.js +0 -353
  79. package/test/store.test.js +0 -476
  80. package/test/templates-manager.test.js +0 -83
  81. package/test/test-utils.test.js +0 -314
  82. package/vite/plugin.js +0 -290
  83. package/vitest.config.js +0 -8
package/test/pwa.test.js DELETED
@@ -1,154 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- generateManifest,
4
- isOnline,
5
- subscribeToConnectivity,
6
- sync,
7
- showNotification,
8
- cache,
9
- checkCapabilities,
10
- PWA
11
- } from '../modules/pwa.js'
12
-
13
- describe('PWA', () => {
14
- describe('Exports', () => {
15
- it('should export all functions', () => {
16
- expect(typeof generateManifest).toBe('function')
17
- expect(typeof isOnline).toBe('function')
18
- expect(typeof subscribeToConnectivity).toBe('function')
19
- expect(typeof sync).toBe('function')
20
- expect(typeof showNotification).toBe('function')
21
- expect(typeof cache).toBe('object')
22
- expect(typeof checkCapabilities).toBe('function')
23
- })
24
-
25
- it('should export PWA namespace', () => {
26
- expect(PWA.generateManifest).toBe(generateManifest)
27
- expect(PWA.isOnline).toBe(isOnline)
28
- expect(PWA.cache).toBe(cache)
29
- })
30
- })
31
-
32
- describe('generateManifest', () => {
33
- it('should generate basic manifest', () => {
34
- const manifest = generateManifest({
35
- name: 'My App',
36
- shortName: 'MyApp'
37
- })
38
-
39
- expect(manifest.name).toBe('My App')
40
- expect(manifest.short_name).toBe('MyApp')
41
- expect(manifest.display).toBe('standalone')
42
- expect(manifest.theme_color).toBe('#000000')
43
- })
44
-
45
- it('should include optional fields', () => {
46
- const manifest = generateManifest({
47
- name: 'Full App',
48
- shortName: 'Full',
49
- description: 'A complete app',
50
- startUrl: '/home',
51
- display: 'fullscreen',
52
- themeColor: '#007bff',
53
- backgroundColor: '#ffffff',
54
- scope: '/app',
55
- icons: [
56
- { src: '/icon.png', sizes: '192x192', type: 'image/png' }
57
- ]
58
- })
59
-
60
- expect(manifest.description).toBe('A complete app')
61
- expect(manifest.start_url).toBe('/home')
62
- expect(manifest.display).toBe('fullscreen')
63
- expect(manifest.theme_color).toBe('#007bff')
64
- expect(manifest.background_color).toBe('#ffffff')
65
- expect(manifest.scope).toBe('/app')
66
- expect(manifest.icons).toHaveLength(1)
67
- })
68
-
69
- it('should use default values', () => {
70
- const manifest = generateManifest({
71
- name: 'Minimal'
72
- })
73
-
74
- expect(manifest.display).toBe('standalone')
75
- expect(manifest.theme_color).toBe('#000000')
76
- expect(manifest.start_url).toBe('./')
77
- })
78
-
79
- it('should include orientation', () => {
80
- const manifest = generateManifest({ name: 'Test' })
81
- expect(manifest.orientation).toBe('any')
82
- })
83
- })
84
-
85
- describe('isOnline', () => {
86
- it('should return navigator.onLine', () => {
87
- const result = isOnline()
88
- expect(typeof result).toBe('boolean')
89
- })
90
- })
91
-
92
- describe('subscribeToConnectivity', () => {
93
- it('should return unsubscribe function', () => {
94
- const unsubscribe = subscribeToConnectivity({})
95
- expect(typeof unsubscribe).toBe('function')
96
-
97
- // Cleanup
98
- unsubscribe()
99
- })
100
-
101
- it('should not throw without callbacks', () => {
102
- const unsubscribe = subscribeToConnectivity({})
103
- expect(typeof unsubscribe).toBe('function')
104
- unsubscribe()
105
- })
106
- })
107
-
108
- describe('sync', () => {
109
- it('should return false without service worker', async () => {
110
- const result = await sync('test-tag')
111
- expect(result).toBe(false)
112
- })
113
- })
114
-
115
- describe('showNotification', () => {
116
- it('should not throw', async () => {
117
- await showNotification('Test', { body: 'Body' })
118
- // Should complete without error
119
- })
120
- })
121
-
122
- describe('cache', () => {
123
- it('should have cache methods', () => {
124
- expect(typeof cache.add).toBe('function')
125
- expect(typeof cache.remove).toBe('function')
126
- expect(typeof cache.clear).toBe('function')
127
- expect(typeof cache.info).toBe('function')
128
- })
129
-
130
- it('should return empty array for info without caches', async () => {
131
- const info = await cache.info()
132
- expect(Array.isArray(info)).toBe(true)
133
- })
134
- })
135
-
136
- describe('checkCapabilities', () => {
137
- it('should return capability object', () => {
138
- const caps = checkCapabilities()
139
-
140
- expect(typeof caps.serviceWorker).toBe('boolean')
141
- expect(typeof caps.push).toBe('boolean')
142
- expect(typeof caps.notifications).toBe('boolean')
143
- expect(typeof caps.backgroundSync).toBe('boolean')
144
- expect(typeof caps.persistentStorage).toBe('boolean')
145
- expect(typeof caps.addToHomeScreen).toBe('boolean')
146
- expect(typeof caps.offline).toBe('boolean')
147
- })
148
-
149
- it('should detect service worker support', () => {
150
- const caps = checkCapabilities()
151
- expect(caps.serviceWorker).toBeDefined()
152
- })
153
- })
154
- })
@@ -1,229 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
- import {
3
- processRoutes,
4
- beforeEach as beforeEachGuard,
5
- afterEach as afterEachHook,
6
- getCurrentRoute,
7
- getPreviousRoute,
8
- isNavigating,
9
- cancelNavigation,
10
- navigate,
11
- push,
12
- replace,
13
- back,
14
- forward,
15
- go,
16
- router,
17
- resetRouter
18
- } from '../modules/router.js'
19
-
20
- // Mock document.location
21
- describe('Router Guards', () => {
22
- let originalLocation
23
- let mockRoutes
24
-
25
- beforeEach(() => {
26
- // Save original location
27
- originalLocation = window.location
28
-
29
- // Create a shared pathname that both window.location and document.location will use
30
- let currentPathname = '/'
31
- let currentSearch = ''
32
-
33
- const mockLocation = {
34
- get pathname() { return currentPathname },
35
- set pathname(value) { currentPathname = value },
36
- get search() { return currentSearch },
37
- set search(value) { currentSearch = value },
38
- get href() { return `http://localhost${currentPathname}${currentSearch}` },
39
- replace: vi.fn(),
40
- assign: vi.fn()
41
- }
42
-
43
- // Mock location
44
- delete window.location
45
- window.location = mockLocation
46
-
47
- // Also mock document.location for router (use same object reference)
48
- document.location = mockLocation
49
-
50
- // Mock history
51
- window.history = {
52
- back: vi.fn(),
53
- forward: vi.fn(),
54
- go: vi.fn()
55
- }
56
-
57
- // Reset router state
58
- vi.clearAllMocks()
59
- resetRouter()
60
-
61
- // Define test routes
62
- mockRoutes = [
63
- { name: 'index', path: ['/'], component: class Index {} },
64
- { name: 'about', path: ['/about'], component: class About {} },
65
- { name: 'user', path: ['/user/:id'], component: class User {} },
66
- {
67
- name: 'admin',
68
- path: ['/admin'],
69
- component: class Admin {},
70
- meta: { requiresAuth: true },
71
- beforeEnter: null
72
- },
73
- { name: 'login', path: ['/login'], component: class Login {} }
74
- ]
75
- })
76
-
77
- afterEach(() => {
78
- // Restore original location
79
- window.location = originalLocation
80
- })
81
-
82
- describe('beforeEach guards', () => {
83
- it('provides to, from, and next to guard', async () => {
84
- const guard = vi.fn((to, from, next) => {
85
- expect(to).toHaveProperty('name')
86
- expect(to).toHaveProperty('path')
87
- expect(to).toHaveProperty('fullPath')
88
- expect(to).toHaveProperty('meta')
89
- expect(typeof next).toBe('function')
90
- next()
91
- })
92
-
93
- beforeEachGuard(guard)
94
- window.location.pathname = '/about'
95
-
96
- await processRoutes(mockRoutes)
97
-
98
- expect(guard).toHaveBeenCalled()
99
- })
100
-
101
- it('blocks navigation with next(false)', async () => {
102
- const guard = vi.fn((to, from, next) => next(false))
103
-
104
- beforeEachGuard(guard)
105
- window.location.pathname = '/about'
106
-
107
- await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation cancelled')
108
- })
109
-
110
- it('allows returning false directly from guard', async () => {
111
- const guard = vi.fn(() => false)
112
-
113
- beforeEachGuard(guard)
114
- window.location.pathname = '/about'
115
-
116
- await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation cancelled')
117
- })
118
-
119
- it('handles errors in guards', async () => {
120
- const guard = vi.fn(() => {
121
- throw new Error('Guard error')
122
- })
123
-
124
- beforeEachGuard(guard)
125
- window.location.pathname = '/about'
126
-
127
- await expect(processRoutes(mockRoutes)).rejects.toThrow('Guard error')
128
- })
129
-
130
- it('removes guard when unsubscribe is called', async () => {
131
- const guard = vi.fn((to, from, next) => next())
132
-
133
- const unsubscribe = beforeEachGuard(guard)
134
- unsubscribe()
135
-
136
- window.location.pathname = '/about'
137
- await processRoutes(mockRoutes)
138
-
139
- // Guard should not be called after unsubscribe
140
- expect(guard).not.toHaveBeenCalled()
141
- })
142
-
143
- it('calls multiple guards in order', async () => {
144
- const order = []
145
-
146
- beforeEachGuard((to, from, next) => {
147
- order.push(1)
148
- next()
149
- })
150
-
151
- beforeEachGuard((to, from, next) => {
152
- order.push(2)
153
- next()
154
- })
155
-
156
- window.location.pathname = '/about'
157
- await processRoutes(mockRoutes)
158
-
159
- expect(order).toEqual([1, 2])
160
- })
161
- })
162
-
163
- describe('afterEach hooks', () => {
164
- it('provides to and from to hook', async () => {
165
- const hook = vi.fn((to, from) => {
166
- expect(to).toHaveProperty('name')
167
- expect(from).toBeNull() // First navigation
168
- })
169
-
170
- afterEachHook(hook)
171
- window.location.pathname = '/about'
172
-
173
- await processRoutes(mockRoutes)
174
-
175
- expect(hook).toHaveBeenCalled()
176
- })
177
-
178
- it('removes hook when unsubscribe is called', async () => {
179
- const hook = vi.fn()
180
-
181
- const unsubscribe = afterEachHook(hook)
182
- unsubscribe()
183
-
184
- window.location.pathname = '/about'
185
- await processRoutes(mockRoutes)
186
-
187
- expect(hook).not.toHaveBeenCalled()
188
- })
189
- })
190
-
191
-
192
-
193
- describe('route state tracking', () => {
194
- it('exposes beforeEach method', () => {
195
- expect(router.beforeEach).toBe(beforeEachGuard)
196
- })
197
-
198
- it('exposes afterEach method', () => {
199
- expect(router.afterEach).toBe(afterEachHook)
200
- })
201
-
202
- it('exposes isNavigating getter', () => {
203
- expect(typeof router.isNavigating).toBe('boolean')
204
- })
205
-
206
- it('exposes navigation methods', () => {
207
- expect(typeof router.push).toBe('function')
208
- expect(typeof router.replace).toBe('function')
209
- expect(typeof router.back).toBe('function')
210
- expect(typeof router.forward).toBe('function')
211
- expect(typeof router.go).toBe('function')
212
- })
213
-
214
- it('back calls history.back', () => {
215
- back()
216
- expect(window.history.back).toHaveBeenCalled()
217
- })
218
-
219
- it('forward calls history.forward', () => {
220
- forward()
221
- expect(window.history.forward).toHaveBeenCalled()
222
- })
223
-
224
- it('go calls history.go', () => {
225
- go(-2)
226
- expect(window.history.go).toHaveBeenCalledWith(-2)
227
- })
228
- })
229
- })
@@ -1,77 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import { processRoutes } from '../modules/router.js'
3
-
4
- const Comp = function Page() {}
5
-
6
- function makeRoute(path) {
7
- return { name: 'page', path: [path], component: Comp }
8
- }
9
-
10
- // Stub document.location for each test
11
- function setPath(pathname) {
12
- Object.defineProperty(globalThis, 'document', {
13
- value: { location: { pathname } },
14
- writable: true,
15
- configurable: true
16
- })
17
- }
18
-
19
- beforeEach(() => {
20
- setPath('/')
21
- })
22
-
23
- describe('processRoutes', () => {
24
- it('resolves a matching route for /', async () => {
25
- setPath('/')
26
- const routes = [makeRoute('/')]
27
- const result = await processRoutes(routes)
28
- expect(result).toHaveLength(1)
29
- expect(result[0].component).toBe(Comp)
30
- })
31
-
32
- it('resolves /index.html as the index route', async () => {
33
- setPath('/index.html')
34
- const routes = [makeRoute('/')]
35
- const result = await processRoutes(routes)
36
- expect(result[0].component).toBe(Comp)
37
- })
38
-
39
- it('resolves /about via trailing slash variant', async () => {
40
- setPath('/about/')
41
- const routes = [makeRoute('/about')]
42
- const result = await processRoutes(routes)
43
- expect(result[0].component).toBe(Comp)
44
- })
45
-
46
- it('resolves /about.html variant', async () => {
47
- setPath('/about.html')
48
- const routes = [makeRoute('/about')]
49
- const result = await processRoutes(routes)
50
- expect(result[0].component).toBe(Comp)
51
- })
52
-
53
- it('resolves /about/index.html variant', async () => {
54
- setPath('/about/index.html')
55
- const routes = [makeRoute('/about')]
56
- const result = await processRoutes(routes)
57
- expect(result[0].component).toBe(Comp)
58
- })
59
-
60
- it('throws when no route matches', async () => {
61
- setPath('/not-found')
62
- const routes = [makeRoute('/')]
63
- await expect(processRoutes(routes)).rejects.toThrow('No route found')
64
- })
65
-
66
- it('does not duplicate SSG paths on repeated calls', async () => {
67
- setPath('/')
68
- const route = makeRoute('/')
69
- await processRoutes([route])
70
- const countBefore = route.path.length
71
- setPath('/index.html')
72
- await processRoutes([route])
73
- // /index.html should still only appear once
74
- const indexHtmlCount = route.path.filter(p => p === '/index.html').length
75
- expect(indexHtmlCount).toBe(1)
76
- })
77
- })