ripple 0.2.115 → 0.2.118

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 (93) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/1-parse/index.js +79 -0
  4. package/src/compiler/phases/3-transform/client/index.js +54 -8
  5. package/src/compiler/phases/3-transform/segments.js +107 -60
  6. package/src/compiler/phases/3-transform/server/index.js +21 -11
  7. package/src/compiler/types/index.d.ts +16 -0
  8. package/src/runtime/index-client.js +19 -185
  9. package/src/runtime/index-server.js +24 -0
  10. package/src/runtime/internal/client/bindings.js +443 -0
  11. package/src/runtime/internal/client/index.js +4 -0
  12. package/src/runtime/internal/client/runtime.js +10 -0
  13. package/src/runtime/internal/client/utils.js +0 -8
  14. package/src/runtime/map.js +11 -1
  15. package/src/runtime/set.js +11 -1
  16. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  17. package/tests/client/_etc.test.ripple +5 -0
  18. package/tests/client/array/array.copy-within.test.ripple +120 -0
  19. package/tests/client/array/array.derived.test.ripple +495 -0
  20. package/tests/client/array/array.iteration.test.ripple +115 -0
  21. package/tests/client/array/array.mutations.test.ripple +385 -0
  22. package/tests/client/array/array.static.test.ripple +237 -0
  23. package/tests/client/array/array.to-methods.test.ripple +93 -0
  24. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  26. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  27. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  28. package/tests/client/basic/basic.collections.test.ripple +94 -0
  29. package/tests/client/basic/basic.components.test.ripple +225 -0
  30. package/tests/client/basic/basic.errors.test.ripple +126 -0
  31. package/tests/client/basic/basic.events.test.ripple +222 -0
  32. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  33. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  34. package/tests/client/basic/basic.styling.test.ripple +63 -0
  35. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  36. package/tests/client/boundaries.test.ripple +2 -21
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  39. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  40. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  41. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  42. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  43. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  44. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  45. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  46. package/tests/client/composite/composite.generics.test.ripple +211 -0
  47. package/tests/client/composite/composite.props.test.ripple +106 -0
  48. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  49. package/tests/client/composite/composite.render.test.ripple +84 -0
  50. package/tests/client/computed-properties.test.ripple +2 -21
  51. package/tests/client/context.test.ripple +5 -22
  52. package/tests/client/date.test.ripple +1 -20
  53. package/tests/client/dynamic-elements.test.ripple +16 -24
  54. package/tests/client/for.test.ripple +4 -23
  55. package/tests/client/head.test.ripple +11 -23
  56. package/tests/client/html.test.ripple +1 -20
  57. package/tests/client/input-value.test.ripple +11 -31
  58. package/tests/client/map.test.ripple +82 -20
  59. package/tests/client/media-query.test.ripple +10 -23
  60. package/tests/client/object.test.ripple +5 -24
  61. package/tests/client/portal.test.ripple +2 -19
  62. package/tests/client/ref.test.ripple +8 -26
  63. package/tests/client/set.test.ripple +84 -22
  64. package/tests/client/svg.test.ripple +1 -22
  65. package/tests/client/switch.test.ripple +6 -25
  66. package/tests/client/tracked-expression.test.ripple +2 -21
  67. package/tests/client/typescript-generics.test.ripple +0 -21
  68. package/tests/client/url/url.derived.test.ripple +83 -0
  69. package/tests/client/url/url.parsing.test.ripple +165 -0
  70. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  71. package/tests/client/url/url.reactivity.test.ripple +449 -0
  72. package/tests/client/url/url.serialization.test.ripple +50 -0
  73. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  74. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  75. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  76. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  77. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  78. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  79. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  80. package/tests/client.d.ts +12 -0
  81. package/tests/server/if.test.ripple +66 -0
  82. package/tests/setup-client.js +28 -0
  83. package/tsconfig.json +4 -2
  84. package/types/index.d.ts +92 -46
  85. package/LICENSE +0 -21
  86. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  87. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  88. package/tests/client/array.test.ripple +0 -1455
  89. package/tests/client/basic.test.ripple +0 -1892
  90. package/tests/client/compiler.test.ripple +0 -541
  91. package/tests/client/composite.test.ripple +0 -692
  92. package/tests/client/url-search-params.test.ripple +0 -912
  93. package/tests/client/url.test.ripple +0 -954
@@ -1,692 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track, TrackedArray, TrackedMap, effect } from 'ripple';
3
-
4
- describe('composite components', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
- it('renders composite components', () => {
24
- component Button({ count }) {
25
- <div>{count}</div>
26
- }
27
-
28
- component App() {
29
- let count = track(0);
30
-
31
- <button onClick={() => @count++}>{'Increment'}</button>
32
- <Button {@count} />
33
- }
34
-
35
- render(App);
36
-
37
- const button = container.querySelector('button');
38
-
39
- button.click();
40
- flushSync();
41
-
42
- expect(container.querySelector('div').textContent).toBe('1');
43
-
44
- button.click();
45
- flushSync();
46
-
47
- expect(container.querySelector('div').textContent).toBe('2');
48
- });
49
-
50
- it('renders composite components with object state', () => {
51
- component Button({ obj }) {
52
- <button class='count2' onClick={() => {
53
- obj.@count++;
54
- }}>{obj.@count}</button>
55
- }
56
-
57
- component App() {
58
- <div>
59
- let obj = {
60
- count: track(0)
61
- };
62
-
63
- <span class='count'>{obj.@count}</span>
64
- <Button obj={obj} />
65
- </div>
66
- }
67
-
68
- render(App);
69
-
70
- const button = container.querySelector('button');
71
-
72
- button.click();
73
- flushSync();
74
-
75
- expect(container.querySelector('.count').textContent).toBe('1');
76
- expect(container.querySelector('.count2').textContent).toBe('1');
77
- });
78
-
79
- it('renders composite components with object state wrapped in an if statement', () => {
80
- component Button({ obj }) {
81
- <button class='count2' onClick={() => {
82
- obj.@count++;
83
- }}>{obj.@count}</button>
84
- }
85
-
86
- component OtherComponent({ obj }) {
87
- <div class='count3'>{obj.@count}</div>
88
- }
89
-
90
- component App() {
91
- <div>
92
- let obj = {
93
- count: track(0)
94
- };
95
-
96
- <span class='count'>{obj.@count}</span>
97
- <span>{' '}</span>
98
- if (obj) {
99
- <Button obj={obj} />
100
- }
101
-
102
- if (obj) {
103
- <OtherComponent obj={obj} />
104
- }
105
- </div>
106
- }
107
-
108
- render(App);
109
-
110
- const button = container.querySelector('button');
111
-
112
- button.click();
113
- flushSync();
114
-
115
- expect(container.querySelector('.count').textContent).toBe('1');
116
- expect(container.querySelector('.count2').textContent).toBe('1');
117
- expect(container.querySelector('.count3').textContent).toBe('1');
118
- });
119
-
120
- it('parents and children have isolated state', () => {
121
- component Button(props) {
122
- let count = track(() => props.count);
123
- <button onClick={() => { @count++; } }>{"child: " + @count}</button>
124
- }
125
-
126
- component App() {
127
- <div>
128
- let count = track(0);
129
-
130
- <button onClick={() => { @count++; } }>{"parent: " + @count}</button>
131
- <Button {@count} />
132
- </div>
133
- }
134
-
135
- render(App);
136
-
137
- const buttons = container.querySelectorAll('button');
138
-
139
- expect(buttons[0].textContent).toBe('parent: 0');
140
- expect(buttons[1].textContent).toBe('child: 0');
141
-
142
- buttons[0].click();
143
- flushSync();
144
-
145
- expect(buttons[0].textContent).toBe('parent: 1');
146
- expect(buttons[1].textContent).toBe('child: 1');
147
-
148
- buttons[1].click();
149
- flushSync();
150
-
151
- expect(buttons[0].textContent).toBe('parent: 1');
152
- expect(buttons[1].textContent).toBe('child: 2');
153
- });
154
-
155
- it('parents and children have isolated connected state (destructured props)', () => {
156
- component Button({count}) {
157
- let local_count = track(() => count);
158
- <button onClick={() => { @local_count++; } }>{"child: " + @local_count}</button>
159
- }
160
-
161
- component App() {
162
- <div>
163
- let count = track(0);
164
-
165
- <button onClick={() => { @count++; } }>{"parent: " + @count}</button>
166
- <Button {@count} />
167
- </div>
168
- }
169
-
170
- render(App);
171
-
172
- const buttons = container.querySelectorAll('button');
173
-
174
- expect(buttons[0].textContent).toBe('parent: 0');
175
- expect(buttons[1].textContent).toBe('child: 0');
176
-
177
- buttons[0].click();
178
- flushSync();
179
-
180
- expect(buttons[0].textContent).toBe('parent: 1');
181
- expect(buttons[1].textContent).toBe('child: 1');
182
-
183
- buttons[1].click();
184
- flushSync();
185
-
186
- expect(buttons[0].textContent).toBe('parent: 1');
187
- expect(buttons[1].textContent).toBe('child: 2');
188
- });
189
-
190
- it('correct handles passing through component props and children', () => {
191
- component Button({ A, B, children }) {
192
- <div>
193
- <A />
194
- <children />
195
- <B />
196
- </div>
197
- }
198
-
199
- component App() {
200
- <Button>
201
- component A() {
202
- <div>{"I am A"}</div>
203
- }
204
- <div>{"other text"}</div>
205
- component B() {
206
- <div>{"I am B"}</div>
207
- }
208
- </Button>
209
- }
210
-
211
- render(App);
212
-
213
- expect(container).toMatchSnapshot();
214
- });
215
-
216
- it('correctly handles default prop values', () => {
217
- component Child({ foo = 456 }) {
218
- <div>{foo}</div>
219
- }
220
-
221
- component App(props) {
222
- let foo = track(123);
223
-
224
- <Child />
225
- <Child {@foo} />
226
- }
227
-
228
- render(App);
229
-
230
- expect(container.querySelectorAll('div')[0].textContent).toBe('456');
231
- expect(container.querySelectorAll('div')[1].textContent).toBe('123');
232
- });
233
-
234
- it('correctly handles default prop values #2', () => {
235
- component Child({ foo = 456 }) {
236
- <div>{foo}</div>
237
- }
238
-
239
- component App(props) {
240
- let foo = 123;
241
-
242
- <Child />
243
- <Child {foo} />
244
- }
245
-
246
- render(App);
247
-
248
- expect(container.querySelectorAll('div')[0].textContent).toBe('456');
249
- expect(container.querySelectorAll('div')[1].textContent).toBe('123');
250
- });
251
-
252
- it('correctly handles no props', () => {
253
- component Child(props) {
254
- <div>{props.@foo}</div>
255
- }
256
-
257
- component App(props) {
258
- let foo = track(123);
259
-
260
- <Child />
261
- <Child {foo} />
262
- }
263
-
264
- render(App);
265
-
266
- expect(container.querySelectorAll('div')[0].textContent).toBe('');
267
- expect(container.querySelectorAll('div')[1].textContent).toBe('123');
268
- });
269
-
270
- it('correctly handles no props #2', () => {
271
- component Child({ foo }) {
272
- <div>{foo}</div>
273
- }
274
-
275
- component App(props) {
276
- let foo = track(123);
277
-
278
- <Child />
279
- <Child {@foo} />
280
- }
281
-
282
- render(App);
283
-
284
- expect(container.querySelectorAll('div')[0].textContent).toBe('');
285
- expect(container.querySelectorAll('div')[1].textContent).toBe('123');
286
- });
287
-
288
- it('handlers generic', () => {
289
- component ArrayTest() {
290
- let items = new TrackedArray<number>();
291
- items.push.apply(items, [1, 2, 3, 4, 5]);
292
-
293
- <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
294
- }
295
-
296
- render(ArrayTest);
297
- });
298
-
299
- it('handles spreading of props', () => {
300
- let logs = [];
301
-
302
- component App() {
303
- const a = track(1);
304
- const b = track(2);
305
- const c = track(3);
306
-
307
- const obj = track(() => ({
308
- @a,
309
- @b,
310
- @c,
311
- }));
312
-
313
- <Child {...@obj} />
314
-
315
- <button onClick={() => { @a++; @b++; @c++; }}>{"Increment all"}</button>
316
- }
317
-
318
- component Child({ a, b, c }) {
319
- effect(() => {
320
- logs.push(`Child effect: ${a}, ${b}, ${c}`);
321
- });
322
-
323
- <div>{a + ' ' + b + ' ' + c}</div>
324
- }
325
-
326
- render(App);
327
- flushSync();
328
-
329
- expect(container.querySelector('div').textContent).toBe('1 2 3');
330
- expect(logs).toEqual(['Child effect: 1, 2, 3']);
331
-
332
- const button = container.querySelector('button');
333
- button.click();
334
- flushSync();
335
-
336
- expect(container.querySelector('div').textContent).toBe('2 3 4');
337
- expect(logs).toEqual(['Child effect: 1, 2, 3', 'Child effect: 2, 3, 4']);
338
- });
339
-
340
- it('handles advanced generic ambiguity and edge cases', () => {
341
- component App() {
342
- // Ambiguous generics vs JSX / less-than parsing scenarios
343
-
344
- // 7. Generic following optional chaining
345
- const maybe = {
346
- factory<T>() {
347
- return {
348
- make<U>() {
349
- return 1;
350
- }
351
- };
352
- }
353
- };
354
- const g = maybe?.factory<number>()?.make<boolean>();
355
-
356
- // 8. Comparison operator (ensure '<' here NOT misparsed as generics)
357
- let x = 10, y = 20;
358
- const h = x < y ? 'lt' : 'ge';
359
-
360
- // 9. Chained comparisons with intervening generics
361
- class Box<T> {
362
- value: T;
363
- constructor(value?: T) {
364
- this.value = value;
365
- }
366
- open<U>() {
367
- return new Box<U>();
368
- }
369
- }
370
- const limit = 100;
371
- const i = new Box<number>().value < limit ? 'ok' : 'no';
372
-
373
- // 10. JSX / Element should still work
374
- <div class="still-works">
375
- <span>{'Test'}</span>
376
- </div>
377
-
378
- // 11. Generic function call vs Element: Identifier followed by generic args
379
- function identity<T>(value: T): T {
380
- return value;
381
- }
382
- const j = identity<number>(42);
383
-
384
- // 12. Member + generic call immediately followed by another call
385
- class Factory {
386
- create<T>() {
387
- return (value: T) => value;
388
- }
389
- }
390
- const factory = new Factory();
391
- const k = factory.create<number>()(123);
392
-
393
- // 13. Multiple generic segments in chain
394
- function foo<T>() {
395
- return {
396
- bar<U>() {
397
- return {
398
- baz<V>() {
399
- return true;
400
- }
401
- };
402
- }
403
- };
404
- }
405
- const l = foo<number>().bar<string>().baz<boolean>();
406
-
407
- // 14. Generic with constraint + default
408
- type Extractor<T extends { id: number } = { id: number }> = (v: T) => number;
409
- const m: Extractor = (v) => v.id;
410
-
411
- // 15. Generic in angle after "new" + trailing call
412
- class Wrapper<T> {
413
- value: T;
414
- constructor() {
415
- this.value = null as unknown as T;
416
- }
417
- unwrap<U>() {
418
- return null as unknown as U;
419
- }
420
- }
421
- const n = new Wrapper<number>().unwrap<string>();
422
-
423
- // 16. Angle brackets inside type assertion vs generic call
424
- function getUnknown(): unknown {
425
- return new Map<string, number>([['a', 1]]);
426
- }
427
- getUnknown.factory = function<T>() {
428
- return {
429
- make<U>() {
430
- return 2;
431
- }
432
- };
433
- };
434
- const raw = getUnknown();
435
- const o = (raw as Map<string, number>).get('a');
436
-
437
- // 17. Generic with comma + trailing less-than comparison on next token
438
- class Pair<T1, T2> {
439
- first: T1;
440
- second: T2;
441
- constructor() {
442
- this.first = null as unknown as T1;
443
- this.second = null as unknown as T2;
444
- }
445
- }
446
- const p = new Pair<number, string>();
447
- const q = 1 < 2 ? p : null;
448
-
449
- // 18. Nested generics with line breaks resembling JSX indentation
450
- interface Node<T> {
451
- value: T;
452
- }
453
- interface Edge<W> {
454
- weight: W;
455
- }
456
- class Graph<N, E> {
457
- nodes: N[];
458
- edges: E[];
459
- constructor() {
460
- this.nodes = [];
461
- this.edges = [];
462
- }
463
- }
464
- const r = new Graph<
465
- Node<string>,
466
- Edge<number>
467
- >();
468
-
469
- // 19. Ternary containing generics in both branches
470
- let flag = true;
471
- const s = flag ? new Box<number>() : new Box<string>();
472
-
473
- // 20. Generic inside template expression
474
- const t = `length=${new TrackedArray<number>().length}`;
475
-
476
- // 21. Optional chaining + generic + property access
477
- const registry = new TrackedMap<string, number>();
478
- const u = registry.get<number>('id')?.toString();
479
-
480
- // 22. Generic call used as callee for another call
481
- function make<T>() {
482
- return (value: T) => value;
483
- }
484
- const v = make<number>()(10);
485
-
486
- // 23. Generic followed by tagged template (ensure not confused with JSX)
487
- function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
488
- return values[0];
489
- }
490
- const tagResult = tagFn<number>`value`;
491
-
492
- // 24. Sequence mixing: (a < b) + generic call in same statement
493
- function compute<T>(x: T, y: T): T {
494
- return y;
495
- }
496
-
497
- const w = (x < y) && compute<number>(x, y);
498
-
499
-
500
- // Additional component focusing on edge crankers
501
-
502
- // 28. Generic after parenthesized new expression
503
- const aa = (new Box<number>()).open<string>();
504
-
505
- // 29. Generic chain right after closing paren of IIFE
506
- class Builder<Kind> {
507
- finalize<Result>() {
508
- return {
509
- result: null as unknown as Result
510
- };
511
- }
512
- }
513
- const builder = new Builder<Number>();
514
- const result = ((function(){ return builder; })() as Builder<Number>).finalize<boolean>();
515
-
516
- // 30. Angle bracket start of conditional expression line
517
- function adjust<T>(value: T): T {
518
- return value;
519
- }
520
- const val =
521
- new Wrapper<number>()
522
- .value < 100
523
- ? adjust<number>(10)
524
- : adjust<number>(20);
525
-
526
-
527
- // 32. Generic with comments inside angle list
528
- class Mapper<Key, Value> {
529
- map: Map<Key, Value>;
530
- constructor() {
531
- this.map = new Map<Key, Value>();
532
- }
533
- }
534
- const gg = new Mapper<
535
- // key type
536
- string,
537
- /* value type */
538
- number
539
- >();
540
-
541
- // 33. Map of generic instance as key
542
- const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
543
- }
544
-
545
- render(App);
546
- });
547
-
548
- it('supports rendering compositie components using <@component> syntax', () => {
549
- component App() {
550
- component basic() {
551
- <div>{'Basic Component'}</div>
552
- }
553
-
554
- const tracked_basic = track(() => basic);
555
-
556
- <@tracked_basic />
557
- }
558
-
559
- render(App);
560
- flushSync();
561
-
562
- expect(container.textContent).toBe('Basic Component');
563
- });
564
-
565
- it('supports rendering compositie components using <object.@component> syntax', () => {
566
- component App() {
567
- component basic() {
568
- <div>{'Basic Component'}</div>
569
- }
570
-
571
- const tracked_basic = track(() => basic);
572
-
573
- const obj = {
574
- tracked_basic,
575
- }
576
-
577
- <obj.@tracked_basic />
578
- }
579
-
580
- render(App);
581
- flushSync();
582
-
583
- expect(container.textContent).toBe('Basic Component');
584
- });
585
-
586
- it('supports rendering compositie components using <@object.@component> syntax', () => {
587
- component App() {
588
- component basic() {
589
- <div>{'Basic Component'}</div>
590
- }
591
-
592
- const tracked_basic = track(() => basic);
593
-
594
- const obj = {
595
- tracked_basic,
596
- }
597
-
598
- const tracked_object = track(obj);
599
-
600
- <@tracked_object.@tracked_basic />
601
- }
602
-
603
- render(App);
604
- flushSync();
605
-
606
- expect(container.textContent).toBe('Basic Component');
607
- });
608
-
609
- it('render simple text as children', () => {
610
- component App() {
611
- let name = 'Click Me';
612
-
613
- <Child
614
- class="my-button"
615
- >{name}</Child>;
616
- }
617
-
618
- component Child({children, ...rest}) {
619
- <button {...rest}><children /></button>
620
- }
621
-
622
- render(App);
623
- expect(container).toMatchSnapshot();
624
- });
625
-
626
- it('handles dynamic component switching', () => {
627
- component Child1() {
628
- <div>{"I am child 1"}</div>
629
- }
630
-
631
- component Child2() {
632
- <div>{"I am child 2"}</div>
633
- }
634
-
635
- component App() {
636
- let thing = track(() => Child1)
637
-
638
- <div id="container">
639
- <@thing />
640
- </div>
641
-
642
- <button onClick={() => @thing = @thing === Child1 ? Child2 : Child1}>{"Change Child"}</button>
643
- }
644
-
645
- render(App);
646
- flushSync();
647
-
648
- expect(container.querySelector('#container').textContent).toBe('I am child 1');
649
-
650
- const button = container.querySelector('button');
651
- button.click();
652
- flushSync();
653
-
654
- expect(container.querySelector('#container').textContent).toBe('I am child 2');
655
-
656
- button.click();
657
- flushSync();
658
-
659
- expect(container.querySelector('#container').textContent).toBe('I am child 1');
660
- });
661
-
662
- it('mutating a tracked value prop should work as intended', () => {
663
- const logs = [];
664
-
665
- component Counter({count}) {
666
- effect(() => {
667
- logs.push(@count);
668
- })
669
-
670
- <button onClick={() => @count = @count + 1}>{'+'}</button>
671
- }
672
-
673
- component App() {
674
- const count = track(0);
675
-
676
- <div>
677
- <Counter count={count} />
678
- </div>
679
- }
680
-
681
- render(App);
682
- flushSync();
683
-
684
- expect(logs).toEqual([0]);
685
-
686
- const button = container.querySelector('button');
687
- button.click();
688
- flushSync();
689
-
690
- expect(logs).toEqual([0, 1]);
691
- })
692
- });