metaowl 0.4.0 → 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 (79) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -15
  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 +29 -11
  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/index.js +0 -328
  39. package/modules/app-mounter.js +0 -104
  40. package/modules/auto-import.js +0 -225
  41. package/modules/cache.js +0 -59
  42. package/modules/composables.js +0 -600
  43. package/modules/error-boundary.js +0 -228
  44. package/modules/fetch.js +0 -51
  45. package/modules/file-router.js +0 -478
  46. package/modules/forms.js +0 -353
  47. package/modules/i18n.js +0 -333
  48. package/modules/layouts.js +0 -431
  49. package/modules/link.js +0 -255
  50. package/modules/meta.js +0 -119
  51. package/modules/odoo-rpc.js +0 -511
  52. package/modules/pwa.js +0 -515
  53. package/modules/router.js +0 -769
  54. package/modules/seo.js +0 -501
  55. package/modules/store.js +0 -409
  56. package/modules/templates-manager.js +0 -89
  57. package/modules/test-utils.js +0 -532
  58. package/test/auto-import.test.js +0 -110
  59. package/test/cache.test.js +0 -55
  60. package/test/composables.test.js +0 -103
  61. package/test/dynamic-routes.test.js +0 -469
  62. package/test/error-boundary.test.js +0 -126
  63. package/test/fetch.test.js +0 -100
  64. package/test/file-router.test.js +0 -55
  65. package/test/forms.test.js +0 -203
  66. package/test/i18n.test.js +0 -188
  67. package/test/layouts.test.js +0 -395
  68. package/test/link.test.js +0 -189
  69. package/test/meta.test.js +0 -146
  70. package/test/odoo-rpc.test.js +0 -547
  71. package/test/pwa.test.js +0 -154
  72. package/test/router-guards.test.js +0 -229
  73. package/test/router.test.js +0 -77
  74. package/test/seo.test.js +0 -353
  75. package/test/store.test.js +0 -476
  76. package/test/templates-manager.test.js +0 -83
  77. package/test/test-utils.test.js +0 -314
  78. package/vite/plugin.js +0 -277
  79. package/vitest.config.js +0 -8
@@ -1,532 +0,0 @@
1
- /**
2
- * @module TestUtils
3
- *
4
- * Testing utilities for MetaOwl OWL applications.
5
- *
6
- * Provides:
7
- * - Mock Store for state management testing
8
- * - Router mocking for navigation testing
9
- * - Component mounting helpers
10
- * - Async test utilities
11
- *
12
- * Usage:
13
- * import { createMockStore, mockRouter, mountComponent } from 'metaowl/test'
14
- *
15
- * // Mock store
16
- * const store = createMockStore({
17
- * state: { count: 0 },
18
- * mutations: { increment: (s) => s.count++ }
19
- * })
20
- *
21
- * // Mock router
22
- * const router = mockRouter({
23
- * initialRoute: '/dashboard',
24
- * routes: [
25
- * { path: '/', component: HomePage },
26
- * { path: '/dashboard', component: DashboardPage }
27
- * ]
28
- * })
29
- *
30
- * // Mount component with mocks
31
- * const component = await mountComponent(MyComponent, {
32
- * store,
33
- * router,
34
- * props: { title: 'Test' }
35
- * })
36
- */
37
-
38
- import { mount, reactive } from '@odoo/owl'
39
-
40
- /**
41
- * Create a mock store for testing.
42
- *
43
- * @param {Object} config - Store configuration
44
- * @param {Object} [config.state={}] - Initial state
45
- * @param {Object} [config.getters={}] - Getters object
46
- * @param {Object} [config.mutations={}] - Mutations object
47
- * @param {Object} [config.actions={}] - Actions object
48
- * @returns {Object} Mock store instance
49
- *
50
- * @example
51
- * const store = createMockStore({
52
- * state: { user: null, count: 0 },
53
- * getters: {
54
- * isLoggedIn: (state) => !!state.user
55
- * },
56
- * mutations: {
57
- * setUser: (state, user) => { state.user = user },
58
- * increment: (state) => { state.count++ }
59
- * },
60
- * actions: {
61
- * login: async ({ commit }, credentials) => {
62
- * const user = await fakeApi.login(credentials)
63
- * commit('setUser', user)
64
- * }
65
- * }
66
- * })
67
- *
68
- * // Use in tests
69
- * store.commit('setUser', { name: 'John' })
70
- * console.log(store.state.user.name) // 'John'
71
- * console.log(store.getters.isLoggedIn.value) // true
72
- *
73
- * await store.dispatch('login', { email, password })
74
- */
75
- export function createMockStore(config = {}) {
76
- const {
77
- state: initialState = {},
78
- getters: getterDefs = {},
79
- mutations: mutationDefs = {},
80
- actions: actionDefs = {}
81
- } = config
82
-
83
- // Create reactive state
84
- const state = reactive({ ...initialState })
85
-
86
- // Create computed getters
87
- const getters = {}
88
- for (const [name, fn] of Object.entries(getterDefs)) {
89
- Object.defineProperty(getters, name, {
90
- get: () => fn(state),
91
- enumerable: true
92
- })
93
- }
94
-
95
- // Create mutations
96
- const mutations = {}
97
- for (const [name, fn] of Object.entries(mutationDefs)) {
98
- mutations[name] = (payload) => {
99
- fn(state, payload)
100
- }
101
- }
102
-
103
- // Create actions
104
- const actions = {}
105
- for (const [name, fn] of Object.entries(actionDefs)) {
106
- actions[name] = async (payload) => {
107
- const context = {
108
- state,
109
- getters,
110
- commit: (mutation, payload) => mutations[mutation]?.(payload),
111
- dispatch: (action, payload) => actions[action]?.(payload)
112
- }
113
- return await fn(context, payload)
114
- }
115
- }
116
-
117
- // Store instance
118
- return {
119
- state,
120
- getters,
121
- mutations,
122
- actions,
123
-
124
- /**
125
- * Commit a mutation.
126
- * @param {string} name - Mutation name
127
- * @param {any} payload - Mutation payload
128
- */
129
- commit(name, payload) {
130
- if (mutations[name]) {
131
- mutations[name](payload)
132
- } else {
133
- console.warn(`[TestUtils] Mutation '${name}' not found`)
134
- }
135
- },
136
-
137
- /**
138
- * Dispatch an action.
139
- * @param {string} name - Action name
140
- * @param {any} payload - Action payload
141
- * @returns {Promise<any>}
142
- */
143
- async dispatch(name, payload) {
144
- if (actions[name]) {
145
- return await actions[name](payload)
146
- } else {
147
- console.warn(`[TestUtils] Action '${name}' not found`)
148
- }
149
- },
150
-
151
- /**
152
- * Reset store to initial state.
153
- */
154
- reset() {
155
- Object.keys(state).forEach(key => delete state[key])
156
- Object.assign(state, initialState)
157
- },
158
-
159
- /**
160
- * Set state directly (for testing).
161
- * @param {Object} newState
162
- */
163
- setState(newState) {
164
- Object.assign(state, newState)
165
- }
166
- }
167
- }
168
-
169
- /**
170
- * Create a mock router for testing.
171
- *
172
- * @param {Object} config - Router configuration
173
- * @param {string} [config.initialRoute='/'] - Initial route path
174
- * @param {Array} [config.routes=[]] - Route definitions
175
- * @returns {Object} Mock router instance
176
- *
177
- * @example
178
- * const router = mockRouter({
179
- * initialRoute: '/',
180
- * routes: [
181
- * { path: '/', name: 'home' },
182
- * { path: '/user/:id', name: 'user' }
183
- * ]
184
- * })
185
- *
186
- * await router.push('/user/123')
187
- * console.log(router.currentRoute.value.path) // '/user/123'
188
- * console.log(router.currentRoute.value.params.id) // '123'
189
- */
190
- export function mockRouter(config = {}) {
191
- const {
192
- initialRoute = '/',
193
- routes = []
194
- } = config
195
-
196
- // Reactive current route
197
- const currentRoute = reactive({
198
- path: initialRoute,
199
- name: null,
200
- params: {},
201
- query: {},
202
- hash: ''
203
- })
204
-
205
- // Navigation guards
206
- const beforeEachGuards = []
207
- const afterEachHooks = []
208
-
209
- // Parse URL into route object
210
- function parseUrl(url) {
211
- const [pathAndQuery, hash = ''] = url.split('#')
212
- const [path, queryString = ''] = pathAndQuery.split('?')
213
-
214
- const query = {}
215
- if (queryString) {
216
- queryString.split('&').forEach(param => {
217
- const [key, value] = param.split('=')
218
- query[decodeURIComponent(key)] = decodeURIComponent(value || '')
219
- })
220
- }
221
-
222
- // Find matching route
223
- let matchedRoute = null
224
- let params = {}
225
-
226
- for (const route of routes) {
227
- const match = matchPath(path, route.path)
228
- if (match) {
229
- matchedRoute = route
230
- params = match.params
231
- break
232
- }
233
- }
234
-
235
- return {
236
- path,
237
- name: matchedRoute?.name || null,
238
- params,
239
- query,
240
- hash
241
- }
242
- }
243
-
244
- // Match path against route pattern
245
- function matchPath(path, pattern) {
246
- // Simple pattern matching (supports :param and * wildcards)
247
- const paramNames = []
248
- const regexPattern = pattern
249
- .replace(/\*/g, '.*')
250
- .replace(/:([^/]+)/g, (match, name) => {
251
- paramNames.push(name)
252
- return '([^/]+)'
253
- })
254
-
255
- const regex = new RegExp(`^${regexPattern}$`)
256
- const match = path.match(regex)
257
-
258
- if (!match) return null
259
-
260
- const params = {}
261
- paramNames.forEach((name, i) => {
262
- params[name] = match[i + 1]
263
- })
264
-
265
- return { params }
266
- }
267
-
268
- // Initialize
269
- Object.assign(currentRoute, parseUrl(initialRoute))
270
-
271
- return {
272
- currentRoute,
273
-
274
- /**
275
- * Navigate to a new route.
276
- * @param {string} path - Target path
277
- */
278
- async push(path) {
279
- const to = parseUrl(path)
280
- const from = { ...currentRoute }
281
-
282
- // Run beforeEach guards
283
- for (const guard of beforeEachGuards) {
284
- const result = await guard(to, from, () => {})
285
- if (result === false) return
286
- }
287
-
288
- Object.assign(currentRoute, to)
289
-
290
- // Run afterEach hooks
291
- for (const hook of afterEachHooks) {
292
- await hook(to, from)
293
- }
294
- },
295
-
296
- /**
297
- * Replace current route.
298
- * @param {string} path - Target path
299
- */
300
- async replace(path) {
301
- await this.push(path)
302
- },
303
-
304
- /**
305
- * Go back in history.
306
- */
307
- back() {
308
- // Mock - does nothing in test environment
309
- },
310
-
311
- /**
312
- * Register beforeEach guard.
313
- * @param {Function} guard
314
- * @returns {Function} Unsubscribe function
315
- */
316
- beforeEach(guard) {
317
- beforeEachGuards.push(guard)
318
- return () => {
319
- const index = beforeEachGuards.indexOf(guard)
320
- if (index > -1) beforeEachGuards.splice(index, 1)
321
- }
322
- },
323
-
324
- /**
325
- * Register afterEach hook.
326
- * @param {Function} hook
327
- * @returns {Function} Unsubscribe function
328
- */
329
- afterEach(hook) {
330
- afterEachHooks.push(hook)
331
- return () => {
332
- const index = afterEachHooks.indexOf(hook)
333
- if (index > -1) afterEachHooks.splice(index, 1)
334
- }
335
- },
336
-
337
- /**
338
- * Generate URL for named route.
339
- * @param {string} name - Route name
340
- * @param {Object} params - Route params
341
- * @returns {string}
342
- */
343
- resolve(name, params = {}) {
344
- const route = routes.find(r => r.name === name)
345
- if (!route) return '/'
346
-
347
- let path = route.path
348
- for (const [key, value] of Object.entries(params)) {
349
- path = path.replace(`:${key}`, value)
350
- }
351
- return path
352
- }
353
- }
354
- }
355
-
356
- /**
357
- * Mount a component with test utilities.
358
- *
359
- * @param {Component} Component - OWL component class
360
- * @param {Object} [options={}] - Mount options
361
- * @param {Object} [options.props={}] - Component props
362
- * @param {Object} [options.store] - Mock store
363
- * @param {Object} [options.router] - Mock router
364
- * @param {Element} [options.target] - Mount target (default: detached div)
365
- * @returns {Promise<Object>} Mounted component instance
366
- *
367
- * @example
368
- * const component = await mountComponent(MyComponent, {
369
- * props: { title: 'Test' },
370
- * store: createMockStore({ state: { user: null } }),
371
- * router: mockRouter({ initialRoute: '/' })
372
- * })
373
- *
374
- * // Access component
375
- * expect(component.props.title).toBe('Test')
376
- * expect(component.env.store.state.user).toBeNull()
377
- */
378
- export async function mountComponent(Component, options = {}) {
379
- const {
380
- props = {},
381
- store = null,
382
- router = null,
383
- target = document.createElement('div')
384
- } = options
385
-
386
- // Create environment with mocks
387
- const env = {}
388
- if (store) env.store = store
389
- if (router) env.router = router
390
-
391
- // Mount component
392
- const component = await mount(Component, {
393
- props,
394
- env,
395
- target
396
- })
397
-
398
- return component
399
- }
400
-
401
- /**
402
- * Wait for a specific time (for async tests).
403
- *
404
- * @param {number} ms - Milliseconds to wait
405
- * @returns {Promise<void>}
406
- */
407
- export function wait(ms) {
408
- return new Promise(resolve => setTimeout(resolve, ms))
409
- }
410
-
411
- /**
412
- * Wait for next tick (DOM update).
413
- *
414
- * @returns {Promise<void>}
415
- */
416
- export function nextTick() {
417
- return new Promise(resolve => {
418
- requestAnimationFrame(resolve)
419
- })
420
- }
421
-
422
- /**
423
- * Flush all promises (useful after state changes).
424
- *
425
- * @returns {Promise<void>}
426
- */
427
- export async function flushPromises() {
428
- await new Promise(resolve => setTimeout(resolve, 0))
429
- }
430
-
431
- /**
432
- * Simulate user events.
433
- */
434
- export const userEvent = {
435
- /**
436
- * Click an element.
437
- * @param {Element} element
438
- */
439
- async click(element) {
440
- element.dispatchEvent(new MouseEvent('click', { bubbles: true }))
441
- await flushPromises()
442
- },
443
-
444
- /**
445
- * Type text into an input.
446
- * @param {HTMLInputElement} input
447
- * @param {string} text
448
- */
449
- async type(input, text) {
450
- input.value = text
451
- input.dispatchEvent(new Event('input', { bubbles: true }))
452
- await flushPromises()
453
- },
454
-
455
- /**
456
- * Submit a form.
457
- * @param {HTMLFormElement} form
458
- */
459
- async submit(form) {
460
- form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
461
- await flushPromises()
462
- },
463
-
464
- /**
465
- * Change a select value.
466
- * @param {HTMLSelectElement} select
467
- * @param {string} value
468
- */
469
- async select(select, value) {
470
- select.value = value
471
- select.dispatchEvent(new Event('change', { bubbles: true }))
472
- await flushPromises()
473
- }
474
- }
475
-
476
- /**
477
- * Create DOM element helpers.
478
- */
479
- export const dom = {
480
- /**
481
- * Query element by selector.
482
- * @param {string} selector
483
- * @param {Element} [container=document]
484
- * @returns {Element|null}
485
- */
486
- query(selector, container = document) {
487
- return container.querySelector(selector)
488
- },
489
-
490
- /**
491
- * Query all elements by selector.
492
- * @param {string} selector
493
- * @param {Element} [container=document]
494
- * @returns {NodeList}
495
- */
496
- queryAll(selector, container = document) {
497
- return container.querySelectorAll(selector)
498
- },
499
-
500
- /**
501
- * Check if element has class.
502
- * @param {Element} element
503
- * @param {string} className
504
- * @returns {boolean}
505
- */
506
- hasClass(element, className) {
507
- return element?.classList?.contains(className) || false
508
- },
509
-
510
- /**
511
- * Get text content.
512
- * @param {Element} element
513
- * @returns {string}
514
- */
515
- text(element) {
516
- return element?.textContent?.trim() || ''
517
- }
518
- }
519
-
520
- // Export namespace
521
- export const TestUtils = {
522
- createMockStore,
523
- mockRouter,
524
- mountComponent,
525
- wait,
526
- nextTick,
527
- flushPromises,
528
- userEvent,
529
- dom
530
- }
531
-
532
- export default TestUtils
@@ -1,110 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { scanComponents, generateComponentDts } from '../modules/auto-import.js'
3
- import { mkdirSync, writeFileSync, rmSync } from 'fs'
4
- import { join } from 'path'
5
- import { tmpdir } from 'os'
6
-
7
- describe('auto-import', () => {
8
- let tempDir
9
-
10
- beforeEach(() => {
11
- tempDir = join(tmpdir(), `metaowl-test-${Date.now()}`)
12
- mkdirSync(tempDir, { recursive: true })
13
- })
14
-
15
- afterEach(() => {
16
- try {
17
- rmSync(tempDir, { recursive: true, force: true })
18
- } catch {
19
- // Ignore cleanup errors
20
- }
21
- })
22
-
23
- describe('scanComponents', () => {
24
- it('should find all component files', async () => {
25
- // Create test components
26
- mkdirSync(join(tempDir, 'Button'), { recursive: true })
27
- mkdirSync(join(tempDir, 'Card'), { recursive: true })
28
- writeFileSync(join(tempDir, 'Button', 'Button.js'), 'export default {}')
29
- writeFileSync(join(tempDir, 'Card', 'Card.js'), 'export default {}')
30
-
31
- const components = await scanComponents(tempDir)
32
-
33
- expect(components).toHaveLength(2)
34
- expect(components).toContain('Button')
35
- expect(components).toContain('Card')
36
- })
37
-
38
- it('should filter helpers', async () => {
39
- mkdirSync(join(tempDir, 'Button'), { recursive: true })
40
- mkdirSync(join(tempDir, 'utils'), { recursive: true })
41
- writeFileSync(join(tempDir, 'Button', 'Button.js'), 'export default {}')
42
- writeFileSync(join(tempDir, 'utils', 'helpers.js'), 'export const utils = {}')
43
-
44
- const components = await scanComponents(tempDir)
45
-
46
- // Finds all JS files recursively
47
- expect(components).toContain('Button')
48
- expect(components).toContain('helpers')
49
- })
50
-
51
- it('should return empty array for non-existent directory', async () => {
52
- const components = await scanComponents('/non-existent/path')
53
- expect(components).toEqual([])
54
- })
55
- })
56
-
57
- describe('generateComponentDts', () => {
58
- it('should generate TypeScript declarations', async () => {
59
- const components = ['Button', 'Card', 'Modal']
60
- const outputPath = join(tempDir, 'components.d.ts')
61
-
62
- await generateComponentDts(components, outputPath)
63
-
64
- // Check file was created
65
- const fs = await import('fs')
66
- const content = fs.readFileSync(outputPath, 'utf-8')
67
-
68
- expect(content).toContain('Button')
69
- expect(content).toContain('Card')
70
- expect(content).toContain('Modal')
71
- expect(content).toContain('declare module')
72
- })
73
-
74
- it('should handle empty components array', async () => {
75
- await generateComponentDts([], join(tempDir, 'empty.d.ts'))
76
-
77
- const fs = await import('fs')
78
- const content = fs.readFileSync(join(tempDir, 'empty.d.ts'), 'utf-8')
79
-
80
- expect(content).toContain('declare module')
81
- })
82
- })
83
-
84
- describe('integration', () => {
85
- it('should work with nested components', async () => {
86
- // Create nested structure
87
- const nestedPath = join(tempDir, 'ui', 'forms')
88
- mkdirSync(nestedPath, { recursive: true })
89
- writeFileSync(join(nestedPath, 'TextField.js'), 'export default {}')
90
- writeFileSync(join(nestedPath, 'SelectField.js'), 'export default {}')
91
-
92
- const components = await scanComponents(tempDir)
93
-
94
- expect(components).toContain('TextField')
95
- expect(components).toContain('SelectField')
96
- })
97
-
98
- it('should ignore non-js files', async () => {
99
- mkdirSync(join(tempDir, 'Button'), { recursive: true })
100
- writeFileSync(join(tempDir, 'Button', 'Button.js'), 'export default {}')
101
- writeFileSync(join(tempDir, 'Button', 'Button.test.js'), 'test')
102
- writeFileSync(join(tempDir, 'Button', 'styles.css'), '/* styles */')
103
-
104
- const components = await scanComponents(tempDir)
105
-
106
- expect(components).toHaveLength(1)
107
- expect(components).toContain('Button')
108
- })
109
- })
110
- })
@@ -1,55 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest'
2
- import Cache from '../modules/cache.js'
3
-
4
- // Minimal localStorage stub
5
- const store = {}
6
- const localStorageMock = {
7
- getItem: (k) => store[k] ?? null,
8
- setItem: (k, v) => { store[k] = v },
9
- removeItem: (k) => { delete store[k] },
10
- clear: () => { Object.keys(store).forEach(k => delete store[k]) },
11
- get length() { return Object.keys(store).length },
12
- key: (i) => Object.keys(store)[i] ?? null
13
- }
14
- Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, writable: true })
15
-
16
- beforeEach(() => localStorageMock.clear())
17
-
18
- describe('Cache', () => {
19
- it('set and get a string value', async () => {
20
- await Cache.set('name', 'metaowl')
21
- expect(await Cache.get('name')).toBe('metaowl')
22
- })
23
-
24
- it('set and get an object', async () => {
25
- await Cache.set('obj', { a: 1, b: [2, 3] })
26
- expect(await Cache.get('obj')).toEqual({ a: 1, b: [2, 3] })
27
- })
28
-
29
- it('get returns null for missing key', async () => {
30
- expect(await Cache.get('missing')).toBeNull()
31
- })
32
-
33
- it('remove deletes the key', async () => {
34
- await Cache.set('tmp', 'x')
35
- await Cache.remove('tmp')
36
- expect(await Cache.get('tmp')).toBeNull()
37
- })
38
-
39
- it('clear removes all keys', async () => {
40
- await Cache.set('a', 1)
41
- await Cache.set('b', 2)
42
- await Cache.clear()
43
- expect(await Cache.get('a')).toBeNull()
44
- expect(await Cache.get('b')).toBeNull()
45
- })
46
-
47
- it('keys returns all stored keys', async () => {
48
- await Cache.set('x', 1)
49
- await Cache.set('y', 2)
50
- const keys = await Cache.keys()
51
- expect(keys).toContain('x')
52
- expect(keys).toContain('y')
53
- expect(keys).toHaveLength(2)
54
- })
55
- })