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
@@ -0,0 +1,495 @@
1
+ import { flushSync, track, TrackedArray } from 'ripple';
2
+
3
+ describe('TrackedArray > derived', () => {
4
+ it('handles array methods that return values (map, filter, etc.)', () => {
5
+ component ArrayTest() {
6
+ let items = new TrackedArray(1, 2, 3, 4, 5);
7
+ let doubled = track(() => items.map(x => x * 2));
8
+ let filtered = track(() => items.filter(x => (x % 2) === 0));
9
+ let reduced = track(() => items.reduce((acc, val) => acc + val, 0));
10
+ let includes = track(() => items.includes(3));
11
+
12
+ <button onClick={() => items.push(6)}>{'add item'}</button>
13
+ <pre>{JSON.stringify(@doubled)}</pre>
14
+ <pre>{JSON.stringify(@filtered)}</pre>
15
+ <pre>{@reduced}</pre>
16
+ <pre>{@includes.toString()}</pre>
17
+ }
18
+
19
+ render(ArrayTest);
20
+
21
+ const addButton = container.querySelector('button');
22
+
23
+ // Initial state
24
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8,10]');
25
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,4]');
26
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('15');
27
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('true');
28
+
29
+ // Test reactivity with these methods
30
+ addButton.click();
31
+ flushSync();
32
+
33
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8,10,12]');
34
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,4,6]');
35
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('21');
36
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('true');
37
+ });
38
+
39
+ it('handles concat method with reactivity', () => {
40
+ component ArrayTest() {
41
+ let items = new TrackedArray(1, 2, 3);
42
+ let concatenated = track(() => items.concat([4, 5], 6, [7, 8]));
43
+
44
+ <button onClick={() => items.push(3.5)}>{'add to original'}</button>
45
+ <pre>{JSON.stringify(items)}</pre>
46
+ <pre>{JSON.stringify(@concatenated)}</pre>
47
+ }
48
+
49
+ render(ArrayTest);
50
+
51
+ const addButton = container.querySelector('button');
52
+
53
+ // Initial state
54
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
55
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4,5,6,7,8]');
56
+
57
+ // Test adding to original array
58
+ addButton.click();
59
+ flushSync();
60
+
61
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,3.5]');
62
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,3.5,4,5,6,7,8]');
63
+ });
64
+
65
+ it('handles array slice method with reactivity', () => {
66
+ component ArrayTest() {
67
+ let items = new TrackedArray(1, 2, 3, 4, 5);
68
+ let sliced = track(() => items.slice(1, 4));
69
+
70
+ <button onClick={() => items[2] = 30}>{'change middle'}</button>
71
+ <pre>{JSON.stringify(items)}</pre>
72
+ <pre>{JSON.stringify(@sliced)}</pre>
73
+ }
74
+
75
+ render(ArrayTest);
76
+
77
+ const changeButton = container.querySelector('button');
78
+
79
+ // Initial state
80
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
81
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,3,4]');
82
+
83
+ // Test reactivity with slice
84
+ changeButton.click();
85
+ flushSync();
86
+
87
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,5]');
88
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,30,4]');
89
+ });
90
+
91
+ it('handles find and findIndex methods with reactivity', () => {
92
+ component ArrayTest() {
93
+ let items = new TrackedArray(5, 10, 15, 20, 25);
94
+ let found = track(() => items.find(x => x > 12));
95
+ let foundIndex = track(() => items.findIndex(x => x > 12));
96
+
97
+ <button onClick={() => {
98
+ items[1] = 13;
99
+ items[0] = 6;
100
+ }}>{'update values'}</button>
101
+ <pre>{@found}</pre>
102
+ <pre>{@foundIndex}</pre>
103
+ }
104
+
105
+ render(ArrayTest);
106
+
107
+ const updateButton = container.querySelector('button');
108
+
109
+ // Initial state
110
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
111
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
112
+
113
+ // Test reactivity with find methods
114
+ updateButton.click();
115
+ flushSync();
116
+
117
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('13');
118
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
119
+ });
120
+
121
+ it('handles findLast and findLastIndex methods with reactivity', () => {
122
+ component ArrayTest() {
123
+ let items = new TrackedArray(5, 15, 10, 20, 15);
124
+ let foundLast = track(() => items.findLast(x => x === 15));
125
+ let foundLastIndex = track(() => items.findLastIndex(x => x === 15));
126
+
127
+ <button onClick={() => {
128
+ items[1] = 25;
129
+ items[4] = 15;
130
+ }}>{'update values'}</button>
131
+ <pre>{@foundLast}</pre>
132
+ <pre>{@foundLastIndex}</pre>
133
+ }
134
+
135
+ render(ArrayTest);
136
+
137
+ const updateButton = container.querySelector('button');
138
+
139
+ // Initial state
140
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
141
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
142
+
143
+ // Test reactivity with findLast methods
144
+ updateButton.click();
145
+ flushSync();
146
+
147
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
148
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
149
+ });
150
+
151
+ it('handles every method with reactivity', () => {
152
+ component ArrayTest() {
153
+ let items = new TrackedArray(2, 4, 6, 8);
154
+ let allEven = track(() => items.every(x => x % 2 === 0));
155
+
156
+ <button onClick={() => items.push(3)}>{'add odd'}</button>
157
+ <button onClick={() => {
158
+ items.pop();
159
+ items.push(10);
160
+ }}>{'ensure all even'}</button>
161
+ <pre>{@allEven.toString()}</pre>
162
+ }
163
+
164
+ render(ArrayTest);
165
+
166
+ const addOddButton = container.querySelectorAll('button')[0];
167
+ const makeEvenButton = container.querySelectorAll('button')[1];
168
+
169
+ // Initial state
170
+ expect(container.querySelector('pre').textContent).toBe('true');
171
+
172
+ // Test adding an odd number
173
+ addOddButton.click();
174
+ flushSync();
175
+ expect(container.querySelector('pre').textContent).toBe('false');
176
+
177
+ // Test fixing the array to all even
178
+ makeEvenButton.click();
179
+ flushSync();
180
+ expect(container.querySelector('pre').textContent).toBe('true');
181
+ });
182
+
183
+ it('handles flat method with reactivity', () => {
184
+ component ArrayTest() {
185
+ let items = new TrackedArray([1, 2], [3, 4], 5);
186
+ let flattened = track(() => items.flat());
187
+
188
+ <button onClick={() => items[0] = [6, 7, 8]}>{'change nested'}</button>
189
+ <pre>{JSON.stringify(items)}</pre>
190
+ <pre>{JSON.stringify(@flattened)}</pre>
191
+ }
192
+
193
+ render(ArrayTest);
194
+
195
+ const changeButton = container.querySelector('button');
196
+
197
+ // Initial state
198
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[[1,2],[3,4],5]');
199
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4,5]');
200
+
201
+ // Test changing a nested array
202
+ changeButton.click();
203
+ flushSync();
204
+
205
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[[6,7,8],[3,4],5]');
206
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[6,7,8,3,4,5]');
207
+ });
208
+
209
+ it('handles flatMap method with reactivity', () => {
210
+ component ArrayTest() {
211
+ let items = new TrackedArray(1, 2, 3);
212
+ let flatMapped = track(() => items.flatMap(x => [x, x * 2]));
213
+
214
+ <button onClick={() => items.push(4)}>{'add item'}</button>
215
+ <pre>{JSON.stringify(items)}</pre>
216
+ <pre>{JSON.stringify(@flatMapped)}</pre>
217
+ }
218
+
219
+ render(ArrayTest);
220
+
221
+ const addButton = container.querySelector('button');
222
+
223
+ // Initial state
224
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
225
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,2,4,3,6]');
226
+
227
+ // Test adding an item
228
+ addButton.click();
229
+ flushSync();
230
+
231
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
232
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,2,4,3,6,4,8]');
233
+ });
234
+
235
+ it('handles join method with reactivity', () => {
236
+ component ArrayTest() {
237
+ let items = new TrackedArray('apple', 'banana', 'cherry');
238
+ let joined = track(() => items.join(', '));
239
+
240
+ <button onClick={() => items.push('date')}>{'add item'}</button>
241
+ <pre>{JSON.stringify(items)}</pre>
242
+ <pre>{@joined}</pre>
243
+ }
244
+
245
+ render(ArrayTest);
246
+
247
+ const addButton = container.querySelector('button');
248
+
249
+ // Initial state
250
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["apple","banana","cherry"]');
251
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('apple, banana, cherry');
252
+
253
+ // Test adding an item
254
+ addButton.click();
255
+ flushSync();
256
+
257
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["apple","banana","cherry","date"]');
258
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('apple, banana, cherry, date');
259
+ });
260
+
261
+ it('handles lastIndexOf method with reactivity', () => {
262
+ component ArrayTest() {
263
+ let items = new TrackedArray(1, 2, 3, 2, 1);
264
+ let lastIndex = track(() => items.lastIndexOf(2));
265
+
266
+ <button onClick={() => {
267
+ items.push(2);
268
+ }}>{'add duplicate'}</button>
269
+ <pre>{JSON.stringify(items)}</pre>
270
+ <pre>{@lastIndex}</pre>
271
+ }
272
+
273
+ render(ArrayTest);
274
+
275
+ const addButton = container.querySelector('button');
276
+
277
+ // Initial state
278
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,2,1]');
279
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
280
+
281
+ // Test adding a duplicate
282
+ addButton.click();
283
+ flushSync();
284
+
285
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,2,1,2]');
286
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
287
+ });
288
+
289
+ it('handles reduceRight method with reactivity', () => {
290
+ component ArrayTest() {
291
+ let items = new TrackedArray('a', 'b', 'c');
292
+ let reduced = track(() => items.reduceRight((acc, val) => acc + val, ''));
293
+
294
+ <button onClick={() => items.push('d')}>{'add item'}</button>
295
+ <pre>{JSON.stringify(items)}</pre>
296
+ <pre>{@reduced}</pre>
297
+ }
298
+
299
+ render(ArrayTest);
300
+
301
+ const addButton = container.querySelector('button');
302
+
303
+ // Initial state
304
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
305
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('cba');
306
+
307
+ // Test adding an item
308
+ addButton.click();
309
+ flushSync();
310
+
311
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
312
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('dcba');
313
+ });
314
+
315
+ it('handles some method with reactivity', () => {
316
+ component ArrayTest() {
317
+ let items = new TrackedArray(1, 3, 5, 7);
318
+ let hasEven = track(() => items.some(x => x % 2 === 0));
319
+
320
+ <button onClick={() => items.push(2)}>{'add even'}</button>
321
+ <button onClick={() => {
322
+ items.pop();
323
+ items.push(9);
324
+ }}>{'ensure all odd'}</button>
325
+ <pre>{@hasEven.toString()}</pre>
326
+ }
327
+
328
+ render(ArrayTest);
329
+
330
+ const addEvenButton = container.querySelectorAll('button')[0];
331
+ const makeOddButton = container.querySelectorAll('button')[1];
332
+
333
+ // Initial state
334
+ expect(container.querySelector('pre').textContent).toBe('false');
335
+
336
+ // Test adding an even number
337
+ addEvenButton.click();
338
+ flushSync();
339
+ expect(container.querySelector('pre').textContent).toBe('true');
340
+
341
+ // Test fixing the array to all odd
342
+ makeOddButton.click();
343
+ flushSync();
344
+ expect(container.querySelector('pre').textContent).toBe('false');
345
+ });
346
+
347
+ it('handles toLocaleString method with reactivity', () => {
348
+ component ArrayTest() {
349
+ let items = new TrackedArray(1000, 2000, 3000);
350
+ let localized = track(() => items.toLocaleString('en-US'));
351
+
352
+ <button onClick={() => {items[2] = 4000}}>{'add item'}</button>
353
+ <pre>{JSON.stringify(items)}</pre>
354
+ <pre>{@localized}</pre>
355
+ }
356
+
357
+ render(ArrayTest);
358
+
359
+ const addButton = container.querySelector('button');
360
+
361
+ // Initial state
362
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1000,2000,3000]');
363
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,000,2,000,3,000');
364
+
365
+ // Test adding an item
366
+ addButton.click();
367
+ flushSync();
368
+
369
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1000,2000,4000]');
370
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,000,2,000,4,000');
371
+ });
372
+
373
+ it('handles toString method with reactivity', () => {
374
+ component ArrayTest() {
375
+ let items = new TrackedArray(1, 2, 3);
376
+ let string = track(() => items.toString());
377
+
378
+ <button onClick={() => items.push(4)}>{'add item'}</button>
379
+ <pre>{JSON.stringify(items)}</pre>
380
+ <pre>{@string}</pre>
381
+ }
382
+
383
+ render(ArrayTest);
384
+
385
+ const addButton = container.querySelector('button');
386
+
387
+ // Initial state
388
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
389
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,2,3');
390
+
391
+ // Test adding an item
392
+ addButton.click();
393
+ flushSync();
394
+
395
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
396
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,2,3,4');
397
+ });
398
+
399
+ it('handles with method with reactivity', (context) => {
400
+ if (!('with' in Array.prototype)) {
401
+ context.skip();
402
+ }
403
+
404
+ component ArrayTest() {
405
+ let items = new TrackedArray(1, 2, 3, 4);
406
+ let withReplaced = track(() => items.with(2, 30));
407
+
408
+ <button onClick={() => items[2] = 50}>{'change original'}</button>
409
+ <pre>{JSON.stringify(items)}</pre>
410
+ <pre>{JSON.stringify(@withReplaced)}</pre>
411
+ }
412
+
413
+ render(ArrayTest);
414
+
415
+ const changeButton = container.querySelector('button');
416
+
417
+ // Initial state
418
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
419
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,30,4]');
420
+
421
+ // Test changing the original array
422
+ changeButton.click();
423
+ flushSync();
424
+
425
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,50,4]');
426
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,30,4]');
427
+ });
428
+
429
+ it('handles toJSON method', () => {
430
+ component ArrayTest() {
431
+ let items = new TrackedArray(1, 2, 3);
432
+
433
+ <button onClick={() => items.push(4)}>{'add'}</button>
434
+ <pre>{JSON.stringify(items)}</pre>
435
+ }
436
+
437
+ render(ArrayTest);
438
+
439
+ const addButton = container.querySelector('button');
440
+
441
+ // Initial state - toJSON is implicitly called by JSON.stringify
442
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3]');
443
+
444
+ // Test reactivity with JSON serialization
445
+ addButton.click();
446
+ flushSync();
447
+
448
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3,4]');
449
+ });
450
+
451
+ it('handles at method with reactivity', () => {
452
+ component ArrayTest() {
453
+ let items = new TrackedArray(10, 20, 30, 40, 50);
454
+ let atIndex2 = track(() => items.at(2));
455
+ let atNegative1 = track(() => items.at(-1));
456
+ let atNegative2 = track(() => items.at(-2));
457
+
458
+ <button onClick={() => items[2] = 300}>{'change index 2'}</button>
459
+ <button onClick={() => items[items.length - 1] = 500}>{'change last'}</button>
460
+ <pre>{JSON.stringify(items)}</pre>
461
+ <pre>{@atIndex2}</pre>
462
+ <pre>{@atNegative1}</pre>
463
+ <pre>{@atNegative2}</pre>
464
+ }
465
+
466
+ render(ArrayTest);
467
+
468
+ const changeIndex2Button = container.querySelectorAll('button')[0];
469
+ const changeLastButton = container.querySelectorAll('button')[1];
470
+
471
+ // Initial state
472
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30,40,50]');
473
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('30');
474
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('50');
475
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
476
+
477
+ // Test changing index 2
478
+ changeIndex2Button.click();
479
+ flushSync();
480
+
481
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,300,40,50]');
482
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('300');
483
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('50');
484
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
485
+
486
+ // Test changing last item
487
+ changeLastButton.click();
488
+ flushSync();
489
+
490
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,300,40,500]');
491
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('300');
492
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('500');
493
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
494
+ });
495
+ });
@@ -0,0 +1,115 @@
1
+ import { flushSync, track, effect, untrack, TrackedArray } from 'ripple';
2
+
3
+ describe('TrackedArray > iteration', () => {
4
+ it('handles entries method with reactivity', () => {
5
+ component ArrayTest() {
6
+ let items = new TrackedArray('a', 'b', 'c');
7
+ let entries = track(() => Array.from(items.entries()));
8
+
9
+ <button onClick={() => items.push('d')}>{'add item'}</button>
10
+ <pre>{JSON.stringify(items)}</pre>
11
+ <pre>{JSON.stringify(@entries)}</pre>
12
+ }
13
+
14
+ render(ArrayTest);
15
+
16
+ const addButton = container.querySelector('button');
17
+
18
+ // Initial state
19
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
20
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[[0,"a"],[1,"b"],[2,"c"]]');
21
+
22
+ // Test adding an item
23
+ addButton.click();
24
+ flushSync();
25
+
26
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
27
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[[0,"a"],[1,"b"],[2,"c"],[3,"d"]]');
28
+ });
29
+
30
+ it('handles keys method with reactivity', () => {
31
+ component ArrayTest() {
32
+ let items = new TrackedArray('a', 'b', 'c');
33
+ let keys = track(() => Array.from(items.keys()));
34
+
35
+ <button onClick={() => items.push('d')}>{'add item'}</button>
36
+ <pre>{JSON.stringify(items)}</pre>
37
+ <pre>{JSON.stringify(@keys)}</pre>
38
+ }
39
+
40
+ render(ArrayTest);
41
+
42
+ const addButton = container.querySelector('button');
43
+
44
+ // Initial state
45
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
46
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[0,1,2]');
47
+
48
+ // Test adding an item
49
+ addButton.click();
50
+ flushSync();
51
+
52
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
53
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[0,1,2,3]');
54
+ });
55
+
56
+ it('handles values method with reactivity', () => {
57
+ component ArrayTest() {
58
+ let items = new TrackedArray('a', 'b', 'c');
59
+ let values = track(() => Array.from(items.values()));
60
+
61
+ <button onClick={() => items.push('d')}>{'add item'}</button>
62
+ <pre>{JSON.stringify(items)}</pre>
63
+ <pre>{JSON.stringify(@values)}</pre>
64
+ }
65
+
66
+ render(ArrayTest);
67
+
68
+ const addButton = container.querySelector('button');
69
+
70
+ // Initial state
71
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
72
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('["a","b","c"]');
73
+
74
+ // Test adding an item
75
+ addButton.click();
76
+ flushSync();
77
+
78
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
79
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('["a","b","c","d"]');
80
+ });
81
+
82
+ it('handles Symbol.iterator with reactivity', () => {
83
+ component ArrayTest() {
84
+ let items = new TrackedArray(1, 2, 3);
85
+ let sum = track(0);
86
+
87
+ effect(() => {
88
+ @sum = 0;
89
+ for (const item of items) {
90
+ untrack(() => {
91
+ @sum += item;
92
+ });
93
+ }
94
+ });
95
+
96
+ <button onClick={() => items.push(4)}>{'add item'}</button>
97
+ <pre>{JSON.stringify(items)}</pre>
98
+ <pre>{@sum}</pre>
99
+ }
100
+
101
+ render(ArrayTest);
102
+ flushSync();
103
+
104
+ const addButton = container.querySelectorAll('button')[0];
105
+
106
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
107
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('6');
108
+
109
+ addButton.click();
110
+ flushSync();
111
+
112
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
113
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('10');
114
+ });
115
+ });