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.
- package/CHANGELOG.md +22 -0
- package/README.md +12 -0
- package/build/runtime/bin/metaowl-build.js +10 -0
- package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
- package/build/runtime/bin/metaowl-dev.js +10 -0
- package/build/runtime/bin/metaowl-generate.js +231 -0
- package/build/runtime/bin/metaowl-lint.js +58 -0
- package/build/runtime/bin/utils.js +68 -0
- package/build/runtime/index.js +141 -0
- package/build/runtime/modules/app-mounter.js +65 -0
- package/build/runtime/modules/auto-import.js +140 -0
- package/build/runtime/modules/cache.js +49 -0
- package/build/runtime/modules/composables.js +353 -0
- package/build/runtime/modules/error-boundary.js +116 -0
- package/build/runtime/modules/fetch.js +31 -0
- package/build/runtime/modules/file-router.js +205 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +167 -0
- package/build/runtime/modules/layouts.js +163 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +264 -0
- package/build/runtime/modules/pwa.js +262 -0
- package/build/runtime/modules/router.js +389 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +196 -0
- package/build/runtime/modules/templates-manager.js +52 -0
- package/build/runtime/modules/test-utils.js +238 -0
- package/build/runtime/vite/plugin.js +183 -0
- package/eslint.js +29 -0
- package/package.json +28 -10
- package/CONTRIBUTING.md +0 -49
- package/bin/metaowl-build.js +0 -12
- package/bin/metaowl-dev.js +0 -12
- package/bin/metaowl-generate.js +0 -339
- package/bin/metaowl-lint.js +0 -71
- package/bin/utils.js +0 -82
- package/eslint.config.js +0 -3
- package/index.js +0 -328
- package/modules/app-mounter.js +0 -104
- package/modules/auto-import.js +0 -225
- package/modules/cache.js +0 -59
- package/modules/composables.js +0 -600
- package/modules/error-boundary.js +0 -228
- package/modules/fetch.js +0 -51
- package/modules/file-router.js +0 -478
- package/modules/forms.js +0 -353
- package/modules/i18n.js +0 -333
- package/modules/layouts.js +0 -431
- package/modules/link.js +0 -255
- package/modules/meta.js +0 -119
- package/modules/odoo-rpc.js +0 -511
- package/modules/pwa.js +0 -515
- package/modules/router.js +0 -769
- package/modules/seo.js +0 -501
- package/modules/store.js +0 -409
- package/modules/templates-manager.js +0 -89
- package/modules/test-utils.js +0 -532
- package/test/auto-import.test.js +0 -110
- package/test/cache.test.js +0 -55
- package/test/composables.test.js +0 -103
- package/test/dynamic-routes.test.js +0 -469
- package/test/error-boundary.test.js +0 -126
- package/test/fetch.test.js +0 -100
- package/test/file-router.test.js +0 -55
- package/test/forms.test.js +0 -203
- package/test/i18n.test.js +0 -188
- package/test/layouts.test.js +0 -395
- package/test/link.test.js +0 -189
- package/test/meta.test.js +0 -146
- package/test/odoo-rpc.test.js +0 -547
- package/test/pwa.test.js +0 -154
- package/test/router-guards.test.js +0 -229
- package/test/router.test.js +0 -77
- package/test/seo.test.js +0 -353
- package/test/store.test.js +0 -476
- package/test/templates-manager.test.js +0 -83
- package/test/test-utils.test.js +0 -314
- package/vite/plugin.js +0 -290
- package/vitest.config.js +0 -8
package/test/store.test.js
DELETED
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
-
import { Store, createPersistencePlugin, createStore } from '../modules/store.js'
|
|
3
|
-
|
|
4
|
-
describe('Store', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
Store.clear()
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
describe('Store.define()', () => {
|
|
10
|
-
it('creates a store factory function', () => {
|
|
11
|
-
const useCounterStore = Store.define('counter', {
|
|
12
|
-
state: () => ({ count: 0 })
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
expect(typeof useCounterStore).toBe('function')
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('returns singleton instance on multiple calls', () => {
|
|
19
|
-
const useCounterStore = Store.define('counter', {
|
|
20
|
-
state: () => ({ count: 0 })
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const store1 = useCounterStore()
|
|
24
|
-
const store2 = useCounterStore()
|
|
25
|
-
|
|
26
|
-
expect(store1).toBe(store2)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('creates reactive state', () => {
|
|
30
|
-
const useCounterStore = Store.define('counter', {
|
|
31
|
-
state: () => ({ count: 0 })
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const store = useCounterStore()
|
|
35
|
-
|
|
36
|
-
expect(store.state.count).toBe(0)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('state management', () => {
|
|
41
|
-
let useCounterStore
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
useCounterStore = Store.define('counter', {
|
|
45
|
-
state: () => ({ count: 0, name: 'test' }),
|
|
46
|
-
mutations: {
|
|
47
|
-
increment: (state) => state.count++,
|
|
48
|
-
setName: (state, name) => { state.name = name },
|
|
49
|
-
add: (state, amount) => { state.count += amount }
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('commits mutations synchronously', () => {
|
|
55
|
-
const store = useCounterStore()
|
|
56
|
-
|
|
57
|
-
store.commit('increment')
|
|
58
|
-
|
|
59
|
-
expect(store.state.count).toBe(1)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('commits mutations with payload', () => {
|
|
63
|
-
const store = useCounterStore()
|
|
64
|
-
|
|
65
|
-
store.commit('setName', 'new name')
|
|
66
|
-
|
|
67
|
-
expect(store.state.name).toBe('new name')
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('commits mutations with numeric payload', () => {
|
|
71
|
-
const store = useCounterStore()
|
|
72
|
-
|
|
73
|
-
store.commit('add', 5)
|
|
74
|
-
|
|
75
|
-
expect(store.state.count).toBe(5)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('throws on unknown mutation', () => {
|
|
79
|
-
const store = useCounterStore()
|
|
80
|
-
|
|
81
|
-
expect(() => store.commit('unknown')).toThrow('Mutation "unknown" not found')
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
describe('getters', () => {
|
|
86
|
-
it('computes getter values', () => {
|
|
87
|
-
const useCounterStore = Store.define('counter', {
|
|
88
|
-
state: () => ({ count: 5 }),
|
|
89
|
-
getters: {
|
|
90
|
-
double: (state) => state.count * 2,
|
|
91
|
-
triple: (state) => state.count * 3
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const store = useCounterStore()
|
|
96
|
-
|
|
97
|
-
expect(store.getters.double).toBe(10)
|
|
98
|
-
expect(store.getters.triple).toBe(15)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('updates getters when state changes', () => {
|
|
102
|
-
const useCounterStore = Store.define('counter', {
|
|
103
|
-
state: () => ({ count: 5 }),
|
|
104
|
-
getters: {
|
|
105
|
-
double: (state) => state.count * 2
|
|
106
|
-
},
|
|
107
|
-
mutations: {
|
|
108
|
-
increment: (state) => state.count++
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const store = useCounterStore()
|
|
113
|
-
expect(store.getters.double).toBe(10)
|
|
114
|
-
|
|
115
|
-
store.commit('increment')
|
|
116
|
-
expect(store.getters.double).toBe(12)
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('actions', () => {
|
|
121
|
-
it('dispatches async actions', async () => {
|
|
122
|
-
const useCounterStore = Store.define('counter', {
|
|
123
|
-
state: () => ({ count: 0 }),
|
|
124
|
-
mutations: {
|
|
125
|
-
increment: (state) => state.count++
|
|
126
|
-
},
|
|
127
|
-
actions: {
|
|
128
|
-
async incrementAsync({ commit }) {
|
|
129
|
-
await new Promise(resolve => setTimeout(resolve, 10))
|
|
130
|
-
commit('increment')
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const store = useCounterStore()
|
|
136
|
-
|
|
137
|
-
await store.dispatch('incrementAsync')
|
|
138
|
-
|
|
139
|
-
expect(store.state.count).toBe(1)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('passes payload to actions', async () => {
|
|
143
|
-
const useCounterStore = Store.define('counter', {
|
|
144
|
-
state: () => ({ count: 0 }),
|
|
145
|
-
mutations: {
|
|
146
|
-
add: (state, amount) => { state.count += amount }
|
|
147
|
-
},
|
|
148
|
-
actions: {
|
|
149
|
-
async addAsync({ commit }, amount) {
|
|
150
|
-
commit('add', amount)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
const store = useCounterStore()
|
|
156
|
-
|
|
157
|
-
await store.dispatch('addAsync', 10)
|
|
158
|
-
|
|
159
|
-
expect(store.state.count).toBe(10)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('provides context to actions', async () => {
|
|
163
|
-
const actionContext = {}
|
|
164
|
-
|
|
165
|
-
const useCounterStore = Store.define('counter2', {
|
|
166
|
-
state: () => ({ count: 0 }),
|
|
167
|
-
getters: { double: (state) => state.count * 2 },
|
|
168
|
-
mutations: { increment: (state) => state.count++ },
|
|
169
|
-
actions: {
|
|
170
|
-
async testContext(context) {
|
|
171
|
-
Object.assign(actionContext, {
|
|
172
|
-
hasState: 'state' in context,
|
|
173
|
-
hasGetters: 'getters' in context,
|
|
174
|
-
hasCommit: typeof context.commit === 'function',
|
|
175
|
-
hasDispatch: typeof context.dispatch === 'function'
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
const store = useCounterStore()
|
|
182
|
-
await store.dispatch('testContext')
|
|
183
|
-
|
|
184
|
-
expect(actionContext.hasState).toBe(true)
|
|
185
|
-
expect(actionContext.hasGetters).toBe(true)
|
|
186
|
-
expect(actionContext.hasCommit).toBe(true)
|
|
187
|
-
expect(actionContext.hasDispatch).toBe(true)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('throws on unknown action', async () => {
|
|
191
|
-
const useCounterStore = Store.define('counter', {
|
|
192
|
-
state: () => ({ count: 0 })
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const store = useCounterStore()
|
|
196
|
-
|
|
197
|
-
await expect(store.dispatch('unknown')).rejects.toThrow('Action "unknown" not found')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('returns action result', async () => {
|
|
201
|
-
const useCounterStore = Store.define('counter', {
|
|
202
|
-
state: () => ({ count: 5 }),
|
|
203
|
-
actions: {
|
|
204
|
-
async fetchData() {
|
|
205
|
-
return { data: 'test' }
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const store = useCounterStore()
|
|
211
|
-
const result = await store.dispatch('fetchData')
|
|
212
|
-
|
|
213
|
-
expect(result).toEqual({ data: 'test' })
|
|
214
|
-
})
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
describe('subscriptions', () => {
|
|
218
|
-
it('notifies subscribers on mutation', () => {
|
|
219
|
-
const useCounterStore = Store.define('counter', {
|
|
220
|
-
state: () => ({ count: 0 }),
|
|
221
|
-
mutations: { increment: (state) => state.count++ }
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
const store = useCounterStore()
|
|
225
|
-
const subscriber = { fn: () => {} }
|
|
226
|
-
const spy = { ...subscriber, fn: (mutation, state, prevState) => {} }
|
|
227
|
-
|
|
228
|
-
let receivedMutation, receivedState, receivedPrevState
|
|
229
|
-
store.subscribe((mutation, state, prevState) => {
|
|
230
|
-
receivedMutation = mutation
|
|
231
|
-
receivedState = state
|
|
232
|
-
receivedPrevState = prevState
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
store.commit('increment')
|
|
236
|
-
|
|
237
|
-
expect(receivedMutation.type).toBe('increment')
|
|
238
|
-
expect(receivedState.count).toBe(1)
|
|
239
|
-
expect(receivedPrevState.count).toBe(0)
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
it('allows unsubscribing', () => {
|
|
243
|
-
const useCounterStore = Store.define('counter', {
|
|
244
|
-
state: () => ({ count: 0 }),
|
|
245
|
-
mutations: { increment: (state) => state.count++ }
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
const store = useCounterStore()
|
|
249
|
-
let callCount = 0
|
|
250
|
-
const unsubscribe = store.subscribe(() => callCount++)
|
|
251
|
-
|
|
252
|
-
store.commit('increment')
|
|
253
|
-
expect(callCount).toBe(1)
|
|
254
|
-
|
|
255
|
-
unsubscribe()
|
|
256
|
-
store.commit('increment')
|
|
257
|
-
expect(callCount).toBe(1)
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
describe('action subscriptions', () => {
|
|
262
|
-
it('notifies action subscribers', async () => {
|
|
263
|
-
const useCounterStore = Store.define('counter', {
|
|
264
|
-
state: () => ({ count: 0 }),
|
|
265
|
-
actions: {
|
|
266
|
-
async testAction() {
|
|
267
|
-
return 'result'
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
const store = useCounterStore()
|
|
273
|
-
const events = []
|
|
274
|
-
|
|
275
|
-
store.subscribeAction((action, status, result) => {
|
|
276
|
-
events.push({ action, status, result })
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
await store.dispatch('testAction')
|
|
280
|
-
|
|
281
|
-
expect(events.length).toBe(2)
|
|
282
|
-
expect(events[0].status).toBe('before')
|
|
283
|
-
expect(events[1].status).toBe('after')
|
|
284
|
-
expect(events[1].result).toBe('result')
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('notifies on action error', async () => {
|
|
288
|
-
const useCounterStore = Store.define('counter', {
|
|
289
|
-
actions: {
|
|
290
|
-
async failingAction() {
|
|
291
|
-
throw new Error('test error')
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
const store = useCounterStore()
|
|
297
|
-
const events = []
|
|
298
|
-
|
|
299
|
-
store.subscribeAction((action, status, result) => {
|
|
300
|
-
events.push({ action, status, result })
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
await store.dispatch('failingAction')
|
|
305
|
-
} catch (e) {
|
|
306
|
-
// expected
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
expect(events.length).toBe(2)
|
|
310
|
-
expect(events[0].status).toBe('before')
|
|
311
|
-
expect(events[1].status).toBe('error')
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
describe('reset', () => {
|
|
316
|
-
it('resets state to initial values', () => {
|
|
317
|
-
const useCounterStore = Store.define('counter', {
|
|
318
|
-
state: () => ({ count: 0, name: 'initial' }),
|
|
319
|
-
mutations: {
|
|
320
|
-
setCount: (state, val) => { state.count = val },
|
|
321
|
-
setName: (state, val) => { state.name = val }
|
|
322
|
-
}
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
const store = useCounterStore()
|
|
326
|
-
store.commit('setCount', 100)
|
|
327
|
-
store.commit('setName', 'changed')
|
|
328
|
-
|
|
329
|
-
store.reset()
|
|
330
|
-
|
|
331
|
-
expect(store.state.count).toBe(0)
|
|
332
|
-
expect(store.state.name).toBe('initial')
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
describe('static methods', () => {
|
|
337
|
-
it('Store.get() retrieves store by id', () => {
|
|
338
|
-
const useCounterStore = Store.define('counter', {
|
|
339
|
-
state: () => ({ count: 0 })
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
const store = useCounterStore()
|
|
343
|
-
const retrieved = Store.get('counter')
|
|
344
|
-
|
|
345
|
-
expect(retrieved).toBe(store)
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
it('Store.has() checks store existence', () => {
|
|
349
|
-
const useCounterStore = Store.define('counter', {
|
|
350
|
-
state: () => ({ count: 0 })
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
useCounterStore() // Initialize the store
|
|
354
|
-
|
|
355
|
-
expect(Store.has('counter')).toBe(true)
|
|
356
|
-
expect(Store.has('unknown')).toBe(false)
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
it('Store.remove() removes store', () => {
|
|
360
|
-
const useCounterStore = Store.define('counter', {
|
|
361
|
-
state: () => ({ count: 0 })
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
useCounterStore()
|
|
365
|
-
expect(Store.has('counter')).toBe(true)
|
|
366
|
-
|
|
367
|
-
Store.remove('counter')
|
|
368
|
-
expect(Store.has('counter')).toBe(false)
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('Store.storeIds() returns all store IDs', () => {
|
|
372
|
-
const useStore1 = Store.define('store1', { state: () => ({}) })
|
|
373
|
-
const useStore2 = Store.define('store2', { state: () => ({}) })
|
|
374
|
-
|
|
375
|
-
useStore1()
|
|
376
|
-
useStore2()
|
|
377
|
-
|
|
378
|
-
const ids = Store.storeIds()
|
|
379
|
-
expect(ids).toContain('store1')
|
|
380
|
-
expect(ids).toContain('store2')
|
|
381
|
-
})
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
describe('persistence plugin', () => {
|
|
385
|
-
it('persists state to storage', () => {
|
|
386
|
-
const storage = {
|
|
387
|
-
data: {},
|
|
388
|
-
getItem(key) { return this.data[key] || null },
|
|
389
|
-
setItem(key, value) { this.data[key] = value }
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
Store.use(createPersistencePlugin({ storage }))
|
|
393
|
-
|
|
394
|
-
const useCounterStore = Store.define('counter', {
|
|
395
|
-
state: () => ({ count: 0 }),
|
|
396
|
-
mutations: { increment: (state) => state.count++ }
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
const store = useCounterStore()
|
|
400
|
-
store.commit('increment')
|
|
401
|
-
|
|
402
|
-
expect(storage.data['metaowl:store:counter']).toContain('"count":1')
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
it('restores state from storage', () => {
|
|
406
|
-
const storage = {
|
|
407
|
-
data: { 'metaowl:store:counter': '{"count":42}' },
|
|
408
|
-
getItem(key) { return this.data[key] || null },
|
|
409
|
-
setItem(key, value) { this.data[key] = value }
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
Store.use(createPersistencePlugin({ storage }))
|
|
413
|
-
|
|
414
|
-
const useCounterStore = Store.define('counter', {
|
|
415
|
-
state: () => ({ count: 0 }),
|
|
416
|
-
mutations: { increment: (state) => state.count++ }
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
const store = useCounterStore()
|
|
420
|
-
|
|
421
|
-
expect(store.state.count).toBe(42)
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
it('persists only specified paths', () => {
|
|
425
|
-
const storage = {
|
|
426
|
-
data: {},
|
|
427
|
-
getItem(key) { return this.data[key] || null },
|
|
428
|
-
setItem(key, value) { this.data[key] = value }
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
Store.use(createPersistencePlugin({ storage, paths: ['persistent'] }))
|
|
432
|
-
|
|
433
|
-
const useStore = Store.define('test', {
|
|
434
|
-
state: () => ({ persistent: 'value', temporary: 'data' }),
|
|
435
|
-
mutations: {
|
|
436
|
-
setPersistent: (state, val) => { state.persistent = val },
|
|
437
|
-
setTemporary: (state, val) => { state.temporary = val }
|
|
438
|
-
}
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
const store = useStore()
|
|
442
|
-
store.commit('setPersistent', 'new value')
|
|
443
|
-
store.commit('setTemporary', 'new temp')
|
|
444
|
-
|
|
445
|
-
const saved = JSON.parse(storage.data['metaowl:store:test'])
|
|
446
|
-
expect(saved.persistent).toBe('new value')
|
|
447
|
-
expect(saved.temporary).toBeUndefined()
|
|
448
|
-
})
|
|
449
|
-
})
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
describe('createStore', () => {
|
|
453
|
-
it('creates reactive state object', () => {
|
|
454
|
-
const state = createStore({ count: 0 })
|
|
455
|
-
|
|
456
|
-
expect(state.count).toBe(0)
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
it('has $patch method for batch updates', () => {
|
|
460
|
-
const state = createStore({ count: 0, name: 'test' })
|
|
461
|
-
|
|
462
|
-
state.$patch({ count: 10, name: 'updated' })
|
|
463
|
-
|
|
464
|
-
expect(state.count).toBe(10)
|
|
465
|
-
expect(state.name).toBe('updated')
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
it('has $reset method to restore initial state', () => {
|
|
469
|
-
const state = createStore({ count: 0 })
|
|
470
|
-
|
|
471
|
-
state.count = 100
|
|
472
|
-
state.$reset()
|
|
473
|
-
|
|
474
|
-
expect(state.count).toBe(0)
|
|
475
|
-
})
|
|
476
|
-
})
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { mergeTemplates, getInternalTemplates } from '../modules/templates-manager.js'
|
|
3
|
-
|
|
4
|
-
vi.mock('@odoo/owl', () => ({
|
|
5
|
-
loadFile: vi.fn(),
|
|
6
|
-
}))
|
|
7
|
-
|
|
8
|
-
import { loadFile } from '@odoo/owl'
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.clearAllMocks()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('mergeTemplates', () => {
|
|
15
|
-
it('returns wrapped templates string from a single file', async () => {
|
|
16
|
-
loadFile.mockResolvedValue('<t t-name="Comp"><div/></t>')
|
|
17
|
-
const result = await mergeTemplates(['/components/Comp.xml'])
|
|
18
|
-
expect(result).toContain('<templates>')
|
|
19
|
-
expect(result).toContain('<t t-name="Comp"><div/></t>')
|
|
20
|
-
expect(result).toContain('</templates>')
|
|
21
|
-
expect(result).toContain('t-name="Link"') // Internal Link template
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('concatenates multiple template files', async () => {
|
|
25
|
-
loadFile
|
|
26
|
-
.mockResolvedValueOnce('<t t-name="A"><div/></t>')
|
|
27
|
-
.mockResolvedValueOnce('<t t-name="B"><span/></t>')
|
|
28
|
-
const result = await mergeTemplates(['/A.xml', '/B.xml'])
|
|
29
|
-
expect(result).toContain('<templates>')
|
|
30
|
-
expect(result).toContain('<t t-name="A"><div/></t>')
|
|
31
|
-
expect(result).toContain('<t t-name="B"><span/></t>')
|
|
32
|
-
expect(result).toContain('</templates>')
|
|
33
|
-
expect(result).toContain('t-name="Link"') // Internal Link template
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('returns templates with internal components for empty array', async () => {
|
|
37
|
-
const result = await mergeTemplates([])
|
|
38
|
-
expect(result).toContain('<templates>')
|
|
39
|
-
expect(result).toContain('</templates>')
|
|
40
|
-
expect(result).toContain('t-name="Link"') // Internal Link template
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('skips failed files and logs error', async () => {
|
|
44
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
45
|
-
loadFile.mockRejectedValue(new Error('404'))
|
|
46
|
-
const result = await mergeTemplates(['/missing.xml'])
|
|
47
|
-
expect(result).toContain('<templates>')
|
|
48
|
-
expect(result).toContain('</templates>')
|
|
49
|
-
expect(result).toContain('t-name="Link"') // Internal Link template despite error
|
|
50
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
51
|
-
expect.stringContaining('[metaowl] Failed to load template: /missing.xml'),
|
|
52
|
-
expect.any(Error)
|
|
53
|
-
)
|
|
54
|
-
consoleSpy.mockRestore()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('collects successful files even when one fails', async () => {
|
|
58
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
59
|
-
loadFile
|
|
60
|
-
.mockResolvedValueOnce('<t t-name="Good"><div/></t>')
|
|
61
|
-
.mockRejectedValueOnce(new Error('404'))
|
|
62
|
-
.mockResolvedValueOnce('<t t-name="Also"><span/></t>')
|
|
63
|
-
const result = await mergeTemplates(['/good.xml', '/missing.xml', '/also.xml'])
|
|
64
|
-
expect(result).toContain('<t t-name="Good"><div/></t>')
|
|
65
|
-
expect(result).toContain('<t t-name="Also"><span/></t>')
|
|
66
|
-
expect(result).toContain('t-name="Link"') // Internal Link template
|
|
67
|
-
consoleSpy.mockRestore()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('includes internal Link component template', async () => {
|
|
71
|
-
const templates = getInternalTemplates()
|
|
72
|
-
expect(templates.length).toBeGreaterThan(0)
|
|
73
|
-
expect(templates[0]).toContain('t-name="Link"')
|
|
74
|
-
expect(templates[0]).toContain('<a')
|
|
75
|
-
expect(templates[0]).toContain('t-att-href')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('calls loadFile once per path', async () => {
|
|
79
|
-
loadFile.mockResolvedValue('<t/>')
|
|
80
|
-
await mergeTemplates(['/a.xml', '/b.xml', '/c.xml'])
|
|
81
|
-
expect(loadFile).toHaveBeenCalledTimes(3)
|
|
82
|
-
})
|
|
83
|
-
})
|