ripple 0.2.98 → 0.2.99
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 +26 -18
- 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';
|
|
@@ -85,15 +85,18 @@ function collection_to_array(collection) {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* @template V
|
|
88
|
+
* @template K
|
|
88
89
|
* @param {Element} node
|
|
89
90
|
* @param {() => V[] | Iterable<V>} get_collection
|
|
90
91
|
* @param {(anchor: Node, value: V, index?: any) => Block} render_fn
|
|
91
92
|
* @param {number} flags
|
|
93
|
+
* @param {(item: V) => K} [get_key]
|
|
92
94
|
* @returns {void}
|
|
93
95
|
*/
|
|
94
|
-
export function for_block(node, get_collection, render_fn, flags) {
|
|
96
|
+
export function for_block(node, get_collection, render_fn, flags, get_key) {
|
|
95
97
|
var is_controlled = (flags & IS_CONTROLLED) !== 0;
|
|
96
98
|
var is_indexed = (flags & IS_INDEXED) !== 0;
|
|
99
|
+
var is_keyed = (flags & IS_KEYED) !== 0;
|
|
97
100
|
var anchor = /** @type {Element | Text} */ (node);
|
|
98
101
|
|
|
99
102
|
if (is_controlled) {
|
|
@@ -104,6 +107,9 @@ export function for_block(node, get_collection, render_fn, flags) {
|
|
|
104
107
|
var block = /** @type {Block} */ (active_block);
|
|
105
108
|
var collection = get_collection();
|
|
106
109
|
var array = collection_to_array(collection);
|
|
110
|
+
if (is_keyed) {
|
|
111
|
+
array = keyed(block, array, /** @type {(item: V) => K} */ (get_key));
|
|
112
|
+
}
|
|
107
113
|
|
|
108
114
|
untrack(() => {
|
|
109
115
|
reconcile(anchor, block, array, render_fn, is_controlled, is_indexed);
|
|
@@ -321,9 +327,9 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
|
|
|
321
327
|
if (j !== undefined) {
|
|
322
328
|
if (fast_path_removal) {
|
|
323
329
|
fast_path_removal = false;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
while (i > a_start) {
|
|
331
|
+
destroy_block(a[a_start++]);
|
|
332
|
+
}
|
|
327
333
|
}
|
|
328
334
|
sources[j - b_start] = i + 1;
|
|
329
335
|
if (pos > j) {
|
|
@@ -396,7 +402,7 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
|
|
|
396
402
|
let result;
|
|
397
403
|
/** @type {Int32Array} */
|
|
398
404
|
let p;
|
|
399
|
-
let
|
|
405
|
+
let max_len = 0;
|
|
400
406
|
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
|
|
401
407
|
/**
|
|
402
408
|
* @param {Int32Array} arr
|
|
@@ -412,8 +418,8 @@ function lis_algorithm(arr) {
|
|
|
412
418
|
let c = 0;
|
|
413
419
|
var len = arr.length;
|
|
414
420
|
|
|
415
|
-
if (len >
|
|
416
|
-
|
|
421
|
+
if (len > max_len) {
|
|
422
|
+
max_len = len;
|
|
417
423
|
result = new Int32Array(len);
|
|
418
424
|
p = new Int32Array(len);
|
|
419
425
|
}
|
|
@@ -466,16 +472,12 @@ function lis_algorithm(arr) {
|
|
|
466
472
|
/**
|
|
467
473
|
* @template V
|
|
468
474
|
* @template K
|
|
475
|
+
* @param {Block} block
|
|
469
476
|
* @param {V[] | Iterable<V>} collection
|
|
470
477
|
* @param {(item: V) => K} key_fn
|
|
471
478
|
* @returns {V[]}
|
|
472
479
|
*/
|
|
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
|
-
|
|
480
|
+
function keyed(block, collection, key_fn) {
|
|
479
481
|
var b_array = collection_to_array(collection);
|
|
480
482
|
var b_keys = b_array.map(key_fn);
|
|
481
483
|
|
|
@@ -488,14 +490,16 @@ export function keyed(collection, key_fn) {
|
|
|
488
490
|
var state = block.s;
|
|
489
491
|
|
|
490
492
|
if (state === null) {
|
|
491
|
-
|
|
493
|
+
// Make a clone of it so we don't mutate the original thereafter
|
|
494
|
+
return b_array.slice();
|
|
492
495
|
}
|
|
493
496
|
|
|
494
497
|
var a_array = state.array;
|
|
498
|
+
var a_blocks = state.blocks;
|
|
495
499
|
var a_keys = a_array.map(key_fn);
|
|
496
500
|
var a = new Map();
|
|
497
501
|
|
|
498
|
-
for (
|
|
502
|
+
for (var i = 0; i < a_keys.length; i++) {
|
|
499
503
|
a.set(a_keys[i], i);
|
|
500
504
|
}
|
|
501
505
|
|
|
@@ -503,8 +507,12 @@ export function keyed(collection, key_fn) {
|
|
|
503
507
|
throw new Error('Duplicate keys are not allowed');
|
|
504
508
|
}
|
|
505
509
|
|
|
506
|
-
for (
|
|
510
|
+
for (var i = 0; i < b_keys.length; i++) {
|
|
507
511
|
var b_val = b_keys[i];
|
|
512
|
+
// if the index is the key, skip
|
|
513
|
+
if (b_val === i) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
508
516
|
var index = a.get(b_val);
|
|
509
517
|
|
|
510
518
|
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
|
+
});
|