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,385 @@
1
+ import { flushSync, track, TrackedArray } from 'ripple';
2
+
3
+ describe('TrackedArray > mutations', () => {
4
+ it('handles direct assignment and length tracking', () => {
5
+ component ArrayTest() {
6
+ let items = new TrackedArray(1, 2, 3);
7
+
8
+ <button onClick={() => items[items.length] = items.length + 1}>{'increment'}</button>
9
+
10
+ <Child items={items} />
11
+ }
12
+
13
+ component Child({ items }: { items: TrackedArray<number> }) {
14
+ <pre>{JSON.stringify(items)}</pre>
15
+ <pre>{items.length}</pre>
16
+ }
17
+
18
+ render(ArrayTest);
19
+
20
+ const button = container.querySelector('button');
21
+
22
+ button.click();
23
+ flushSync();
24
+
25
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
26
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
27
+
28
+ button.click();
29
+ flushSync();
30
+
31
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
32
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
33
+ });
34
+
35
+ it('handles push and pop operations with reactivity', () => {
36
+ component ArrayTest() {
37
+ let items = new TrackedArray(1, 2, 3);
38
+ let lastItem = track(() => items[items.length - 1]);
39
+
40
+ <button onClick={() => items.push(4)}>{'push'}</button>
41
+ <button onClick={() => items.pop()}>{'pop'}</button>
42
+ <pre>{JSON.stringify(items)}</pre>
43
+ <pre>{items.length}</pre>
44
+ <pre>{@lastItem}</pre>
45
+ }
46
+
47
+ render(ArrayTest);
48
+
49
+ const pushButton = container.querySelectorAll('button')[0];
50
+ const popButton = container.querySelectorAll('button')[1];
51
+
52
+ // Initial state
53
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
54
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
55
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
56
+
57
+ // Test push operation
58
+ pushButton.click();
59
+ flushSync();
60
+
61
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
62
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
63
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('4');
64
+
65
+ // Test pop operation
66
+ popButton.click();
67
+ flushSync();
68
+
69
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
70
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
71
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
72
+ });
73
+
74
+ it('handles shift and unshift operations with reactivity', () => {
75
+ component ArrayTest() {
76
+ let items = new TrackedArray(2, 3, 4);
77
+ let firstItem = track(() => items[0]);
78
+
79
+ <button onClick={() => items.unshift(1)}>{'unshift'}</button>
80
+ <button onClick={() => items.shift()}>{'shift'}</button>
81
+ <pre>{JSON.stringify(items)}</pre>
82
+ <pre>{items.length}</pre>
83
+ <pre>{@firstItem}</pre>
84
+ }
85
+
86
+ render(ArrayTest);
87
+
88
+ const unshiftButton = container.querySelectorAll('button')[0];
89
+ const shiftButton = container.querySelectorAll('button')[1];
90
+
91
+ // Initial state
92
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,3,4]');
93
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
94
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('2');
95
+
96
+ // Test unshift operation
97
+ unshiftButton.click();
98
+ flushSync();
99
+
100
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4]');
101
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
102
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
103
+
104
+ // Test shift operation
105
+ shiftButton.click();
106
+ flushSync();
107
+
108
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,3,4]');
109
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
110
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('2');
111
+ });
112
+
113
+ it('handles splice operation with reactivity', () => {
114
+ component ArrayTest() {
115
+ let items = new TrackedArray<number | string>(1, 2, 3, 4, 5);
116
+ let middleItem = track(() => items[2]);
117
+
118
+ <button onClick={() => items.splice(1, 2, 'a', 'b')}>{'splice'}</button>
119
+ <pre>{JSON.stringify(items)}</pre>
120
+ <pre>{items.length}</pre>
121
+ <pre>{@middleItem}</pre>
122
+ }
123
+
124
+ render(ArrayTest);
125
+
126
+ const spliceButton = container.querySelector('button');
127
+
128
+ // Initial state
129
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
130
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
131
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
132
+
133
+ // Test splice operation
134
+ spliceButton.click();
135
+ flushSync();
136
+
137
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,"a","b",4,5]');
138
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
139
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('b');
140
+ });
141
+
142
+ it('handles fill operation with reactivity', () => {
143
+ component ArrayTest() {
144
+ let items = new TrackedArray(1, 2, 3, 4, 5);
145
+ let secondItem = track(() => items[1]);
146
+
147
+ <button onClick={() => items.fill(0, 1, 4)}>{'fill'}</button>
148
+ <pre>{JSON.stringify(items)}</pre>
149
+ <pre>{@secondItem}</pre>
150
+ }
151
+
152
+ render(ArrayTest);
153
+
154
+ const fillButton = container.querySelector('button');
155
+
156
+ // Initial state
157
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
158
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
159
+
160
+ // Test fill operation
161
+ fillButton.click();
162
+ flushSync();
163
+
164
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,0,0,0,5]');
165
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
166
+ });
167
+
168
+ it('handles reverse operation with reactivity', () => {
169
+ component ArrayTest() {
170
+ let items = new TrackedArray(1, 2, 3, 4, 5);
171
+ let firstItem = track(() => items[0]);
172
+ let lastItem = track(() => items[4]);
173
+
174
+ <button onClick={() => items.reverse()}>{'reverse'}</button>
175
+ <pre>{JSON.stringify(items)}</pre>
176
+ <pre>{@firstItem}</pre>
177
+ <pre>{@lastItem}</pre>
178
+ }
179
+
180
+ render(ArrayTest);
181
+
182
+ const reverseButton = container.querySelector('button');
183
+
184
+ // Initial state
185
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
186
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
187
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
188
+
189
+ // Test reverse operation
190
+ reverseButton.click();
191
+ flushSync();
192
+
193
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,4,3,2,1]');
194
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
195
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
196
+ });
197
+
198
+ it('handles sort operation with reactivity', () => {
199
+ component ArrayTest() {
200
+ let items = new TrackedArray(5, 3, 1, 4, 2);
201
+ let secondItem = track(() => items[1]);
202
+
203
+ <button onClick={() => items.sort()}>{'sort ascending'}</button>
204
+ <button onClick={() => items.sort((a, b) => b - a)}>{'sort descending'}</button>
205
+ <pre>{JSON.stringify(items)}</pre>
206
+ <pre>{@secondItem}</pre>
207
+ }
208
+
209
+ render(ArrayTest);
210
+
211
+ const sortAscButton = container.querySelectorAll('button')[0];
212
+ const sortDescButton = container.querySelectorAll('button')[1];
213
+
214
+ // Initial state
215
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,3,1,4,2]');
216
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
217
+
218
+ // Test sort ascending
219
+ sortAscButton.click();
220
+ flushSync();
221
+
222
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
223
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
224
+
225
+ // Test sort descending
226
+ sortDescButton.click();
227
+ flushSync();
228
+
229
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5,4,3,2,1]');
230
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
231
+ });
232
+
233
+ it('handles array modification through forEach()', () => {
234
+ component ArrayTest() {
235
+ let items = new TrackedArray(1, 2, 3);
236
+
237
+ <button onClick={() => items.forEach((item, i) => items[i] = item * 2)}>{'double all'}</button>
238
+ <pre>{JSON.stringify(items)}</pre>
239
+ }
240
+
241
+ render(ArrayTest);
242
+
243
+ const doubleButton = container.querySelector('button');
244
+
245
+ // Initial state
246
+ expect(container.querySelector('pre').textContent).toBe('[1,2,3]');
247
+
248
+ // Test iteration with side effects
249
+ doubleButton.click();
250
+ flushSync();
251
+
252
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
253
+ });
254
+
255
+ it('handles array modification through iterator', () => {
256
+ component ArrayTest() {
257
+ let items = new TrackedArray(1, 2, 3);
258
+
259
+ <button onClick={() => items.forEach((item, i) => items[i] = item * 2)}>{'double all'}</button>
260
+
261
+ for (const item of items) {
262
+ <pre>{item}</pre>
263
+ }
264
+ }
265
+
266
+ render(ArrayTest);
267
+
268
+ const doubleButton = container.querySelector('button');
269
+
270
+ // Initial state
271
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('1');
272
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
273
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('3');
274
+
275
+ // Test iteration with side effects
276
+ doubleButton.click();
277
+ flushSync();
278
+
279
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('2');
280
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
281
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('6');
282
+ });
283
+
284
+ it('handles array index access with reactivity', () => {
285
+ component ArrayTest() {
286
+ let items = new TrackedArray(10, 20, 30);
287
+ let firstItem = track(() => items[0]);
288
+ let secondItem = track(() => items[1]);
289
+
290
+ <button onClick={() => items[0] = 100}>{'change first'}</button>
291
+ <pre>{@firstItem}</pre>
292
+ <pre>{@secondItem}</pre>
293
+ <pre>{items[0]}</pre>
294
+ <pre>{items[1]}</pre>
295
+ }
296
+
297
+ render(ArrayTest);
298
+
299
+ const changeButton = container.querySelector('button');
300
+
301
+ // Initial state
302
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('10');
303
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('20');
304
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('10');
305
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('20');
306
+
307
+ // Test changing array element directly
308
+ changeButton.click();
309
+ flushSync();
310
+
311
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('100');
312
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('20');
313
+ expect(container.querySelectorAll('pre')[2].textContent).toBe('100');
314
+ expect(container.querySelectorAll('pre')[3].textContent).toBe('20');
315
+ });
316
+
317
+ it('handles length property for reactivity', () => {
318
+ component ArrayTest() {
319
+ let items = new TrackedArray(1, 2, 3);
320
+ let length = track(() => items.length);
321
+
322
+ <button onClick={() => items.length = 5}>{'expand'}</button>
323
+ <button onClick={() => items.length = 2}>{'shrink'}</button>
324
+ <pre>{JSON.stringify(items)}</pre>
325
+ <pre>{@length}</pre>
326
+ }
327
+
328
+ render(ArrayTest);
329
+
330
+ const expandButton = container.querySelectorAll('button')[0];
331
+ const shrinkButton = container.querySelectorAll('button')[1];
332
+
333
+ // Initial state
334
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
335
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
336
+
337
+ // Test expand
338
+ expandButton.click();
339
+ flushSync();
340
+
341
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,null,null]');
342
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
343
+
344
+ // Test shrink
345
+ shrinkButton.click();
346
+ flushSync();
347
+
348
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2]');
349
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
350
+ });
351
+
352
+ it('handles setting length property and resizing the array', () => {
353
+ component ArrayTest() {
354
+ let items = new TrackedArray(1, 2, 3, 4, 5);
355
+
356
+ <button onClick={() => items.length = 3}>{'truncate'}</button>
357
+ <button onClick={() => items.length = 7}>{'expand'}</button>
358
+ <pre>{JSON.stringify(items)}</pre>
359
+ <pre>{items.length}</pre>
360
+ }
361
+
362
+ render(ArrayTest);
363
+
364
+ const truncateButton = container.querySelectorAll('button')[0];
365
+ const expandButton = container.querySelectorAll('button')[1];
366
+
367
+ // Initial state
368
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,4,5]');
369
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('5');
370
+
371
+ // Test truncating
372
+ truncateButton.click();
373
+ flushSync();
374
+
375
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
376
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
377
+
378
+ // Test expanding
379
+ expandButton.click();
380
+ flushSync();
381
+
382
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3,null,null,null,null]');
383
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('7');
384
+ });
385
+ });
@@ -0,0 +1,237 @@
1
+ import { flushSync, TrackedArray } from 'ripple';
2
+ import { MAX_ARRAY_LENGTH } from '../../../src/runtime/internal/client/constants.js';
3
+
4
+ describe('TrackedArray > static', () => {
5
+ it('handles static methods - from and of', () => {
6
+ component ArrayTest() {
7
+ let itemsFrom = TrackedArray.from([1, 2, 3], (x: number) => x * 2);
8
+ let itemsOf = TrackedArray.of(4, 5, 6);
9
+
10
+ <button onClick={() => itemsFrom.push(8)}>{'add to from'}</button>
11
+ <button onClick={() => itemsOf.push(7)}>{'add to of'}</button>
12
+ <pre>{JSON.stringify(itemsFrom)}</pre>
13
+ <pre>{JSON.stringify(itemsOf)}</pre>
14
+ }
15
+
16
+ render(ArrayTest);
17
+
18
+ const addFromButton = container.querySelectorAll('button')[0];
19
+ const addOfButton = container.querySelectorAll('button')[1];
20
+
21
+ // Initial state
22
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6]');
23
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,5,6]');
24
+
25
+ // Test adding to from-created array
26
+ addFromButton.click();
27
+ flushSync();
28
+
29
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[2,4,6,8]');
30
+
31
+ // Test adding to of-created array
32
+ addOfButton.click();
33
+ flushSync();
34
+
35
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,5,6,7]');
36
+ });
37
+
38
+ ('fromAsync' in Array.prototype ? describe : describe.skip)('TrackedArray fromAsync', async () => {
39
+ it('handles static fromAsync method with reactivity', async () => {
40
+ component Parent() {
41
+ try {
42
+ <ArrayTest />
43
+ } pending {
44
+ <div>{'Loading placeholder...'}</div>
45
+ }
46
+ }
47
+
48
+ component ArrayTest() {
49
+ let items = await TrackedArray.fromAsync([1, 2, 3]);
50
+
51
+ <button onClick={() => {
52
+ if (items) items.push(4);
53
+ }}>{'add item'}</button>
54
+
55
+ <pre>{JSON.stringify(items)}</pre>
56
+ }
57
+
58
+ render(Parent);
59
+
60
+ await new Promise(resolve => setTimeout(resolve, 0));
61
+ flushSync();
62
+
63
+ const addButton = container.querySelector('button');
64
+
65
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
66
+
67
+ // Test adding an item to the async-created array
68
+ addButton.click();
69
+ flushSync();
70
+
71
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4]');
72
+ });
73
+
74
+ it('handles static fromAsync method with mapping function', async () => {
75
+ component Parent() {
76
+ try {
77
+ <ArrayTest />
78
+ } pending {
79
+ <div>{'Loading placeholder...'}</div>
80
+ }
81
+ }
82
+
83
+ component ArrayTest() {
84
+ let items = await TrackedArray.fromAsync(
85
+ [1, 2, 3],
86
+ (x: number) => x * 2
87
+ );
88
+
89
+ <button onClick={() => {
90
+ if (items) items.push(8);
91
+ }}>{'add item'}</button>
92
+ <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
93
+ }
94
+
95
+ render(Parent);
96
+
97
+ await new Promise(resolve => setTimeout(resolve, 0));
98
+ flushSync();
99
+
100
+ const addButton = container.querySelector('button');
101
+
102
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
103
+
104
+ addButton.click();
105
+ flushSync();
106
+
107
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6,8]');
108
+ });
109
+
110
+ // TODO: Fix this test case, needs some async love around try statements being using in a not template way
111
+ it.skip('handles error in fromAsync method', async () => {
112
+ component Parent() {
113
+ try {
114
+ <ArrayTest />
115
+ } pending {
116
+ <div>{'Loading placeholder...'}</div>
117
+ }
118
+ }
119
+
120
+ component ArrayTest() {
121
+ let items = null;
122
+ let error = null;
123
+
124
+ // try {
125
+ // items = await TrackedArray.fromAsync(Promise.reject(new Error('Async error')));
126
+ // } catch (e) {
127
+ // }
128
+ }
129
+
130
+ component ArrayTest() {
131
+ let items = null;
132
+ let error = null;
133
+
134
+ try {
135
+ // items = await TrackedArray.fromAsync(Promise.reject(new Error('Async error')));
136
+ } catch (e) {
137
+ error = (e as Error).message;
138
+ }
139
+
140
+ <pre>{error ? 'Error: ' + error : 'No error'}</pre>
141
+ <pre>{items ? JSON.stringify(items) : 'No items'}</pre>
142
+ }
143
+
144
+ render(Parent);
145
+
146
+ await new Promise(resolve => setTimeout(resolve, 0));
147
+ flushSync();
148
+
149
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('Error: Async error');
150
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('No items');
151
+ });
152
+ });
153
+
154
+ describe('Creates TrackedArray with a single element', () => {
155
+ it('specifies int', () => {
156
+ component ArrayTest() {
157
+ let items = new TrackedArray(3);
158
+ <pre>{JSON.stringify(items)}</pre>
159
+ <pre>{items.length}</pre>
160
+ }
161
+
162
+ render(ArrayTest);
163
+
164
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[null,null,null]');
165
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
166
+ });
167
+
168
+ it('errors on exceeding max array size', () => {
169
+ component ArrayTest() {
170
+ let error = null;
171
+
172
+ try {
173
+ new TrackedArray(MAX_ARRAY_LENGTH + 1);
174
+ } catch (e) {
175
+ error = (e as Error).message;
176
+ }
177
+
178
+ <pre>{error}</pre>
179
+ }
180
+
181
+ render(ArrayTest);
182
+
183
+ expect(container.querySelector('pre').textContent).toBe('Invalid array length');
184
+ });
185
+
186
+ it('specifies int using static from method', () => {
187
+ component ArrayTest() {
188
+ let items = TrackedArray.from([4]);
189
+ <pre>{JSON.stringify(items)}</pre>
190
+ <pre>{items.length}</pre>
191
+ }
192
+
193
+ render(ArrayTest);
194
+
195
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[4]');
196
+ // expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
197
+ });
198
+
199
+ it('specifies int using static of method', () => {
200
+ component ArrayTest() {
201
+ let items = TrackedArray.of(5);
202
+ <pre>{JSON.stringify(items)}</pre>
203
+ <pre>{items.length}</pre>
204
+ }
205
+
206
+ render(ArrayTest);
207
+
208
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[5]');
209
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
210
+ });
211
+
212
+ ('fromAsync' in Array.prototype ? it : it.skip)('specifies int using static fromAsync method', async () => {
213
+ component Parent() {
214
+ try {
215
+ <ArrayTest />
216
+ } pending {
217
+ <div>{'Loading placeholder...'}</div>
218
+ }
219
+ }
220
+
221
+ component ArrayTest() {
222
+ const items = await TrackedArray.fromAsync([6]);
223
+
224
+ <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
225
+ <pre>{items ? items.length : ''}</pre>
226
+ }
227
+
228
+ render(Parent);
229
+
230
+ await new Promise(resolve => setTimeout(resolve, 0));
231
+ flushSync();
232
+
233
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[6]');
234
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
235
+ });
236
+ });
237
+ });