rask-ui 0.28.2 → 0.28.4

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 (43) hide show
  1. package/dist/component.d.ts +1 -0
  2. package/dist/component.d.ts.map +1 -1
  3. package/dist/component.js +7 -2
  4. package/dist/tests/batch.test.js +202 -12
  5. package/dist/tests/createContext.test.js +50 -37
  6. package/dist/tests/error.test.js +25 -12
  7. package/dist/tests/renderCount.test.d.ts +2 -0
  8. package/dist/tests/renderCount.test.d.ts.map +1 -0
  9. package/dist/tests/renderCount.test.js +95 -0
  10. package/dist/tests/scopeEnforcement.test.d.ts +2 -0
  11. package/dist/tests/scopeEnforcement.test.d.ts.map +1 -0
  12. package/dist/tests/scopeEnforcement.test.js +157 -0
  13. package/dist/tests/useAction.test.d.ts +2 -0
  14. package/dist/tests/useAction.test.d.ts.map +1 -0
  15. package/dist/tests/useAction.test.js +132 -0
  16. package/dist/tests/useAsync.test.d.ts +2 -0
  17. package/dist/tests/useAsync.test.d.ts.map +1 -0
  18. package/dist/tests/useAsync.test.js +499 -0
  19. package/dist/tests/useDerived.test.d.ts +2 -0
  20. package/dist/tests/useDerived.test.d.ts.map +1 -0
  21. package/dist/tests/useDerived.test.js +407 -0
  22. package/dist/tests/useEffect.test.d.ts +2 -0
  23. package/dist/tests/useEffect.test.d.ts.map +1 -0
  24. package/dist/tests/useEffect.test.js +600 -0
  25. package/dist/tests/useLookup.test.d.ts +2 -0
  26. package/dist/tests/useLookup.test.d.ts.map +1 -0
  27. package/dist/tests/useLookup.test.js +299 -0
  28. package/dist/tests/useRef.test.d.ts +2 -0
  29. package/dist/tests/useRef.test.d.ts.map +1 -0
  30. package/dist/tests/useRef.test.js +189 -0
  31. package/dist/tests/useState.test.d.ts +2 -0
  32. package/dist/tests/useState.test.d.ts.map +1 -0
  33. package/dist/tests/useState.test.js +178 -0
  34. package/dist/tests/useSuspend.test.d.ts +2 -0
  35. package/dist/tests/useSuspend.test.d.ts.map +1 -0
  36. package/dist/tests/useSuspend.test.js +752 -0
  37. package/dist/tests/useView.test.d.ts +2 -0
  38. package/dist/tests/useView.test.d.ts.map +1 -0
  39. package/dist/tests/useView.test.js +305 -0
  40. package/dist/transformer.d.ts.map +1 -1
  41. package/dist/transformer.js +1 -5
  42. package/dist/useState.js +4 -2
  43. package/package.json +1 -1
@@ -0,0 +1,600 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "rask-ui/jsx-runtime";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { useEffect } from "../useEffect";
4
+ import { useState } from "../useState";
5
+ import { render } from "../index";
6
+ describe("useEffect", () => {
7
+ it("should run immediately on creation", () => {
8
+ const effectFn = vi.fn();
9
+ function Component() {
10
+ useEffect(effectFn);
11
+ return () => _jsx("div", { children: "test" });
12
+ }
13
+ const container = document.createElement("div");
14
+ render(_jsx(Component, {}), container);
15
+ expect(effectFn).toHaveBeenCalledTimes(1);
16
+ });
17
+ it("should track reactive dependencies", async () => {
18
+ const effectFn = vi.fn();
19
+ let state;
20
+ function Component() {
21
+ state = useState({ count: 0 });
22
+ useEffect(() => {
23
+ effectFn();
24
+ state.count; // Access to track
25
+ });
26
+ return () => _jsx("div", { children: "test" });
27
+ }
28
+ const container = document.createElement("div");
29
+ render(_jsx(Component, {}), container);
30
+ expect(effectFn).toHaveBeenCalledTimes(1);
31
+ state.count = 1;
32
+ await new Promise((resolve) => setTimeout(resolve, 0));
33
+ expect(effectFn).toHaveBeenCalledTimes(2);
34
+ });
35
+ it("should re-run when dependencies change", async () => {
36
+ const results = [];
37
+ let state;
38
+ function Component() {
39
+ state = useState({ count: 0 });
40
+ useEffect(() => {
41
+ results.push(state.count);
42
+ });
43
+ return () => _jsx("div", { children: "test" });
44
+ }
45
+ const container = document.createElement("div");
46
+ render(_jsx(Component, {}), container);
47
+ expect(results).toEqual([0]);
48
+ state.count = 1;
49
+ await new Promise((resolve) => setTimeout(resolve, 0));
50
+ expect(results).toEqual([0, 1]);
51
+ state.count = 2;
52
+ await new Promise((resolve) => setTimeout(resolve, 0));
53
+ expect(results).toEqual([0, 1, 2]);
54
+ });
55
+ it("should run on microtask, not synchronously", () => {
56
+ const results = [];
57
+ let state;
58
+ function Component() {
59
+ state = useState({ count: 0 });
60
+ useEffect(() => {
61
+ results.push(state.count);
62
+ });
63
+ return () => _jsx("div", { children: "test" });
64
+ }
65
+ const container = document.createElement("div");
66
+ render(_jsx(Component, {}), container);
67
+ expect(results).toEqual([0]); // Initial run is synchronous
68
+ state.count = 1;
69
+ // Should not have run yet (microtask not flushed)
70
+ expect(results).toEqual([0]);
71
+ });
72
+ it("should handle multiple effects on same state", async () => {
73
+ const results1 = [];
74
+ const results2 = [];
75
+ let state;
76
+ function Component() {
77
+ state = useState({ count: 0 });
78
+ useEffect(() => {
79
+ results1.push(state.count);
80
+ });
81
+ useEffect(() => {
82
+ results2.push(state.count * 2);
83
+ });
84
+ return () => _jsx("div", { children: "test" });
85
+ }
86
+ const container = document.createElement("div");
87
+ render(_jsx(Component, {}), container);
88
+ expect(results1).toEqual([0]);
89
+ expect(results2).toEqual([0]);
90
+ state.count = 5;
91
+ await new Promise((resolve) => setTimeout(resolve, 0));
92
+ expect(results1).toEqual([0, 5]);
93
+ expect(results2).toEqual([0, 10]);
94
+ });
95
+ it("should only track dependencies accessed during execution", async () => {
96
+ const effectFn = vi.fn();
97
+ let state;
98
+ function Component() {
99
+ state = useState({ a: 1, b: 2 });
100
+ useEffect(() => {
101
+ effectFn();
102
+ state.a; // Only track 'a'
103
+ });
104
+ return () => _jsx("div", { children: "test" });
105
+ }
106
+ const container = document.createElement("div");
107
+ render(_jsx(Component, {}), container);
108
+ expect(effectFn).toHaveBeenCalledTimes(1);
109
+ // Change 'b' (not tracked)
110
+ state.b = 100;
111
+ await new Promise((resolve) => setTimeout(resolve, 0));
112
+ expect(effectFn).toHaveBeenCalledTimes(1); // Should not re-run
113
+ // Change 'a' (tracked)
114
+ state.a = 10;
115
+ await new Promise((resolve) => setTimeout(resolve, 0));
116
+ expect(effectFn).toHaveBeenCalledTimes(2); // Should re-run
117
+ });
118
+ it("should re-track dependencies on each run", async () => {
119
+ const effectFn = vi.fn();
120
+ let state;
121
+ function Component() {
122
+ state = useState({ useA: true, a: 1, b: 2 });
123
+ useEffect(() => {
124
+ effectFn();
125
+ if (state.useA) {
126
+ state.a;
127
+ }
128
+ else {
129
+ state.b;
130
+ }
131
+ });
132
+ return () => _jsx("div", { children: "test" });
133
+ }
134
+ const container = document.createElement("div");
135
+ render(_jsx(Component, {}), container);
136
+ expect(effectFn).toHaveBeenCalledTimes(1);
137
+ // Change 'a' (currently tracked)
138
+ state.a = 10;
139
+ await new Promise((resolve) => setTimeout(resolve, 0));
140
+ expect(effectFn).toHaveBeenCalledTimes(2);
141
+ // Change 'b' (not tracked)
142
+ state.b = 20;
143
+ await new Promise((resolve) => setTimeout(resolve, 0));
144
+ expect(effectFn).toHaveBeenCalledTimes(2); // No change
145
+ // Switch to using 'b'
146
+ state.useA = false;
147
+ await new Promise((resolve) => setTimeout(resolve, 0));
148
+ expect(effectFn).toHaveBeenCalledTimes(3);
149
+ // Change 'a' (no longer tracked)
150
+ state.a = 100;
151
+ await new Promise((resolve) => setTimeout(resolve, 0));
152
+ expect(effectFn).toHaveBeenCalledTimes(3); // No change
153
+ // Change 'b' (now tracked)
154
+ state.b = 200;
155
+ await new Promise((resolve) => setTimeout(resolve, 0));
156
+ expect(effectFn).toHaveBeenCalledTimes(4);
157
+ });
158
+ it("should handle effects that modify state", async () => {
159
+ let state;
160
+ function Component() {
161
+ state = useState({ input: 1, output: 0 });
162
+ useEffect(() => {
163
+ state.output = state.input * 2;
164
+ });
165
+ return () => _jsx("div", { children: "test" });
166
+ }
167
+ const container = document.createElement("div");
168
+ render(_jsx(Component, {}), container);
169
+ expect(state.output).toBe(2);
170
+ state.input = 5;
171
+ await new Promise((resolve) => setTimeout(resolve, 0));
172
+ expect(state.output).toBe(10);
173
+ });
174
+ it("should handle nested state access", async () => {
175
+ const results = [];
176
+ let state;
177
+ function Component() {
178
+ state = useState({
179
+ user: {
180
+ profile: {
181
+ name: "Alice",
182
+ },
183
+ },
184
+ });
185
+ useEffect(() => {
186
+ results.push(state.user.profile.name);
187
+ });
188
+ return () => _jsx("div", { children: "test" });
189
+ }
190
+ const container = document.createElement("div");
191
+ render(_jsx(Component, {}), container);
192
+ expect(results).toEqual(["Alice"]);
193
+ state.user.profile.name = "Bob";
194
+ await new Promise((resolve) => setTimeout(resolve, 0));
195
+ expect(results).toEqual(["Alice", "Bob"]);
196
+ });
197
+ it("should handle array access", async () => {
198
+ const results = [];
199
+ let state;
200
+ function Component() {
201
+ state = useState({ items: [1, 2, 3] });
202
+ useEffect(() => {
203
+ results.push(state.items.length);
204
+ });
205
+ return () => _jsx("div", { children: "test" });
206
+ }
207
+ const container = document.createElement("div");
208
+ render(_jsx(Component, {}), container);
209
+ expect(results).toEqual([3]);
210
+ state.items.push(4);
211
+ await new Promise((resolve) => setTimeout(resolve, 0));
212
+ expect(results).toEqual([3, 4]);
213
+ state.items.pop();
214
+ await new Promise((resolve) => setTimeout(resolve, 0));
215
+ expect(results).toEqual([3, 4, 3]);
216
+ });
217
+ it("should handle effects accessing array elements", async () => {
218
+ const results = [];
219
+ let state;
220
+ function Component() {
221
+ state = useState({ items: [1, 2, 3] });
222
+ useEffect(() => {
223
+ const sum = state.items.reduce((acc, val) => acc + val, 0);
224
+ results.push(sum);
225
+ });
226
+ return () => _jsx("div", { children: "test" });
227
+ }
228
+ const container = document.createElement("div");
229
+ render(_jsx(Component, {}), container);
230
+ expect(results).toEqual([6]);
231
+ state.items.push(4);
232
+ await new Promise((resolve) => setTimeout(resolve, 0));
233
+ expect(results).toEqual([6, 10]);
234
+ state.items[0] = 10;
235
+ await new Promise((resolve) => setTimeout(resolve, 0));
236
+ expect(results).toEqual([6, 10, 19]);
237
+ });
238
+ it("should batch multiple state changes before re-running", async () => {
239
+ const effectFn = vi.fn();
240
+ let state;
241
+ function Component() {
242
+ state = useState({ a: 1, b: 2 });
243
+ useEffect(() => {
244
+ effectFn();
245
+ state.a;
246
+ state.b;
247
+ });
248
+ return () => _jsx("div", { children: "test" });
249
+ }
250
+ const container = document.createElement("div");
251
+ render(_jsx(Component, {}), container);
252
+ expect(effectFn).toHaveBeenCalledTimes(1);
253
+ // Multiple changes in same turn
254
+ state.a = 10;
255
+ state.b = 20;
256
+ state.a = 15;
257
+ await new Promise((resolve) => setTimeout(resolve, 0));
258
+ // Should only run once for all changes
259
+ expect(effectFn).toHaveBeenCalledTimes(2);
260
+ });
261
+ it("should handle effects with no dependencies", async () => {
262
+ let runCount = 0;
263
+ function Component() {
264
+ useEffect(() => {
265
+ runCount++;
266
+ // No reactive state accessed
267
+ });
268
+ return () => _jsx("div", { children: "test" });
269
+ }
270
+ const container = document.createElement("div");
271
+ render(_jsx(Component, {}), container);
272
+ expect(runCount).toBe(1);
273
+ // Wait to ensure it doesn't run again
274
+ await new Promise((resolve) => setTimeout(resolve, 10));
275
+ expect(runCount).toBe(1);
276
+ });
277
+ it("should handle effects that conditionally access state", async () => {
278
+ const effectFn = vi.fn();
279
+ let state;
280
+ function Component() {
281
+ state = useState({ enabled: true, value: 5 });
282
+ useEffect(() => {
283
+ effectFn();
284
+ if (state.enabled) {
285
+ state.value;
286
+ }
287
+ });
288
+ return () => _jsx("div", { children: "test" });
289
+ }
290
+ const container = document.createElement("div");
291
+ render(_jsx(Component, {}), container);
292
+ expect(effectFn).toHaveBeenCalledTimes(1);
293
+ // Change value (tracked because enabled is true)
294
+ state.value = 10;
295
+ await new Promise((resolve) => setTimeout(resolve, 0));
296
+ expect(effectFn).toHaveBeenCalledTimes(2);
297
+ // Disable
298
+ state.enabled = false;
299
+ await new Promise((resolve) => setTimeout(resolve, 0));
300
+ expect(effectFn).toHaveBeenCalledTimes(3);
301
+ // Change value (not tracked because enabled is false)
302
+ state.value = 20;
303
+ await new Promise((resolve) => setTimeout(resolve, 0));
304
+ expect(effectFn).toHaveBeenCalledTimes(3); // No change
305
+ });
306
+ it("should handle complex dependency graphs", async () => {
307
+ const results = [];
308
+ let state;
309
+ function Component() {
310
+ state = useState({
311
+ multiplier: 2,
312
+ values: [1, 2, 3],
313
+ });
314
+ useEffect(() => {
315
+ const sum = state.values.reduce((acc, val) => acc + val, 0);
316
+ results.push(sum * state.multiplier);
317
+ });
318
+ return () => _jsx("div", { children: "test" });
319
+ }
320
+ const container = document.createElement("div");
321
+ render(_jsx(Component, {}), container);
322
+ expect(results).toEqual([12]); // (1+2+3) * 2
323
+ state.multiplier = 3;
324
+ await new Promise((resolve) => setTimeout(resolve, 0));
325
+ expect(results).toEqual([12, 18]); // (1+2+3) * 3
326
+ state.values.push(4);
327
+ await new Promise((resolve) => setTimeout(resolve, 0));
328
+ expect(results).toEqual([12, 18, 30]); // (1+2+3+4) * 3
329
+ });
330
+ it("should handle effects that run other synchronous code", async () => {
331
+ const sideEffects = [];
332
+ let state;
333
+ function Component() {
334
+ state = useState({ count: 0 });
335
+ useEffect(() => {
336
+ sideEffects.push("effect-start");
337
+ const value = state.count;
338
+ sideEffects.push(`value-${value}`);
339
+ sideEffects.push("effect-end");
340
+ });
341
+ return () => _jsx("div", { children: "test" });
342
+ }
343
+ const container = document.createElement("div");
344
+ render(_jsx(Component, {}), container);
345
+ expect(sideEffects).toEqual(["effect-start", "value-0", "effect-end"]);
346
+ state.count = 1;
347
+ await new Promise((resolve) => setTimeout(resolve, 0));
348
+ expect(sideEffects).toEqual([
349
+ "effect-start",
350
+ "value-0",
351
+ "effect-end",
352
+ "effect-start",
353
+ "value-1",
354
+ "effect-end",
355
+ ]);
356
+ });
357
+ it("should handle rapid state changes", async () => {
358
+ const effectFn = vi.fn();
359
+ let state;
360
+ function Component() {
361
+ state = useState({ count: 0 });
362
+ useEffect(() => {
363
+ effectFn();
364
+ state.count;
365
+ });
366
+ return () => _jsx("div", { children: "test" });
367
+ }
368
+ const container = document.createElement("div");
369
+ render(_jsx(Component, {}), container);
370
+ expect(effectFn).toHaveBeenCalledTimes(1);
371
+ // Rapid changes
372
+ for (let i = 1; i <= 10; i++) {
373
+ state.count = i;
374
+ }
375
+ await new Promise((resolve) => setTimeout(resolve, 0));
376
+ // Should batch all changes into one effect run
377
+ expect(effectFn).toHaveBeenCalledTimes(2);
378
+ });
379
+ it("should access latest state values when effect runs", async () => {
380
+ const results = [];
381
+ let state;
382
+ function Component() {
383
+ state = useState({ count: 0 });
384
+ useEffect(() => {
385
+ results.push(state.count);
386
+ });
387
+ return () => _jsx("div", { children: "test" });
388
+ }
389
+ const container = document.createElement("div");
390
+ render(_jsx(Component, {}), container);
391
+ state.count = 1;
392
+ state.count = 2;
393
+ state.count = 3;
394
+ await new Promise((resolve) => setTimeout(resolve, 0));
395
+ // Should see latest value when effect runs
396
+ expect(results).toEqual([0, 3]);
397
+ });
398
+ it("should call dispose function before re-executing", async () => {
399
+ const disposeCalls = [];
400
+ const effectCalls = [];
401
+ let state;
402
+ function Component() {
403
+ state = useState({ count: 0 });
404
+ useEffect(() => {
405
+ effectCalls.push(state.count);
406
+ return () => {
407
+ // Dispose sees the current state at the time it's called
408
+ disposeCalls.push(state.count);
409
+ };
410
+ });
411
+ return () => _jsx("div", { children: "test" });
412
+ }
413
+ const container = document.createElement("div");
414
+ render(_jsx(Component, {}), container);
415
+ expect(effectCalls).toEqual([0]);
416
+ expect(disposeCalls).toEqual([]);
417
+ state.count = 1;
418
+ await new Promise((resolve) => setTimeout(resolve, 0));
419
+ // Dispose is called after state change, before effect re-runs
420
+ expect(disposeCalls).toEqual([1]);
421
+ expect(effectCalls).toEqual([0, 1]);
422
+ state.count = 2;
423
+ await new Promise((resolve) => setTimeout(resolve, 0));
424
+ expect(disposeCalls).toEqual([1, 2]);
425
+ expect(effectCalls).toEqual([0, 1, 2]);
426
+ });
427
+ it("should handle dispose function with cleanup logic", async () => {
428
+ const subscriptions = [];
429
+ let state;
430
+ function Component() {
431
+ state = useState({ url: "/api/data" });
432
+ useEffect(() => {
433
+ const currentUrl = state.url;
434
+ subscriptions.push(`subscribe:${currentUrl}`);
435
+ return () => {
436
+ subscriptions.push(`unsubscribe:${currentUrl}`);
437
+ };
438
+ });
439
+ return () => _jsx("div", { children: "test" });
440
+ }
441
+ const container = document.createElement("div");
442
+ render(_jsx(Component, {}), container);
443
+ expect(subscriptions).toEqual(["subscribe:/api/data"]);
444
+ state.url = "/api/users";
445
+ await new Promise((resolve) => setTimeout(resolve, 0));
446
+ expect(subscriptions).toEqual([
447
+ "subscribe:/api/data",
448
+ "unsubscribe:/api/data",
449
+ "subscribe:/api/users",
450
+ ]);
451
+ state.url = "/api/posts";
452
+ await new Promise((resolve) => setTimeout(resolve, 0));
453
+ expect(subscriptions).toEqual([
454
+ "subscribe:/api/data",
455
+ "unsubscribe:/api/data",
456
+ "subscribe:/api/users",
457
+ "unsubscribe:/api/users",
458
+ "subscribe:/api/posts",
459
+ ]);
460
+ });
461
+ it("should handle effects without dispose function", async () => {
462
+ const effectCalls = [];
463
+ let state;
464
+ function Component() {
465
+ state = useState({ count: 0 });
466
+ useEffect(() => {
467
+ effectCalls.push(state.count);
468
+ // No dispose function returned
469
+ });
470
+ return () => _jsx("div", { children: "test" });
471
+ }
472
+ const container = document.createElement("div");
473
+ render(_jsx(Component, {}), container);
474
+ expect(effectCalls).toEqual([0]);
475
+ state.count = 1;
476
+ await new Promise((resolve) => setTimeout(resolve, 0));
477
+ expect(effectCalls).toEqual([0, 1]);
478
+ state.count = 2;
479
+ await new Promise((resolve) => setTimeout(resolve, 0));
480
+ expect(effectCalls).toEqual([0, 1, 2]);
481
+ });
482
+ it("should handle dispose function that throws error", async () => {
483
+ const effectCalls = [];
484
+ const consoleErrorSpy = vi
485
+ .spyOn(console, "error")
486
+ .mockImplementation(() => { });
487
+ let state;
488
+ function Component() {
489
+ state = useState({ count: 0 });
490
+ useEffect(() => {
491
+ effectCalls.push(state.count);
492
+ return () => {
493
+ throw new Error("Dispose error");
494
+ };
495
+ });
496
+ return () => _jsx("div", { children: "test" });
497
+ }
498
+ const container = document.createElement("div");
499
+ render(_jsx(Component, {}), container);
500
+ expect(effectCalls).toEqual([0]);
501
+ state.count = 1;
502
+ await new Promise((resolve) => setTimeout(resolve, 0));
503
+ // Effect should still have run despite dispose throwing
504
+ expect(effectCalls).toEqual([0, 1]);
505
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Error in effect dispose function:", expect.any(Error));
506
+ consoleErrorSpy.mockRestore();
507
+ });
508
+ it("should call dispose with latest closure values", async () => {
509
+ const disposeValues = [];
510
+ let state;
511
+ function Component() {
512
+ state = useState({ count: 0 });
513
+ useEffect(() => {
514
+ const capturedCount = state.count;
515
+ return () => {
516
+ disposeValues.push(capturedCount);
517
+ };
518
+ });
519
+ return () => _jsx("div", { children: "test" });
520
+ }
521
+ const container = document.createElement("div");
522
+ render(_jsx(Component, {}), container);
523
+ state.count = 1;
524
+ await new Promise((resolve) => setTimeout(resolve, 0));
525
+ expect(disposeValues).toEqual([0]);
526
+ state.count = 5;
527
+ await new Promise((resolve) => setTimeout(resolve, 0));
528
+ expect(disposeValues).toEqual([0, 1]);
529
+ state.count = 10;
530
+ await new Promise((resolve) => setTimeout(resolve, 0));
531
+ expect(disposeValues).toEqual([0, 1, 5]);
532
+ });
533
+ it("should handle rapid state changes with dispose", async () => {
534
+ const effectFn = vi.fn();
535
+ const disposeFn = vi.fn();
536
+ let state;
537
+ function Component() {
538
+ state = useState({ count: 0 });
539
+ useEffect(() => {
540
+ effectFn();
541
+ state.count;
542
+ return disposeFn;
543
+ });
544
+ return () => _jsx("div", { children: "test" });
545
+ }
546
+ const container = document.createElement("div");
547
+ render(_jsx(Component, {}), container);
548
+ expect(effectFn).toHaveBeenCalledTimes(1);
549
+ expect(disposeFn).toHaveBeenCalledTimes(0);
550
+ // Rapid changes should batch
551
+ state.count = 1;
552
+ state.count = 2;
553
+ state.count = 3;
554
+ await new Promise((resolve) => setTimeout(resolve, 0));
555
+ // Effect and dispose should each be called once more
556
+ expect(effectFn).toHaveBeenCalledTimes(2);
557
+ expect(disposeFn).toHaveBeenCalledTimes(1);
558
+ });
559
+ it("should run effects synchronously before render when props change", async () => {
560
+ const renderLog = [];
561
+ function Child(props) {
562
+ const state = useState({ internalValue: 0 });
563
+ useEffect(() => {
564
+ // Update internal state based on prop
565
+ state.internalValue = props.value * 2;
566
+ });
567
+ return () => {
568
+ renderLog.push(`render:${state.internalValue}`);
569
+ return _jsx("div", { class: "child-component", children: state.internalValue });
570
+ };
571
+ }
572
+ function Parent() {
573
+ const state = useState({ count: 1 });
574
+ return () => {
575
+ renderLog.push(`parent-render:${state.count}`);
576
+ return (_jsxs("div", { children: [_jsx(Child, { value: state.count }), _jsx("button", { onClick: () => {
577
+ state.count = 2;
578
+ }, children: "Increment" })] }));
579
+ };
580
+ }
581
+ const container = document.createElement("div");
582
+ document.body.appendChild(container);
583
+ render(_jsx(Parent, {}), container);
584
+ await new Promise((resolve) => setTimeout(resolve, 10));
585
+ // Initial render: parent renders, then child renders with effect-updated state
586
+ expect(renderLog).toEqual(["parent-render:1", "render:2"]);
587
+ const childDiv = container.querySelector(".child-component");
588
+ expect(childDiv?.textContent).toBe("2");
589
+ // Clear log and trigger update
590
+ renderLog.length = 0;
591
+ const button = container.querySelector("button");
592
+ button.click();
593
+ await new Promise((resolve) => setTimeout(resolve, 10));
594
+ // After prop update: parent renders, child's effect runs synchronously updating state,
595
+ // then child renders once with the updated state
596
+ expect(renderLog).toEqual(["parent-render:2", "render:4"]);
597
+ expect(childDiv?.textContent).toBe("4");
598
+ document.body.removeChild(container);
599
+ });
600
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useLookup.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLookup.test.d.ts","sourceRoot":"","sources":["../../src/tests/useLookup.test.tsx"],"names":[],"mappings":""}