ripple 0.2.42 → 0.2.44

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.
@@ -1,13 +1,13 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mount, flushSync, effect, untrack, RippleArray } from 'ripple';
3
+ import { ARRAY_SET_INDEX_AT } from '../src/runtime/internal/client/constants.js';
2
4
 
3
- import { mount, flushSync, RippleArray } from 'ripple';
4
-
5
- describe('array', () => {
5
+ describe('RippleArray', () => {
6
6
  let container;
7
7
 
8
8
  function render(component) {
9
9
  mount(component, {
10
- target: container
10
+ target: container
11
11
  });
12
12
  }
13
13
 
@@ -21,11 +21,11 @@ describe('array', () => {
21
21
  container = null;
22
22
  });
23
23
 
24
- it('handles direct assignment', () => {
24
+ it('handles direct assignment and length tracking', () => {
25
25
  component ArrayTest() {
26
26
  let items = new RippleArray(1, 2, 3);
27
27
 
28
- <button onClick={() => items[items.$length] = items.$length + 1}>{'increment'}</button>
28
+ <button onClick={() => items[items.length] = items.length + 1}>{'increment'}</button>
29
29
 
30
30
  <Child items={items} />
31
31
  }
@@ -35,20 +35,1434 @@ describe('array', () => {
35
35
  <pre>{items.$length}</pre>
36
36
  }
37
37
 
38
+ render(ArrayTest);
39
+
40
+ const button = container.querySelector('button');
41
+
42
+ button.click();
43
+ flushSync();
44
+
45
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
46
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
47
+
48
+ button.click();
49
+ flushSync();
50
+
51
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
52
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
53
+ });
54
+
55
+ it('handles push and pop operations with reactivity', () => {
56
+ component ArrayTest() {
57
+ let items = new RippleArray(1, 2, 3);
58
+ let $lastItem = items[items.$length - 1];
59
+
60
+ <button onClick={() => items.push(4)}>{'push'}</button>
61
+ <button onClick={() => items.pop()}>{'pop'}</button>
62
+ <pre>{JSON.stringify(items)}</pre>
63
+ <pre>{items.$length}</pre>
64
+ <pre>{$lastItem}</pre>
65
+ }
66
+
67
+ render(ArrayTest);
68
+
69
+ const pushButton = container.querySelectorAll('button')[0];
70
+ const popButton = container.querySelectorAll('button')[1];
71
+
72
+ // Initial state
73
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
74
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
75
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
76
+
77
+ // Test push operation
78
+ pushButton.click();
79
+ flushSync();
80
+
81
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
82
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
83
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('4');
84
+
85
+ // Test pop operation
86
+ popButton.click();
87
+ flushSync();
88
+
89
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
90
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
91
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
92
+ });
93
+
94
+ it('handles shift and unshift operations with reactivity', () => {
95
+ component ArrayTest() {
96
+ let items = new RippleArray(2, 3, 4);
97
+ let $firstItem = items[0];
98
+
99
+ <button onClick={() => items.unshift(1)}>{'unshift'}</button>
100
+ <button onClick={() => items.shift()}>{'shift'}</button>
101
+ <pre>{JSON.stringify(items)}</pre>
102
+ <pre>{items.$length}</pre>
103
+ <pre>{$firstItem}</pre>
104
+ }
105
+
38
106
  render(ArrayTest);
39
107
 
40
- const button = container.querySelector('button');
108
+ const unshiftButton = container.querySelectorAll('button')[0];
109
+ const shiftButton = container.querySelectorAll('button')[1];
41
110
 
42
- button.click();
111
+ // Initial state
112
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,3,4]');
113
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
114
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('2');
115
+
116
+ // Test unshift operation
117
+ unshiftButton.click();
43
118
  flushSync();
44
119
 
45
120
  expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
46
121
  expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
122
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
47
123
 
48
- button.click();
124
+ // Test shift operation
125
+ shiftButton.click();
49
126
  flushSync();
50
127
 
128
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,3,4]');
129
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
130
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('2');
131
+ });
132
+
133
+ it('handles splice operation with reactivity', () => {
134
+ component ArrayTest() {
135
+ let items = new RippleArray(1, 2, 3, 4, 5);
136
+ let $middleItem = items[2];
137
+
138
+ <button onClick={() => items.splice(1, 2, 'a', 'b')}>{'splice'}</button>
139
+ <pre>{JSON.stringify(items)}</pre>
140
+ <pre>{items.$length}</pre>
141
+ <pre>{$middleItem}</pre>
142
+ }
143
+
144
+ render(ArrayTest);
145
+
146
+ const spliceButton = container.querySelector('button');
147
+
148
+ // Initial state
51
149
  expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
52
150
  expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
151
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
152
+
153
+ // Test splice operation
154
+ spliceButton.click();
155
+ flushSync();
156
+
157
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,"a","b",4,5]');
158
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
159
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('b');
53
160
  });
54
- });
161
+
162
+ it('handles fill operation with reactivity', () => {
163
+ component ArrayTest() {
164
+ let items = new RippleArray(1, 2, 3, 4, 5);
165
+ let $secondItem = items[1];
166
+
167
+ <button onClick={() => items.fill(0, 1, 4)}>{'fill'}</button>
168
+ <pre>{JSON.stringify(items)}</pre>
169
+ <pre>{$secondItem}</pre>
170
+ }
171
+
172
+ render(ArrayTest);
173
+
174
+ const fillButton = container.querySelector('button');
175
+
176
+ // Initial state
177
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
178
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
179
+
180
+ // Test fill operation
181
+ fillButton.click();
182
+ flushSync();
183
+
184
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,0,0,0,5]');
185
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
186
+ });
187
+
188
+ it('handles reverse operation with reactivity', () => {
189
+ component ArrayTest() {
190
+ let items = new RippleArray(1, 2, 3, 4, 5);
191
+ let $firstItem = items[0];
192
+ let $lastItem = items[4];
193
+
194
+ <button onClick={() => items.reverse()}>{'reverse'}</button>
195
+ <pre>{JSON.stringify(items)}</pre>
196
+ <pre>{$firstItem}</pre>
197
+ <pre>{$lastItem}</pre>
198
+ }
199
+
200
+ render(ArrayTest);
201
+
202
+ const reverseButton = container.querySelector('button');
203
+
204
+ // Initial state
205
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
206
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
207
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
208
+
209
+ // Test reverse operation
210
+ reverseButton.click();
211
+ flushSync();
212
+
213
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,4,3,2,1]');
214
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
215
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
216
+ });
217
+
218
+ it('handles sort operation with reactivity', () => {
219
+ component ArrayTest() {
220
+ let items = new RippleArray(5, 3, 1, 4, 2);
221
+ let $secondItem = items[1];
222
+
223
+ <button onClick={() => items.sort()}>{'sort ascending'}</button>
224
+ <button onClick={() => items.sort((a, b) => b - a)}>{'sort descending'}</button>
225
+ <pre>{JSON.stringify(items)}</pre>
226
+ <pre>{$secondItem}</pre>
227
+ }
228
+
229
+ render(ArrayTest);
230
+
231
+ const sortAscButton = container.querySelectorAll('button')[0];
232
+ const sortDescButton = container.querySelectorAll('button')[1];
233
+
234
+ // Initial state
235
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,3,1,4,2]');
236
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
237
+
238
+ // Test sort ascending
239
+ sortAscButton.click();
240
+ flushSync();
241
+
242
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
243
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
244
+
245
+ // Test sort descending
246
+ sortDescButton.click();
247
+ flushSync();
248
+
249
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,4,3,2,1]');
250
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
251
+ });
252
+
253
+ it('handles array methods that return values (map, filter, etc.)', () => {
254
+ component ArrayTest() {
255
+ let items = new RippleArray(1, 2, 3, 4, 5);
256
+ let $doubled = items.map(x => x * 2);
257
+ let $filtered = items.filter(x => (x % 2) === 0);
258
+ let $reduced = items.reduce((acc, val) => acc + val, 0);
259
+ let $includes = items.includes(3);
260
+
261
+ <button onClick={() => items.push(6)}>{'add item'}</button>
262
+ <pre>{JSON.stringify($doubled)}</pre>
263
+ <pre>{JSON.stringify($filtered)}</pre>
264
+ <pre>{$reduced}</pre>
265
+ <pre>{$includes.toString()}</pre>
266
+ }
267
+
268
+ render(ArrayTest);
269
+
270
+ const addButton = container.querySelector('button');
271
+
272
+ // Initial state
273
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8,10]');
274
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,4]');
275
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('15');
276
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('true');
277
+
278
+ // Test reactivity with these methods
279
+ addButton.click();
280
+ flushSync();
281
+
282
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8,10,12]');
283
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,4,6]');
284
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('21');
285
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('true');
286
+ });
287
+
288
+ it('handles array modification through forEach()', () => {
289
+ component ArrayTest() {
290
+ let items = new RippleArray(1, 2, 3);
291
+
292
+ <button onClick={() => items.forEach((item, i) => items[i] = item * 2)}>{'double all'}</button>
293
+ <pre>{JSON.stringify(items)}</pre>
294
+ }
295
+
296
+ render(ArrayTest);
297
+
298
+ const doubleButton = container.querySelector('button');
299
+
300
+ // Initial state
301
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3]');
302
+
303
+ // Test iteration with side effects
304
+ doubleButton.click();
305
+ flushSync();
306
+
307
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
308
+ });
309
+
310
+ it('handles entries method with reactivity', () => {
311
+ component ArrayTest() {
312
+ let items = new RippleArray('a', 'b', 'c');
313
+ let $entries = Array.from(items.entries());
314
+
315
+ <button onClick={() => items.push('d')}>{'add item'}</button>
316
+ <pre>{JSON.stringify(items)}</pre>
317
+ <pre>{JSON.stringify($entries)}</pre>
318
+ }
319
+
320
+ render(ArrayTest);
321
+
322
+ const addButton = container.querySelector('button');
323
+
324
+ // Initial state
325
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
326
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[[0,"a"],[1,"b"],[2,"c"]]');
327
+
328
+ // Test adding an item
329
+ addButton.click();
330
+ flushSync();
331
+
332
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
333
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[[0,"a"],[1,"b"],[2,"c"],[3,"d"]]');
334
+ });
335
+
336
+ it('handles concat method with reactivity', () => {
337
+ component ArrayTest() {
338
+ let items = new RippleArray(1, 2, 3);
339
+ let $concatenated = items.concat([4, 5], 6, [7, 8]);
340
+
341
+ <button onClick={() => items.push(3.5)}>{'add to original'}</button>
342
+ <pre>{JSON.stringify(items)}</pre>
343
+ <pre>{JSON.stringify($concatenated)}</pre>
344
+ }
345
+
346
+ render(ArrayTest);
347
+
348
+ const addButton = container.querySelector('button');
349
+
350
+ // Initial state
351
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
352
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4,5,6,7,8]');
353
+
354
+ // Test adding to original array
355
+ addButton.click();
356
+ flushSync();
357
+
358
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,3.5]');
359
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,3.5,4,5,6,7,8]');
360
+ });
361
+
362
+ it('handles array modification through iterator', () => {
363
+ component ArrayTest() {
364
+ let items = new RippleArray(1, 2, 3);
365
+
366
+ <button onClick={() => items.forEach((item, i) => items[i] = item * 2)}>{'double all'}</button>
367
+
368
+ for (const item of items) {
369
+ <pre>{item}</pre>
370
+ }
371
+ }
372
+
373
+ render(ArrayTest);
374
+
375
+ const doubleButton = container.querySelector('button');
376
+
377
+ // Initial state
378
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('1');
379
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
380
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
381
+
382
+ // Test iteration with side effects
383
+ doubleButton.click();
384
+ flushSync();
385
+
386
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('2');
387
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
388
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('6');
389
+ });
390
+
391
+ it('handles $length property for reactivity', () => {
392
+ component ArrayTest() {
393
+ let items = new RippleArray(1, 2, 3);
394
+ let $length = items.$length;
395
+
396
+ <button onClick={() => items.$length = 5}>{'expand'}</button>
397
+ <button onClick={() => items.$length = 2}>{'shrink'}</button>
398
+ <pre>{JSON.stringify(items)}</pre>
399
+ <pre>{$length}</pre>
400
+ }
401
+
402
+ render(ArrayTest);
403
+
404
+ const expandButton = container.querySelectorAll('button')[0];
405
+ const shrinkButton = container.querySelectorAll('button')[1];
406
+
407
+ // Initial state
408
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
409
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
410
+
411
+ // Test expand
412
+ expandButton.click();
413
+ flushSync();
414
+
415
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,null,null]');
416
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
417
+
418
+ // Test shrink
419
+ shrinkButton.click();
420
+ flushSync();
421
+
422
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2]');
423
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
424
+ });
425
+
426
+ it('handles static methods - from and of', () => {
427
+ component ArrayTest() {
428
+ let itemsFrom = RippleArray.from([1, 2, 3], x => x * 2);
429
+ let itemsOf = RippleArray.of(4, 5, 6);
430
+
431
+ <button onClick={() => itemsFrom.push(8)}>{'add to from'}</button>
432
+ <button onClick={() => itemsOf.push(7)}>{'add to of'}</button>
433
+ <pre>{JSON.stringify(itemsFrom)}</pre>
434
+ <pre>{JSON.stringify(itemsOf)}</pre>
435
+ }
436
+
437
+ render(ArrayTest);
438
+
439
+ const addFromButton = container.querySelectorAll('button')[0];
440
+ const addOfButton = container.querySelectorAll('button')[1];
441
+
442
+ // Initial state
443
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6]');
444
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,5,6]');
445
+
446
+ // Test adding to from-created array
447
+ addFromButton.click();
448
+ flushSync();
449
+
450
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8]');
451
+
452
+ // Test adding to of-created array
453
+ addOfButton.click();
454
+ flushSync();
455
+
456
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,5,6,7]');
457
+ });
458
+
459
+ it('handles toJSON method', () => {
460
+ component ArrayTest() {
461
+ let items = new RippleArray(1, 2, 3);
462
+
463
+ <button onClick={() => items.push(4)}>{'add'}</button>
464
+ <pre>{JSON.stringify(items)}</pre>
465
+ }
466
+
467
+ render(ArrayTest);
468
+
469
+ const addButton = container.querySelector('button');
470
+
471
+ // Initial state - toJSON is implicitly called by JSON.stringify
472
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3]');
473
+
474
+ // Test reactivity with JSON serialization
475
+ addButton.click();
476
+ flushSync();
477
+
478
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3,4]');
479
+ });
480
+
481
+ it('handles array index access with reactivity', () => {
482
+ component ArrayTest() {
483
+ let items = new RippleArray(10, 20, 30);
484
+ let $firstItem = items[0];
485
+ let $secondItem = items[1];
486
+
487
+ <button onClick={() => items[0] = 100}>{'change first'}</button>
488
+ <pre>{$firstItem}</pre>
489
+ <pre>{$secondItem}</pre>
490
+ <pre>{items[0]}</pre>
491
+ <pre>{items[1]}</pre>
492
+ }
493
+
494
+ render(ArrayTest);
495
+
496
+ const changeButton = container.querySelector('button');
497
+
498
+ // Initial state
499
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('10');
500
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('20');
501
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('10');
502
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('20');
503
+
504
+ // Test changing array element directly
505
+ changeButton.click();
506
+ flushSync();
507
+
508
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('100');
509
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('20');
510
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('100');
511
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('20');
512
+ });
513
+
514
+ it('handles array slice method with reactivity', () => {
515
+ component ArrayTest() {
516
+ let items = new RippleArray(1, 2, 3, 4, 5);
517
+ let $sliced = items.slice(1, 4);
518
+
519
+ <button onClick={() => items[2] = 30}>{'change middle'}</button>
520
+ <pre>{JSON.stringify(items)}</pre>
521
+ <pre>{JSON.stringify($sliced)}</pre>
522
+ }
523
+
524
+ render(ArrayTest);
525
+
526
+ const changeButton = container.querySelector('button');
527
+
528
+ // Initial state
529
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
530
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,3,4]');
531
+
532
+ // Test reactivity with slice
533
+ changeButton.click();
534
+ flushSync();
535
+
536
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,5]');
537
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[2,30,4]');
538
+ });
539
+
540
+ it('handles find and findIndex methods with reactivity', () => {
541
+ component ArrayTest() {
542
+ let items = new RippleArray(5, 10, 15, 20, 25);
543
+ let $found = items.find(x => x > 12);
544
+ let $foundIndex = items.findIndex(x => x > 12);
545
+
546
+ <button onClick={() => {
547
+ items[1] = 13;
548
+ items[0] = 6;
549
+ }}>{'update values'}</button>
550
+ <pre>{$found}</pre>
551
+ <pre>{$foundIndex}</pre>
552
+ }
553
+
554
+ render(ArrayTest);
555
+
556
+ const updateButton = container.querySelector('button');
557
+
558
+ // Initial state
559
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
560
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
561
+
562
+ // Test reactivity with find methods
563
+ updateButton.click();
564
+ flushSync();
565
+
566
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('13');
567
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
568
+ });
569
+
570
+ // Additional tests for RippleArray methods
571
+
572
+ it('handles findLast and findLastIndex methods with reactivity', () => {
573
+ component ArrayTest() {
574
+ let items = new RippleArray(5, 15, 10, 20, 15);
575
+ let $foundLast = items.findLast(x => x === 15);
576
+ let $foundLastIndex = items.findLastIndex(x => x === 15);
577
+
578
+ <button onClick={() => {
579
+ items[1] = 25;
580
+ items[4] = 15;
581
+ }}>{'update values'}</button>
582
+ <pre>{$foundLast}</pre>
583
+ <pre>{$foundLastIndex}</pre>
584
+ }
585
+
586
+ render(ArrayTest);
587
+
588
+ const updateButton = container.querySelector('button');
589
+
590
+ // Initial state
591
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
592
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
593
+
594
+ // Test reactivity with findLast methods
595
+ updateButton.click();
596
+ flushSync();
597
+
598
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('15');
599
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
600
+ });
601
+
602
+ it('handles every method with reactivity', () => {
603
+ component ArrayTest() {
604
+ let items = new RippleArray(2, 4, 6, 8);
605
+ let $allEven = items.every(x => x % 2 === 0);
606
+
607
+ <button onClick={() => items.push(3)}>{'add odd'}</button>
608
+ <button onClick={() => {
609
+ items.pop();
610
+ items.push(10);
611
+ }}>{'ensure all even'}</button>
612
+ <pre>{$allEven.toString()}</pre>
613
+ }
614
+
615
+ render(ArrayTest);
616
+
617
+ const addOddButton = container.querySelectorAll('button')[0];
618
+ const makeEvenButton = container.querySelectorAll('button')[1];
619
+
620
+ // Initial state
621
+ expect(container.querySelector('pre').textContent).toBe('true');
622
+
623
+ // Test adding an odd number
624
+ addOddButton.click();
625
+ flushSync();
626
+ expect(container.querySelector('pre').textContent).toBe('false');
627
+
628
+ // Test fixing the array to all even
629
+ makeEvenButton.click();
630
+ flushSync();
631
+ expect(container.querySelector('pre').textContent).toBe('true');
632
+ });
633
+
634
+ it('handles flat method with reactivity', () => {
635
+ component ArrayTest() {
636
+ let items = new RippleArray([1, 2], [3, 4], 5);
637
+ let $flattened = items.flat();
638
+
639
+ <button onClick={() => items[0] = [6, 7, 8]}>{'change nested'}</button>
640
+ <pre>{JSON.stringify(items)}</pre>
641
+ <pre>{JSON.stringify($flattened)}</pre>
642
+ }
643
+
644
+ render(ArrayTest);
645
+
646
+ const changeButton = container.querySelector('button');
647
+
648
+ // Initial state
649
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[[1,2],[3,4],5]');
650
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4,5]');
651
+
652
+ // Test changing a nested array
653
+ changeButton.click();
654
+ flushSync();
655
+
656
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[[6,7,8],[3,4],5]');
657
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[6,7,8,3,4,5]');
658
+ });
659
+
660
+ it('handles flatMap method with reactivity', () => {
661
+ component ArrayTest() {
662
+ let items = new RippleArray(1, 2, 3);
663
+ let $flatMapped = items.flatMap(x => [x, x * 2]);
664
+
665
+ <button onClick={() => items.push(4)}>{'add item'}</button>
666
+ <pre>{JSON.stringify(items)}</pre>
667
+ <pre>{JSON.stringify($flatMapped)}</pre>
668
+ }
669
+
670
+ render(ArrayTest);
671
+
672
+ const addButton = container.querySelector('button');
673
+
674
+ // Initial state
675
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
676
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,2,4,3,6]');
677
+
678
+ // Test adding an item
679
+ addButton.click();
680
+ flushSync();
681
+
682
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
683
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,2,4,3,6,4,8]');
684
+ });
685
+
686
+ it('handles join method with reactivity', () => {
687
+ component ArrayTest() {
688
+ let items = new RippleArray('apple', 'banana', 'cherry');
689
+ let $joined = items.join(', ');
690
+
691
+ <button onClick={() => items.push('date')}>{'add item'}</button>
692
+ <pre>{JSON.stringify(items)}</pre>
693
+ <pre>{$joined}</pre>
694
+ }
695
+
696
+ render(ArrayTest);
697
+
698
+ const addButton = container.querySelector('button');
699
+
700
+ // Initial state
701
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["apple","banana","cherry"]');
702
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('apple, banana, cherry');
703
+
704
+ // Test adding an item
705
+ addButton.click();
706
+ flushSync();
707
+
708
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["apple","banana","cherry","date"]');
709
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('apple, banana, cherry, date');
710
+ });
711
+
712
+ it('handles keys method with reactivity', () => {
713
+ component ArrayTest() {
714
+ let items = new RippleArray('a', 'b', 'c');
715
+ let $keys = Array.from(items.keys());
716
+
717
+ <button onClick={() => items.push('d')}>{'add item'}</button>
718
+ <pre>{JSON.stringify(items)}</pre>
719
+ <pre>{JSON.stringify($keys)}</pre>
720
+ }
721
+
722
+ render(ArrayTest);
723
+
724
+ const addButton = container.querySelector('button');
725
+
726
+ // Initial state
727
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
728
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[0,1,2]');
729
+
730
+ // Test adding an item
731
+ addButton.click();
732
+ flushSync();
733
+
734
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
735
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[0,1,2,3]');
736
+ });
737
+
738
+ it('handles lastIndexOf method with reactivity', () => {
739
+ component ArrayTest() {
740
+ let items = new RippleArray(1, 2, 3, 2, 1);
741
+ let $lastIndex = items.lastIndexOf(2);
742
+
743
+ <button onClick={() => {
744
+ items.push(2);
745
+ }}>{'add duplicate'}</button>
746
+ <pre>{JSON.stringify(items)}</pre>
747
+ <pre>{$lastIndex}</pre>
748
+ }
749
+
750
+ render(ArrayTest);
751
+
752
+ const addButton = container.querySelector('button');
753
+
754
+ // Initial state
755
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,2,1]');
756
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
757
+
758
+ // Test adding a duplicate
759
+ addButton.click();
760
+ flushSync();
761
+
762
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,2,1,2]');
763
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
764
+ });
765
+
766
+ it('handles reduceRight method with reactivity', () => {
767
+ component ArrayTest() {
768
+ let items = new RippleArray('a', 'b', 'c');
769
+ let $reduced = items.reduceRight((acc, val) => acc + val, '');
770
+
771
+ <button onClick={() => items.push('d')}>{'add item'}</button>
772
+ <pre>{JSON.stringify(items)}</pre>
773
+ <pre>{$reduced}</pre>
774
+ }
775
+
776
+ render(ArrayTest);
777
+
778
+ const addButton = container.querySelector('button');
779
+
780
+ // Initial state
781
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
782
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('cba');
783
+
784
+ // Test adding an item
785
+ addButton.click();
786
+ flushSync();
787
+
788
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
789
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('dcba');
790
+ });
791
+
792
+ it('handles some method with reactivity', () => {
793
+ component ArrayTest() {
794
+ let items = new RippleArray(1, 3, 5, 7);
795
+ let $hasEven = items.some(x => x % 2 === 0);
796
+
797
+ <button onClick={() => items.push(2)}>{'add even'}</button>
798
+ <button onClick={() => {
799
+ items.pop();
800
+ items.push(9);
801
+ }}>{'ensure all odd'}</button>
802
+ <pre>{$hasEven.toString()}</pre>
803
+ }
804
+
805
+ render(ArrayTest);
806
+
807
+ const addEvenButton = container.querySelectorAll('button')[0];
808
+ const makeOddButton = container.querySelectorAll('button')[1];
809
+
810
+ // Initial state
811
+ expect(container.querySelector('pre').textContent).toBe('false');
812
+
813
+ // Test adding an even number
814
+ addEvenButton.click();
815
+ flushSync();
816
+ expect(container.querySelector('pre').textContent).toBe('true');
817
+
818
+ // Test fixing the array to all odd
819
+ makeOddButton.click();
820
+ flushSync();
821
+ expect(container.querySelector('pre').textContent).toBe('false');
822
+ });
823
+
824
+ it('handles toLocaleString method with reactivity', () => {
825
+ component ArrayTest() {
826
+ let items = new RippleArray(1000, 2000, 3000);
827
+ let $localized = items.toLocaleString('en-US');
828
+
829
+ <button onClick={() => {items[2] = 4000}}>{'add item'}</button>
830
+ <pre>{JSON.stringify(items)}</pre>
831
+ <pre>{$localized}</pre>
832
+ }
833
+
834
+ render(ArrayTest);
835
+
836
+ const addButton = container.querySelector('button');
837
+
838
+ // Initial state
839
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1000,2000,3000]');
840
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,000,2,000,3,000');
841
+
842
+ // Test adding an item
843
+ addButton.click();
844
+ flushSync();
845
+
846
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1000,2000,4000]');
847
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,000,2,000,4,000');
848
+ });
849
+
850
+ it('handles toReversed method with reactivity', (context) => {
851
+ if (!('toReversed' in Array.prototype)) {
852
+ context.skip();
853
+ }
854
+
855
+ component ArrayTest() {
856
+ let items = new RippleArray(1, 2, 3, 4);
857
+ let $reversed = items.toReversed();
858
+
859
+ <button onClick={() => items.push(5)}>{'add item'}</button>
860
+ <pre>{JSON.stringify(items)}</pre>
861
+ <pre>{JSON.stringify($reversed)}</pre>
862
+ }
863
+
864
+ render(ArrayTest);
865
+
866
+ const addButton = container.querySelector('button');
867
+
868
+ // Initial state
869
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
870
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,3,2,1]');
871
+
872
+ // Test adding an item
873
+ addButton.click();
874
+ flushSync();
875
+
876
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
877
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[5,4,3,2,1]');
878
+ });
879
+
880
+ it('handles toSorted method with reactivity', () => {
881
+ if (!('toSorted' in Array.prototype)) {
882
+ context.skip();
883
+ }
884
+
885
+ component ArrayTest() {
886
+ let items = new RippleArray(3, 1, 4, 2);
887
+ let $sorted = items.toSorted();
888
+
889
+ <button onClick={() => items.push(0)}>{'add item'}</button>
890
+ <pre>{JSON.stringify(items)}</pre>
891
+ <pre>{JSON.stringify($sorted)}</pre>
892
+ }
893
+
894
+ render(ArrayTest);
895
+
896
+ const addButton = container.querySelector('button');
897
+
898
+ // Initial state
899
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[3,1,4,2]');
900
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4]');
901
+
902
+ // Test adding an item
903
+ addButton.click();
904
+ flushSync();
905
+
906
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[3,1,4,2,0]');
907
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[0,1,2,3,4]');
908
+ });
909
+
910
+ it('handles toSpliced method with reactivity', () => {
911
+ if (!('toSpliced' in Array.prototype)) {
912
+ context.skip();
913
+ }
914
+
915
+ component ArrayTest() {
916
+ let items = new RippleArray(1, 2, 3, 4, 5);
917
+ let $spliced = items.toSpliced(1, 2, 'a', 'b');
918
+
919
+ <button onClick={() => items[2] = 30}>{'change item'}</button>
920
+ <pre>{JSON.stringify(items)}</pre>
921
+ <pre>{JSON.stringify($spliced)}</pre>
922
+ }
923
+
924
+ render(ArrayTest);
925
+
926
+ const changeButton = container.querySelector('button');
927
+
928
+ // Initial state
929
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
930
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,"a","b",4,5]');
931
+
932
+ // Test changing an item
933
+ changeButton.click();
934
+ flushSync();
935
+
936
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,5]');
937
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,"a","b",4,5]');
938
+ });
939
+
940
+ it('handles toString method with reactivity', () => {
941
+ component ArrayTest() {
942
+ let items = new RippleArray(1, 2, 3);
943
+ let $string = items.toString();
944
+
945
+ <button onClick={() => items.push(4)}>{'add item'}</button>
946
+ <pre>{JSON.stringify(items)}</pre>
947
+ <pre>{$string}</pre>
948
+ }
949
+
950
+ render(ArrayTest);
951
+
952
+ const addButton = container.querySelector('button');
953
+
954
+ // Initial state
955
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
956
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,2,3');
957
+
958
+ // Test adding an item
959
+ addButton.click();
960
+ flushSync();
961
+
962
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
963
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1,2,3,4');
964
+ });
965
+
966
+ it('handles Symbol.iterator with reactivity', () => {
967
+ component ArrayTest() {
968
+ let items = new RippleArray(1, 2, 3);
969
+ let $sum = 0;
970
+
971
+ effect(() => {
972
+ $sum = 0;
973
+ for (const item of items) {
974
+ untrack(() => {
975
+ $sum += item;
976
+ });
977
+ }
978
+ });
979
+
980
+ <button onClick={() => items.push(4)}>{'add item'}</button>
981
+ <pre>{JSON.stringify(items)}</pre>
982
+ <pre>{$sum}</pre>
983
+ }
984
+
985
+ render(ArrayTest);
986
+ flushSync();
987
+
988
+ const addButton = container.querySelectorAll('button')[0];
989
+
990
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
991
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('6');
992
+
993
+ addButton.click();
994
+ flushSync();
995
+
996
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
997
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('10');
998
+ });
999
+
1000
+ it('handles values method with reactivity', () => {
1001
+ component ArrayTest() {
1002
+ let items = new RippleArray('a', 'b', 'c');
1003
+ let $values = Array.from(items.values());
1004
+
1005
+ <button onClick={() => items.push('d')}>{'add item'}</button>
1006
+ <pre>{JSON.stringify(items)}</pre>
1007
+ <pre>{JSON.stringify($values)}</pre>
1008
+ }
1009
+
1010
+ render(ArrayTest);
1011
+
1012
+ const addButton = container.querySelector('button');
1013
+
1014
+ // Initial state
1015
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c"]');
1016
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('["a","b","c"]');
1017
+
1018
+ // Test adding an item
1019
+ addButton.click();
1020
+ flushSync();
1021
+
1022
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["a","b","c","d"]');
1023
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('["a","b","c","d"]');
1024
+ });
1025
+
1026
+ it('handles with method with reactivity', (context) => {
1027
+ if (!('with' in Array.prototype)) {
1028
+ context.skip();
1029
+ }
1030
+
1031
+ component ArrayTest() {
1032
+ let items = new RippleArray(1, 2, 3, 4);
1033
+ let $withReplaced = items.with(2, 30);
1034
+
1035
+ <button onClick={() => items[2] = 50}>{'change original'}</button>
1036
+ <pre>{JSON.stringify(items)}</pre>
1037
+ <pre>{JSON.stringify($withReplaced)}</pre>
1038
+ }
1039
+
1040
+ render(ArrayTest);
1041
+
1042
+ const changeButton = container.querySelector('button');
1043
+
1044
+ // Initial state
1045
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
1046
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,30,4]');
1047
+
1048
+ // Test changing the original array
1049
+ changeButton.click();
1050
+ flushSync();
1051
+
1052
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,50,4]');
1053
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,30,4]');
1054
+ });
1055
+
1056
+ it('handles at method with reactivity', () => {
1057
+ component ArrayTest() {
1058
+ let items = new RippleArray(10, 20, 30, 40, 50);
1059
+ let $atIndex2 = items.at(2);
1060
+ let $atNegative1 = items.at(-1);
1061
+ let $atNegative2 = items.at(-2);
1062
+
1063
+ <button onClick={() => items[2] = 300}>{'change index 2'}</button>
1064
+ <button onClick={() => items[items.length - 1] = 500}>{'change last'}</button>
1065
+ <pre>{JSON.stringify(items)}</pre>
1066
+ <pre>{$atIndex2}</pre>
1067
+ <pre>{$atNegative1}</pre>
1068
+ <pre>{$atNegative2}</pre>
1069
+ }
1070
+
1071
+ render(ArrayTest);
1072
+
1073
+ const changeIndex2Button = container.querySelectorAll('button')[0];
1074
+ const changeLastButton = container.querySelectorAll('button')[1];
1075
+
1076
+ // Initial state
1077
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30,40,50]');
1078
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('30');
1079
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('50');
1080
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
1081
+
1082
+ // Test changing index 2
1083
+ changeIndex2Button.click();
1084
+ flushSync();
1085
+
1086
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,300,40,50]');
1087
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('300');
1088
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('50');
1089
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
1090
+
1091
+ // Test changing last item
1092
+ changeLastButton.click();
1093
+ flushSync();
1094
+
1095
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,300,40,500]');
1096
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('300');
1097
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('500');
1098
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('40');
1099
+ });
1100
+
1101
+ it('handles ARRAY_SET_INDEX_AT method with reactivity', () => {
1102
+ component ArrayTest() {
1103
+ let items = new RippleArray(1, 2, 3, 4, 5);
1104
+ let $thirdItem = items[2];
1105
+
1106
+ <button onClick={() => items[ARRAY_SET_INDEX_AT](2, 30)}>{'set index 2'}</button>
1107
+ <button onClick={() => items[ARRAY_SET_INDEX_AT](-1, 50)}>{'set last with negative'}</button>
1108
+ <button onClick={() => items[ARRAY_SET_INDEX_AT](6, 60)}>{'extend array'}</button>
1109
+ <pre>{JSON.stringify(items)}</pre>
1110
+ <pre>{items.$length}</pre>
1111
+ <pre>{$thirdItem}</pre>
1112
+ }
1113
+
1114
+ render(ArrayTest);
1115
+
1116
+ const setIndex2Button = container.querySelectorAll('button')[0];
1117
+ const setLastButton = container.querySelectorAll('button')[1];
1118
+ const extendButton = container.querySelectorAll('button')[2];
1119
+
1120
+ // Initial state
1121
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
1122
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
1123
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
1124
+
1125
+ // Test setting index 2
1126
+ setIndex2Button.click();
1127
+ flushSync();
1128
+
1129
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,5]');
1130
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
1131
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('30');
1132
+
1133
+ // Test setting with negative index
1134
+ setLastButton.click();
1135
+ flushSync();
1136
+
1137
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,50]');
1138
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
1139
+
1140
+ // Test extending array
1141
+ extendButton.click();
1142
+ flushSync();
1143
+
1144
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,30,4,50,null,60]');
1145
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('7');
1146
+ });
1147
+
1148
+ it('handles ARRAY_SET_INDEX_AT error cases', () => {
1149
+ component ArrayTest() {
1150
+ let items = new RippleArray(1, 2, 3);
1151
+ let $error = null;
1152
+
1153
+ <button onClick={() => {
1154
+ try {
1155
+ items[ARRAY_SET_INDEX_AT](1.5, 10);
1156
+ } catch (e) {
1157
+ $error = e.message;
1158
+ }
1159
+ }}>{'try non-integer'}</button>
1160
+ <button onClick={() => {
1161
+ try {
1162
+ $error = null;
1163
+ items[ARRAY_SET_INDEX_AT](-5, 10);
1164
+ } catch (e) {
1165
+ $error = e.message;
1166
+ }
1167
+ }}>{'try out of bounds negative'}</button>
1168
+ <pre>{$error}</pre>
1169
+ }
1170
+
1171
+ render(ArrayTest);
1172
+
1173
+ const nonIntegerButton = container.querySelectorAll('button')[0];
1174
+ const outOfBoundsButton = container.querySelectorAll('button')[1];
1175
+
1176
+ // Test non-integer index
1177
+ nonIntegerButton.click();
1178
+ flushSync();
1179
+
1180
+ expect(container.querySelector('pre').textContent).toBe('Provided index must be a valid integer');
1181
+
1182
+ // Test out of bounds negative index
1183
+ outOfBoundsButton.click();
1184
+ flushSync();
1185
+
1186
+ expect(container.querySelector('pre').textContent).toBe('Provided negative index out of bounds');
1187
+ });
1188
+
1189
+ it('handles setting $length property and resizing the array', () => {
1190
+ component ArrayTest() {
1191
+ let items = new RippleArray(1, 2, 3, 4, 5);
1192
+
1193
+ <button onClick={() => items.$length = 3}>{'truncate'}</button>
1194
+ <button onClick={() => items.$length = 7}>{'expand'}</button>
1195
+ <pre>{JSON.stringify(items)}</pre>
1196
+ <pre>{items.$length}</pre>
1197
+ }
1198
+
1199
+ render(ArrayTest);
1200
+
1201
+ const truncateButton = container.querySelectorAll('button')[0];
1202
+ const expandButton = container.querySelectorAll('button')[1];
1203
+
1204
+ // Initial state
1205
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
1206
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
1207
+
1208
+ // Test truncating
1209
+ truncateButton.click();
1210
+ flushSync();
1211
+
1212
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
1213
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
1214
+
1215
+ // Test expanding
1216
+ expandButton.click();
1217
+ flushSync();
1218
+
1219
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,null,null,null,null]');
1220
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('7');
1221
+ });
1222
+
1223
+ it('throws error when trying to set length property directly', () => {
1224
+ component ArrayTest() {
1225
+ let items = new RippleArray(1, 2, 3);
1226
+ let $error = null;
1227
+
1228
+ <button onClick={() => {
1229
+ try {
1230
+ items.length = 5;
1231
+ } catch (e) {
1232
+ $error = e.message;
1233
+ }
1234
+ }}>{'try set length'}</button>
1235
+ <pre>{$error}</pre>
1236
+ }
1237
+
1238
+ render(ArrayTest);
1239
+
1240
+ const button = container.querySelector('button');
1241
+ button.click();
1242
+ flushSync();
1243
+
1244
+ expect(container.querySelector('pre').textContent).toBe('Cannot set length on RippleArray, use $length instead');
1245
+ });
1246
+
1247
+ ('fromAsync' in Array.prototype ? describe : describe.skip)('RippleArray fromAsync', () => {
1248
+ it('handles static fromAsync method with reactivity', async () => {
1249
+ component ArrayTest() {
1250
+ let itemsPromise = RippleArray.fromAsync(Promise.resolve([1, 2, 3]));
1251
+ let items = null;
1252
+ let error = null;
1253
+
1254
+ try {
1255
+ items = await itemsPromise;
1256
+ } catch (e) {
1257
+ error = e.message;
1258
+ }
1259
+
1260
+ <button onClick={() => {
1261
+ if (items) items.push(4);
1262
+ }}>{'add item'}</button>
1263
+ <pre>{error ? 'Error: ' + error : 'Loaded'}</pre>
1264
+ <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
1265
+ }
1266
+
1267
+ render(ArrayTest);
1268
+
1269
+ // Wait for promise to resolve
1270
+ await new Promise(resolve => setTimeout(resolve, 50));
1271
+ flushSync();
1272
+
1273
+ const addButton = container.querySelector('button');
1274
+
1275
+ // Check that the promise resolved correctly
1276
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('Loaded');
1277
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3]');
1278
+
1279
+ // Test adding an item to the async-created array
1280
+ addButton.click();
1281
+ flushSync();
1282
+
1283
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4]');
1284
+ });
1285
+
1286
+ it('handles static fromAsync method with mapping function', async () => {
1287
+ component ArrayTest() {
1288
+ let itemsPromise = RippleArray.fromAsync(
1289
+ Promise.resolve([1, 2, 3]),
1290
+ x => x * 2
1291
+ );
1292
+ let items = null;
1293
+
1294
+ items = await itemsPromise;
1295
+
1296
+ <button onClick={() => {
1297
+ if (items) items.push(8);
1298
+ }}>{'add item'}</button>
1299
+ <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
1300
+ }
1301
+
1302
+ render(ArrayTest);
1303
+
1304
+ // Wait for promise to resolve
1305
+ await new Promise(resolve => setTimeout(resolve, 50));
1306
+ flushSync();
1307
+
1308
+ const addButton = container.querySelector('button');
1309
+
1310
+ // Check that the promise resolved correctly with mapping applied
1311
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
1312
+
1313
+ // Test adding an item to the async-created array
1314
+ addButton.click();
1315
+ flushSync();
1316
+
1317
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6,8]');
1318
+ });
1319
+
1320
+ it('handles error in fromAsync method', async () => {
1321
+ component ArrayTest() {
1322
+ let itemsPromise = RippleArray.fromAsync(Promise.reject(new Error('Async error')));
1323
+ let items = null;
1324
+ let error = null;
1325
+
1326
+ try {
1327
+ items = await itemsPromise;
1328
+ } catch (e) {
1329
+ error = e.message;
1330
+ }
1331
+
1332
+ <pre>{error ? 'Error: ' + error : 'No error'}</pre>
1333
+ <pre>{items ? JSON.stringify(items) : 'No items'}</pre>
1334
+ }
1335
+
1336
+ render(ArrayTest);
1337
+
1338
+ // Wait for promise to reject
1339
+ await new Promise(resolve => setTimeout(resolve, 50));
1340
+ flushSync();
1341
+
1342
+ // Check that the error was caught correctly
1343
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('Error: Async error');
1344
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('No items');
1345
+ });
1346
+ });
1347
+
1348
+ describe('RippleArray copyWithin', () => {
1349
+ it('handles copyWithin operation with reactivity', () => {
1350
+ component ArrayTest() {
1351
+ let items = new RippleArray(1, 2, 3, 4, 5);
1352
+ let $firstItem = items[0];
1353
+ let $thirdItem = items[2];
1354
+ let $fourthItem = items[3];
1355
+
1356
+ <button onClick={() => items.copyWithin(0, 3)}>{'copy end to start'}</button>
1357
+ <button onClick={() => items.copyWithin(2, 0, 2)}>{'copy start to middle'}</button>
1358
+ <pre>{JSON.stringify(items)}</pre>
1359
+ <pre>{$firstItem}</pre>
1360
+ <pre>{$thirdItem}</pre>
1361
+ <pre>{$fourthItem}</pre>
1362
+ }
1363
+
1364
+ render(ArrayTest);
1365
+
1366
+ const copyEndToStartButton = container.querySelectorAll('button')[0];
1367
+ const copyStartToMiddleButton = container.querySelectorAll('button')[1];
1368
+
1369
+ // Initial state
1370
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
1371
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
1372
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
1373
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('4');
1374
+
1375
+ // Test copyWithin from end to start
1376
+ copyEndToStartButton.click();
1377
+ flushSync();
1378
+
1379
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[4,5,3,4,5]');
1380
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
1381
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
1382
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('4');
1383
+
1384
+ // Test copyWithin from start to middle
1385
+ copyStartToMiddleButton.click();
1386
+ flushSync();
1387
+
1388
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[4,5,4,5,5]');
1389
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
1390
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('4');
1391
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('5');
1392
+ });
1393
+
1394
+ it('handles copyWithin with negative indexes and reactivity', () => {
1395
+ component ArrayTest() {
1396
+ let items = new RippleArray(1, 2, 3, 4, 5);
1397
+ let $secondItem = items[1];
1398
+ let $thirdItem = items[2];
1399
+
1400
+ <button onClick={() => items.copyWithin(-4, -2)}>{'copy with negative indexes'}</button>
1401
+ <pre>{JSON.stringify(items)}</pre>
1402
+ <pre>{$secondItem}</pre>
1403
+ <pre>{$thirdItem}</pre>
1404
+ }
1405
+
1406
+ render(ArrayTest);
1407
+
1408
+ const copyButton = container.querySelector('button');
1409
+
1410
+ // Initial state
1411
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
1412
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
1413
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
1414
+
1415
+ // Test copyWithin with negative indexes
1416
+ copyButton.click();
1417
+ flushSync();
1418
+
1419
+ // copyWithin(-4, -2) should copy [4,5] to positions [1,2]
1420
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,4,5,4,5]');
1421
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
1422
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
1423
+ });
1424
+
1425
+ it('handles copyWithin with overlapping ranges', () => {
1426
+ component ArrayTest() {
1427
+ let items = new RippleArray(1, 2, 3, 4, 5);
1428
+ let $entries = items.entries();
1429
+
1430
+ <button onClick={() => items.copyWithin(2, 1, 4)}>{'copy with overlap'}</button>
1431
+ <pre>{JSON.stringify(items)}</pre>
1432
+
1433
+ for (const [i, value] of $entries) {
1434
+ <pre>{`items[${i}]: ${value}`}</pre>
1435
+ }
1436
+ }
1437
+
1438
+ render(ArrayTest);
1439
+
1440
+ const copyButton = container.querySelector('button');
1441
+
1442
+ // Initial state
1443
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
1444
+
1445
+ // Test values from reactive bindings
1446
+ for (let i = 0; i < 5; i++) {
1447
+ expect(container.querySelectorAll('pre')[i + 1].textContent).toBe(`items[${i}]: ${i + 1}`);
1448
+ }
1449
+
1450
+ // Test copyWithin with overlapping ranges
1451
+ copyButton.click();
1452
+ flushSync();
1453
+
1454
+ // copyWithin(2, 1, 4) should copy [2,3,4] to positions [2,3,4]
1455
+ // resulting in [1,2,2,3,4]
1456
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,2,3,4]');
1457
+
1458
+ // Test that reactive bindings updated
1459
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('items[0]: 1');
1460
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('items[1]: 2');
1461
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('items[2]: 2');
1462
+ expect(container.querySelectorAll('pre')[4].textContent).toBe('items[3]: 3');
1463
+ expect(container.querySelectorAll('pre')[5].textContent).toBe('items[4]: 4');
1464
+ });
1465
+ });
1466
+ });
1467
+
1468
+