ripple 0.2.100 → 0.2.102

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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.100",
6
+ "version": "0.2.102",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -34,8 +34,8 @@
34
34
  "default": "./src/server/index.js"
35
35
  },
36
36
  "./compiler": {
37
- "types": "./types/index.d.ts",
38
- "require": "./compiler/index.js",
37
+ "types": "./src/compiler/index.d.ts",
38
+ "require": "./src/compiler/index.js",
39
39
  "default": "./src/compiler/index.js"
40
40
  },
41
41
  "./validator": {
@@ -0,0 +1,66 @@
1
+ import type { Program } from 'estree';
2
+
3
+ // ============================================================================
4
+ // Compiler API Exports
5
+ // ============================================================================
6
+ /**
7
+ * Result of compilation operation
8
+ */
9
+ export interface CompileResult {
10
+ /** The transformed AST */
11
+ ast: Program;
12
+ /** The generated JavaScript code with source map */
13
+ js: {
14
+ code: string;
15
+ map: any;
16
+ };
17
+ /** The generated CSS */
18
+ css: string;
19
+ }
20
+
21
+ /**
22
+ * Result of Volar mappings compilation
23
+ */
24
+ export interface VolarMappingsResult {
25
+ /** Array of code mappings for Volar integration */
26
+ [key: string]: any;
27
+ }
28
+
29
+ /**
30
+ * Compilation options
31
+ */
32
+ export interface CompileOptions {
33
+ /** Compilation mode: 'client' or 'server' */
34
+ mode?: 'client' | 'server';
35
+ }
36
+
37
+ /**
38
+ * Parse Ripple source code to ESTree AST
39
+ * @param source - The Ripple source code to parse
40
+ * @returns The parsed ESTree Program AST
41
+ */
42
+ export function parse(source: string): Program;
43
+
44
+ /**
45
+ * Compile Ripple source code to JS/CSS output
46
+ * @param source - The Ripple source code to compile
47
+ * @param filename - The filename for source map generation
48
+ * @param options - Compilation options (mode: 'client' or 'server')
49
+ * @returns The compilation result with AST, JS, and CSS
50
+ */
51
+ export function compile(
52
+ source: string,
53
+ filename: string,
54
+ options?: CompileOptions,
55
+ ): CompileResult;
56
+
57
+ /**
58
+ * Compile Ripple source to Volar mappings for editor integration
59
+ * @param source - The Ripple source code
60
+ * @param filename - The filename for source map generation
61
+ * @returns Volar mappings object for editor integration
62
+ */
63
+ export function compile_to_volar_mappings(
64
+ source: string,
65
+ filename: string,
66
+ ): VolarMappingsResult;
@@ -11,7 +11,6 @@ import {
11
11
  TEMPLATE_FRAGMENT,
12
12
  TEMPLATE_SVG_NAMESPACE,
13
13
  TEMPLATE_MATHML_NAMESPACE,
14
- IS_KEYED,
15
14
  } from '../../../../constants.js';
16
15
  import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js';
17
16
  import {
@@ -1027,9 +1026,6 @@ const visitors = {
1027
1026
  if (index != null) {
1028
1027
  flags |= IS_INDEXED;
1029
1028
  }
1030
- if (key != null) {
1031
- flags |= IS_KEYED;
1032
- }
1033
1029
 
1034
1030
  // do only if not controller
1035
1031
  if (!is_controlled) {
@@ -1039,12 +1035,11 @@ const visitors = {
1039
1035
  const id = context.state.flush_node(is_controlled);
1040
1036
  const pattern = node.left.declarations[0].id;
1041
1037
  const body_scope = context.state.scopes.get(node.body);
1042
- const is_keyed = key && (flags & IS_KEYED) !== 0;
1043
1038
 
1044
1039
  context.state.init.push(
1045
1040
  b.stmt(
1046
1041
  b.call(
1047
- '_$_.for',
1042
+ key != null ? '_$_.for_keyed' : '_$_.for',
1048
1043
  id,
1049
1044
  b.thunk(context.visit(node.right)),
1050
1045
  b.arrow(
@@ -1057,7 +1052,7 @@ const visitors = {
1057
1052
  ),
1058
1053
  ),
1059
1054
  b.literal(flags),
1060
- is_keyed ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key)) : undefined,
1055
+ key != null ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key)) : undefined,
1061
1056
  ),
1062
1057
  ),
1063
1058
  );
@@ -32,10 +32,6 @@ import type {
32
32
  Pattern,
33
33
  } from 'estree';
34
34
 
35
- export interface CompileResult {
36
- // TODO
37
- }
38
-
39
35
  /**
40
36
  * Parse error information
41
37
  */
@@ -298,4 +294,4 @@ export interface DelegatedEventResult {
298
294
  hoisted: boolean;
299
295
  /** The hoisted function */
300
296
  function?: FunctionExpression | FunctionDeclaration | ArrowFunctionExpression;
301
- }
297
+ }
package/src/constants.js CHANGED
@@ -2,7 +2,6 @@ export const TEMPLATE_FRAGMENT = 1;
2
2
  export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
3
3
  export const IS_CONTROLLED = 1 << 2;
4
4
  export const IS_INDEXED = 1 << 3;
5
- export const IS_KEYED = 1 << 4;
6
5
  export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
7
6
  export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
8
7
 
@@ -1,6 +1,6 @@
1
- /** @import { Block, Tracked } from '#client' */
1
+ /** @import { Block } from '#client' */
2
2
 
3
- import { IS_CONTROLLED, IS_INDEXED, IS_KEYED } from '../../../constants.js';
3
+ import { IS_CONTROLLED, IS_INDEXED } from '../../../constants.js';
4
4
  import { branch, destroy_block, destroy_block_children, render } from './blocks.js';
5
5
  import { FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
6
6
  import { create_text, next_sibling } from './operations.js';
@@ -69,21 +69,48 @@ function move(block, anchor) {
69
69
  /**
70
70
  * @template V
71
71
  * @param {V[] | Iterable<V>} collection
72
- * @param {boolean} clone
73
72
  * @returns {V[]}
74
73
  */
75
- function collection_to_array(collection, clone) {
74
+ function collection_to_array(collection) {
76
75
  var array = is_array(collection) ? collection : collection == null ? [] : array_from(collection);
77
76
 
78
77
  // If we are working with a tracked array, then we need to get a copy of
79
78
  // the elements, as the array itself is proxied, and not useful in diffing
80
- if (clone || TRACKED_ARRAY in array) {
79
+ if (TRACKED_ARRAY in array) {
81
80
  array = array_from(array);
82
81
  }
83
82
 
84
83
  return array;
85
84
  }
86
85
 
86
+ /**
87
+ * @template V
88
+ * @param {Element} node
89
+ * @param {() => V[] | Iterable<V>} get_collection
90
+ * @param {(anchor: Node, value: V, index?: any) => Block} render_fn
91
+ * @param {number} flags
92
+ * @returns {void}
93
+ */
94
+ export function for_block(node, get_collection, render_fn, flags) {
95
+ var is_controlled = (flags & IS_CONTROLLED) !== 0;
96
+ var is_indexed = (flags & IS_INDEXED) !== 0;
97
+ var anchor = /** @type {Element | Text} */ (node);
98
+
99
+ if (is_controlled) {
100
+ anchor = node.appendChild(create_text());
101
+ }
102
+
103
+ render(() => {
104
+ var block = /** @type {Block} */ (active_block);
105
+ var collection = get_collection();
106
+ var array = collection_to_array(collection);
107
+
108
+ untrack(() => {
109
+ reconcile_by_ref(anchor, block, array, render_fn, is_controlled, is_indexed);
110
+ });
111
+ }, FOR_BLOCK);
112
+ }
113
+
87
114
  /**
88
115
  * @template V
89
116
  * @template K
@@ -94,10 +121,9 @@ function collection_to_array(collection, clone) {
94
121
  * @param {(item: V) => K} [get_key]
95
122
  * @returns {void}
96
123
  */
97
- export function for_block(node, get_collection, render_fn, flags, get_key) {
124
+ export function for_block_keyed(node, get_collection, render_fn, flags, get_key) {
98
125
  var is_controlled = (flags & IS_CONTROLLED) !== 0;
99
126
  var is_indexed = (flags & IS_INDEXED) !== 0;
100
- var is_keyed = (flags & IS_KEYED) !== 0;
101
127
  var anchor = /** @type {Element | Text} */ (node);
102
128
 
103
129
  if (is_controlled) {
@@ -107,13 +133,18 @@ export function for_block(node, get_collection, render_fn, flags, get_key) {
107
133
  render(() => {
108
134
  var block = /** @type {Block} */ (active_block);
109
135
  var collection = get_collection();
110
- var array = collection_to_array(collection, is_keyed);
111
- if (is_keyed) {
112
- array = keyed(block, array, /** @type {(item: V) => K} */ (get_key));
113
- }
136
+ var array = collection_to_array(collection);
114
137
 
115
138
  untrack(() => {
116
- reconcile(anchor, block, array, render_fn, is_controlled, is_indexed);
139
+ reconcile_by_key(
140
+ anchor,
141
+ block,
142
+ array,
143
+ render_fn,
144
+ is_controlled,
145
+ is_indexed,
146
+ /** @type {(item: V) => K} */ (get_key),
147
+ );
117
148
  });
118
149
  }, FOR_BLOCK);
119
150
  }
@@ -146,15 +177,17 @@ function update_index(block, index) {
146
177
 
147
178
  /**
148
179
  * @template V
180
+ * @template K
149
181
  * @param {Element | Text} anchor
150
182
  * @param {Block} block
151
183
  * @param {V[]} b
152
184
  * @param {(anchor: Node, value: V, index?: any) => Block} render_fn
153
185
  * @param {boolean} is_controlled
154
186
  * @param {boolean} is_indexed
187
+ * @param {(item: V) => K} get_key
155
188
  * @returns {void}
156
189
  */
157
- function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
190
+ function reconcile_by_key(anchor, block, b, render_fn, is_controlled, is_indexed, get_key) {
158
191
  var state = block.s;
159
192
 
160
193
  // Variables used in conditional branches - declare with initial values
@@ -181,6 +214,277 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
181
214
  state = block.s = {
182
215
  array: [],
183
216
  blocks: [],
217
+ keys: null,
218
+ };
219
+ }
220
+
221
+ var a = state.array;
222
+ var a_length = a.length;
223
+ var b_length = b.length;
224
+ var j = 0;
225
+
226
+ // Fast-path for clear
227
+ if (is_controlled && b_length === 0) {
228
+ if (a_length > 0) {
229
+ reconcile_fast_clear(anchor, block, b);
230
+ }
231
+ return;
232
+ }
233
+ var b_blocks = Array(b_length);
234
+ var b_keys = b.map(get_key);
235
+
236
+ // Fast-path for create
237
+ if (a_length === 0) {
238
+ for (; j < b_length; j++) {
239
+ b_blocks[j] = create_item(anchor, b[j], j, render_fn, is_indexed);
240
+ }
241
+ state.array = b;
242
+ state.blocks = b_blocks;
243
+ state.keys = b_keys;
244
+ return;
245
+ }
246
+
247
+ var a_blocks = state.blocks;
248
+ var a_keys = state.keys;
249
+ var a_val = a[j];
250
+ var b_val = b[j];
251
+ var a_key = a_keys[j];
252
+ var b_key = b_keys[j];
253
+ var a_end = a_length - 1;
254
+ var b_end = b_length - 1;
255
+ var b_block;
256
+
257
+ outer: {
258
+ while (a_key === b_key) {
259
+ a[j] = b_val;
260
+ b_block = b_blocks[j] = a_blocks[j];
261
+ if (is_indexed) {
262
+ update_index(b_block, j);
263
+ }
264
+ ++j;
265
+ if (j > a_end || j > b_end) {
266
+ break outer;
267
+ }
268
+ a_val = a[j];
269
+ b_val = b[j];
270
+ a_key = a_keys[j];
271
+ b_key = b_keys[j];
272
+ }
273
+
274
+ a_val = a[a_end];
275
+ b_val = b[b_end];
276
+ a_key = a_keys[a_end];
277
+ b_key = b_keys[b_end];
278
+
279
+ while (a_key === b_key) {
280
+ a[a_end] = b_val;
281
+ b_block = b_blocks[b_end] = a_blocks[a_end];
282
+ if (is_indexed) {
283
+ update_index(b_block, b_end);
284
+ }
285
+ a_end--;
286
+ b_end--;
287
+ if (j > a_end || j > b_end) {
288
+ break outer;
289
+ }
290
+ a_val = a[a_end];
291
+ b_val = b[b_end];
292
+ a_key = a_keys[a_end];
293
+ b_key = b_keys[b_end];
294
+ }
295
+ }
296
+
297
+ var fast_path_removal = false;
298
+
299
+ if (j > a_end) {
300
+ if (j <= b_end) {
301
+ while (j <= b_end) {
302
+ b_val = b[j];
303
+ var target = j >= a_length ? anchor : a_blocks[j].s.start;
304
+ b_blocks[j] = create_item(target, b_val, j, render_fn, is_indexed);
305
+ j++;
306
+ }
307
+ }
308
+ } else if (j > b_end) {
309
+ while (j <= a_end) {
310
+ destroy_block(a_blocks[j++]);
311
+ }
312
+ } else {
313
+ a_start = j;
314
+ b_start = j;
315
+ a_left = a_end - j + 1;
316
+ b_left = b_end - j + 1;
317
+ sources = new Int32Array(b_left + 1);
318
+ moved = false;
319
+ pos = 0;
320
+ patched = 0;
321
+ i = 0;
322
+
323
+ fast_path_removal = is_controlled && a_left === a_length;
324
+
325
+ // When sizes are small, just loop them through
326
+ if (b_length < 4 || (a_left | b_left) < 32) {
327
+ for (i = a_start; i <= a_end; ++i) {
328
+ a_val = a[i];
329
+ a_key = a_keys[i];
330
+ if (patched < b_left) {
331
+ for (j = b_start; j <= b_end; j++) {
332
+ b_val = b[j];
333
+ b_key = b_keys[j];
334
+ if (a_key === b_key) {
335
+ sources[j - b_start] = i + 1;
336
+ if (fast_path_removal) {
337
+ fast_path_removal = false;
338
+ while (a_start < i) {
339
+ destroy_block(a_blocks[a_start++]);
340
+ }
341
+ }
342
+ if (pos > j) {
343
+ moved = true;
344
+ } else {
345
+ pos = j;
346
+ }
347
+ b_block = b_blocks[j] = a_blocks[i];
348
+ if (is_indexed) {
349
+ update_index(b_block, j);
350
+ }
351
+ ++patched;
352
+ break;
353
+ }
354
+ }
355
+ if (!fast_path_removal && j > b_end) {
356
+ destroy_block(a_blocks[i]);
357
+ }
358
+ } else if (!fast_path_removal) {
359
+ destroy_block(a_blocks[i]);
360
+ }
361
+ }
362
+ } else {
363
+ var map = new Map();
364
+
365
+ for (i = b_start; i <= b_end; ++i) {
366
+ map.set(b_keys[i], i);
367
+ }
368
+
369
+ for (i = a_start; i <= a_end; ++i) {
370
+ a_val = a[i];
371
+ a_key = a_keys[i];
372
+
373
+ if (patched < b_left) {
374
+ j = map.get(a_key);
375
+
376
+ if (j !== undefined) {
377
+ if (fast_path_removal) {
378
+ fast_path_removal = false;
379
+ while (i > a_start) {
380
+ destroy_block(a[a_start++]);
381
+ }
382
+ }
383
+ sources[j - b_start] = i + 1;
384
+ if (pos > j) {
385
+ moved = true;
386
+ } else {
387
+ pos = j;
388
+ }
389
+ block = b_blocks[j] = a_blocks[i];
390
+ if (is_indexed) {
391
+ update_index(block, j);
392
+ }
393
+ ++patched;
394
+ } else if (!fast_path_removal) {
395
+ destroy_block(a_blocks[i]);
396
+ }
397
+ } else if (!fast_path_removal) {
398
+ destroy_block(a_blocks[i]);
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ if (fast_path_removal) {
405
+ reconcile_fast_clear(anchor, block, []);
406
+ reconcile_by_key(anchor, block, b, render_fn, is_controlled, is_indexed, get_key);
407
+ return;
408
+ } else if (moved) {
409
+ var next_pos = 0;
410
+ var seq = lis_algorithm(sources);
411
+ j = seq.length - 1;
412
+
413
+ for (i = b_left - 1; i >= 0; i--) {
414
+ if (sources[i] === 0) {
415
+ pos = i + b_start;
416
+ b_val = b[pos];
417
+ next_pos = pos + 1;
418
+
419
+ var target = next_pos < b_length ? b_blocks[next_pos].s.start : anchor;
420
+ b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed);
421
+ } else if (j < 0 || i !== seq[j]) {
422
+ pos = i + b_start;
423
+ b_val = b[pos];
424
+ next_pos = pos + 1;
425
+
426
+ var target = next_pos < b_length ? b_blocks[next_pos].s.start : anchor;
427
+ move(b_blocks[pos], target);
428
+ } else {
429
+ j--;
430
+ }
431
+ }
432
+ } else if (patched !== b_left) {
433
+ for (i = b_left - 1; i >= 0; i--) {
434
+ if (sources[i] === 0) {
435
+ pos = i + b_start;
436
+ b_val = b[pos];
437
+ next_pos = pos + 1;
438
+
439
+ var target = next_pos < b_length ? b_blocks[next_pos].s.start : anchor;
440
+ b_blocks[pos] = create_item(target, b_val, pos, render_fn, is_indexed);
441
+ }
442
+ }
443
+ }
444
+
445
+ state.array = b;
446
+ state.blocks = b_blocks;
447
+ state.keys = b_keys;
448
+ }
449
+
450
+ /**
451
+ * @template V
452
+ * @param {Element | Text} anchor
453
+ * @param {Block} block
454
+ * @param {V[]} b
455
+ * @param {(anchor: Node, value: V, index?: any) => Block} render_fn
456
+ * @param {boolean} is_controlled
457
+ * @param {boolean} is_indexed
458
+ * @returns {void}
459
+ */
460
+ function reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed) {
461
+ var state = block.s;
462
+
463
+ // Variables used in conditional branches - declare with initial values
464
+ /** @type {number} */
465
+ var a_start = 0;
466
+ /** @type {number} */
467
+ var b_start = 0;
468
+ /** @type {number} */
469
+ var a_left = 0;
470
+ /** @type {number} */
471
+ var b_left = 0;
472
+ /** @type {Int32Array} */
473
+ var sources = new Int32Array(0);
474
+ /** @type {boolean} */
475
+ var moved = false;
476
+ /** @type {number} */
477
+ var pos = 0;
478
+ /** @type {number} */
479
+ var patched = 0;
480
+ /** @type {number} */
481
+ var i = 0;
482
+
483
+ if (state === null) {
484
+ state = block.s = {
485
+ array: [],
486
+ blocks: [],
487
+ keys: null,
184
488
  };
185
489
  }
186
490
 
@@ -338,7 +642,6 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
338
642
  } else {
339
643
  pos = j;
340
644
  }
341
- b_val = b[j];
342
645
  block = b_blocks[j] = a_blocks[i];
343
646
  if (is_indexed) {
344
647
  update_index(block, j);
@@ -356,7 +659,7 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
356
659
 
357
660
  if (fast_path_removal) {
358
661
  reconcile_fast_clear(anchor, block, []);
359
- reconcile(anchor, block, b, render_fn, is_controlled, is_indexed);
662
+ reconcile_by_ref(anchor, block, b, render_fn, is_controlled, is_indexed);
360
663
  return;
361
664
  } else if (moved) {
362
665
  var next_pos = 0;
@@ -469,55 +772,3 @@ function lis_algorithm(arr) {
469
772
 
470
773
  return seq;
471
774
  }
472
-
473
- /**
474
- * @template V
475
- * @template K
476
- * @param {Block} block
477
- * @param {V[]} b_array
478
- * @param {(item: V) => K} key_fn
479
- * @returns {V[]}
480
- */
481
- function keyed(block, b_array, key_fn) {
482
- var b_keys = b_array.map(key_fn);
483
-
484
- // We only need to do this in DEV
485
- var b = new Set(b_keys);
486
- if (b.size !== b_keys.length) {
487
- throw new Error('Duplicate keys are not allowed');
488
- }
489
-
490
- var state = block.s;
491
-
492
- if (state === null) {
493
- // Make a clone of it so we don't mutate the original thereafter
494
- return b_array;
495
- }
496
-
497
- var a_array = state.array;
498
- var a_keys = a_array.map(key_fn);
499
- var a = new Map();
500
-
501
- for (var i = 0; i < a_keys.length; i++) {
502
- a.set(a_keys[i], i);
503
- }
504
-
505
- if (a.size !== a_keys.length) {
506
- throw new Error('Duplicate keys are not allowed');
507
- }
508
-
509
- for (var i = 0; i < b_keys.length; i++) {
510
- var b_val = b_keys[i];
511
- // if the index is the key, skip
512
- if (b_val === i) {
513
- continue;
514
- }
515
- var index = a.get(b_val);
516
-
517
- if (index !== undefined) {
518
- b_array[i] = a_array[index];
519
- }
520
- }
521
-
522
- return b_array;
523
- }
@@ -51,7 +51,7 @@ export {
51
51
 
52
52
  export { composite } from './composite.js';
53
53
 
54
- export { for_block as for } from './for.js';
54
+ export { for_block as for, for_block_keyed as for_keyed } from './for.js';
55
55
 
56
56
  export { if_block as if } from './if.js';
57
57
 
@@ -198,6 +198,46 @@ exports[`for statements > correctly handles the index in a for...of loop 3`] = `
198
198
  </div>
199
199
  `;
200
200
 
201
+ exports[`for statements > handles reversing an array manually 1`] = `
202
+ <div>
203
+ <!---->
204
+ <div>
205
+ 0:Item 1
206
+ </div>
207
+ <div>
208
+ 1:Item 2
209
+ </div>
210
+ <div>
211
+ 2:Item 3
212
+ </div>
213
+ <!---->
214
+ <button>
215
+ Reverse
216
+ </button>
217
+
218
+ </div>
219
+ `;
220
+
221
+ exports[`for statements > handles reversing an array manually 2`] = `
222
+ <div>
223
+ <!---->
224
+ <div>
225
+ 0:Item 3
226
+ </div>
227
+ <div>
228
+ 1:Item 2
229
+ </div>
230
+ <div>
231
+ 2:Item 1
232
+ </div>
233
+ <!---->
234
+ <button>
235
+ Reverse
236
+ </button>
237
+
238
+ </div>
239
+ `;
240
+
201
241
  exports[`for statements > render a simple dynamic array 1`] = `
202
242
  <div>
203
243
  <!---->
@@ -144,4 +144,39 @@ describe('for statements', () => {
144
144
 
145
145
  expect(container).toMatchSnapshot();
146
146
  });
147
+
148
+ it('handles reversing an array manually', () => {
149
+ component App() {
150
+ let items = track([
151
+ #{ id: 1, text: 'Item 1' },
152
+ #{ id: 2, text: 'Item 2' },
153
+ #{ id: 3, text: 'Item 3' },
154
+ ]);
155
+
156
+ for (let item of @items; index i; key item.id) {
157
+ <div>{i + ':' + item.text}</div>
158
+ }
159
+
160
+ <button onClick={() => {
161
+ @items[0].id = 3;
162
+ @items[1].id = 2;
163
+ @items[2].id = 1;
164
+
165
+ @items = [
166
+ @items[0],
167
+ @items[1],
168
+ @items[2],
169
+ ];
170
+ }}>{"Reverse"}</button>
171
+ }
172
+
173
+ render(App);
174
+ expect(container).toMatchSnapshot();
175
+
176
+ const button = container.querySelector('button');
177
+ button.click();
178
+ flushSync();
179
+
180
+ expect(container).toMatchSnapshot();
181
+ })
147
182
  });