@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.
- package/LICENSE +21 -0
- package/README.md +993 -26
- package/dist/adapters/indexedDB.cjs +0 -1
- package/dist/adapters/indexedDB.mjs +0 -1
- package/dist/adapters/localStorage.cjs +0 -1
- package/dist/adapters/localStorage.mjs +0 -1
- package/dist/adapters/memory.cjs +0 -1
- package/dist/adapters/memory.mjs +0 -1
- package/dist/adapters/sessionStorage.cjs +0 -1
- package/dist/adapters/sessionStorage.mjs +0 -1
- package/dist/async-entry.d.ts +0 -1
- package/dist/async.cjs +0 -1
- package/dist/async.d.ts +0 -1
- package/dist/async.mjs +0 -1
- package/dist/batch.d.ts +0 -1
- package/dist/compose.d.ts +0 -1
- package/dist/computed-entry.d.ts +0 -1
- package/dist/computed.cjs +0 -1
- package/dist/computed.d.ts +0 -1
- package/dist/computed.mjs +0 -1
- package/dist/devtools/history.d.ts +0 -1
- package/dist/devtools/index.d.ts +0 -1
- package/dist/devtools/redux-bridge.d.ts +0 -1
- package/dist/devtools/snapshots.d.ts +0 -1
- package/dist/devtools/withDevtools.d.ts +0 -1
- package/dist/devtools.cjs +0 -1
- package/dist/devtools.mjs +0 -1
- package/dist/extensions/noop.d.ts +0 -1
- package/dist/index.cjs +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +0 -1
- package/dist/persist/adapters/indexedDB.d.ts +0 -1
- package/dist/persist/adapters/localStorage.d.ts +0 -1
- package/dist/persist/adapters/memory.d.ts +0 -1
- package/dist/persist/adapters/sessionStorage.d.ts +0 -1
- package/dist/persist/debounce.d.ts +0 -1
- package/dist/persist/hydrate.d.ts +0 -1
- package/dist/persist/index.d.ts +0 -1
- package/dist/persist/serialize.d.ts +0 -1
- package/dist/persist.cjs +0 -1
- package/dist/persist.mjs +0 -1
- package/dist/proxy.d.ts +0 -1
- package/dist/registry-qtr1UpFU.js +0 -1
- package/dist/registry-zaKZ1P-s.js +0 -1
- package/dist/registry.d.ts +0 -1
- package/dist/signals/createSignal.d.ts +0 -1
- package/dist/signals/index.d.ts +0 -1
- package/dist/signals/useSignal.d.ts +0 -1
- package/dist/signals.cjs +0 -1
- package/dist/signals.mjs +0 -1
- package/dist/store.d.ts +0 -1
- package/dist/sync/channel.d.ts +0 -1
- package/dist/sync/index.d.ts +0 -1
- package/dist/sync/protocol.d.ts +0 -1
- package/dist/sync/withSync.d.ts +0 -1
- package/dist/sync.cjs +0 -1
- package/dist/sync.mjs +0 -1
- package/dist/types.d.ts +0 -1
- package/package.json +9 -3
- package/CHANGELOG.md +0 -151
- package/benchmarks/run.ts +0 -102
- package/benchmarks/week2.md +0 -9
- package/benchmarks/week2.ts +0 -64
- package/benchmarks/week4.md +0 -13
- package/benchmarks/week4.ts +0 -178
- package/benchmarks/week5.md +0 -15
- package/benchmarks/week5.ts +0 -184
- package/coverage/coverage-summary.json +0 -31
- package/dist/adapters/indexedDB.cjs.map +0 -1
- package/dist/adapters/indexedDB.mjs.map +0 -1
- package/dist/adapters/localStorage.cjs.map +0 -1
- package/dist/adapters/localStorage.mjs.map +0 -1
- package/dist/adapters/memory.cjs.map +0 -1
- package/dist/adapters/memory.mjs.map +0 -1
- package/dist/adapters/sessionStorage.cjs.map +0 -1
- package/dist/adapters/sessionStorage.mjs.map +0 -1
- package/dist/async-entry.d.ts.map +0 -1
- package/dist/async.cjs.map +0 -1
- package/dist/async.d.ts.map +0 -1
- package/dist/async.mjs.map +0 -1
- package/dist/batch.d.ts.map +0 -1
- package/dist/compose.d.ts.map +0 -1
- package/dist/computed-entry.d.ts.map +0 -1
- package/dist/computed.cjs.map +0 -1
- package/dist/computed.d.ts.map +0 -1
- package/dist/computed.mjs.map +0 -1
- package/dist/devtools/history.d.ts.map +0 -1
- package/dist/devtools/index.d.ts.map +0 -1
- package/dist/devtools/redux-bridge.d.ts.map +0 -1
- package/dist/devtools/snapshots.d.ts.map +0 -1
- package/dist/devtools/withDevtools.d.ts.map +0 -1
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.mjs.map +0 -1
- package/dist/extensions/noop.d.ts.map +0 -1
- package/dist/index.cjs.js +0 -118
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.esm.js +0 -116
- package/dist/index.esm.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/persist/adapters/indexedDB.d.ts.map +0 -1
- package/dist/persist/adapters/localStorage.d.ts.map +0 -1
- package/dist/persist/adapters/memory.d.ts.map +0 -1
- package/dist/persist/adapters/sessionStorage.d.ts.map +0 -1
- package/dist/persist/debounce.d.ts.map +0 -1
- package/dist/persist/hydrate.d.ts.map +0 -1
- package/dist/persist/index.d.ts.map +0 -1
- package/dist/persist/serialize.d.ts.map +0 -1
- package/dist/persist.cjs.map +0 -1
- package/dist/persist.mjs.map +0 -1
- package/dist/proxy.d.ts.map +0 -1
- package/dist/registry-D3X0HSbl.js +0 -26
- package/dist/registry-D3X0HSbl.js.map +0 -1
- package/dist/registry-RDjbeJdx.js +0 -29
- package/dist/registry-RDjbeJdx.js.map +0 -1
- package/dist/registry-qtr1UpFU.js.map +0 -1
- package/dist/registry-zaKZ1P-s.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/signals/createSignal.d.ts.map +0 -1
- package/dist/signals/index.d.ts.map +0 -1
- package/dist/signals/useSignal.d.ts.map +0 -1
- package/dist/signals.cjs.map +0 -1
- package/dist/signals.mjs.map +0 -1
- package/dist/stats.html +0 -4949
- package/dist/store.d.ts.map +0 -1
- package/dist/sync/channel.d.ts.map +0 -1
- package/dist/sync/index.d.ts.map +0 -1
- package/dist/sync/protocol.d.ts.map +0 -1
- package/dist/sync/withSync.d.ts.map +0 -1
- package/dist/sync.cjs.map +0 -1
- package/dist/sync.mjs.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/rollup.config.mjs +0 -44
- package/src/async-entry.ts +0 -6
- package/src/async.ts +0 -240
- package/src/batch.ts +0 -33
- package/src/compose.ts +0 -50
- package/src/computed-entry.ts +0 -6
- package/src/computed.ts +0 -187
- package/src/devtools/history.ts +0 -103
- package/src/devtools/index.ts +0 -5
- package/src/devtools/redux-bridge.ts +0 -70
- package/src/devtools/snapshots.ts +0 -54
- package/src/devtools/withDevtools.ts +0 -196
- package/src/extensions/noop.ts +0 -12
- package/src/index.ts +0 -4
- package/src/persist/adapters/indexedDB.ts +0 -114
- package/src/persist/adapters/localStorage.ts +0 -28
- package/src/persist/adapters/memory.ts +0 -26
- package/src/persist/adapters/sessionStorage.ts +0 -28
- package/src/persist/debounce.ts +0 -28
- package/src/persist/hydrate.ts +0 -60
- package/src/persist/index.ts +0 -141
- package/src/persist/serialize.ts +0 -60
- package/src/proxy.ts +0 -87
- package/src/registry.ts +0 -67
- package/src/signals/createSignal.ts +0 -81
- package/src/signals/index.ts +0 -20
- package/src/signals/useSignal.ts +0 -18
- package/src/store.ts +0 -250
- package/src/sync/channel.ts +0 -15
- package/src/sync/index.ts +0 -3
- package/src/sync/protocol.ts +0 -18
- package/src/sync/withSync.ts +0 -147
- package/src/types.ts +0 -159
- package/tests/async.test.ts +0 -1100
- package/tests/batch.test.ts +0 -41
- package/tests/compose.test.ts +0 -209
- package/tests/computed.test.ts +0 -867
- package/tests/devtools.test.ts +0 -1039
- package/tests/integration/persist.integration.test.ts +0 -258
- package/tests/integration/signals.integration.test.ts +0 -309
- package/tests/integration.test.ts +0 -278
- package/tests/persist/adapters/indexedDB.adapter.test.ts +0 -185
- package/tests/persist/adapters/localStorage.adapter.test.ts +0 -105
- package/tests/persist/adapters/memory.adapter.test.ts +0 -112
- package/tests/persist/adapters/sessionStorage.adapter.test.ts +0 -128
- package/tests/persist/debounce.test.ts +0 -121
- package/tests/persist/hydrate.test.ts +0 -120
- package/tests/persist/migrate.test.ts +0 -208
- package/tests/persist/persist.test.ts +0 -357
- package/tests/persist/serialize.test.ts +0 -128
- package/tests/proxy.test.ts +0 -473
- package/tests/registry.test.ts +0 -67
- package/tests/signals/derived.test.ts +0 -244
- package/tests/signals/inference.test.ts +0 -108
- package/tests/signals/signal.test.ts +0 -348
- package/tests/signals/useSignal.test.tsx +0 -275
- package/tests/store.test.ts +0 -482
- package/tests/stress.test.ts +0 -268
- package/tests/sync.test.ts +0 -576
- package/tests/types.test.ts +0 -32
- package/tests/v0.3.test.ts +0 -813
- package/tree-shake-test.js +0 -1
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -22
- package/vitest_play.ts +0 -7
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { createStore } from '../../src/store';
|
|
3
|
-
import { signal } from '../../src/signals/createSignal';
|
|
4
|
-
import type { Store } from '../../src/types';
|
|
5
|
-
|
|
6
|
-
describe('derived signals', () => {
|
|
7
|
-
interface State {
|
|
8
|
-
count: number;
|
|
9
|
-
text: string;
|
|
10
|
-
active: boolean;
|
|
11
|
-
items: number[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let store: Store<State>;
|
|
15
|
-
let unsubscribes: (() => void)[] = [];
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
store = createStore<State>({
|
|
19
|
-
count: 10,
|
|
20
|
-
text: 'hello',
|
|
21
|
-
active: true,
|
|
22
|
-
items: [1, 2, 3],
|
|
23
|
-
});
|
|
24
|
-
unsubscribes = [];
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
unsubscribes.forEach((unsub) => unsub());
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('get() — value transformation', () => {
|
|
32
|
-
it('returns transform(storeValue) not the raw storeValue', () => {
|
|
33
|
-
const sig = signal(store, 'count', (v) => v * 2);
|
|
34
|
-
expect(sig.get()).toBe(20);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('returns correct result for numeric transform (x => x * 2)', () => {
|
|
38
|
-
const sig = signal(store, 'count', (x) => x * 2);
|
|
39
|
-
expect(sig.get()).toBe(20);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('returns correct result for string transform (x => x.toUpperCase())', () => {
|
|
43
|
-
const sig = signal(store, 'text', (x) => x.toUpperCase());
|
|
44
|
-
expect(sig.get()).toBe('HELLO');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('returns correct result for boolean transform (x => !x)', () => {
|
|
48
|
-
const sig = signal(store, 'active', (x) => !x);
|
|
49
|
-
expect(sig.get()).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('returns correct result for object mapping (x => ({ doubled: x * 2 }))', () => {
|
|
53
|
-
const sig = signal(store, 'count', (x) => ({ doubled: x * 2 }));
|
|
54
|
-
expect(sig.get()).toEqual({ doubled: 20 });
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('returns correct result for array transform (x => [...x].reverse())', () => {
|
|
58
|
-
const sig = signal(store, 'items', (x) => [...x].reverse());
|
|
59
|
-
expect(sig.get()).toEqual([3, 2, 1]);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('updates correctly when the underlying store key changes', () => {
|
|
63
|
-
const sig = signal(store, 'count', (v) => v + 1);
|
|
64
|
-
store.setState({ count: 50 });
|
|
65
|
-
expect(sig.get()).toBe(51);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('does NOT update when an unrelated key changes', () => {
|
|
69
|
-
const sig = signal(store, 'count', (v) => v * 1);
|
|
70
|
-
store.setState({ text: 'changed' });
|
|
71
|
-
expect(sig.get()).toBe(10);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('subscribe() — transform-aware filtering', () => {
|
|
76
|
-
it('notifies listener when underlying key changes AND transform output changes', () => {
|
|
77
|
-
const sig = signal(store, 'count', (v) => v % 2 === 0);
|
|
78
|
-
const listener = vi.fn();
|
|
79
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
80
|
-
|
|
81
|
-
store.setState({ count: 11 }); // true -> false
|
|
82
|
-
expect(listener).toHaveBeenCalledWith(false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('does NOT notify when underlying key changes but transform output is Object.is equal', () => {
|
|
86
|
-
const sig = signal(store, 'count', () => true); // Always returns true
|
|
87
|
-
const listener = vi.fn();
|
|
88
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
89
|
-
|
|
90
|
-
store.setState({ count: 100 });
|
|
91
|
-
expect(listener).not.toHaveBeenCalled();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('does NOT notify when unrelated store key changes', () => {
|
|
95
|
-
const sig = signal(store, 'count', (v) => v);
|
|
96
|
-
const listener = vi.fn();
|
|
97
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
98
|
-
|
|
99
|
-
store.setState({ text: 'unrelated' });
|
|
100
|
-
expect(listener).not.toHaveBeenCalled();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('DOES notify when transform output changes reference (new object/array each time)', () => {
|
|
104
|
-
const sig = signal(store, 'count', (v) => ({ val: v }));
|
|
105
|
-
const listener = vi.fn();
|
|
106
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
107
|
-
|
|
108
|
-
store.setState({ count: 10 }); // value same (10), but transform returns NEW object
|
|
109
|
-
expect(listener).toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('listener receives the TRANSFORMED value, not the raw store value', () => {
|
|
113
|
-
const sig = signal(store, 'count', (v) => v.toString());
|
|
114
|
-
const listener = vi.fn();
|
|
115
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
116
|
-
|
|
117
|
-
store.setState({ count: 500 });
|
|
118
|
-
expect(listener).toHaveBeenCalledWith('500');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('listener receives latest transform output after multiple store changes', () => {
|
|
122
|
-
const sig = signal(store, 'count', (v) => v * 10);
|
|
123
|
-
const listener = vi.fn();
|
|
124
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
125
|
-
|
|
126
|
-
store.setState({ count: 1 });
|
|
127
|
-
store.setState({ count: 2 });
|
|
128
|
-
expect(listener).toHaveBeenLastCalledWith(20);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('subscribe() — same rules as standard signal', () => {
|
|
133
|
-
it('unsubscribe stops notifications', () => {
|
|
134
|
-
const sig = signal(store, 'count', (v) => v * 2);
|
|
135
|
-
const listener = vi.fn();
|
|
136
|
-
const unsub = sig.subscribe(listener);
|
|
137
|
-
unsub();
|
|
138
|
-
store.setState({ count: 20 });
|
|
139
|
-
expect(listener).not.toHaveBeenCalled();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('multiple subscribers all receive transform output', () => {
|
|
143
|
-
const sig = signal(store, 'count', (v) => v + 1);
|
|
144
|
-
const l1 = vi.fn();
|
|
145
|
-
const l2 = vi.fn();
|
|
146
|
-
unsubscribes.push(sig.subscribe(l1));
|
|
147
|
-
unsubscribes.push(sig.subscribe(l2));
|
|
148
|
-
|
|
149
|
-
store.setState({ count: 100 });
|
|
150
|
-
expect(l1).toHaveBeenCalledWith(101);
|
|
151
|
-
expect(l2).toHaveBeenCalledWith(101);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('removing one subscriber does not affect others', () => {
|
|
155
|
-
const sig = signal(store, 'count', (v) => v);
|
|
156
|
-
const l1 = vi.fn();
|
|
157
|
-
const l2 = vi.fn();
|
|
158
|
-
const unsub1 = sig.subscribe(l1);
|
|
159
|
-
unsubscribes.push(sig.subscribe(l2));
|
|
160
|
-
|
|
161
|
-
unsub1();
|
|
162
|
-
store.setState({ count: 5 });
|
|
163
|
-
expect(l1).not.toHaveBeenCalled();
|
|
164
|
-
expect(l2).toHaveBeenCalledWith(5);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('set() — read-only enforcement', () => {
|
|
169
|
-
it('throws with EXACT message regardless of what value is passed', () => {
|
|
170
|
-
const sig = signal(store, 'count', (v) => v * 2);
|
|
171
|
-
const errorMsg = 'Storve: cannot call set() on a derived signal. Derived signals are read-only.';
|
|
172
|
-
|
|
173
|
-
const setter = sig as unknown as { set: (v: unknown) => void };
|
|
174
|
-
expect(() => setter.set(100)).toThrow(errorMsg);
|
|
175
|
-
expect(() => setter.set('hi')).toThrow(errorMsg);
|
|
176
|
-
expect(() => setter.set((v: unknown) => v)).toThrow(errorMsg);
|
|
177
|
-
expect(() => setter.set(null)).toThrow(errorMsg);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('throw does not corrupt store state — store is unchanged after throw', () => {
|
|
181
|
-
const sig = signal(store, 'count', (v) => v * 2);
|
|
182
|
-
try { (sig as unknown as { set: (v: number) => void }).set(999); } catch { /* expected */ }
|
|
183
|
-
expect(store.getState().count).toBe(10);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('throw does not break existing signal subscribers', () => {
|
|
187
|
-
const sig = signal(store, 'count', (v) => v * 2);
|
|
188
|
-
const listener = vi.fn();
|
|
189
|
-
unsubscribes.push(sig.subscribe(listener));
|
|
190
|
-
|
|
191
|
-
try { (sig as unknown as { set: (v: number) => void }).set(999); } catch { /* expected */ }
|
|
192
|
-
|
|
193
|
-
store.setState({ count: 11 });
|
|
194
|
-
expect(listener).toHaveBeenCalledWith(22);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('_derived flag', () => {
|
|
199
|
-
it('is true on every derived signal', () => {
|
|
200
|
-
const sig = signal(store, 'count', (v) => v);
|
|
201
|
-
expect(sig._derived).toBe(true);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('standard signal has _derived === false (contrast check)', () => {
|
|
205
|
-
const sig = signal(store, 'count');
|
|
206
|
-
expect(sig._derived).toBe(false);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe('Composition', () => {
|
|
211
|
-
it('derived signal on top of a key that is itself updated by another signal\'s set()', () => {
|
|
212
|
-
const baseSig = signal(store, 'count');
|
|
213
|
-
const derivedSig = signal(store, 'count', (v) => v * 2);
|
|
214
|
-
|
|
215
|
-
baseSig.set(50);
|
|
216
|
-
expect(derivedSig.get()).toBe(100);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('two derived signals on same key with different transforms are independent', () => {
|
|
220
|
-
const sigA = signal(store, 'count', (v) => v * 2);
|
|
221
|
-
const sigB = signal(store, 'count', (v) => v + 10);
|
|
222
|
-
|
|
223
|
-
store.setState({ count: 5 });
|
|
224
|
-
expect(sigA.get()).toBe(10);
|
|
225
|
-
expect(sigB.get()).toBe(15);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('each fires only when their own transform output changes', () => {
|
|
229
|
-
const sigA = signal(store, 'count', (v) => v > 10);
|
|
230
|
-
const sigB = signal(store, 'count', (v) => v > 20);
|
|
231
|
-
const lA = vi.fn();
|
|
232
|
-
const lB = vi.fn();
|
|
233
|
-
unsubscribes.push(sigA.subscribe(lA));
|
|
234
|
-
unsubscribes.push(sigB.subscribe(lB));
|
|
235
|
-
|
|
236
|
-
store.setState({ count: 15 }); // lA fires (false -> true), lB doesn't (false -> false)
|
|
237
|
-
expect(lA).toHaveBeenCalledWith(true);
|
|
238
|
-
expect(lB).not.toHaveBeenCalled();
|
|
239
|
-
|
|
240
|
-
store.setState({ count: 25 }); // lA doesn't fire (true -> true), lB fires (false -> true)
|
|
241
|
-
expect(lB).toHaveBeenCalledWith(true);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createStore } from '../../src/store';
|
|
3
|
-
import { signal } from '../../src/signals/createSignal';
|
|
4
|
-
|
|
5
|
-
describe('signal inference', () => {
|
|
6
|
-
const store = createStore({
|
|
7
|
-
count: 0,
|
|
8
|
-
name: 'alice',
|
|
9
|
-
active: false,
|
|
10
|
-
items: [1, 2, 3] as number[],
|
|
11
|
-
user: { id: 1, role: 'admin' }
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('valid inference', () => {
|
|
15
|
-
it('signal(store, \'count\') infers Signal<number>', () => {
|
|
16
|
-
const countSig = signal(store, 'count');
|
|
17
|
-
expect(countSig.get()).toBe(0);
|
|
18
|
-
// Verify type via assignment
|
|
19
|
-
const n: number = countSig.get();
|
|
20
|
-
expect(n).toBe(0);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('signal(store, \'name\') infers Signal<string>', () => {
|
|
24
|
-
const nameSig = signal(store, 'name');
|
|
25
|
-
expect(nameSig.get()).toBe('alice');
|
|
26
|
-
const s: string = nameSig.get();
|
|
27
|
-
expect(s).toBe('alice');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('signal(store, \'active\') infers Signal<boolean>', () => {
|
|
31
|
-
const activeSig = signal(store, 'active');
|
|
32
|
-
expect(activeSig.get()).toBe(false);
|
|
33
|
-
const b: boolean = activeSig.get();
|
|
34
|
-
expect(b).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('signal(store, \'items\') infers Signal<number[]>', () => {
|
|
38
|
-
const itemsSig = signal(store, 'items');
|
|
39
|
-
expect(Array.isArray(itemsSig.get())).toBe(true);
|
|
40
|
-
const arr: number[] = itemsSig.get();
|
|
41
|
-
expect(arr).toEqual([1, 2, 3]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('signal(store, \'user\') infers Signal<{id: number, role: string}>', () => {
|
|
45
|
-
const userSig = signal(store, 'user');
|
|
46
|
-
expect(userSig.get().id).toBe(1);
|
|
47
|
-
const user: { id: number; role: string } = userSig.get();
|
|
48
|
-
expect(user.role).toBe('admin');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('signal(store, \'count\', v => v * 2) infers Signal<number>', () => {
|
|
52
|
-
const doubleSig = signal(store, 'count', (v) => v * 2);
|
|
53
|
-
expect(doubleSig.get()).toBe(0);
|
|
54
|
-
const n: number = doubleSig.get();
|
|
55
|
-
expect(n).toBe(0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('signal(store, \'count\', v => v.toString()) infers Signal<string>', () => {
|
|
59
|
-
const strSig = signal(store, 'count', (v) => v.toString());
|
|
60
|
-
expect(typeof strSig.get()).toBe('string');
|
|
61
|
-
const s: string = strSig.get();
|
|
62
|
-
expect(s).toBe('0');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('signal(store, \'count\', v => v > 0) infers Signal<boolean>', () => {
|
|
66
|
-
const boolSig = signal(store, 'count', (v) => v > 0);
|
|
67
|
-
expect(typeof boolSig.get()).toBe('boolean');
|
|
68
|
-
const b: boolean = boolSig.get();
|
|
69
|
-
expect(b).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('signal(store, \'name\', v => v.length) infers Signal<number>', () => {
|
|
73
|
-
const lenSig = signal(store, 'name', (v) => v.length);
|
|
74
|
-
expect(lenSig.get()).toBe(5);
|
|
75
|
-
const n: number = lenSig.get();
|
|
76
|
-
expect(n).toBe(5);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('signal(store, \'items\', v => v.map(x => x * 2)) infers Signal<number[]>', () => {
|
|
80
|
-
const mappedSig = signal(store, 'items', (v) => v.map((x) => x * 2));
|
|
81
|
-
expect(mappedSig.get()).toEqual([2, 4, 6]);
|
|
82
|
-
const arr: number[] = mappedSig.get();
|
|
83
|
-
expect(arr).toEqual([2, 4, 6]);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('invalid usage', () => {
|
|
88
|
-
it('should fail compilation for invalid keys and types', () => {
|
|
89
|
-
const countSig = signal(store, 'count');
|
|
90
|
-
const strSig = signal(store, 'count', (v) => v.toString());
|
|
91
|
-
|
|
92
|
-
// @ts-expect-error — 'nonExistent' is not a key of the store
|
|
93
|
-
signal(store, 'nonExistent');
|
|
94
|
-
|
|
95
|
-
// @ts-expect-error — 123 is not a key of the store
|
|
96
|
-
signal(store, 123);
|
|
97
|
-
|
|
98
|
-
// @ts-expect-error — set() receives wrong type: count is number, not string
|
|
99
|
-
countSig.set('hello');
|
|
100
|
-
|
|
101
|
-
// @ts-expect-error — set() updater must return number, not string
|
|
102
|
-
countSig.set((prev) => prev.toString());
|
|
103
|
-
|
|
104
|
-
// @ts-expect-error — set() on derived signal accepts wrong type
|
|
105
|
-
expect(() => { strSig.set(42); }).toThrow();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|