@storve/core 1.0.1 → 1.0.3

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +993 -26
  3. package/dist/adapters/indexedDB.cjs +0 -1
  4. package/dist/adapters/indexedDB.mjs +0 -1
  5. package/dist/adapters/localStorage.cjs +0 -1
  6. package/dist/adapters/localStorage.mjs +0 -1
  7. package/dist/adapters/memory.cjs +0 -1
  8. package/dist/adapters/memory.mjs +0 -1
  9. package/dist/adapters/sessionStorage.cjs +0 -1
  10. package/dist/adapters/sessionStorage.mjs +0 -1
  11. package/dist/async-entry.d.ts +0 -1
  12. package/dist/async.cjs +0 -1
  13. package/dist/async.d.ts +0 -1
  14. package/dist/async.mjs +0 -1
  15. package/dist/batch.d.ts +0 -1
  16. package/dist/compose.d.ts +0 -1
  17. package/dist/computed-entry.d.ts +0 -1
  18. package/dist/computed.cjs +0 -1
  19. package/dist/computed.d.ts +0 -1
  20. package/dist/computed.mjs +0 -1
  21. package/dist/devtools/history.d.ts +0 -1
  22. package/dist/devtools/index.d.ts +0 -1
  23. package/dist/devtools/redux-bridge.d.ts +0 -1
  24. package/dist/devtools/snapshots.d.ts +0 -1
  25. package/dist/devtools/withDevtools.d.ts +0 -1
  26. package/dist/devtools.cjs +0 -1
  27. package/dist/devtools.mjs +0 -1
  28. package/dist/extensions/noop.d.ts +0 -1
  29. package/dist/index.cjs +0 -1
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.mjs +0 -1
  32. package/dist/persist/adapters/indexedDB.d.ts +0 -1
  33. package/dist/persist/adapters/localStorage.d.ts +0 -1
  34. package/dist/persist/adapters/memory.d.ts +0 -1
  35. package/dist/persist/adapters/sessionStorage.d.ts +0 -1
  36. package/dist/persist/debounce.d.ts +0 -1
  37. package/dist/persist/hydrate.d.ts +0 -1
  38. package/dist/persist/index.d.ts +0 -1
  39. package/dist/persist/serialize.d.ts +0 -1
  40. package/dist/persist.cjs +0 -1
  41. package/dist/persist.mjs +0 -1
  42. package/dist/proxy.d.ts +0 -1
  43. package/dist/registry-qtr1UpFU.js +0 -1
  44. package/dist/registry-zaKZ1P-s.js +0 -1
  45. package/dist/registry.d.ts +0 -1
  46. package/dist/signals/createSignal.d.ts +0 -1
  47. package/dist/signals/index.d.ts +0 -1
  48. package/dist/signals/useSignal.d.ts +0 -1
  49. package/dist/signals.cjs +0 -1
  50. package/dist/signals.mjs +0 -1
  51. package/dist/store.d.ts +0 -1
  52. package/dist/sync/channel.d.ts +0 -1
  53. package/dist/sync/index.d.ts +0 -1
  54. package/dist/sync/protocol.d.ts +0 -1
  55. package/dist/sync/withSync.d.ts +0 -1
  56. package/dist/sync.cjs +0 -1
  57. package/dist/sync.mjs +0 -1
  58. package/dist/types.d.ts +0 -1
  59. package/package.json +9 -3
  60. package/CHANGELOG.md +0 -151
  61. package/benchmarks/run.ts +0 -102
  62. package/benchmarks/week2.md +0 -9
  63. package/benchmarks/week2.ts +0 -64
  64. package/benchmarks/week4.md +0 -13
  65. package/benchmarks/week4.ts +0 -178
  66. package/benchmarks/week5.md +0 -15
  67. package/benchmarks/week5.ts +0 -184
  68. package/coverage/coverage-summary.json +0 -31
  69. package/dist/adapters/indexedDB.cjs.map +0 -1
  70. package/dist/adapters/indexedDB.mjs.map +0 -1
  71. package/dist/adapters/localStorage.cjs.map +0 -1
  72. package/dist/adapters/localStorage.mjs.map +0 -1
  73. package/dist/adapters/memory.cjs.map +0 -1
  74. package/dist/adapters/memory.mjs.map +0 -1
  75. package/dist/adapters/sessionStorage.cjs.map +0 -1
  76. package/dist/adapters/sessionStorage.mjs.map +0 -1
  77. package/dist/async-entry.d.ts.map +0 -1
  78. package/dist/async.cjs.map +0 -1
  79. package/dist/async.d.ts.map +0 -1
  80. package/dist/async.mjs.map +0 -1
  81. package/dist/batch.d.ts.map +0 -1
  82. package/dist/compose.d.ts.map +0 -1
  83. package/dist/computed-entry.d.ts.map +0 -1
  84. package/dist/computed.cjs.map +0 -1
  85. package/dist/computed.d.ts.map +0 -1
  86. package/dist/computed.mjs.map +0 -1
  87. package/dist/devtools/history.d.ts.map +0 -1
  88. package/dist/devtools/index.d.ts.map +0 -1
  89. package/dist/devtools/redux-bridge.d.ts.map +0 -1
  90. package/dist/devtools/snapshots.d.ts.map +0 -1
  91. package/dist/devtools/withDevtools.d.ts.map +0 -1
  92. package/dist/devtools.cjs.map +0 -1
  93. package/dist/devtools.mjs.map +0 -1
  94. package/dist/extensions/noop.d.ts.map +0 -1
  95. package/dist/index.cjs.js +0 -118
  96. package/dist/index.cjs.js.map +0 -1
  97. package/dist/index.cjs.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.esm.js +0 -116
  100. package/dist/index.esm.js.map +0 -1
  101. package/dist/index.mjs.map +0 -1
  102. package/dist/persist/adapters/indexedDB.d.ts.map +0 -1
  103. package/dist/persist/adapters/localStorage.d.ts.map +0 -1
  104. package/dist/persist/adapters/memory.d.ts.map +0 -1
  105. package/dist/persist/adapters/sessionStorage.d.ts.map +0 -1
  106. package/dist/persist/debounce.d.ts.map +0 -1
  107. package/dist/persist/hydrate.d.ts.map +0 -1
  108. package/dist/persist/index.d.ts.map +0 -1
  109. package/dist/persist/serialize.d.ts.map +0 -1
  110. package/dist/persist.cjs.map +0 -1
  111. package/dist/persist.mjs.map +0 -1
  112. package/dist/proxy.d.ts.map +0 -1
  113. package/dist/registry-D3X0HSbl.js +0 -26
  114. package/dist/registry-D3X0HSbl.js.map +0 -1
  115. package/dist/registry-RDjbeJdx.js +0 -29
  116. package/dist/registry-RDjbeJdx.js.map +0 -1
  117. package/dist/registry-qtr1UpFU.js.map +0 -1
  118. package/dist/registry-zaKZ1P-s.js.map +0 -1
  119. package/dist/registry.d.ts.map +0 -1
  120. package/dist/signals/createSignal.d.ts.map +0 -1
  121. package/dist/signals/index.d.ts.map +0 -1
  122. package/dist/signals/useSignal.d.ts.map +0 -1
  123. package/dist/signals.cjs.map +0 -1
  124. package/dist/signals.mjs.map +0 -1
  125. package/dist/stats.html +0 -4949
  126. package/dist/store.d.ts.map +0 -1
  127. package/dist/sync/channel.d.ts.map +0 -1
  128. package/dist/sync/index.d.ts.map +0 -1
  129. package/dist/sync/protocol.d.ts.map +0 -1
  130. package/dist/sync/withSync.d.ts.map +0 -1
  131. package/dist/sync.cjs.map +0 -1
  132. package/dist/sync.mjs.map +0 -1
  133. package/dist/types.d.ts.map +0 -1
  134. package/rollup.config.mjs +0 -44
  135. package/src/async-entry.ts +0 -6
  136. package/src/async.ts +0 -240
  137. package/src/batch.ts +0 -33
  138. package/src/compose.ts +0 -50
  139. package/src/computed-entry.ts +0 -6
  140. package/src/computed.ts +0 -187
  141. package/src/devtools/history.ts +0 -103
  142. package/src/devtools/index.ts +0 -5
  143. package/src/devtools/redux-bridge.ts +0 -70
  144. package/src/devtools/snapshots.ts +0 -54
  145. package/src/devtools/withDevtools.ts +0 -196
  146. package/src/extensions/noop.ts +0 -12
  147. package/src/index.ts +0 -4
  148. package/src/persist/adapters/indexedDB.ts +0 -114
  149. package/src/persist/adapters/localStorage.ts +0 -28
  150. package/src/persist/adapters/memory.ts +0 -26
  151. package/src/persist/adapters/sessionStorage.ts +0 -28
  152. package/src/persist/debounce.ts +0 -28
  153. package/src/persist/hydrate.ts +0 -60
  154. package/src/persist/index.ts +0 -141
  155. package/src/persist/serialize.ts +0 -60
  156. package/src/proxy.ts +0 -87
  157. package/src/registry.ts +0 -67
  158. package/src/signals/createSignal.ts +0 -81
  159. package/src/signals/index.ts +0 -20
  160. package/src/signals/useSignal.ts +0 -18
  161. package/src/store.ts +0 -250
  162. package/src/sync/channel.ts +0 -15
  163. package/src/sync/index.ts +0 -3
  164. package/src/sync/protocol.ts +0 -18
  165. package/src/sync/withSync.ts +0 -147
  166. package/src/types.ts +0 -159
  167. package/tests/async.test.ts +0 -1100
  168. package/tests/batch.test.ts +0 -41
  169. package/tests/compose.test.ts +0 -209
  170. package/tests/computed.test.ts +0 -867
  171. package/tests/devtools.test.ts +0 -1039
  172. package/tests/integration/persist.integration.test.ts +0 -258
  173. package/tests/integration/signals.integration.test.ts +0 -309
  174. package/tests/integration.test.ts +0 -278
  175. package/tests/persist/adapters/indexedDB.adapter.test.ts +0 -185
  176. package/tests/persist/adapters/localStorage.adapter.test.ts +0 -105
  177. package/tests/persist/adapters/memory.adapter.test.ts +0 -112
  178. package/tests/persist/adapters/sessionStorage.adapter.test.ts +0 -128
  179. package/tests/persist/debounce.test.ts +0 -121
  180. package/tests/persist/hydrate.test.ts +0 -120
  181. package/tests/persist/migrate.test.ts +0 -208
  182. package/tests/persist/persist.test.ts +0 -357
  183. package/tests/persist/serialize.test.ts +0 -128
  184. package/tests/proxy.test.ts +0 -473
  185. package/tests/registry.test.ts +0 -67
  186. package/tests/signals/derived.test.ts +0 -244
  187. package/tests/signals/inference.test.ts +0 -108
  188. package/tests/signals/signal.test.ts +0 -348
  189. package/tests/signals/useSignal.test.tsx +0 -275
  190. package/tests/store.test.ts +0 -482
  191. package/tests/stress.test.ts +0 -268
  192. package/tests/sync.test.ts +0 -576
  193. package/tests/types.test.ts +0 -32
  194. package/tests/v0.3.test.ts +0 -813
  195. package/tree-shake-test.js +0 -1
  196. package/tsconfig.json +0 -15
  197. package/vitest.config.ts +0 -22
  198. package/vitest_play.ts +0 -7
@@ -1,278 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { createStore } from '../src/store';
3
-
4
- describe('3.1 Real World Scenarios', () => {
5
- describe('Counter store', () => {
6
- it('increment, decrement, reset', () => {
7
- const store = createStore({ count: 0 });
8
- store.setState((s) => ({ count: s.count + 1 }));
9
- expect(store.getState().count).toBe(1);
10
- store.setState((s) => ({ count: s.count - 1 }));
11
- expect(store.getState().count).toBe(0);
12
- store.setState({ count: 0 });
13
- });
14
-
15
- it('multiple components subscribed', () => {
16
- const store = createStore({ count: 0 });
17
- const s1 = vi.fn(), s2 = vi.fn();
18
- store.subscribe(s1); store.subscribe(s2);
19
- store.setState({ count: 1 });
20
- expect(s1).toHaveBeenCalled();
21
- expect(s2).toHaveBeenCalled();
22
- });
23
- });
24
-
25
- describe('Todo store', () => {
26
- it('add todo', () => {
27
- const store = createStore({ todos: [] as unknown[] });
28
- store.setState(s => ({ todos: [...s.todos, { id: 1 }] }));
29
- expect(store.getState().todos.length).toBe(1);
30
- });
31
- it('remove todo', () => {
32
- const store = createStore({ todos: [{ id: 1 }] });
33
- store.setState(() => ({ todos: [] }));
34
- expect(store.getState().todos.length).toBe(0);
35
- });
36
- it('toggle todo complete', () => {
37
- const store = createStore({ todos: [{ id: 1, done: false }] });
38
- store.setState(() => ({ todos: [{ id: 1, done: true }] }));
39
- expect(store.getState().todos[0].done).toBe(true);
40
- });
41
- it('filter completed todos via computed (derived)', () => {
42
- const store = createStore({ todos: [{ id: 1, done: true }, { id: 2, done: false }] });
43
- const completed = store.getState().todos.filter(t => t.done);
44
- expect(completed.length).toBe(1);
45
- });
46
- it('clear all todos', () => {
47
- const store = createStore({ todos: [{ id: 1 }] });
48
- store.setState({ todos: [] });
49
- expect(store.getState().todos.length).toBe(0);
50
- });
51
- });
52
-
53
- describe('User profile store', () => {
54
- it.each([
55
- ['update name', { user: { name: 'A', address: { city: 'X' } } }, { user: { name: 'B', address: { city: 'X' } } }],
56
- ['update nested address', { user: { name: 'A', address: { city: 'X' } } }, { user: { name: 'A', address: { city: 'Y' } } }],
57
- ['replace entire nested object', { user: { name: 'A', address: { city: 'X' } } }, { user: { name: 'B', address: { city: 'Y' } } }]
58
- ])('%s', (_, initial, expected) => {
59
- const store = createStore(initial);
60
- store.setState(expected);
61
- expect(store.getState()).toEqual(expected);
62
- });
63
- });
64
-
65
- describe('Shopping cart store', () => {
66
- it.each([
67
- ['add item', { items: [] }, { items: [{ id: 1, qty: 1 }] }],
68
- ['remove item', { items: [{ id: 1 }] }, { items: [] }],
69
- ['update quantity', { items: [{ id: 1, qty: 1 }] }, { items: [{ id: 1, qty: 2 }] }],
70
- ['clear cart', { items: [{ id: 1 }] }, { items: [] }]
71
- ])('%s', (_, initial, expected) => {
72
- const store = createStore(initial as Record<string, unknown>);
73
- store.setState(expected as Record<string, unknown>);
74
- expect(store.getState()).toEqual(expected);
75
- });
76
-
77
- it('total computed from items', () => {
78
- const store = createStore({ items: [{ price: 10, qty: 2 }, { price: 5, qty: 1 }] });
79
- const total = store.getState().items.reduce((acc, item) => acc + item.price * item.qty, 0);
80
- expect(total).toBe(25);
81
- });
82
- });
83
-
84
- describe('Theme store', () => {
85
- it('toggle dark/light mode', () => {
86
- const store = createStore({ theme: 'light' });
87
- store.setState({ theme: 'dark' });
88
- expect(store.getState().theme).toBe('dark');
89
- });
90
- });
91
-
92
- describe('Auth store', () => {
93
- it('login sets user', () => {
94
- const store = createStore({ user: null as ({ id: number } | null) });
95
- store.setState({ user: { id: 1 } });
96
- expect(store.getState().user).toEqual({ id: 1 });
97
- });
98
- it('logout clears user', () => {
99
- const store = createStore({ user: { id: 1 } as ({ id: number } | null) });
100
- store.setState({ user: null });
101
- expect(store.getState().user).toBeNull();
102
- });
103
- });
104
-
105
- describe('Pagination store', () => {
106
- it.each([
107
- ['next page', { page: 1 }, { page: 2 }],
108
- ['previous page', { page: 2 }, { page: 1 }],
109
- ['jump to page', { page: 1 }, { page: 5 }]
110
- ])('%s', (_, initial, expected) => {
111
- const store = createStore(initial);
112
- store.setState(expected);
113
- expect(store.getState()).toEqual(expected);
114
- });
115
- });
116
-
117
- describe('Form store', () => {
118
- it.each([
119
- ['update field value', { f1: '', f2: '' }, { f1: 'A', f2: '' }],
120
- ['reset all fields', { f1: 'A', f2: 'B' }, { f1: '', f2: '' }]
121
- ])('%s', (_, initial, expected) => {
122
- const store = createStore(initial);
123
- store.setState(expected);
124
- expect(store.getState()).toEqual(expected);
125
- });
126
-
127
- it('validate fields', () => {
128
- const store = createStore({ email: 'test', errors: {} as Record<string, unknown> });
129
- if (!store.getState().email.includes('@')) {
130
- store.setState({ errors: { email: 'invalid' } });
131
- }
132
- expect(store.getState().errors.email).toBe('invalid');
133
- });
134
- });
135
-
136
- describe('Multi-store', () => {
137
- it('two stores are fully independent', () => {
138
- const s1 = createStore({ a: 1 });
139
- const s2 = createStore({ b: 2 });
140
- s1.setState({ a: 10 });
141
- expect(s2.getState().b).toBe(2);
142
- });
143
- it('store A change does not notify store B subscribers', () => {
144
- const s1 = createStore({ a: 1 });
145
- const s2 = createStore({ b: 2 });
146
- const l2 = vi.fn();
147
- s2.subscribe(l2);
148
- s1.setState({ a: 10 });
149
- expect(l2).not.toHaveBeenCalled();
150
- });
151
- it('both stores can be subscribed simultaneously', () => {
152
- const s1 = createStore({ a: 1 });
153
- const s2 = createStore({ b: 2 });
154
- const l1 = vi.fn(), l2 = vi.fn();
155
- s1.subscribe(l1); s2.subscribe(l2);
156
- s1.setState({ a: 10 });
157
- s2.setState({ b: 20 });
158
- expect(l1).toHaveBeenCalledTimes(1);
159
- expect(l2).toHaveBeenCalledTimes(1);
160
- });
161
- });
162
- });
163
-
164
- describe('3.2 Subscription Lifecycle', () => {
165
- it('Subscribe → update → unsubscribe → update — listener not called after', () => {
166
- const store = createStore({ a: 1 });
167
- const listener = vi.fn();
168
- const unsub = store.subscribe(listener);
169
- store.setState({ a: 2 });
170
- unsub();
171
- store.setState({ a: 3 });
172
- expect(listener).toHaveBeenCalledTimes(1);
173
- });
174
-
175
- it('Subscribe → update → re-subscribe → update — listener called again', () => {
176
- const store = createStore({ a: 1 });
177
- const listener = vi.fn();
178
- let unsub = store.subscribe(listener);
179
- store.setState({ a: 2 });
180
- unsub();
181
- unsub = store.subscribe(listener);
182
- store.setState({ a: 3 });
183
- expect(listener).toHaveBeenCalledTimes(2);
184
- });
185
-
186
- it('Subscribe multiple → unsubscribe all → update — no listeners called', () => {
187
- const store = createStore({ a: 1 });
188
- const l1 = vi.fn(), l2 = vi.fn();
189
- const u1 = store.subscribe(l1);
190
- const u2 = store.subscribe(l2);
191
- u1(); u2();
192
- store.setState({ a: 2 });
193
- expect(l1).not.toHaveBeenCalled();
194
- expect(l2).not.toHaveBeenCalled();
195
- });
196
-
197
- it('Subscribe → many updates → unsubscribe — correct call count', () => {
198
- const store = createStore({ a: 1 });
199
- const listener = vi.fn();
200
- const unsub = store.subscribe(listener);
201
- for (let i = 0; i < 10; i++) store.setState({ a: i });
202
- unsub();
203
- expect(listener).toHaveBeenCalledTimes(10);
204
- });
205
-
206
- it('Subscribe inside subscribe callback — nested subscription works', () => {
207
- const store = createStore({ a: 1 });
208
- const l2 = vi.fn();
209
- let once = false;
210
- store.subscribe(() => {
211
- if (!once) { store.subscribe(l2); once = true; }
212
- });
213
- store.setState({ a: 2 });
214
- store.setState({ a: 3 });
215
- expect(l2).toHaveBeenCalled();
216
- });
217
-
218
- it('Unsubscribe inside subscribe callback — safe to call mid-notification', () => {
219
- const store = createStore({ a: 1 });
220
- const l1 = vi.fn();
221
- const unsub1 = store.subscribe(() => {
222
- l1();
223
- unsub1();
224
- });
225
- store.setState({ a: 2 });
226
- store.setState({ a: 3 });
227
- expect(l1).toHaveBeenCalledTimes(1);
228
- });
229
-
230
- it('Store with zero subscribers — setState does not throw', () => {
231
- const store = createStore({ a: 1 });
232
- expect(() => store.setState({ a: 2 })).not.toThrow();
233
- });
234
-
235
- it('Store with zero subscribers — getState still works', () => {
236
- const store = createStore({ a: 1 });
237
- store.setState({ a: 2 });
238
- expect(store.getState().a).toBe(2);
239
- });
240
- });
241
-
242
- describe('3.3 State Immutability', () => {
243
- it('Mutating returned getState() does not affect store state', () => {
244
- const store = createStore({ a: 1 });
245
- const state = store.getState();
246
- const listener = vi.fn();
247
- store.subscribe(listener);
248
- (state as Record<string, unknown>).a = 2;
249
- expect(listener).not.toHaveBeenCalled();
250
- });
251
-
252
- it('Mutating original definition object does not affect store state', () => {
253
- const initial = { a: 1 };
254
- const store = createStore(initial);
255
- const listener = vi.fn();
256
- store.subscribe(listener);
257
- initial.a = 2;
258
- expect(listener).not.toHaveBeenCalled();
259
- });
260
-
261
- it('Mutating setState argument after calling setState has no effect', () => {
262
- const store = createStore({ obj: { val: 1 } });
263
- const update = { obj: { val: 2 } };
264
- store.setState(update);
265
- const listener = vi.fn();
266
- store.subscribe(listener);
267
- update.obj.val = 3;
268
- expect(listener).not.toHaveBeenCalled();
269
- });
270
-
271
- it('Two stores created from same definition object are independent', () => {
272
- const def = { a: 1 };
273
- const s1 = createStore({ ...def });
274
- const s2 = createStore({ ...def });
275
- s1.setState({ a: 2 });
276
- expect(s2.getState().a).toBe(1);
277
- });
278
- });
@@ -1,185 +0,0 @@
1
- import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { indexedDBAdapter } from '../../../src/persist/adapters/indexedDB'
3
-
4
- function createIDBMock() {
5
- const store = new Map<string, string>()
6
- const mockDB = {
7
- objectStoreNames: { contains: vi.fn(() => true) },
8
- createObjectStore: vi.fn(),
9
- transaction: vi.fn(() => ({
10
- objectStore: vi.fn(() => ({
11
- get: vi.fn((key: string) => {
12
- const req: { onsuccess: (() => void) | null, onerror: (() => void) | null, result: string | null } = {
13
- onsuccess: null,
14
- onerror: null,
15
- result: store.get(key) ?? null
16
- }
17
- setTimeout(() => req.onsuccess?.(), 0)
18
- return req
19
- }),
20
- put: vi.fn((value: string, key: string) => {
21
- store.set(key, value)
22
- const req: { onsuccess: (() => void) | null, onerror: (() => void) | null } = { onsuccess: null, onerror: null }
23
- setTimeout(() => req.onsuccess?.(), 0)
24
- return req
25
- }),
26
- delete: vi.fn((key: string) => {
27
- store.delete(key)
28
- const req: { onsuccess: (() => void) | null, onerror: (() => void) | null } = { onsuccess: null, onerror: null }
29
- setTimeout(() => req.onsuccess?.(), 0)
30
- return req
31
- }),
32
- }))
33
- }))
34
- }
35
- return { store, mockDB }
36
- }
37
-
38
- describe('indexedDBAdapter', () => {
39
- let store: Map<string, string>
40
- let mockDB: ReturnType<typeof createIDBMock>['mockDB']
41
- let openSpy: ReturnType<typeof vi.fn>
42
-
43
- beforeEach(() => {
44
- const mock = createIDBMock()
45
- store = mock.store
46
- mockDB = mock.mockDB
47
-
48
- openSpy = vi.fn(() => {
49
- const request: {
50
- onsuccess: (() => void) | null
51
- onerror: (() => void) | null
52
- onupgradeneeded: (() => void) | null
53
- result: typeof mockDB
54
- } = {
55
- onsuccess: null,
56
- onerror: null,
57
- onupgradeneeded: null,
58
- result: mockDB
59
- }
60
- setTimeout(() => request.onsuccess?.(), 0)
61
- return request
62
- })
63
-
64
- vi.stubGlobal('indexedDB', { open: openSpy })
65
- })
66
-
67
- afterEach(() => {
68
- vi.restoreAllMocks()
69
- vi.unstubAllGlobals()
70
- })
71
-
72
- describe('Normal environment (indexedDB available)', () => {
73
- it('getItem returns null for a key that was never set', async () => {
74
- const adapter = indexedDBAdapter()
75
- const result = await adapter.getItem('missing')
76
- expect(result).toBeNull()
77
- })
78
-
79
- it('getItem returns correct value after setItem', async () => {
80
- const adapter = indexedDBAdapter()
81
- await adapter.setItem('key', 'value')
82
- const result = await adapter.getItem('key')
83
- expect(result).toBe('value')
84
- })
85
-
86
- it('setItem stores value correctly', async () => {
87
- const adapter = indexedDBAdapter()
88
- await adapter.setItem('key', 'value')
89
- expect(store.get('key')).toBe('value')
90
- })
91
-
92
- it('setItem overwrites existing value', async () => {
93
- const adapter = indexedDBAdapter()
94
- await adapter.setItem('key', 'first')
95
- await adapter.setItem('key', 'second')
96
- const result = await adapter.getItem('key')
97
- expect(result).toBe('second')
98
- })
99
-
100
- it('removeItem removes the key (getItem returns null after)', async () => {
101
- const adapter = indexedDBAdapter()
102
- await adapter.setItem('delete-me', 'please')
103
- await adapter.removeItem('delete-me')
104
- const result = await adapter.getItem('delete-me')
105
- expect(result).toBeNull()
106
- })
107
-
108
- it('removeItem is a no-op when key does not exist', async () => {
109
- const adapter = indexedDBAdapter()
110
- await expect(adapter.removeItem('never-set')).resolves.toBeUndefined()
111
- })
112
-
113
- it('DB is opened only once across multiple operations (lazy + shared promise)', async () => {
114
- const adapter = indexedDBAdapter()
115
- await adapter.setItem('a', '1')
116
- await adapter.getItem('a')
117
- await adapter.removeItem('a')
118
-
119
- expect(openSpy).toHaveBeenCalledTimes(1)
120
- })
121
-
122
- it('custom dbName is used when provided', async () => {
123
- const adapter = indexedDBAdapter('custom-db')
124
- await adapter.getItem('test')
125
- expect(openSpy).toHaveBeenCalledWith('custom-db', 1)
126
- })
127
- })
128
-
129
- describe('Error handling', () => {
130
- beforeEach(() => {
131
- // make open fail
132
- openSpy.mockImplementation(() => {
133
- const request: { onerror: (() => void) | null } = { onerror: null }
134
- setTimeout(() => request.onerror?.(), 0)
135
- return request
136
- })
137
- })
138
-
139
- it('when DB open fails, getItem resolves to null without throwing', async () => {
140
- const adapter = indexedDBAdapter()
141
- const result = await adapter.getItem('key')
142
- expect(result).toBeNull()
143
- })
144
-
145
- it('when DB open fails, setItem resolves without throwing', async () => {
146
- const adapter = indexedDBAdapter()
147
- await expect(adapter.setItem('key', 'val')).resolves.toBeUndefined()
148
- })
149
-
150
- it('when DB open fails, a warning is logged (vi.spyOn console.warn)', async () => {
151
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
152
- const adapter = indexedDBAdapter()
153
- await adapter.getItem('key')
154
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('[storve] Failed to open IndexedDB'))
155
- })
156
- })
157
-
158
- describe('SSR guard (indexedDB undefined)', () => {
159
- beforeEach(() => {
160
- vi.stubGlobal('indexedDB', undefined)
161
- })
162
-
163
- it('getItem returns Promise<null> without throwing', async () => {
164
- const adapter = indexedDBAdapter()
165
- const result = await adapter.getItem('ssr-key')
166
- expect(result).toBeNull()
167
- })
168
-
169
- it('setItem returns Promise<void> without throwing', async () => {
170
- const adapter = indexedDBAdapter()
171
- await expect(adapter.setItem('ssr-key', 'value')).resolves.toBeUndefined()
172
- })
173
-
174
- it('removeItem returns Promise<void> without throwing', async () => {
175
- const adapter = indexedDBAdapter()
176
- await expect(adapter.removeItem('ssr-key')).resolves.toBeUndefined()
177
- })
178
-
179
- it('indexedDB is never accessed', async () => {
180
- const adapter = indexedDBAdapter()
181
- await adapter.getItem('key')
182
- expect(openSpy).not.toHaveBeenCalled()
183
- })
184
- })
185
- })
@@ -1,105 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { vi, describe, it, expect, afterEach } from 'vitest'
3
- import { localStorageAdapter } from '../../../src/persist/adapters/localStorage'
4
-
5
- describe('localStorageAdapter', () => {
6
- afterEach(() => {
7
- vi.restoreAllMocks()
8
- vi.unstubAllGlobals()
9
- if (typeof localStorage !== 'undefined') localStorage.clear()
10
- })
11
-
12
- describe('browser environment', () => {
13
- it('getItem returns null for a key that does not exist', async () => {
14
- const adapter = localStorageAdapter()
15
- const result = await adapter.getItem('missing')
16
- expect(result).toBeNull()
17
- })
18
-
19
- it('getItem returns the correct value after setItem', async () => {
20
- const adapter = localStorageAdapter()
21
- await adapter.setItem('key', 'value')
22
- const result = await adapter.getItem('key')
23
- expect(result).toBe('value')
24
- })
25
-
26
- it('setItem stores value in window.localStorage correctly', async () => {
27
- const adapter = localStorageAdapter()
28
- await adapter.setItem('direct', 'data')
29
- expect(localStorage.getItem('direct')).toBe('data')
30
- })
31
-
32
- it('setItem overwrites existing value for the same key', async () => {
33
- const adapter = localStorageAdapter()
34
- await adapter.setItem('key', 'first')
35
- await adapter.setItem('key', 'second')
36
- const result = await adapter.getItem('key')
37
- expect(result).toBe('second')
38
- })
39
-
40
- it('removeItem removes the key (getItem returns null after)', async () => {
41
- const adapter = localStorageAdapter()
42
- await adapter.setItem('delete-me', 'please')
43
- await adapter.removeItem('delete-me')
44
- const result = await adapter.getItem('delete-me')
45
- expect(result).toBeNull()
46
- })
47
-
48
- it('removeItem is a no-op when key does not exist (no error)', async () => {
49
- const adapter = localStorageAdapter()
50
- expect(() => adapter.removeItem('never-set')).not.toThrow()
51
- })
52
-
53
- it('delegates directly to window.localStorage — verify with vi.spyOn', async () => {
54
- const spySet = vi.spyOn(Storage.prototype, 'setItem')
55
- const spyGet = vi.spyOn(Storage.prototype, 'getItem')
56
- const spyRemove = vi.spyOn(Storage.prototype, 'removeItem')
57
-
58
- const adapter = localStorageAdapter()
59
- await adapter.setItem('spyKey', 'spyValue')
60
- expect(spySet).toHaveBeenCalledWith('spyKey', 'spyValue')
61
-
62
- await adapter.getItem('spyKey')
63
- expect(spyGet).toHaveBeenCalledWith('spyKey')
64
-
65
- await adapter.removeItem('spyKey')
66
- expect(spyRemove).toHaveBeenCalledWith('spyKey')
67
- })
68
- })
69
-
70
- describe('SSR — window undefined', () => {
71
- it('getItem returns null without throwing', async () => {
72
- vi.stubGlobal('window', undefined)
73
- const adapter = localStorageAdapter()
74
- const result = await adapter.getItem('ssr-key')
75
- expect(result).toBeNull()
76
- })
77
-
78
- it('setItem does nothing without throwing', async () => {
79
- vi.stubGlobal('window', undefined)
80
- const adapter = localStorageAdapter()
81
- expect(() => adapter.setItem('ssr-key', 'value')).not.toThrow()
82
- })
83
-
84
- it('removeItem does nothing without throwing', async () => {
85
- vi.stubGlobal('window', undefined)
86
- const adapter = localStorageAdapter()
87
- expect(() => adapter.removeItem('ssr-key')).not.toThrow()
88
- })
89
-
90
- it('localStorage is never accessed when window is undefined', async () => {
91
- const spyGet = vi.spyOn(Storage.prototype, 'getItem')
92
- const spySet = vi.spyOn(Storage.prototype, 'setItem')
93
-
94
- vi.stubGlobal('window', undefined)
95
- const adapter = localStorageAdapter()
96
-
97
- await adapter.getItem('key')
98
- await adapter.setItem('key', 'val')
99
- await adapter.removeItem('key')
100
-
101
- expect(spyGet).not.toHaveBeenCalled()
102
- expect(spySet).not.toHaveBeenCalled()
103
- })
104
- })
105
- })
@@ -1,112 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { memoryAdapter } from '../../../src/persist/adapters/memory'
3
-
4
- describe('memoryAdapter', () => {
5
- describe('getItem', () => {
6
- it('returns null for a key that has never been set', async () => {
7
- const adapter = memoryAdapter()
8
- const result = await adapter.getItem('missing')
9
- expect(result).toBeNull()
10
- })
11
-
12
- it('returns the correct value after setItem has been called', async () => {
13
- const adapter = memoryAdapter()
14
- await adapter.setItem('key', 'value')
15
- const result = await adapter.getItem('key')
16
- expect(result).toBe('value')
17
- })
18
-
19
- it('returns null after removeItem has been called on that key', async () => {
20
- const adapter = memoryAdapter()
21
- await adapter.setItem('key', 'value')
22
- await adapter.removeItem('key')
23
- const result = await adapter.getItem('key')
24
- expect(result).toBeNull()
25
- })
26
-
27
- it('is case-sensitive (key \'Count\' and key \'count\' are different)', async () => {
28
- const adapter = memoryAdapter()
29
- await adapter.setItem('Count', '1')
30
- await adapter.setItem('count', '2')
31
-
32
- const countUpper = await adapter.getItem('Count')
33
- const countLower = await adapter.getItem('count')
34
-
35
- expect(countUpper).toBe('1')
36
- expect(countLower).toBe('2')
37
- })
38
- })
39
-
40
- describe('setItem', () => {
41
- it('stores a string value correctly', async () => {
42
- const adapter = memoryAdapter()
43
- await adapter.setItem('token', 'abc')
44
- expect(await adapter.getItem('token')).toBe('abc')
45
- })
46
-
47
- it('overwrites an existing value for the same key', async () => {
48
- const adapter = memoryAdapter()
49
- await adapter.setItem('key', 'first')
50
- await adapter.setItem('key', 'second')
51
- expect(await adapter.getItem('key')).toBe('second')
52
- })
53
-
54
- it('stores multiple keys independently', async () => {
55
- const adapter = memoryAdapter()
56
- await adapter.setItem('a', '1')
57
- await adapter.setItem('b', '2')
58
-
59
- expect(await adapter.getItem('a')).toBe('1')
60
- expect(await adapter.getItem('b')).toBe('2')
61
- })
62
- })
63
-
64
- describe('removeItem', () => {
65
- it('removes an existing key (getItem returns null after)', async () => {
66
- const adapter = memoryAdapter()
67
- await adapter.setItem('target', 'content')
68
- await adapter.removeItem('target')
69
- expect(await adapter.getItem('target')).toBeNull()
70
- })
71
-
72
- it('is a no-op when key does not exist (no error thrown)', async () => {
73
- const adapter = memoryAdapter()
74
- // should not throw
75
- expect(() => adapter.removeItem('never-set')).not.toThrow()
76
- })
77
- })
78
-
79
- describe('Isolation', () => {
80
- it('two separate memoryAdapter() instances do not share data', async () => {
81
- const adapter1 = memoryAdapter()
82
- const adapter2 = memoryAdapter()
83
-
84
- await adapter1.setItem('key', 'value')
85
- expect(await adapter2.getItem('key')).toBeNull()
86
- })
87
-
88
- it('setting a key in instance A does not affect instance B', async () => {
89
- const adapterA = memoryAdapter()
90
- const adapterB = memoryAdapter()
91
-
92
- await adapterA.setItem('shared', 'A')
93
- await adapterB.setItem('shared', 'B')
94
-
95
- expect(await adapterA.getItem('shared')).toBe('A')
96
- expect(await adapterB.getItem('shared')).toBe('B')
97
- })
98
-
99
- it('clearing instance A does not affect instance B', async () => {
100
- const adapterA = memoryAdapter()
101
- const adapterB = memoryAdapter()
102
-
103
- await adapterA.setItem('key', 'value')
104
- await adapterB.setItem('key', 'value')
105
-
106
- await adapterA.removeItem('key')
107
-
108
- expect(await adapterA.getItem('key')).toBeNull()
109
- expect(await adapterB.getItem('key')).toBe('value')
110
- })
111
- })
112
- })