signalium 0.2.7 → 0.3.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/.turbo/turbo-build.log +12 -0
- package/CHANGELOG.md +12 -0
- package/dist/cjs/config.d.ts +14 -5
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +23 -14
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/debug.d.ts +3 -0
- package/dist/cjs/debug.d.ts.map +1 -0
- package/dist/cjs/debug.js +16 -0
- package/dist/cjs/debug.js.map +1 -0
- package/dist/cjs/hooks.d.ts +45 -0
- package/dist/cjs/hooks.d.ts.map +1 -0
- package/dist/cjs/hooks.js +260 -0
- package/dist/cjs/hooks.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +21 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/context.d.ts +4 -0
- package/dist/cjs/react/context.d.ts.map +1 -0
- package/dist/cjs/react/context.js +10 -0
- package/dist/cjs/react/context.js.map +1 -0
- package/dist/cjs/react/index.d.ts +5 -0
- package/dist/cjs/react/index.d.ts.map +1 -0
- package/dist/cjs/react/index.js +12 -0
- package/dist/cjs/react/index.js.map +1 -0
- package/dist/cjs/react/provider.d.ts +7 -0
- package/dist/cjs/react/provider.d.ts.map +1 -0
- package/dist/cjs/react/provider.js +13 -0
- package/dist/cjs/react/provider.js.map +1 -0
- package/dist/cjs/react/signal-value.d.ts +3 -0
- package/dist/cjs/react/signal-value.d.ts.map +1 -0
- package/dist/cjs/react/signal-value.js +42 -0
- package/dist/cjs/react/signal-value.js.map +1 -0
- package/dist/cjs/react/state.d.ts +3 -0
- package/dist/cjs/react/state.d.ts.map +1 -0
- package/dist/cjs/react/state.js +13 -0
- package/dist/cjs/react/state.js.map +1 -0
- package/dist/cjs/scheduling.d.ts +5 -0
- package/dist/cjs/scheduling.d.ts.map +1 -1
- package/dist/cjs/scheduling.js +59 -5
- package/dist/cjs/scheduling.js.map +1 -1
- package/dist/cjs/signals.d.ts +28 -65
- package/dist/cjs/signals.d.ts.map +1 -1
- package/dist/cjs/signals.js +223 -65
- package/dist/cjs/signals.js.map +1 -1
- package/dist/cjs/trace.d.ts +127 -0
- package/dist/cjs/trace.d.ts.map +1 -0
- package/dist/cjs/trace.js +319 -0
- package/dist/cjs/trace.js.map +1 -0
- package/dist/cjs/types.d.ts +66 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.d.ts +4 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +80 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/config.d.ts +14 -5
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +19 -11
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/debug.d.ts +3 -0
- package/dist/esm/debug.d.ts.map +1 -0
- package/dist/esm/debug.js +3 -0
- package/dist/esm/debug.js.map +1 -0
- package/dist/esm/hooks.d.ts +45 -0
- package/dist/esm/hooks.d.ts.map +1 -0
- package/dist/esm/hooks.js +243 -0
- package/dist/esm/hooks.js.map +1 -0
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/context.d.ts +4 -0
- package/dist/esm/react/context.d.ts.map +1 -0
- package/dist/esm/react/context.js +6 -0
- package/dist/esm/react/context.js.map +1 -0
- package/dist/esm/react/index.d.ts +5 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +5 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/react/provider.d.ts +7 -0
- package/dist/esm/react/provider.d.ts.map +1 -0
- package/dist/esm/react/provider.js +10 -0
- package/dist/esm/react/provider.js.map +1 -0
- package/dist/esm/react/signal-value.d.ts +3 -0
- package/dist/esm/react/signal-value.d.ts.map +1 -0
- package/dist/esm/react/signal-value.js +38 -0
- package/dist/esm/react/signal-value.js.map +1 -0
- package/dist/esm/react/state.d.ts +3 -0
- package/dist/esm/react/state.d.ts.map +1 -0
- package/dist/esm/react/state.js +10 -0
- package/dist/esm/react/state.js.map +1 -0
- package/dist/esm/scheduling.d.ts +5 -0
- package/dist/esm/scheduling.d.ts.map +1 -1
- package/dist/esm/scheduling.js +51 -1
- package/dist/esm/scheduling.js.map +1 -1
- package/dist/esm/signals.d.ts +28 -65
- package/dist/esm/signals.d.ts.map +1 -1
- package/dist/esm/signals.js +215 -61
- package/dist/esm/signals.js.map +1 -1
- package/dist/esm/trace.d.ts +127 -0
- package/dist/esm/trace.d.ts.map +1 -0
- package/dist/esm/trace.js +311 -0
- package/dist/esm/trace.js.map +1 -0
- package/dist/esm/types.d.ts +66 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.d.ts +4 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +75 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +43 -2
- package/src/__tests__/hooks/async-computed.test.ts +190 -0
- package/src/__tests__/hooks/async-task.test.ts +227 -0
- package/src/__tests__/hooks/computed.test.ts +126 -0
- package/src/__tests__/hooks/context.test.ts +527 -0
- package/src/__tests__/hooks/nesting.test.ts +303 -0
- package/src/__tests__/hooks/params-and-state.test.ts +168 -0
- package/src/__tests__/hooks/subscription.test.ts +97 -0
- package/src/__tests__/signals/async.test.ts +416 -0
- package/src/__tests__/signals/basic.test.ts +399 -0
- package/src/__tests__/signals/subscription.test.ts +632 -0
- package/src/__tests__/signals/watcher.test.ts +253 -0
- package/src/__tests__/utils/async.ts +6 -0
- package/src/__tests__/utils/builders.ts +22 -0
- package/src/__tests__/utils/instrumented-hooks.ts +309 -0
- package/src/__tests__/utils/instrumented-signals.ts +281 -0
- package/src/__tests__/utils/permute.ts +74 -0
- package/src/config.ts +32 -17
- package/src/debug.ts +14 -0
- package/src/hooks.ts +429 -0
- package/src/index.ts +28 -3
- package/src/react/__tests__/react.test.tsx +135 -0
- package/src/react/context.ts +8 -0
- package/src/react/index.ts +4 -0
- package/src/react/provider.tsx +18 -0
- package/src/react/signal-value.ts +56 -0
- package/src/react/state.ts +13 -0
- package/src/scheduling.ts +69 -1
- package/src/signals.ts +331 -157
- package/src/trace.ts +449 -0
- package/src/types.ts +86 -0
- package/src/utils.ts +83 -0
- package/tsconfig.json +2 -1
- package/vitest.workspace.ts +24 -0
- package/src/__tests__/async.test.ts +0 -426
- package/src/__tests__/basic.test.ts +0 -378
- package/src/__tests__/subscription.test.ts +0 -645
- package/src/__tests__/utils/instrumented.ts +0 -326
@@ -0,0 +1,253 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
2
|
+
import {
|
3
|
+
createStateSignal,
|
4
|
+
createComputedSignal,
|
5
|
+
createAsyncComputedSignal,
|
6
|
+
createSubscriptionSignal,
|
7
|
+
createWatcherSignal,
|
8
|
+
} from '../utils/instrumented-signals.js';
|
9
|
+
import { result } from '../utils/builders.js';
|
10
|
+
import { nextTick, sleep } from '../utils/async.js';
|
11
|
+
|
12
|
+
describe('Watcher functionality', () => {
|
13
|
+
describe('with computeds', () => {
|
14
|
+
test('watches computed values', async () => {
|
15
|
+
const a = createStateSignal(1);
|
16
|
+
const b = createStateSignal(2);
|
17
|
+
|
18
|
+
const c = createComputedSignal(() => {
|
19
|
+
return a.get() + b.get();
|
20
|
+
});
|
21
|
+
|
22
|
+
let value;
|
23
|
+
const w = createWatcherSignal(() => {
|
24
|
+
value = c.get();
|
25
|
+
});
|
26
|
+
|
27
|
+
w.addListener(() => {
|
28
|
+
// do something
|
29
|
+
});
|
30
|
+
|
31
|
+
expect(value).toBe(undefined);
|
32
|
+
expect(w).toHaveSignalCounts({ compute: 0, effect: 0 });
|
33
|
+
|
34
|
+
await nextTick();
|
35
|
+
|
36
|
+
expect(value).toBe(3);
|
37
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
38
|
+
|
39
|
+
a.set(2);
|
40
|
+
|
41
|
+
await nextTick();
|
42
|
+
|
43
|
+
expect(value).toBe(4);
|
44
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
45
|
+
});
|
46
|
+
|
47
|
+
test('immediate option works', async () => {
|
48
|
+
const a = createStateSignal(1);
|
49
|
+
const b = createStateSignal(2);
|
50
|
+
|
51
|
+
const c = createComputedSignal(() => {
|
52
|
+
return a.get() + b.get();
|
53
|
+
});
|
54
|
+
|
55
|
+
let value;
|
56
|
+
const w = createWatcherSignal(() => {
|
57
|
+
value = c.get();
|
58
|
+
});
|
59
|
+
|
60
|
+
w.addListener(
|
61
|
+
() => {
|
62
|
+
// do something
|
63
|
+
},
|
64
|
+
{ immediate: true },
|
65
|
+
);
|
66
|
+
|
67
|
+
expect(value).toBe(3);
|
68
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
69
|
+
|
70
|
+
a.set(2);
|
71
|
+
|
72
|
+
await nextTick();
|
73
|
+
|
74
|
+
expect(value).toBe(4);
|
75
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('with async computeds', () => {
|
80
|
+
test('watches async computed values', async () => {
|
81
|
+
const a = createStateSignal(1);
|
82
|
+
const b = createStateSignal(2);
|
83
|
+
|
84
|
+
const c = createAsyncComputedSignal(async () => {
|
85
|
+
return a.get() + b.get();
|
86
|
+
});
|
87
|
+
|
88
|
+
let value;
|
89
|
+
const w = createWatcherSignal(() => {
|
90
|
+
return c.get();
|
91
|
+
});
|
92
|
+
|
93
|
+
const unsub = w.addListener(v => {
|
94
|
+
value = { ...v };
|
95
|
+
// do something
|
96
|
+
});
|
97
|
+
|
98
|
+
expect(value).toEqual(undefined);
|
99
|
+
expect(w).toHaveSignalCounts({ compute: 0, effect: 0 });
|
100
|
+
|
101
|
+
await sleep(); // First tick to start async computation
|
102
|
+
|
103
|
+
expect(value).toEqual(result(undefined, 'pending', 'initial'));
|
104
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
105
|
+
|
106
|
+
await nextTick(); // Extra tick for async resolution
|
107
|
+
|
108
|
+
expect(value).toEqual(result(3, 'success', 'resolved'));
|
109
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
110
|
+
|
111
|
+
a.set(2);
|
112
|
+
|
113
|
+
await sleep(); // First tick to start async computation
|
114
|
+
|
115
|
+
expect(value).toEqual(result(3, 'pending', 'resolved'));
|
116
|
+
expect(w).toHaveSignalCounts({ compute: 3, effect: 3 });
|
117
|
+
|
118
|
+
await nextTick(); // Extra tick for async resolution
|
119
|
+
|
120
|
+
expect(value).toEqual(result(4, 'success', 'resolved'));
|
121
|
+
expect(w).toHaveSignalCounts({ compute: 4, effect: 4 });
|
122
|
+
|
123
|
+
unsub();
|
124
|
+
});
|
125
|
+
|
126
|
+
test('immediate option works', async () => {
|
127
|
+
const a = createStateSignal(1);
|
128
|
+
const b = createStateSignal(2);
|
129
|
+
|
130
|
+
const c = createAsyncComputedSignal(async () => {
|
131
|
+
return a.get() + b.get();
|
132
|
+
});
|
133
|
+
|
134
|
+
const w = createWatcherSignal(() => {
|
135
|
+
return c.get();
|
136
|
+
});
|
137
|
+
|
138
|
+
let value;
|
139
|
+
w.addListener(
|
140
|
+
v => {
|
141
|
+
value = { ...v };
|
142
|
+
// do something
|
143
|
+
},
|
144
|
+
{
|
145
|
+
immediate: true,
|
146
|
+
},
|
147
|
+
);
|
148
|
+
|
149
|
+
expect(value).toEqual(result(undefined, 'pending', 'initial'));
|
150
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
151
|
+
|
152
|
+
await nextTick(); // Extra tick for async resolution
|
153
|
+
|
154
|
+
expect(value).toEqual(result(3, 'success', 'resolved'));
|
155
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
156
|
+
|
157
|
+
a.set(2);
|
158
|
+
|
159
|
+
await sleep(); // First tick to start async computation
|
160
|
+
|
161
|
+
expect(value).toEqual(result(3, 'pending', 'resolved'));
|
162
|
+
expect(w).toHaveSignalCounts({ compute: 3, effect: 3 });
|
163
|
+
|
164
|
+
await nextTick(); // Extra tick for async resolution
|
165
|
+
|
166
|
+
expect(value).toEqual(result(4, 'success', 'resolved'));
|
167
|
+
expect(w).toHaveSignalCounts({ compute: 4, effect: 4 });
|
168
|
+
});
|
169
|
+
});
|
170
|
+
|
171
|
+
describe('with subscriptions', () => {
|
172
|
+
test('watches subscription values', async () => {
|
173
|
+
let value = createStateSignal(1);
|
174
|
+
|
175
|
+
const s = createSubscriptionSignal((get, set) => {
|
176
|
+
set(value.get());
|
177
|
+
|
178
|
+
return {
|
179
|
+
update() {
|
180
|
+
set(value.get());
|
181
|
+
},
|
182
|
+
};
|
183
|
+
});
|
184
|
+
|
185
|
+
let watchedValue;
|
186
|
+
const w = createWatcherSignal(() => {
|
187
|
+
watchedValue = s.get();
|
188
|
+
});
|
189
|
+
|
190
|
+
w.addListener(() => {
|
191
|
+
// do something
|
192
|
+
});
|
193
|
+
|
194
|
+
expect(watchedValue).toBe(undefined);
|
195
|
+
expect(w).toHaveSignalCounts({ compute: 0, effect: 0 });
|
196
|
+
expect(s).toHaveSignalValueAndCounts(undefined, { subscribe: 0 });
|
197
|
+
|
198
|
+
await nextTick();
|
199
|
+
|
200
|
+
expect(watchedValue).toBe(1);
|
201
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
202
|
+
expect(s).toHaveSignalValueAndCounts(1, { subscribe: 1 });
|
203
|
+
|
204
|
+
value.set(2);
|
205
|
+
s.get();
|
206
|
+
|
207
|
+
await nextTick();
|
208
|
+
|
209
|
+
expect(watchedValue).toBe(2);
|
210
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
211
|
+
expect(s).toHaveSignalValueAndCounts(2, { subscribe: 1 });
|
212
|
+
});
|
213
|
+
|
214
|
+
test('immediate option works', async () => {
|
215
|
+
let value = createStateSignal(1);
|
216
|
+
|
217
|
+
const s = createSubscriptionSignal((get, set) => {
|
218
|
+
set(value.get());
|
219
|
+
|
220
|
+
return {
|
221
|
+
update() {
|
222
|
+
set(value.get());
|
223
|
+
},
|
224
|
+
};
|
225
|
+
});
|
226
|
+
|
227
|
+
let watchedValue;
|
228
|
+
const w = createWatcherSignal(() => {
|
229
|
+
watchedValue = s.get();
|
230
|
+
});
|
231
|
+
|
232
|
+
w.addListener(
|
233
|
+
() => {
|
234
|
+
// do something
|
235
|
+
},
|
236
|
+
{ immediate: true },
|
237
|
+
);
|
238
|
+
|
239
|
+
expect(watchedValue).toBe(1);
|
240
|
+
expect(w).toHaveSignalCounts({ compute: 1, effect: 1 });
|
241
|
+
expect(s).toHaveSignalValueAndCounts(1, { subscribe: 1 });
|
242
|
+
|
243
|
+
value.set(2);
|
244
|
+
s.get();
|
245
|
+
|
246
|
+
await nextTick();
|
247
|
+
|
248
|
+
expect(watchedValue).toBe(2);
|
249
|
+
expect(w).toHaveSignalCounts({ compute: 2, effect: 2 });
|
250
|
+
expect(s).toHaveSignalValueAndCounts(2, { subscribe: 1 });
|
251
|
+
});
|
252
|
+
});
|
253
|
+
});
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { expect } from 'vitest';
|
2
|
+
import { AsyncResult } from '../../types.js';
|
3
|
+
|
4
|
+
export const result = <T>(
|
5
|
+
value: T | undefined,
|
6
|
+
promiseState: 'pending' | 'error' | 'success',
|
7
|
+
readyState: 'initial' | 'ready' | 'resolved',
|
8
|
+
error?: any,
|
9
|
+
): AsyncResult<T> =>
|
10
|
+
({
|
11
|
+
result: value,
|
12
|
+
error,
|
13
|
+
isPending: promiseState === 'pending',
|
14
|
+
isError: promiseState === 'error',
|
15
|
+
isSuccess: promiseState === 'success',
|
16
|
+
|
17
|
+
isReady: readyState === 'ready' || readyState === 'resolved',
|
18
|
+
didResolve: readyState === 'resolved',
|
19
|
+
|
20
|
+
await: expect.any(Function),
|
21
|
+
invalidate: expect.any(Function),
|
22
|
+
}) as AsyncResult<T>;
|
@@ -0,0 +1,309 @@
|
|
1
|
+
import { afterEach, Assertion, beforeEach, expect } from 'vitest';
|
2
|
+
import {
|
3
|
+
asyncComputed as _asyncComputed,
|
4
|
+
asyncTask as _asyncTask,
|
5
|
+
computed as _computed,
|
6
|
+
subscription as _subscription,
|
7
|
+
watcher,
|
8
|
+
SignalSubscribe,
|
9
|
+
withContext,
|
10
|
+
clearRootScope,
|
11
|
+
} from '../../hooks.js';
|
12
|
+
import { SignalOptionsWithInit, SignalSubscription, Watcher } from '../../types.js';
|
13
|
+
|
14
|
+
class SignalHookCounts {
|
15
|
+
name: string;
|
16
|
+
|
17
|
+
get = 0;
|
18
|
+
set = 0;
|
19
|
+
compute = 0;
|
20
|
+
|
21
|
+
resolve = 0;
|
22
|
+
|
23
|
+
subscribe = 0;
|
24
|
+
update = 0;
|
25
|
+
unsubscribe = 0;
|
26
|
+
internalGet = 0;
|
27
|
+
internalSet = 0;
|
28
|
+
|
29
|
+
effect = 0;
|
30
|
+
|
31
|
+
constructor(name: string) {
|
32
|
+
this.name = name;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
const countsKeys = Object.keys(new SignalHookCounts('')).filter(k => k !== 'name') as (keyof SignalHookCounts)[];
|
37
|
+
|
38
|
+
let currentOrder: string[] | undefined = [];
|
39
|
+
const COUNTS = new WeakMap<object, SignalHookCounts>();
|
40
|
+
|
41
|
+
interface CustomMatchers<R = unknown> {
|
42
|
+
toHaveHookValue: (v: any) => Assertion<R>;
|
43
|
+
toHaveCounts: (counts: Partial<SignalHookCounts>) => Assertion<R>;
|
44
|
+
toHaveValueAndCounts: (v: any, counts: Partial<SignalHookCounts>) => Assertion<R>;
|
45
|
+
toHaveComputedOrder: (order: string[]) => Assertion<R>;
|
46
|
+
withParams: R extends (...args: infer P) => any ? (...args: P) => Assertion<R> : (...args: any[]) => Assertion<R>;
|
47
|
+
withContexts: (contexts: Record<symbol, any>) => Assertion<R>;
|
48
|
+
}
|
49
|
+
|
50
|
+
declare module 'vitest' {
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
52
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
54
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
55
|
+
}
|
56
|
+
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
58
|
+
const NEXT_ARGS = new WeakMap<Function, any[]>();
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
60
|
+
const NEXT_CONTEXTS = new WeakMap<Function, Record<symbol, any>>();
|
61
|
+
|
62
|
+
let w: Watcher<unknown> | undefined;
|
63
|
+
|
64
|
+
let unsubs: (() => void)[] = [];
|
65
|
+
|
66
|
+
afterEach(() => {
|
67
|
+
unsubs.forEach(unsub => unsub());
|
68
|
+
});
|
69
|
+
|
70
|
+
function toHaveHookValue(
|
71
|
+
this: { equals(a: unknown, b: unknown): boolean },
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
73
|
+
hook: Function,
|
74
|
+
value: any,
|
75
|
+
) {
|
76
|
+
const args = NEXT_ARGS.get(hook) ?? [];
|
77
|
+
const contexts = NEXT_CONTEXTS.get(hook);
|
78
|
+
|
79
|
+
NEXT_ARGS.delete(hook);
|
80
|
+
NEXT_CONTEXTS.delete(hook);
|
81
|
+
|
82
|
+
let w = watcher(() => {
|
83
|
+
if (contexts) {
|
84
|
+
return withContext(contexts, () => {
|
85
|
+
return hook(...args);
|
86
|
+
});
|
87
|
+
} else {
|
88
|
+
return hook(...args);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
|
92
|
+
let signalValue: any;
|
93
|
+
unsubs.push(
|
94
|
+
w.addListener(
|
95
|
+
(v: any) => {
|
96
|
+
signalValue = v;
|
97
|
+
},
|
98
|
+
{
|
99
|
+
immediate: true,
|
100
|
+
},
|
101
|
+
),
|
102
|
+
);
|
103
|
+
|
104
|
+
if (signalValue && typeof signalValue === 'object' && 'result' in signalValue) {
|
105
|
+
signalValue = signalValue.result;
|
106
|
+
}
|
107
|
+
|
108
|
+
return {
|
109
|
+
pass: this.equals(signalValue, value),
|
110
|
+
message: () => `Expected signal value to be ${JSON.stringify(value)}, but got ${JSON.stringify(signalValue)}`,
|
111
|
+
};
|
112
|
+
}
|
113
|
+
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
115
|
+
function toHaveCounts(hook: Function, counts: SignalHookCounts) {
|
116
|
+
const signalCounts = COUNTS.get(hook);
|
117
|
+
|
118
|
+
if (!signalCounts) {
|
119
|
+
return {
|
120
|
+
pass: false,
|
121
|
+
message: () => 'Signal not found in counts map',
|
122
|
+
};
|
123
|
+
}
|
124
|
+
|
125
|
+
for (const key of countsKeys) {
|
126
|
+
const count = counts[key];
|
127
|
+
|
128
|
+
if (count !== undefined && signalCounts[key] !== count) {
|
129
|
+
return {
|
130
|
+
pass: false,
|
131
|
+
message: () => `Expected ${key} count to be ${count} but got ${signalCounts[key]}`,
|
132
|
+
};
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
return {
|
137
|
+
pass: true,
|
138
|
+
message: () => 'Counts match',
|
139
|
+
};
|
140
|
+
}
|
141
|
+
|
142
|
+
expect.extend({
|
143
|
+
toHaveHookValue,
|
144
|
+
toHaveCounts,
|
145
|
+
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
147
|
+
withParams(fn: Function, ...args: any[]) {
|
148
|
+
NEXT_ARGS.set(fn, args);
|
149
|
+
|
150
|
+
return {
|
151
|
+
pass: true,
|
152
|
+
message: () => 'Params match',
|
153
|
+
};
|
154
|
+
},
|
155
|
+
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
157
|
+
withContexts(fn: Function, contexts: Record<symbol, any>) {
|
158
|
+
NEXT_CONTEXTS.set(fn, contexts);
|
159
|
+
|
160
|
+
return {
|
161
|
+
pass: true,
|
162
|
+
message: () => 'Contexts match',
|
163
|
+
};
|
164
|
+
},
|
165
|
+
|
166
|
+
toHaveValueAndCounts(signal, value, counts) {
|
167
|
+
const valueResult = toHaveHookValue.call(this, signal, value);
|
168
|
+
const countsResult = toHaveCounts.call(this, signal, counts);
|
169
|
+
|
170
|
+
return {
|
171
|
+
pass: valueResult.pass && countsResult.pass,
|
172
|
+
message: () => {
|
173
|
+
const messages = [
|
174
|
+
!valueResult.pass && valueResult.message(),
|
175
|
+
!countsResult.pass && countsResult.message(),
|
176
|
+
].filter(m => m);
|
177
|
+
|
178
|
+
return messages.join('\n');
|
179
|
+
},
|
180
|
+
};
|
181
|
+
},
|
182
|
+
|
183
|
+
toHaveComputedOrder(fn: () => void, expectedOrder: string[]) {
|
184
|
+
const order = (currentOrder = []);
|
185
|
+
|
186
|
+
fn();
|
187
|
+
|
188
|
+
currentOrder = undefined;
|
189
|
+
|
190
|
+
return {
|
191
|
+
pass: this.equals(order, expectedOrder),
|
192
|
+
message: () => `Expected compute count to be ${expectedOrder.toString()} but got ${order.toString()}`,
|
193
|
+
};
|
194
|
+
},
|
195
|
+
});
|
196
|
+
|
197
|
+
export const wrapHook = <T, Args extends unknown[]>(original: object, fn: (...args: Args) => T) => {
|
198
|
+
const counts = COUNTS.get(original);
|
199
|
+
|
200
|
+
if (!counts) {
|
201
|
+
throw new Error('Signal not found in counts map');
|
202
|
+
}
|
203
|
+
|
204
|
+
COUNTS.set(fn, counts);
|
205
|
+
|
206
|
+
return fn;
|
207
|
+
};
|
208
|
+
|
209
|
+
export const computed: typeof _computed = (fn, opts) => {
|
210
|
+
const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
|
211
|
+
|
212
|
+
const wrapper = _computed((...args) => {
|
213
|
+
counts.compute++;
|
214
|
+
|
215
|
+
return fn(...(args as any));
|
216
|
+
}, opts);
|
217
|
+
|
218
|
+
COUNTS.set(wrapper, counts);
|
219
|
+
|
220
|
+
return wrapper;
|
221
|
+
};
|
222
|
+
|
223
|
+
export const asyncComputed: typeof _asyncComputed = (fn, opts) => {
|
224
|
+
const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
|
225
|
+
|
226
|
+
const wrapper = _asyncComputed((...args) => {
|
227
|
+
counts.compute++;
|
228
|
+
|
229
|
+
return fn(...(args as any));
|
230
|
+
}, opts) as any;
|
231
|
+
|
232
|
+
COUNTS.set(wrapper, counts);
|
233
|
+
|
234
|
+
return wrapper;
|
235
|
+
};
|
236
|
+
|
237
|
+
export const asyncTask: typeof _asyncTask = (fn, opts) => {
|
238
|
+
const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
|
239
|
+
|
240
|
+
const wrapper = _asyncTask((...args) => {
|
241
|
+
counts.compute++;
|
242
|
+
|
243
|
+
return fn(...(args as any));
|
244
|
+
}, opts) as any;
|
245
|
+
|
246
|
+
COUNTS.set(wrapper, counts);
|
247
|
+
|
248
|
+
return wrapper;
|
249
|
+
};
|
250
|
+
|
251
|
+
export const subscription = <T, Args extends unknown[]>(
|
252
|
+
fn: SignalSubscribe<T, Args>,
|
253
|
+
opts?: Partial<SignalOptionsWithInit<T, Args>>,
|
254
|
+
): ReturnType<typeof _subscription<T, Args>> => {
|
255
|
+
const counts = new SignalHookCounts(opts?.desc ?? 'unknown');
|
256
|
+
|
257
|
+
let wrapper = _subscription<T, Args>(({ get, set }, ...args) => {
|
258
|
+
counts.subscribe++;
|
259
|
+
counts.compute++;
|
260
|
+
|
261
|
+
const result = fn(
|
262
|
+
{
|
263
|
+
get: () => {
|
264
|
+
counts.internalGet++;
|
265
|
+
return get();
|
266
|
+
},
|
267
|
+
set: v => {
|
268
|
+
counts.internalSet++;
|
269
|
+
set(v);
|
270
|
+
},
|
271
|
+
},
|
272
|
+
...args,
|
273
|
+
);
|
274
|
+
|
275
|
+
let subscriptionWrapper: SignalSubscription | (() => unknown) | undefined;
|
276
|
+
|
277
|
+
if (result) {
|
278
|
+
if (typeof result === 'function') {
|
279
|
+
subscriptionWrapper = () => {
|
280
|
+
counts.unsubscribe++;
|
281
|
+
result();
|
282
|
+
};
|
283
|
+
} else {
|
284
|
+
subscriptionWrapper = {};
|
285
|
+
|
286
|
+
if (result.unsubscribe) {
|
287
|
+
subscriptionWrapper.unsubscribe = () => {
|
288
|
+
counts.unsubscribe++;
|
289
|
+
result.unsubscribe!();
|
290
|
+
};
|
291
|
+
}
|
292
|
+
|
293
|
+
if (result.update) {
|
294
|
+
subscriptionWrapper.update = () => {
|
295
|
+
counts.compute++;
|
296
|
+
counts.update++;
|
297
|
+
result.update!();
|
298
|
+
};
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
|
303
|
+
return subscriptionWrapper;
|
304
|
+
}, opts);
|
305
|
+
|
306
|
+
COUNTS.set(wrapper, counts);
|
307
|
+
|
308
|
+
return wrapper;
|
309
|
+
};
|