ripple 0.2.98 → 0.2.100
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 +1 -1
- package/src/compiler/phases/1-parse/index.js +13 -7
- package/src/compiler/phases/2-analyze/index.js +8 -0
- package/src/compiler/phases/3-transform/client/index.js +8 -1
- package/src/constants.js +3 -2
- package/src/runtime/index-client.js +0 -2
- package/src/runtime/internal/client/for.js +29 -22
- package/tests/client/__snapshots__/for.test.ripple.snap +103 -63
- package/tests/client/composite.test.ripple +1 -45
- package/tests/client/for.test.ripple +31 -2
- package/tests/client/typescript-generics.test.ripple +102 -0
package/package.json
CHANGED
|
@@ -461,14 +461,20 @@ function RipplePlugin(config) {
|
|
|
461
461
|
|
|
462
462
|
if (this.isContextual('index')) {
|
|
463
463
|
this.next(); // consume 'index'
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
node.index = this.parseIdent();
|
|
467
|
-
} else {
|
|
464
|
+
node.index = this.parseExpression();
|
|
465
|
+
if (node.index.type !== 'Identifier') {
|
|
468
466
|
this.raise(this.start, 'Expected identifier after "index" keyword');
|
|
469
467
|
}
|
|
470
|
-
|
|
471
|
-
|
|
468
|
+
this.eat(tt.semi);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (this.isContextual('key')) {
|
|
472
|
+
this.next(); // consume 'key'
|
|
473
|
+
node.key = this.parseExpression();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (this.isContextual('index')) {
|
|
477
|
+
this.raise(this.start, '"index" must come before "key" in for-of loop');
|
|
472
478
|
}
|
|
473
479
|
} else if (!isForIn) {
|
|
474
480
|
// Set index to null for standard for-of loops
|
|
@@ -858,7 +864,7 @@ function RipplePlugin(config) {
|
|
|
858
864
|
const element = this.startNode();
|
|
859
865
|
element.start = position.index;
|
|
860
866
|
element.loc.start = position;
|
|
861
|
-
|
|
867
|
+
element.metadata = {};
|
|
862
868
|
element.type = 'Element';
|
|
863
869
|
this.#path.push(element);
|
|
864
870
|
element.children = [];
|
|
@@ -466,6 +466,14 @@ const visitors = {
|
|
|
466
466
|
if (attr.name.type === 'Identifier') {
|
|
467
467
|
attribute_names.add(attr.name);
|
|
468
468
|
|
|
469
|
+
if (attr.name.name === 'key') {
|
|
470
|
+
error(
|
|
471
|
+
'The `key` attribute is not a thing in Ripple, and cannot be used on DOM elements. If you are using a for loop, then use the `for (let item of items; key item.id)` syntax.',
|
|
472
|
+
state.analysis.module.filename,
|
|
473
|
+
attr,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
469
477
|
if (is_event_attribute(attr.name.name)) {
|
|
470
478
|
const event_name = attr.name.name.slice(2).toLowerCase();
|
|
471
479
|
const handler = visit(attr.value, state);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
TEMPLATE_FRAGMENT,
|
|
12
12
|
TEMPLATE_SVG_NAMESPACE,
|
|
13
13
|
TEMPLATE_MATHML_NAMESPACE,
|
|
14
|
+
IS_KEYED,
|
|
14
15
|
} from '../../../../constants.js';
|
|
15
16
|
import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js';
|
|
16
17
|
import {
|
|
@@ -1020,11 +1021,15 @@ const visitors = {
|
|
|
1020
1021
|
}
|
|
1021
1022
|
const is_controlled = node.is_controlled;
|
|
1022
1023
|
const index = node.index;
|
|
1024
|
+
const key = node.key;
|
|
1023
1025
|
let flags = is_controlled ? IS_CONTROLLED : 0;
|
|
1024
1026
|
|
|
1025
|
-
if (index
|
|
1027
|
+
if (index != null) {
|
|
1026
1028
|
flags |= IS_INDEXED;
|
|
1027
1029
|
}
|
|
1030
|
+
if (key != null) {
|
|
1031
|
+
flags |= IS_KEYED;
|
|
1032
|
+
}
|
|
1028
1033
|
|
|
1029
1034
|
// do only if not controller
|
|
1030
1035
|
if (!is_controlled) {
|
|
@@ -1034,6 +1039,7 @@ const visitors = {
|
|
|
1034
1039
|
const id = context.state.flush_node(is_controlled);
|
|
1035
1040
|
const pattern = node.left.declarations[0].id;
|
|
1036
1041
|
const body_scope = context.state.scopes.get(node.body);
|
|
1042
|
+
const is_keyed = key && (flags & IS_KEYED) !== 0;
|
|
1037
1043
|
|
|
1038
1044
|
context.state.init.push(
|
|
1039
1045
|
b.stmt(
|
|
@@ -1051,6 +1057,7 @@ const visitors = {
|
|
|
1051
1057
|
),
|
|
1052
1058
|
),
|
|
1053
1059
|
b.literal(flags),
|
|
1060
|
+
is_keyed ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key)) : undefined,
|
|
1054
1061
|
),
|
|
1055
1062
|
),
|
|
1056
1063
|
);
|
package/src/constants.js
CHANGED
|
@@ -2,6 +2,7 @@ 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
|
|
6
|
-
export const
|
|
5
|
+
export const IS_KEYED = 1 << 4;
|
|
6
|
+
export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
|
|
7
|
+
export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
|
|
7
8
|
|
|
@@ -60,8 +60,6 @@ export { TrackedMap } from './map.js';
|
|
|
60
60
|
|
|
61
61
|
export { TrackedDate } from './date.js';
|
|
62
62
|
|
|
63
|
-
export { keyed } from './internal/client/for.js';
|
|
64
|
-
|
|
65
63
|
export { user_effect as effect } from './internal/client/blocks.js';
|
|
66
64
|
|
|
67
65
|
export { Portal } from './internal/client/portal.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @import { Block } from '#client' */
|
|
1
|
+
/** @import { Block, Tracked } from '#client' */
|
|
2
2
|
|
|
3
|
-
import { IS_CONTROLLED, IS_INDEXED } from '../../../constants.js';
|
|
3
|
+
import { IS_CONTROLLED, IS_INDEXED, IS_KEYED } 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,14 +69,15 @@ function move(block, anchor) {
|
|
|
69
69
|
/**
|
|
70
70
|
* @template V
|
|
71
71
|
* @param {V[] | Iterable<V>} collection
|
|
72
|
+
* @param {boolean} clone
|
|
72
73
|
* @returns {V[]}
|
|
73
74
|
*/
|
|
74
|
-
function collection_to_array(collection) {
|
|
75
|
+
function collection_to_array(collection, clone) {
|
|
75
76
|
var array = is_array(collection) ? collection : collection == null ? [] : array_from(collection);
|
|
76
77
|
|
|
77
78
|
// If we are working with a tracked array, then we need to get a copy of
|
|
78
79
|
// the elements, as the array itself is proxied, and not useful in diffing
|
|
79
|
-
if (TRACKED_ARRAY in array) {
|
|
80
|
+
if (clone || TRACKED_ARRAY in array) {
|
|
80
81
|
array = array_from(array);
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -85,15 +86,18 @@ function collection_to_array(collection) {
|
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
88
|
* @template V
|
|
89
|
+
* @template K
|
|
88
90
|
* @param {Element} node
|
|
89
91
|
* @param {() => V[] | Iterable<V>} get_collection
|
|
90
92
|
* @param {(anchor: Node, value: V, index?: any) => Block} render_fn
|
|
91
93
|
* @param {number} flags
|
|
94
|
+
* @param {(item: V) => K} [get_key]
|
|
92
95
|
* @returns {void}
|
|
93
96
|
*/
|
|
94
|
-
export function for_block(node, get_collection, render_fn, flags) {
|
|
97
|
+
export function for_block(node, get_collection, render_fn, flags, get_key) {
|
|
95
98
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
96
99
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
100
|
+
var is_keyed = (flags & IS_KEYED) !== 0;
|
|
97
101
|
var anchor = /** @type {Element | Text} */ (node);
|
|
98
102
|
|
|
99
103
|
if (is_controlled) {
|
|
@@ -103,7 +107,10 @@ export function for_block(node, get_collection, render_fn, flags) {
|
|
|
103
107
|
render(() => {
|
|
104
108
|
var block = /** @type {Block} */ (active_block);
|
|
105
109
|
var collection = get_collection();
|
|
106
|
-
var array = collection_to_array(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
|
+
}
|
|
107
114
|
|
|
108
115
|
untrack(() => {
|
|
109
116
|
reconcile(anchor, block, array, render_fn, is_controlled, is_indexed);
|
|
@@ -321,9 +328,9 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
|
|
|
321
328
|
if (j !== undefined) {
|
|
322
329
|
if (fast_path_removal) {
|
|
323
330
|
fast_path_removal = false;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
331
|
+
while (i > a_start) {
|
|
332
|
+
destroy_block(a[a_start++]);
|
|
333
|
+
}
|
|
327
334
|
}
|
|
328
335
|
sources[j - b_start] = i + 1;
|
|
329
336
|
if (pos > j) {
|
|
@@ -396,7 +403,7 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
|
|
|
396
403
|
let result;
|
|
397
404
|
/** @type {Int32Array} */
|
|
398
405
|
let p;
|
|
399
|
-
let
|
|
406
|
+
let max_len = 0;
|
|
400
407
|
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
|
|
401
408
|
/**
|
|
402
409
|
* @param {Int32Array} arr
|
|
@@ -412,8 +419,8 @@ function lis_algorithm(arr) {
|
|
|
412
419
|
let c = 0;
|
|
413
420
|
var len = arr.length;
|
|
414
421
|
|
|
415
|
-
if (len >
|
|
416
|
-
|
|
422
|
+
if (len > max_len) {
|
|
423
|
+
max_len = len;
|
|
417
424
|
result = new Int32Array(len);
|
|
418
425
|
p = new Int32Array(len);
|
|
419
426
|
}
|
|
@@ -466,17 +473,12 @@ function lis_algorithm(arr) {
|
|
|
466
473
|
/**
|
|
467
474
|
* @template V
|
|
468
475
|
* @template K
|
|
469
|
-
* @param {
|
|
476
|
+
* @param {Block} block
|
|
477
|
+
* @param {V[]} b_array
|
|
470
478
|
* @param {(item: V) => K} key_fn
|
|
471
479
|
* @returns {V[]}
|
|
472
480
|
*/
|
|
473
|
-
|
|
474
|
-
var block = active_block;
|
|
475
|
-
if (block === null || (block.f & FOR_BLOCK) === 0) {
|
|
476
|
-
throw new Error('keyed() must be used inside a for block');
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
var b_array = collection_to_array(collection);
|
|
481
|
+
function keyed(block, b_array, key_fn) {
|
|
480
482
|
var b_keys = b_array.map(key_fn);
|
|
481
483
|
|
|
482
484
|
// We only need to do this in DEV
|
|
@@ -488,6 +490,7 @@ export function keyed(collection, key_fn) {
|
|
|
488
490
|
var state = block.s;
|
|
489
491
|
|
|
490
492
|
if (state === null) {
|
|
493
|
+
// Make a clone of it so we don't mutate the original thereafter
|
|
491
494
|
return b_array;
|
|
492
495
|
}
|
|
493
496
|
|
|
@@ -495,7 +498,7 @@ export function keyed(collection, key_fn) {
|
|
|
495
498
|
var a_keys = a_array.map(key_fn);
|
|
496
499
|
var a = new Map();
|
|
497
500
|
|
|
498
|
-
for (
|
|
501
|
+
for (var i = 0; i < a_keys.length; i++) {
|
|
499
502
|
a.set(a_keys[i], i);
|
|
500
503
|
}
|
|
501
504
|
|
|
@@ -503,8 +506,12 @@ export function keyed(collection, key_fn) {
|
|
|
503
506
|
throw new Error('Duplicate keys are not allowed');
|
|
504
507
|
}
|
|
505
508
|
|
|
506
|
-
for (
|
|
509
|
+
for (var i = 0; i < b_keys.length; i++) {
|
|
507
510
|
var b_val = b_keys[i];
|
|
511
|
+
// if the index is the key, skip
|
|
512
|
+
if (b_val === i) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
508
515
|
var index = a.get(b_val);
|
|
509
516
|
|
|
510
517
|
if (index !== undefined) {
|
|
@@ -1,49 +1,98 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`for statements > correctly
|
|
3
|
+
exports[`for statements > correctly handles intermediate statements in for block 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<div>
|
|
6
6
|
<div>
|
|
7
|
-
|
|
7
|
+
<div>
|
|
8
|
+
1
|
|
9
|
+
</div>
|
|
10
|
+
<div>
|
|
11
|
+
1
|
|
12
|
+
</div>
|
|
8
13
|
</div>
|
|
9
14
|
<div>
|
|
10
|
-
|
|
15
|
+
<div>
|
|
16
|
+
2
|
|
17
|
+
</div>
|
|
18
|
+
<div>
|
|
19
|
+
2
|
|
20
|
+
</div>
|
|
11
21
|
</div>
|
|
12
22
|
<div>
|
|
13
|
-
|
|
23
|
+
<div>
|
|
24
|
+
3
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
3
|
|
28
|
+
</div>
|
|
14
29
|
</div>
|
|
15
30
|
|
|
16
31
|
</div>
|
|
17
32
|
<button>
|
|
18
33
|
Add Item
|
|
19
34
|
</button>
|
|
20
|
-
<button>
|
|
21
|
-
Reverse
|
|
22
|
-
</button>
|
|
23
35
|
|
|
24
36
|
</div>
|
|
25
37
|
`;
|
|
26
38
|
|
|
27
|
-
exports[`for statements > correctly
|
|
39
|
+
exports[`for statements > correctly handles intermediate statements in for block 2`] = `
|
|
28
40
|
<div>
|
|
29
41
|
<div>
|
|
30
42
|
<div>
|
|
31
|
-
|
|
43
|
+
<div>
|
|
44
|
+
1
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
1
|
|
48
|
+
</div>
|
|
32
49
|
</div>
|
|
33
50
|
<div>
|
|
34
|
-
|
|
51
|
+
<div>
|
|
52
|
+
2
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
2
|
|
56
|
+
</div>
|
|
35
57
|
</div>
|
|
36
58
|
<div>
|
|
37
|
-
|
|
59
|
+
<div>
|
|
60
|
+
3
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
3
|
|
64
|
+
</div>
|
|
38
65
|
</div>
|
|
39
66
|
<div>
|
|
40
|
-
|
|
67
|
+
<div>
|
|
68
|
+
4
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
4
|
|
72
|
+
</div>
|
|
41
73
|
</div>
|
|
42
74
|
|
|
43
75
|
</div>
|
|
44
76
|
<button>
|
|
45
77
|
Add Item
|
|
46
78
|
</button>
|
|
79
|
+
|
|
80
|
+
</div>
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
exports[`for statements > correctly handles keyed for...of loops 1`] = `
|
|
84
|
+
<div>
|
|
85
|
+
<!---->
|
|
86
|
+
<div>
|
|
87
|
+
0:Item 1
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
1:Item 2
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
2:Item 3
|
|
94
|
+
</div>
|
|
95
|
+
<!---->
|
|
47
96
|
<button>
|
|
48
97
|
Reverse
|
|
49
98
|
</button>
|
|
@@ -51,20 +100,37 @@ exports[`for statements > correctly handle the index in a for...of loop 2`] = `
|
|
|
51
100
|
</div>
|
|
52
101
|
`;
|
|
53
102
|
|
|
54
|
-
exports[`for statements > correctly
|
|
103
|
+
exports[`for statements > correctly handles keyed for...of loops 2`] = `
|
|
104
|
+
<div>
|
|
105
|
+
<!---->
|
|
106
|
+
<div>
|
|
107
|
+
0:Item 3
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
1:Item 2
|
|
111
|
+
</div>
|
|
112
|
+
<div>
|
|
113
|
+
2:Item 1
|
|
114
|
+
</div>
|
|
115
|
+
<!---->
|
|
116
|
+
<button>
|
|
117
|
+
Reverse
|
|
118
|
+
</button>
|
|
119
|
+
|
|
120
|
+
</div>
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
exports[`for statements > correctly handles the index in a for...of loop 1`] = `
|
|
55
124
|
<div>
|
|
56
125
|
<div>
|
|
57
126
|
<div>
|
|
58
|
-
0 :
|
|
59
|
-
</div>
|
|
60
|
-
<div>
|
|
61
|
-
1 : c
|
|
127
|
+
0 : a
|
|
62
128
|
</div>
|
|
63
129
|
<div>
|
|
64
|
-
|
|
130
|
+
1 : b
|
|
65
131
|
</div>
|
|
66
132
|
<div>
|
|
67
|
-
|
|
133
|
+
2 : c
|
|
68
134
|
</div>
|
|
69
135
|
|
|
70
136
|
</div>
|
|
@@ -78,82 +144,56 @@ exports[`for statements > correctly handle the index in a for...of loop 3`] = `
|
|
|
78
144
|
</div>
|
|
79
145
|
`;
|
|
80
146
|
|
|
81
|
-
exports[`for statements > correctly handles
|
|
147
|
+
exports[`for statements > correctly handles the index in a for...of loop 2`] = `
|
|
82
148
|
<div>
|
|
83
149
|
<div>
|
|
84
150
|
<div>
|
|
85
|
-
|
|
86
|
-
1
|
|
87
|
-
</div>
|
|
88
|
-
<div>
|
|
89
|
-
1
|
|
90
|
-
</div>
|
|
151
|
+
0 : a
|
|
91
152
|
</div>
|
|
92
153
|
<div>
|
|
93
|
-
|
|
94
|
-
2
|
|
95
|
-
</div>
|
|
96
|
-
<div>
|
|
97
|
-
2
|
|
98
|
-
</div>
|
|
154
|
+
1 : b
|
|
99
155
|
</div>
|
|
100
156
|
<div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
3
|
|
106
|
-
</div>
|
|
157
|
+
2 : c
|
|
158
|
+
</div>
|
|
159
|
+
<div>
|
|
160
|
+
3 : d
|
|
107
161
|
</div>
|
|
108
162
|
|
|
109
163
|
</div>
|
|
110
164
|
<button>
|
|
111
165
|
Add Item
|
|
112
166
|
</button>
|
|
167
|
+
<button>
|
|
168
|
+
Reverse
|
|
169
|
+
</button>
|
|
113
170
|
|
|
114
171
|
</div>
|
|
115
172
|
`;
|
|
116
173
|
|
|
117
|
-
exports[`for statements > correctly handles
|
|
174
|
+
exports[`for statements > correctly handles the index in a for...of loop 3`] = `
|
|
118
175
|
<div>
|
|
119
176
|
<div>
|
|
120
177
|
<div>
|
|
121
|
-
|
|
122
|
-
1
|
|
123
|
-
</div>
|
|
124
|
-
<div>
|
|
125
|
-
1
|
|
126
|
-
</div>
|
|
178
|
+
0 : d
|
|
127
179
|
</div>
|
|
128
180
|
<div>
|
|
129
|
-
|
|
130
|
-
2
|
|
131
|
-
</div>
|
|
132
|
-
<div>
|
|
133
|
-
2
|
|
134
|
-
</div>
|
|
181
|
+
1 : c
|
|
135
182
|
</div>
|
|
136
183
|
<div>
|
|
137
|
-
|
|
138
|
-
3
|
|
139
|
-
</div>
|
|
140
|
-
<div>
|
|
141
|
-
3
|
|
142
|
-
</div>
|
|
184
|
+
2 : b
|
|
143
185
|
</div>
|
|
144
186
|
<div>
|
|
145
|
-
|
|
146
|
-
4
|
|
147
|
-
</div>
|
|
148
|
-
<div>
|
|
149
|
-
4
|
|
150
|
-
</div>
|
|
187
|
+
3 : a
|
|
151
188
|
</div>
|
|
152
189
|
|
|
153
190
|
</div>
|
|
154
191
|
<button>
|
|
155
192
|
Add Item
|
|
156
193
|
</button>
|
|
194
|
+
<button>
|
|
195
|
+
Reverse
|
|
196
|
+
</button>
|
|
157
197
|
|
|
158
198
|
</div>
|
|
159
199
|
`;
|
|
@@ -341,50 +341,6 @@ describe('composite components', () => {
|
|
|
341
341
|
component App() {
|
|
342
342
|
// Ambiguous generics vs JSX / less-than parsing scenarios
|
|
343
343
|
|
|
344
|
-
// 1. Simple "new" with generic (should NOT become an <Element>)
|
|
345
|
-
const a = new TrackedArray<number>();
|
|
346
|
-
|
|
347
|
-
// 2. Multi-line generic with newline after '<'
|
|
348
|
-
const b = new TrackedArray<
|
|
349
|
-
string
|
|
350
|
-
>();
|
|
351
|
-
|
|
352
|
-
// // 3. Member expression + generic
|
|
353
|
-
// class List<T> {
|
|
354
|
-
// items: T[];
|
|
355
|
-
// constructor() {
|
|
356
|
-
// this.items = [];
|
|
357
|
-
// }
|
|
358
|
-
// }
|
|
359
|
-
// class Containers {
|
|
360
|
-
// List<T>() {
|
|
361
|
-
// return new List<T>();
|
|
362
|
-
// }
|
|
363
|
-
// }
|
|
364
|
-
|
|
365
|
-
// const c = new Containers.List<string>();
|
|
366
|
-
|
|
367
|
-
// 4. Chained call with generic method
|
|
368
|
-
const someSource = new Array<number>(1, 2, 3);
|
|
369
|
-
const d = someSource.map<number>((x) => x * 2).filter<boolean>((x) => !!x);
|
|
370
|
-
|
|
371
|
-
// 5. Nested generics
|
|
372
|
-
const e = new Map<string, Promise<number>>();
|
|
373
|
-
|
|
374
|
-
// // 6. Generic after a call expression result
|
|
375
|
-
// function getBuilder<T>() {
|
|
376
|
-
// return {
|
|
377
|
-
// build<U>() {
|
|
378
|
-
// return {
|
|
379
|
-
// build<V>() {
|
|
380
|
-
// return 42;
|
|
381
|
-
// }
|
|
382
|
-
// };
|
|
383
|
-
// }
|
|
384
|
-
// };
|
|
385
|
-
// }
|
|
386
|
-
// const f = getBuilder()<ResultType>().build<number>();
|
|
387
|
-
|
|
388
344
|
// // 7. Generic following optional chaining
|
|
389
345
|
// const maybe = {};
|
|
390
346
|
// const g = maybe?.factory<number>()?.make<boolean>();
|
|
@@ -408,7 +364,7 @@ describe('composite components', () => {
|
|
|
408
364
|
|
|
409
365
|
// 10. JSX / Element should still work
|
|
410
366
|
<div class="still-works">
|
|
411
|
-
<span>{
|
|
367
|
+
<span>{'Test'}</span>
|
|
412
368
|
</div>
|
|
413
369
|
|
|
414
370
|
// // 11. Generic function call vs Element: Identifier followed by generic args
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mount, flushSync, TrackedArray } from 'ripple';
|
|
2
|
+
import { mount, flushSync, TrackedArray, track } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('for statements', () => {
|
|
5
5
|
let container;
|
|
@@ -85,7 +85,7 @@ describe('for statements', () => {
|
|
|
85
85
|
expect(container).toMatchSnapshot();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
it('correctly
|
|
88
|
+
it('correctly handles the index in a for...of loop', () => {
|
|
89
89
|
component App() {
|
|
90
90
|
const items = new TrackedArray('a', 'b', 'c');
|
|
91
91
|
|
|
@@ -115,4 +115,33 @@ describe('for statements', () => {
|
|
|
115
115
|
|
|
116
116
|
expect(container).toMatchSnapshot();
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
it('correctly handles keyed for...of loops', () => {
|
|
120
|
+
component App() {
|
|
121
|
+
let items = track([
|
|
122
|
+
{ id: 1, text: 'Item 1' },
|
|
123
|
+
{ id: 2, text: 'Item 2' },
|
|
124
|
+
{ id: 3, text: 'Item 3' },
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
for (let item of @items; index i; key item.id) {
|
|
128
|
+
<div>{i + ':' + item.text}</div>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
<button onClick={() => {
|
|
132
|
+
@items = @items.toReversed();
|
|
133
|
+
}}>{'Reverse'}</button>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render(App);
|
|
137
|
+
|
|
138
|
+
expect(container).toMatchSnapshot();
|
|
139
|
+
|
|
140
|
+
const button = container.querySelector('button');
|
|
141
|
+
|
|
142
|
+
button.click();
|
|
143
|
+
flushSync();
|
|
144
|
+
|
|
145
|
+
expect(container).toMatchSnapshot();
|
|
146
|
+
});
|
|
118
147
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('generic patterns', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('tests simple generic function', () => {
|
|
24
|
+
component App() {
|
|
25
|
+
const e = new Map<string, Promise<number>>();
|
|
26
|
+
const a = new Array<number>();
|
|
27
|
+
|
|
28
|
+
const b = new Array<
|
|
29
|
+
string
|
|
30
|
+
>();
|
|
31
|
+
|
|
32
|
+
<div class="still-works">
|
|
33
|
+
<span>{'Test'}</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
const someSource = new Array<number>(1, 2, 3);
|
|
37
|
+
const d = someSource.map<number>((x) => x * 2).filter<boolean>((x) => !!x);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
render(App);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('tests member expression)', () => {
|
|
44
|
+
component App() {
|
|
45
|
+
class List<T> {
|
|
46
|
+
items: T[];
|
|
47
|
+
constructor() {
|
|
48
|
+
this.items = [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
<div class="still-works">
|
|
53
|
+
<span>{'Test'}</span>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
class Containers {
|
|
57
|
+
static List<T>() {
|
|
58
|
+
return new List<T>();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const c = Containers.List<string>();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
render(App);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Complex generics tests
|
|
70
|
+
* These currently break the parser
|
|
71
|
+
* We can't just use skip and then the tests are still parsed and compiled
|
|
72
|
+
*/
|
|
73
|
+
/*
|
|
74
|
+
describe('complex generics', () => {
|
|
75
|
+
it('tests after a call expression result', () => {
|
|
76
|
+
component App() {
|
|
77
|
+
function getBuilder() {
|
|
78
|
+
return <T>() => ({
|
|
79
|
+
build<U>(): { build<V>(): V; data: T; key: U } {
|
|
80
|
+
return {
|
|
81
|
+
build<V>(): V {
|
|
82
|
+
return 42 as V;
|
|
83
|
+
},
|
|
84
|
+
data: undefined as T,
|
|
85
|
+
key: undefined as U
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
<div class="still-works">
|
|
92
|
+
<span>{'Test'}</span>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
type ResultType = string;
|
|
96
|
+
|
|
97
|
+
const f = getBuilder()<ResultType>().build<number>();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
*/
|
|
102
|
+
});
|