signalium 0.2.8 → 0.3.1

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 (161) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/cjs/config.d.ts +14 -5
  4. package/dist/cjs/config.d.ts.map +1 -1
  5. package/dist/cjs/config.js +23 -14
  6. package/dist/cjs/config.js.map +1 -1
  7. package/dist/cjs/debug.d.ts +3 -0
  8. package/dist/cjs/debug.d.ts.map +1 -0
  9. package/dist/cjs/debug.js +16 -0
  10. package/dist/cjs/debug.js.map +1 -0
  11. package/dist/cjs/hooks.d.ts +45 -0
  12. package/dist/cjs/hooks.d.ts.map +1 -0
  13. package/dist/cjs/hooks.js +263 -0
  14. package/dist/cjs/hooks.js.map +1 -0
  15. package/dist/cjs/index.d.ts +5 -3
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js +21 -8
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/react/context.d.ts +4 -0
  20. package/dist/cjs/react/context.d.ts.map +1 -0
  21. package/dist/cjs/react/context.js +10 -0
  22. package/dist/cjs/react/context.js.map +1 -0
  23. package/dist/cjs/react/index.d.ts +5 -0
  24. package/dist/cjs/react/index.d.ts.map +1 -0
  25. package/dist/cjs/react/index.js +12 -0
  26. package/dist/cjs/react/index.js.map +1 -0
  27. package/dist/cjs/react/provider.d.ts +7 -0
  28. package/dist/cjs/react/provider.d.ts.map +1 -0
  29. package/dist/cjs/react/provider.js +13 -0
  30. package/dist/cjs/react/provider.js.map +1 -0
  31. package/dist/cjs/react/setup.d.ts +2 -0
  32. package/dist/cjs/react/setup.d.ts.map +1 -0
  33. package/dist/cjs/react/setup.js +13 -0
  34. package/dist/cjs/react/setup.js.map +1 -0
  35. package/dist/cjs/react/signal-value.d.ts +2 -0
  36. package/dist/cjs/react/signal-value.d.ts.map +1 -0
  37. package/dist/cjs/react/signal-value.js +35 -0
  38. package/dist/cjs/react/signal-value.js.map +1 -0
  39. package/dist/cjs/react/state.d.ts +3 -0
  40. package/dist/cjs/react/state.d.ts.map +1 -0
  41. package/dist/cjs/react/state.js +13 -0
  42. package/dist/cjs/react/state.js.map +1 -0
  43. package/dist/cjs/scheduling.d.ts +5 -0
  44. package/dist/cjs/scheduling.d.ts.map +1 -1
  45. package/dist/cjs/scheduling.js +59 -5
  46. package/dist/cjs/scheduling.js.map +1 -1
  47. package/dist/cjs/signals.d.ts +28 -68
  48. package/dist/cjs/signals.d.ts.map +1 -1
  49. package/dist/cjs/signals.js +223 -76
  50. package/dist/cjs/signals.js.map +1 -1
  51. package/dist/cjs/trace.d.ts +127 -0
  52. package/dist/cjs/trace.d.ts.map +1 -0
  53. package/dist/cjs/trace.js +319 -0
  54. package/dist/cjs/trace.js.map +1 -0
  55. package/dist/cjs/types.d.ts +66 -0
  56. package/dist/cjs/types.d.ts.map +1 -0
  57. package/dist/cjs/types.js +3 -0
  58. package/dist/cjs/types.js.map +1 -0
  59. package/dist/cjs/utils.d.ts +4 -0
  60. package/dist/cjs/utils.d.ts.map +1 -0
  61. package/dist/cjs/utils.js +80 -0
  62. package/dist/cjs/utils.js.map +1 -0
  63. package/dist/esm/config.d.ts +14 -5
  64. package/dist/esm/config.d.ts.map +1 -1
  65. package/dist/esm/config.js +19 -11
  66. package/dist/esm/config.js.map +1 -1
  67. package/dist/esm/debug.d.ts +3 -0
  68. package/dist/esm/debug.d.ts.map +1 -0
  69. package/dist/esm/debug.js +3 -0
  70. package/dist/esm/debug.js.map +1 -0
  71. package/dist/esm/hooks.d.ts +45 -0
  72. package/dist/esm/hooks.d.ts.map +1 -0
  73. package/dist/esm/hooks.js +246 -0
  74. package/dist/esm/hooks.js.map +1 -0
  75. package/dist/esm/index.d.ts +5 -3
  76. package/dist/esm/index.d.ts.map +1 -1
  77. package/dist/esm/index.js +4 -2
  78. package/dist/esm/index.js.map +1 -1
  79. package/dist/esm/react/context.d.ts +4 -0
  80. package/dist/esm/react/context.d.ts.map +1 -0
  81. package/dist/esm/react/context.js +6 -0
  82. package/dist/esm/react/context.js.map +1 -0
  83. package/dist/esm/react/index.d.ts +5 -0
  84. package/dist/esm/react/index.d.ts.map +1 -0
  85. package/dist/esm/react/index.js +5 -0
  86. package/dist/esm/react/index.js.map +1 -0
  87. package/dist/esm/react/provider.d.ts +7 -0
  88. package/dist/esm/react/provider.d.ts.map +1 -0
  89. package/dist/esm/react/provider.js +10 -0
  90. package/dist/esm/react/provider.js.map +1 -0
  91. package/dist/esm/react/setup.d.ts +2 -0
  92. package/dist/esm/react/setup.d.ts.map +1 -0
  93. package/dist/esm/react/setup.js +10 -0
  94. package/dist/esm/react/setup.js.map +1 -0
  95. package/dist/esm/react/signal-value.d.ts +2 -0
  96. package/dist/esm/react/signal-value.d.ts.map +1 -0
  97. package/dist/esm/react/signal-value.js +32 -0
  98. package/dist/esm/react/signal-value.js.map +1 -0
  99. package/dist/esm/react/state.d.ts +3 -0
  100. package/dist/esm/react/state.d.ts.map +1 -0
  101. package/dist/esm/react/state.js +10 -0
  102. package/dist/esm/react/state.js.map +1 -0
  103. package/dist/esm/scheduling.d.ts +5 -0
  104. package/dist/esm/scheduling.d.ts.map +1 -1
  105. package/dist/esm/scheduling.js +51 -1
  106. package/dist/esm/scheduling.js.map +1 -1
  107. package/dist/esm/signals.d.ts +28 -68
  108. package/dist/esm/signals.d.ts.map +1 -1
  109. package/dist/esm/signals.js +215 -72
  110. package/dist/esm/signals.js.map +1 -1
  111. package/dist/esm/trace.d.ts +127 -0
  112. package/dist/esm/trace.d.ts.map +1 -0
  113. package/dist/esm/trace.js +311 -0
  114. package/dist/esm/trace.js.map +1 -0
  115. package/dist/esm/types.d.ts +66 -0
  116. package/dist/esm/types.d.ts.map +1 -0
  117. package/dist/esm/types.js +2 -0
  118. package/dist/esm/types.js.map +1 -0
  119. package/dist/esm/utils.d.ts +4 -0
  120. package/dist/esm/utils.d.ts.map +1 -0
  121. package/dist/esm/utils.js +75 -0
  122. package/dist/esm/utils.js.map +1 -0
  123. package/package.json +43 -2
  124. package/src/__tests__/hooks/async-computed.test.ts +190 -0
  125. package/src/__tests__/hooks/async-task.test.ts +227 -0
  126. package/src/__tests__/hooks/computed.test.ts +126 -0
  127. package/src/__tests__/hooks/context.test.ts +527 -0
  128. package/src/__tests__/hooks/nesting.test.ts +303 -0
  129. package/src/__tests__/hooks/params-and-state.test.ts +168 -0
  130. package/src/__tests__/hooks/subscription.test.ts +97 -0
  131. package/src/__tests__/signals/async.test.ts +416 -0
  132. package/src/__tests__/signals/basic.test.ts +399 -0
  133. package/src/__tests__/signals/subscription.test.ts +632 -0
  134. package/src/__tests__/signals/watcher.test.ts +253 -0
  135. package/src/__tests__/utils/async.ts +6 -0
  136. package/src/__tests__/utils/builders.ts +22 -0
  137. package/src/__tests__/utils/instrumented-hooks.ts +309 -0
  138. package/src/__tests__/utils/instrumented-signals.ts +281 -0
  139. package/src/__tests__/utils/permute.ts +74 -0
  140. package/src/config.ts +32 -17
  141. package/src/debug.ts +14 -0
  142. package/src/hooks.ts +433 -0
  143. package/src/index.ts +28 -3
  144. package/src/react/__tests__/react.test.tsx +227 -0
  145. package/src/react/context.ts +8 -0
  146. package/src/react/index.ts +4 -0
  147. package/src/react/provider.tsx +18 -0
  148. package/src/react/setup.ts +10 -0
  149. package/src/react/signal-value.ts +49 -0
  150. package/src/react/state.ts +13 -0
  151. package/src/scheduling.ts +69 -1
  152. package/src/signals.ts +328 -169
  153. package/src/trace.ts +449 -0
  154. package/src/types.ts +86 -0
  155. package/src/utils.ts +83 -0
  156. package/tsconfig.json +2 -1
  157. package/vitest.workspace.ts +24 -0
  158. package/src/__tests__/async.test.ts +0 -426
  159. package/src/__tests__/basic.test.ts +0 -378
  160. package/src/__tests__/subscription.test.ts +0 -645
  161. package/src/__tests__/utils/instrumented.ts +0 -326
@@ -0,0 +1,527 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { createContext, useContext, withContext } from '../../hooks.js';
3
+ import { state } from '../../index.js';
4
+ import { permute } from '../utils/permute.js';
5
+ import { nextTick } from '../utils/async.js';
6
+ import { asyncComputed } from '../utils/instrumented-hooks.js';
7
+
8
+ describe('contexts', () => {
9
+ test('throws when useContext is used outside of a signal', () => {
10
+ expect(() => {
11
+ useContext(createContext('test'));
12
+ }).toThrow('useContext must be used within a signal hook');
13
+ });
14
+
15
+ test('async computed maintains context ownership across await boundaries', async () => {
16
+ const ctx = createContext('default');
17
+
18
+ const inner = asyncComputed(async () => {
19
+ await Promise.resolve();
20
+ return 'inner-value';
21
+ });
22
+
23
+ const outer = asyncComputed(async () => {
24
+ const innerResult = inner();
25
+ const result = innerResult.await();
26
+ // Use context after awaiting inner result
27
+ const contextValue = useContext(ctx);
28
+ return result + '-' + contextValue;
29
+ });
30
+
31
+ // Test in parent scope
32
+ expect(outer).toHaveValueAndCounts(undefined, { compute: 1 });
33
+
34
+ // Wait for async computation to complete
35
+ await nextTick();
36
+ await nextTick();
37
+ expect(outer).toHaveValueAndCounts('inner-value-default', { compute: 2 });
38
+
39
+ // Test in child scope
40
+ expect(outer)
41
+ .withContexts({ [ctx]: 'child' })
42
+ .toHaveValueAndCounts(undefined, { compute: 3 });
43
+
44
+ // Verify parent scope maintains separate computed
45
+ await nextTick();
46
+
47
+ expect(outer)
48
+ .withContexts({ [ctx]: 'child' })
49
+ .toHaveValueAndCounts('inner-value-child', { compute: 3 });
50
+ expect(outer).toHaveValueAndCounts('inner-value-default', { compute: 3 });
51
+ });
52
+
53
+ permute(1, create => {
54
+ test('computed signals are cached per context scope', () => {
55
+ const ctx = createContext('default');
56
+ const value = state(0);
57
+
58
+ const computed = create(() => {
59
+ return useContext(ctx) + value.get();
60
+ });
61
+
62
+ // Same scope should reuse computation
63
+ expect(computed).toHaveValueAndCounts('default0', { compute: 1 });
64
+ expect(computed).toHaveValueAndCounts('default0', { compute: 1 });
65
+
66
+ const result = withContext({ [ctx]: 'other' }, () => {
67
+ // Different scope should compute again
68
+ return computed();
69
+ });
70
+
71
+ expect(computed)
72
+ .withContexts({ [ctx]: 'other' })
73
+ .toHaveValueAndCounts('other0', { compute: 2 });
74
+ expect(computed)
75
+ .withContexts({ [ctx]: 'other' })
76
+ .toHaveValueAndCounts('other0', { compute: 2 });
77
+
78
+ expect(computed).toHaveValueAndCounts('default0', { compute: 2 });
79
+ });
80
+
81
+ test('computed forks when accessing forked context after being shared', async () => {
82
+ const ctx = createContext('default');
83
+ const value = state(0);
84
+
85
+ const computed = create(() => {
86
+ // Initially only depends on value, not context
87
+ const v = value.get();
88
+ if (v > 0) {
89
+ // After value changes, depends on context
90
+ return useContext(ctx);
91
+ }
92
+ return 'default';
93
+ });
94
+
95
+ // Initially computed is shared between scopes since it doesn't use context
96
+ expect(computed)
97
+ .withContexts({ [ctx]: 'scope1' })
98
+ .toHaveValueAndCounts('default', { compute: 1 });
99
+ expect(computed)
100
+ .withContexts({ [ctx]: 'scope2' })
101
+ .toHaveValueAndCounts('default', { compute: 1 });
102
+
103
+ // Change value to make computed use context
104
+ value.set(1);
105
+
106
+ await nextTick();
107
+
108
+ // Now computed should fork and use the different context values
109
+ expect(computed)
110
+ .withContexts({ [ctx]: 'scope1' })
111
+ .toHaveValueAndCounts('scope1', { compute: 3 });
112
+ expect(computed)
113
+ .withContexts({ [ctx]: 'scope2' })
114
+ .toHaveValueAndCounts('scope2', { compute: 3 });
115
+
116
+ // Ensure that computed is cached correctly
117
+ expect(computed)
118
+ .withContexts({ [ctx]: 'scope1' })
119
+ .toHaveValueAndCounts('scope1', { compute: 3 });
120
+ expect(computed)
121
+ .withContexts({ [ctx]: 'scope2' })
122
+ .toHaveValueAndCounts('scope2', { compute: 3 });
123
+ });
124
+
125
+ test('computed forks correctly regardless of access order', () => {
126
+ const ctx = createContext('default');
127
+ const value = state(0);
128
+
129
+ const computed = create(() => {
130
+ // Initially only depends on value, not context
131
+ const v = value.get();
132
+ if (v > 0) {
133
+ // After value changes, depends on context
134
+ return useContext(ctx);
135
+ }
136
+ return v;
137
+ });
138
+
139
+ // Create two scopes with different context values, but access in reverse order
140
+ expect(computed)
141
+ .withContexts({ [ctx]: 'scope1' })
142
+ .toHaveValueAndCounts(0, { compute: 1 });
143
+
144
+ expect(computed)
145
+ .withContexts({ [ctx]: 'scope2' })
146
+ .toHaveValueAndCounts(0, { compute: 1 }); // Still shared since no context dependency
147
+
148
+ // Change value to make computed use context
149
+ value.set(1);
150
+
151
+ // Now computed should fork and use the different context values
152
+ // Access in reverse order compared to first test
153
+ expect(computed)
154
+ .withContexts({ [ctx]: 'scope2' })
155
+ .toHaveValueAndCounts('scope2', { compute: 2 });
156
+
157
+ expect(computed)
158
+ .withContexts({ [ctx]: 'scope1' })
159
+ .toHaveValueAndCounts('scope1', { compute: 3 });
160
+
161
+ // Ensure that computed is cached correctly
162
+ expect(computed)
163
+ .withContexts({ [ctx]: 'scope1' })
164
+ .toHaveValueAndCounts('scope1', { compute: 3 });
165
+
166
+ expect(computed)
167
+ .withContexts({ [ctx]: 'scope2' })
168
+ .toHaveValueAndCounts('scope2', { compute: 3 });
169
+ });
170
+
171
+ test('computed ownership transfers correctly between parent and child scopes', () => {
172
+ const ctx = createContext('default');
173
+ const value = state(0);
174
+
175
+ const computed = create(() => {
176
+ // Initially only depends on value, not context
177
+ const v = value.get();
178
+ if (v > 0) {
179
+ // After value changes, depends on context
180
+ return useContext(ctx) + v;
181
+ }
182
+ return v;
183
+ });
184
+
185
+ // Initially access in parent scope
186
+ expect(computed).toHaveValueAndCounts(0, { compute: 1 });
187
+
188
+ // Child scope reuses original computed instance since no context dependency
189
+ expect(computed)
190
+ .withContexts({ [ctx]: 'child' })
191
+ .toHaveValueAndCounts(0, { compute: 1 });
192
+
193
+ // Change value to make computed use context
194
+ value.set(1);
195
+
196
+ // Child scope takes ownership of parent instance
197
+ expect(computed)
198
+ .withContexts({ [ctx]: 'child' })
199
+ .toHaveValueAndCounts('child1', { compute: 2 });
200
+
201
+ // Parent scope gets its own computed instance
202
+ expect(computed).toHaveValueAndCounts('default1', { compute: 3 });
203
+
204
+ // Third scope gets its own computed instance
205
+ expect(computed)
206
+ .withContexts({ [ctx]: 'third' })
207
+ .toHaveValueAndCounts('third1', { compute: 4 });
208
+
209
+ // Ensure computeds are cached correctly
210
+ expect(computed)
211
+ .withContexts({ [ctx]: 'child' })
212
+ .toHaveValueAndCounts('child1', { compute: 4 });
213
+
214
+ expect(computed).toHaveValueAndCounts('default1', { compute: 4 });
215
+
216
+ // Verify all scopes maintain their separate computeds
217
+ value.set(2);
218
+
219
+ expect(computed)
220
+ .withContexts({ [ctx]: 'child' })
221
+ .toHaveValueAndCounts('child2', { compute: 5 });
222
+
223
+ expect(computed).toHaveValueAndCounts('default2', { compute: 6 });
224
+
225
+ expect(computed)
226
+ .withContexts({ [ctx]: 'third' })
227
+ .toHaveValueAndCounts('third2', { compute: 7 });
228
+ });
229
+ });
230
+
231
+ permute(2, (create1, create2) => {
232
+ test('contexts are properly scoped', () => {
233
+ const ctx = createContext('default');
234
+
235
+ const computed1 = create1(() => {
236
+ return useContext(ctx);
237
+ });
238
+
239
+ expect(computed1).toHaveValueAndCounts('default', { compute: 1 });
240
+
241
+ const computed2 = create2(() => {
242
+ return withContext({ [ctx]: 'override' }, () => {
243
+ return computed1();
244
+ });
245
+ });
246
+
247
+ expect(computed2).toHaveValueAndCounts('override', { compute: 1 });
248
+ expect(computed1).toHaveCounts({ compute: 2 });
249
+ });
250
+
251
+ test('context dependencies are tracked correctly', () => {
252
+ const ctx1 = createContext('default1');
253
+ const ctx2 = createContext('default2');
254
+
255
+ const computed1 = create1(() => {
256
+ // Only depends on ctx1
257
+ return useContext(ctx1);
258
+ });
259
+
260
+ const computed2 = create2(() => {
261
+ // Depends on both contexts
262
+ return computed1() + useContext(ctx2);
263
+ });
264
+
265
+ expect(computed2).toHaveValueAndCounts('default1default2', { compute: 1 });
266
+ expect(computed1).toHaveCounts({ compute: 1 });
267
+
268
+ expect(computed2)
269
+ .withContexts({ [ctx1]: 'override1' })
270
+ .toHaveValueAndCounts('override1default2', { compute: 2 });
271
+ expect(computed1).toHaveCounts({ compute: 2 });
272
+
273
+ expect(computed2)
274
+ .withContexts({ [ctx2]: 'override2' })
275
+ .toHaveValueAndCounts('default1override2', { compute: 3 });
276
+ expect(computed1).toHaveCounts({ compute: 2 });
277
+
278
+ expect(computed2)
279
+ .withContexts({ [ctx1]: 'override1', [ctx2]: 'override2' })
280
+ .toHaveValueAndCounts('override1override2', { compute: 4 });
281
+ expect(computed1).toHaveCounts({ compute: 3 });
282
+
283
+ // Should reuse cached value since ctx2 didn't change
284
+ expect(computed1)
285
+ .withContexts({ [ctx2]: 'override1' })
286
+ .toHaveValueAndCounts('default1', { compute: 3 });
287
+ });
288
+
289
+ test('context scopes inherit from parent scope when nested in computeds', () => {
290
+ const ctx1 = createContext('default1');
291
+ const ctx2 = createContext('default2');
292
+
293
+ const computed1 = create1(() => {
294
+ return useContext(ctx1) + useContext(ctx2);
295
+ });
296
+
297
+ const computed2 = create2(() => {
298
+ return (
299
+ useContext(ctx2) +
300
+ withContext({ [ctx2]: ':inner-override2' }, () => {
301
+ return computed1();
302
+ })
303
+ );
304
+ });
305
+
306
+ expect(computed2).toHaveValueAndCounts('default2default1:inner-override2', { compute: 1 });
307
+ expect(computed1).toHaveCounts({ compute: 1 });
308
+
309
+ expect(computed2)
310
+ .withContexts({ [ctx1]: 'override1' })
311
+ .toHaveValueAndCounts('default2override1:inner-override2', { compute: 2 });
312
+ expect(computed1).toHaveCounts({ compute: 2 });
313
+
314
+ expect(computed2)
315
+ .withContexts({ [ctx1]: 'override1', [ctx2]: 'override2' })
316
+ .toHaveValueAndCounts('override2override1:inner-override2', { compute: 3 });
317
+ expect(computed1).toHaveCounts({ compute: 3 });
318
+
319
+ expect(computed2)
320
+ .withContexts({ [ctx2]: 'override2' })
321
+ .toHaveValueAndCounts('override2default1:inner-override2', { compute: 4 });
322
+ expect(computed1).toHaveCounts({ compute: 4 });
323
+ });
324
+ });
325
+
326
+ permute(3, (create1, create2, create3) => {
327
+ test('the gauntlet (params + state + context)', async () => {
328
+ const ctx = createContext('ctxdefault');
329
+ const value = state('value');
330
+
331
+ const inner1 = create1((a: number) => {
332
+ if (a === 3) {
333
+ return ['inner1', useContext(ctx)];
334
+ } else if (a === 4) {
335
+ return ['inner1', value.get()];
336
+ }
337
+
338
+ return ['inner1'];
339
+ });
340
+
341
+ const inner2 = create2((a: number) => {
342
+ if (a === 3) {
343
+ return value.get() === 'value' ? ['inner2'] : ['inner2', useContext(ctx)];
344
+ } else if (a === 4) {
345
+ return withContext({ [ctx]: 'ctxinneroverride' }, () => {
346
+ return ['inner2', inner1(3), value.get()];
347
+ });
348
+ }
349
+
350
+ return ['inner2', inner1(a)];
351
+ });
352
+
353
+ const outer = create3((a: number) => {
354
+ if (a === 1) {
355
+ return [inner1(1), inner2(2)];
356
+ } else if (a === 2) {
357
+ return [inner1(2), inner2(3)];
358
+ } else if (a === 3) {
359
+ return [inner1(3), inner2(4)];
360
+ } else if (a === 4) {
361
+ return [useContext(ctx), inner2(4)];
362
+ } else if (a === 5) {
363
+ return [inner1(5), value.get()];
364
+ }
365
+ });
366
+
367
+ // a === 1
368
+ expect(outer)
369
+ .withParams(1)
370
+ .toHaveValueAndCounts([['inner1'], ['inner2', ['inner1']]], { compute: 1 });
371
+ expect(inner1).toHaveCounts({ compute: 2 });
372
+ expect(inner2).toHaveCounts({ compute: 1 });
373
+
374
+ expect(outer)
375
+ .withContexts({ [ctx]: 'ctxoverride' })
376
+ .withParams(1)
377
+ .toHaveValueAndCounts([['inner1'], ['inner2', ['inner1']]], { compute: 1 });
378
+ expect(inner1).toHaveCounts({ compute: 2 });
379
+ expect(inner2).toHaveCounts({ compute: 1 });
380
+
381
+ // a === 2
382
+ expect(outer)
383
+ .withParams(2)
384
+ .toHaveValueAndCounts([['inner1'], ['inner2']], {
385
+ compute: 2,
386
+ });
387
+ expect(inner1).toHaveCounts({ compute: 2 });
388
+ expect(inner2).toHaveCounts({ compute: 2 });
389
+
390
+ expect(outer)
391
+ .withParams(2)
392
+ .withContexts({ [ctx]: 'ctxoverride' })
393
+ .toHaveValueAndCounts([['inner1'], ['inner2']], {
394
+ compute: 2,
395
+ });
396
+ expect(inner1).toHaveCounts({ compute: 2 });
397
+ expect(inner2).toHaveCounts({ compute: 2 });
398
+
399
+ // a === 3
400
+ expect(outer)
401
+ .withParams(3)
402
+ .toHaveValueAndCounts(
403
+ [
404
+ ['inner1', 'ctxdefault'],
405
+ ['inner2', ['inner1', 'ctxinneroverride'], 'value'],
406
+ ],
407
+ {
408
+ compute: 3,
409
+ },
410
+ );
411
+ expect(inner1).toHaveCounts({ compute: 4 });
412
+ expect(inner2).toHaveCounts({ compute: 3 });
413
+
414
+ expect(outer)
415
+ .withParams(3)
416
+ .withContexts({ [ctx]: 'ctxoverride' })
417
+ .toHaveValueAndCounts(
418
+ [
419
+ ['inner1', 'ctxoverride'],
420
+ ['inner2', ['inner1', 'ctxinneroverride'], 'value'],
421
+ ],
422
+ {
423
+ compute: 4,
424
+ },
425
+ );
426
+ expect(inner1).toHaveCounts({ compute: 6 });
427
+ expect(inner2).toHaveCounts({ compute: 4 });
428
+
429
+ // a === 4
430
+ expect(outer)
431
+ .withParams(4)
432
+ .toHaveValueAndCounts(['ctxdefault', ['inner2', ['inner1', 'ctxinneroverride'], 'value']], {
433
+ compute: 5,
434
+ });
435
+ expect(inner1).toHaveCounts({ compute: 6 });
436
+ expect(inner2).toHaveCounts({ compute: 4 });
437
+
438
+ expect(outer)
439
+ .withParams(4)
440
+ .withContexts({ [ctx]: 'ctxoverride' })
441
+ .toHaveValueAndCounts(['ctxoverride', ['inner2', ['inner1', 'ctxinneroverride'], 'value']], {
442
+ compute: 6,
443
+ });
444
+ expect(inner1).toHaveCounts({ compute: 6 });
445
+ expect(inner2).toHaveCounts({ compute: 4 });
446
+
447
+ // a === 5
448
+ expect(outer)
449
+ .withParams(5)
450
+ .toHaveValueAndCounts([['inner1'], 'value'], {
451
+ compute: 7,
452
+ });
453
+ expect(inner1).toHaveCounts({ compute: 7 });
454
+ expect(inner2).toHaveCounts({ compute: 4 });
455
+
456
+ expect(outer)
457
+ .withParams(5)
458
+ .withContexts({ [ctx]: 'ctxoverride' })
459
+ .toHaveValueAndCounts([['inner1'], 'value'], {
460
+ compute: 8,
461
+ });
462
+ expect(inner1).toHaveCounts({ compute: 8 });
463
+ expect(inner2).toHaveCounts({ compute: 4 });
464
+
465
+ value.set('value2');
466
+ await nextTick();
467
+
468
+ // a === 1
469
+ expect(outer)
470
+ .withParams(1)
471
+ .toHaveHookValue([['inner1'], ['inner2', ['inner1']]]);
472
+
473
+ expect(outer)
474
+ .withContexts({ [ctx]: 'ctxoverride' })
475
+ .withParams(1)
476
+ .toHaveHookValue([['inner1'], ['inner2', ['inner1']]]);
477
+
478
+ // a === 2
479
+ expect(outer)
480
+ .withParams(2)
481
+ .toHaveHookValue([['inner1'], ['inner2', 'ctxdefault']]);
482
+
483
+ expect(outer)
484
+ .withParams(2)
485
+ .withContexts({ [ctx]: 'ctxoverride' })
486
+ .toHaveHookValue([['inner1'], ['inner2', 'ctxoverride']]);
487
+
488
+ // a === 3
489
+ expect(outer)
490
+ .withParams(3)
491
+ .toHaveHookValue([
492
+ ['inner1', 'ctxdefault'],
493
+ ['inner2', ['inner1', 'ctxinneroverride'], 'value2'],
494
+ ]);
495
+
496
+ expect(outer)
497
+ .withParams(3)
498
+ .withContexts({ [ctx]: 'ctxoverride' })
499
+ .toHaveHookValue([
500
+ ['inner1', 'ctxoverride'],
501
+ ['inner2', ['inner1', 'ctxinneroverride'], 'value2'],
502
+ ]);
503
+
504
+ // a === 4
505
+ expect(outer)
506
+ .withParams(4)
507
+ .toHaveHookValue(['ctxdefault', ['inner2', ['inner1', 'ctxinneroverride'], 'value2']]);
508
+
509
+ expect(outer)
510
+ .withParams(4)
511
+ .withContexts({ [ctx]: 'ctxoverride' })
512
+ .toHaveHookValue(['ctxoverride', ['inner2', ['inner1', 'ctxinneroverride'], 'value2']]);
513
+
514
+ // a === 5
515
+ expect(outer)
516
+ .withParams(5)
517
+ .toHaveHookValue([['inner1'], 'value2']);
518
+ expect(outer)
519
+ .withParams(5)
520
+ .withContexts({ [ctx]: 'ctxoverride' })
521
+ .toHaveHookValue([['inner1'], 'value2']);
522
+
523
+ expect(inner1).toHaveCounts({ compute: 10 });
524
+ expect(inner2).toHaveCounts({ compute: 9 });
525
+ });
526
+ });
527
+ });