ripple 0.2.28 → 0.2.29
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 +11 -1
- package/src/compiler/phases/2-analyze/index.js +7 -0
- package/src/compiler/phases/3-transform/index.js +23 -2
- package/src/compiler/utils.js +5 -4
- package/src/runtime/index.js +2 -0
- package/src/runtime/map.js +147 -0
- package/src/runtime/set.js +30 -53
- package/tests/boundaries.ripple +109 -0
- package/tests/compiler.test.ripple +21 -0
- package/tests/map.test.ripple +134 -0
- package/tests/set.test.ripple +1 -1
- package/types/index.d.ts +5 -0
- /package/tests/{use.test.ripple → decorators.ripple} +0 -0
package/package.json
CHANGED
|
@@ -424,12 +424,22 @@ function RipplePlugin(config) {
|
|
|
424
424
|
// This node is used for Prettier, we don't actually need
|
|
425
425
|
// the node for Ripple's transform process
|
|
426
426
|
element.children = [component.css];
|
|
427
|
+
// Ensure we escape JSX <tag></tag> context
|
|
428
|
+
const tokContexts = this.acornTypeScript.tokContexts;
|
|
429
|
+
const curContext = this.curContext();
|
|
430
|
+
|
|
431
|
+
if (curContext === tokContexts.tc_expr) {
|
|
432
|
+
this.context.pop();
|
|
433
|
+
}
|
|
434
|
+
|
|
427
435
|
return element;
|
|
428
436
|
} else {
|
|
437
|
+
this.enterScope(0);
|
|
429
438
|
this.parseTemplateBody(element.children);
|
|
439
|
+
this.exitScope();
|
|
430
440
|
}
|
|
441
|
+
// Ensure we escape JSX <tag></tag> context
|
|
431
442
|
const tokContexts = this.acornTypeScript.tokContexts;
|
|
432
|
-
|
|
433
443
|
const curContext = this.curContext();
|
|
434
444
|
|
|
435
445
|
if (curContext === tokContexts.tc_expr) {
|
|
@@ -151,6 +151,13 @@ const visitors = {
|
|
|
151
151
|
const { state, visit, path } = context;
|
|
152
152
|
|
|
153
153
|
for (const declarator of node.declarations) {
|
|
154
|
+
if (is_inside_component(context) && node.kind === 'var') {
|
|
155
|
+
error(
|
|
156
|
+
'var declarations are not allowed in components, use let or const instead',
|
|
157
|
+
state.analysis.module.filename,
|
|
158
|
+
declarator,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
154
161
|
const metadata = { tracking: false, await: false };
|
|
155
162
|
const parent = path.at(-1);
|
|
156
163
|
const init_is_untracked =
|
|
@@ -133,6 +133,7 @@ const visitors = {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (
|
|
136
|
+
!is_inside_component(context, true) ||
|
|
136
137
|
context.state.to_ts ||
|
|
137
138
|
(parent?.type === 'MemberExpression' && parent.property === node) ||
|
|
138
139
|
is_inside_call_expression(context) ||
|
|
@@ -164,6 +165,10 @@ const visitors = {
|
|
|
164
165
|
context.state.metadata.tracking = true;
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
if (!is_inside_component(context, true) || is_inside_call_expression(context)) {
|
|
169
|
+
return context.next();
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
return b.call(
|
|
168
173
|
'$.with_scope',
|
|
169
174
|
b.id('__block'),
|
|
@@ -572,7 +577,18 @@ const visitors = {
|
|
|
572
577
|
);
|
|
573
578
|
}
|
|
574
579
|
|
|
575
|
-
|
|
580
|
+
const init = [];
|
|
581
|
+
const update = [];
|
|
582
|
+
|
|
583
|
+
transform_children(node.children, { visit, state: { ...state, init, update }, root: false });
|
|
584
|
+
|
|
585
|
+
if (init.length > 0) {
|
|
586
|
+
state.init.push(b.block(init));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (update.length > 0) {
|
|
590
|
+
state.init.push(b.stmt(b.call('$.render', b.thunk(b.block(update)))));
|
|
591
|
+
}
|
|
576
592
|
|
|
577
593
|
state.template.push(`</${node.id.name}>`);
|
|
578
594
|
} else {
|
|
@@ -1258,7 +1274,7 @@ function transform_ts_child(node, context) {
|
|
|
1258
1274
|
.filter((attr) => {
|
|
1259
1275
|
if (attr.type === 'UseAttribute') {
|
|
1260
1276
|
use_attributes.push(attr);
|
|
1261
|
-
return false;
|
|
1277
|
+
return false;
|
|
1262
1278
|
}
|
|
1263
1279
|
return true;
|
|
1264
1280
|
})
|
|
@@ -1415,7 +1431,12 @@ function transform_ts_child(node, context) {
|
|
|
1415
1431
|
),
|
|
1416
1432
|
),
|
|
1417
1433
|
);
|
|
1434
|
+
} else if (node.type === 'Component') {
|
|
1435
|
+
const component = visit(node, context.state);
|
|
1436
|
+
|
|
1437
|
+
state.init.push(component);
|
|
1418
1438
|
} else {
|
|
1439
|
+
debugger;
|
|
1419
1440
|
throw new Error('TODO');
|
|
1420
1441
|
}
|
|
1421
1442
|
}
|
package/src/compiler/utils.js
CHANGED
|
@@ -327,15 +327,16 @@ export function build_hoisted_params(node, context) {
|
|
|
327
327
|
return params;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
export function is_inside_component(context) {
|
|
330
|
+
export function is_inside_component(context, includes_functions = false) {
|
|
331
331
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
332
332
|
const context_node = context.path[i];
|
|
333
333
|
const type = context_node.type;
|
|
334
334
|
|
|
335
335
|
if (
|
|
336
|
-
|
|
337
|
-
type === '
|
|
338
|
-
|
|
336
|
+
!includes_functions &&
|
|
337
|
+
(type === 'FunctionExpression' ||
|
|
338
|
+
type === 'ArrowFunctionExpression' ||
|
|
339
|
+
type === 'FunctionDeclaration')
|
|
339
340
|
) {
|
|
340
341
|
return false;
|
|
341
342
|
}
|
package/src/runtime/index.js
CHANGED
|
@@ -42,6 +42,8 @@ export { RippleArray } from './array.js';
|
|
|
42
42
|
|
|
43
43
|
export { RippleSet } from './set.js';
|
|
44
44
|
|
|
45
|
+
export { RippleMap } from './map.js';
|
|
46
|
+
|
|
45
47
|
export { keyed } from './internal/client/for.js';
|
|
46
48
|
|
|
47
49
|
export { user_effect as effect } from './internal/client/blocks.js';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { get, increment, scope, set, tracked } from './internal/client/runtime.js';
|
|
2
|
+
|
|
3
|
+
const introspect_methods = ['entries', 'forEach', 'values', Symbol.iterator];
|
|
4
|
+
|
|
5
|
+
let init = false;
|
|
6
|
+
|
|
7
|
+
export class RippleMap extends Map {
|
|
8
|
+
#tracked_size;
|
|
9
|
+
#tracked_items = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(iterable) {
|
|
12
|
+
super();
|
|
13
|
+
|
|
14
|
+
var block = scope();
|
|
15
|
+
|
|
16
|
+
if (iterable) {
|
|
17
|
+
for (var [key, value] of iterable) {
|
|
18
|
+
super.set(key, value);
|
|
19
|
+
this.#tracked_items.set(key, tracked(0, block));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.#tracked_size = tracked(this.size, block);
|
|
24
|
+
|
|
25
|
+
if (!init) {
|
|
26
|
+
init = true;
|
|
27
|
+
this.#init();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#init() {
|
|
32
|
+
var proto = RippleMap.prototype;
|
|
33
|
+
var map_proto = Map.prototype;
|
|
34
|
+
|
|
35
|
+
for (const method of introspect_methods) {
|
|
36
|
+
proto[method] = function (...v) {
|
|
37
|
+
this.$size;
|
|
38
|
+
this.#read_all();
|
|
39
|
+
|
|
40
|
+
return map_proto[method].apply(this, v);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get(key) {
|
|
46
|
+
var tracked_items = this.#tracked_items;
|
|
47
|
+
var t = tracked_items.get(key);
|
|
48
|
+
|
|
49
|
+
if (t === undefined) {
|
|
50
|
+
// same logic as has
|
|
51
|
+
this.$size;
|
|
52
|
+
} else {
|
|
53
|
+
get(t);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return super.get(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
has(key) {
|
|
60
|
+
var has = super.has(key);
|
|
61
|
+
var tracked_items = this.#tracked_items;
|
|
62
|
+
var t = tracked_items.get(key);
|
|
63
|
+
|
|
64
|
+
if (t === undefined) {
|
|
65
|
+
// if no tracked it also means super didn't have it
|
|
66
|
+
// It's not possible to have a disconnect, we tract each key
|
|
67
|
+
// If the key doesn't exist, track the size in case it's added later
|
|
68
|
+
// but don't create tracked entries willy-nilly to track all possible keys
|
|
69
|
+
this.$size;
|
|
70
|
+
} else {
|
|
71
|
+
get(t);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return has;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
set(key, value) {
|
|
78
|
+
var block = scope();
|
|
79
|
+
var tracked_items = this.#tracked_items;
|
|
80
|
+
var t = tracked_items.get(key);
|
|
81
|
+
var prev_res = super.get(key);
|
|
82
|
+
|
|
83
|
+
super.set(key, value);
|
|
84
|
+
|
|
85
|
+
if (!t) {
|
|
86
|
+
tracked_items.set(key, tracked(0, block));
|
|
87
|
+
set(this.#tracked_size, this.size, block);
|
|
88
|
+
} else if (prev_res !== value) {
|
|
89
|
+
increment(t, block);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
delete(key) {
|
|
96
|
+
var block = scope();
|
|
97
|
+
var tracked_items = this.#tracked_items;
|
|
98
|
+
var t = tracked_items.get(key);
|
|
99
|
+
var result = super.delete(key);
|
|
100
|
+
|
|
101
|
+
if (t) {
|
|
102
|
+
increment(t, block);
|
|
103
|
+
tracked_items.delete(key);
|
|
104
|
+
set(this.#tracked_size, this.size, block);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
clear() {
|
|
111
|
+
var block = scope();
|
|
112
|
+
|
|
113
|
+
if (super.size === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (var [_, t] of this.#tracked_items) {
|
|
118
|
+
increment(t, block);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
super.clear();
|
|
122
|
+
this.#tracked_items.clear();
|
|
123
|
+
set(this.#tracked_size, 0, block);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
keys() {
|
|
127
|
+
this.$size;
|
|
128
|
+
return super.keys();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#read_all() {
|
|
132
|
+
for (const [, t] of this.#tracked_items) {
|
|
133
|
+
get(t);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get $size() {
|
|
138
|
+
return get(this.#tracked_size);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
toJSON() {
|
|
142
|
+
this.$size;
|
|
143
|
+
this.#read_all();
|
|
144
|
+
|
|
145
|
+
return [...this];
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/runtime/set.js
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import { get, increment, scope, set, tracked } from './internal/client/runtime.js';
|
|
2
2
|
|
|
3
|
-
const introspect_methods = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Symbol.iterator
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
const compare_other_methods = [
|
|
12
|
-
'isDisjointFrom',
|
|
13
|
-
'isSubsetOf',
|
|
14
|
-
'isSupersetOf',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const new_other_methods = [
|
|
18
|
-
'difference',
|
|
19
|
-
'intersection',
|
|
20
|
-
'symmetricDifference',
|
|
21
|
-
'union',
|
|
22
|
-
];
|
|
3
|
+
const introspect_methods = ['entries', 'forEach', 'keys', 'values', Symbol.iterator];
|
|
4
|
+
|
|
5
|
+
const compare_other_methods = ['isDisjointFrom', 'isSubsetOf', 'isSupersetOf'];
|
|
6
|
+
|
|
7
|
+
const new_other_methods = ['difference', 'intersection', 'symmetricDifference', 'union'];
|
|
23
8
|
|
|
24
9
|
let init = false;
|
|
25
10
|
|
|
@@ -91,9 +76,7 @@ export class RippleSet extends Set {
|
|
|
91
76
|
other.$size;
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
return new RippleSet(
|
|
95
|
-
set_proto[method].apply(this, [other, ...v])
|
|
96
|
-
);
|
|
79
|
+
return new RippleSet(set_proto[method].apply(this, [other, ...v]));
|
|
97
80
|
};
|
|
98
81
|
}
|
|
99
82
|
}
|
|
@@ -113,21 +96,17 @@ export class RippleSet extends Set {
|
|
|
113
96
|
delete(value) {
|
|
114
97
|
var block = scope();
|
|
115
98
|
|
|
116
|
-
if (super.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (t) {
|
|
121
|
-
increment(t, block);
|
|
122
|
-
}
|
|
99
|
+
if (!super.delete(value)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
set(this.#tracked_size, this.size, block);
|
|
103
|
+
var t = this.#tracked_items.get(value);
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
increment(t, block);
|
|
106
|
+
this.#tracked_items.delete(value);
|
|
107
|
+
set(this.#tracked_size, this.size, block);
|
|
129
108
|
|
|
130
|
-
return
|
|
109
|
+
return true;
|
|
131
110
|
}
|
|
132
111
|
|
|
133
112
|
has(value) {
|
|
@@ -137,34 +116,32 @@ export class RippleSet extends Set {
|
|
|
137
116
|
var t = tracked_items.get(value);
|
|
138
117
|
|
|
139
118
|
if (t === undefined) {
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
t = tracked(0, block);
|
|
149
|
-
tracked_items.set(value, t);
|
|
119
|
+
// if no tracked it also means super didn't have it
|
|
120
|
+
// It's not possible to have a disconnect, we track each value
|
|
121
|
+
// If the value doesn't exist, track the size in case it's added later
|
|
122
|
+
// but don't create tracked entries willy-nilly to track all possible values
|
|
123
|
+
this.$size;
|
|
124
|
+
} else {
|
|
125
|
+
get(t);
|
|
150
126
|
}
|
|
151
127
|
|
|
152
|
-
get(t);
|
|
153
128
|
return has;
|
|
154
129
|
}
|
|
155
130
|
|
|
156
131
|
clear() {
|
|
157
132
|
var block = scope();
|
|
158
133
|
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
134
|
+
if (super.size === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
163
137
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
set(this.#tracked_size, 0, block);
|
|
138
|
+
for (var [_, t] of this.#tracked_items) {
|
|
139
|
+
increment(t, block);
|
|
167
140
|
}
|
|
141
|
+
|
|
142
|
+
super.clear();
|
|
143
|
+
this.#tracked_items.clear();
|
|
144
|
+
set(this.#tracked_size, 0, block);
|
|
168
145
|
}
|
|
169
146
|
|
|
170
147
|
get $size() {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mount, flushSync, effect } from 'ripple';
|
|
4
|
+
|
|
5
|
+
describe('passing reactivity between boundaries tests', () => {
|
|
6
|
+
let container;
|
|
7
|
+
|
|
8
|
+
function render(component) {
|
|
9
|
+
mount(component, {
|
|
10
|
+
target: container
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
container = document.createElement('div');
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
document.body.removeChild(container);
|
|
21
|
+
container = null;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('can pass reactivity between functions with simple arrays and destructuring', () => {
|
|
25
|
+
let log: string[] = [];
|
|
26
|
+
|
|
27
|
+
function createDouble([ $count ]) {
|
|
28
|
+
const $double = $count * 2;
|
|
29
|
+
|
|
30
|
+
effect(() => {
|
|
31
|
+
log.push('Count:' + $count);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return [ $double ];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
component App() {
|
|
38
|
+
let $count = 0;
|
|
39
|
+
|
|
40
|
+
const [ $double ] = createDouble([ $count ]);
|
|
41
|
+
|
|
42
|
+
<div>{'Double: ' + $double}</div>
|
|
43
|
+
<button onClick={() => { $count++; }}>{'Increment'}</button>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render(App);
|
|
47
|
+
flushSync();
|
|
48
|
+
|
|
49
|
+
expect(container.querySelector('div').textContent).toBe('Double: 0');
|
|
50
|
+
expect(log).toEqual(['Count:0']);
|
|
51
|
+
|
|
52
|
+
const button = container.querySelector('button');
|
|
53
|
+
|
|
54
|
+
button.click();
|
|
55
|
+
flushSync();
|
|
56
|
+
|
|
57
|
+
expect(container.querySelector('div').textContent).toBe('Double: 2');
|
|
58
|
+
expect(log).toEqual(['Count:0', 'Count:1']);
|
|
59
|
+
|
|
60
|
+
button.click();
|
|
61
|
+
flushSync();
|
|
62
|
+
|
|
63
|
+
expect(container.querySelector('div').textContent).toBe('Double: 4');
|
|
64
|
+
expect(log).toEqual(['Count:0', 'Count:1', 'Count:2']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('can pass reactivity between functions with simple objects and destructuring', () => {
|
|
68
|
+
let log: string[] = [];
|
|
69
|
+
|
|
70
|
+
function createDouble({ $count }) {
|
|
71
|
+
const $double = $count * 2;
|
|
72
|
+
|
|
73
|
+
effect(() => {
|
|
74
|
+
log.push('Count:' + $count);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { $double };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
component App() {
|
|
81
|
+
let $count = 0;
|
|
82
|
+
|
|
83
|
+
const { $double } = createDouble({ $count });
|
|
84
|
+
|
|
85
|
+
<div>{'Double: ' + $double}</div>
|
|
86
|
+
<button onClick={() => { $count++; }}>{'Increment'}</button>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(App);
|
|
90
|
+
flushSync();
|
|
91
|
+
|
|
92
|
+
expect(container.querySelector('div').textContent).toBe('Double: 0');
|
|
93
|
+
expect(log).toEqual(['Count:0']);
|
|
94
|
+
|
|
95
|
+
const button = container.querySelector('button');
|
|
96
|
+
|
|
97
|
+
button.click();
|
|
98
|
+
flushSync();
|
|
99
|
+
|
|
100
|
+
expect(container.querySelector('div').textContent).toBe('Double: 2');
|
|
101
|
+
expect(log).toEqual(['Count:0', 'Count:1']);
|
|
102
|
+
|
|
103
|
+
button.click();
|
|
104
|
+
flushSync();
|
|
105
|
+
|
|
106
|
+
expect(container.querySelector('div').textContent).toBe('Double: 4');
|
|
107
|
+
expect(log).toEqual(['Count:0', 'Count:1', 'Count:2']);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -58,4 +58,25 @@ describe('compiler success tests', () => {
|
|
|
58
58
|
render(App);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
it('render lexical blocks without crashing', () => {
|
|
62
|
+
component App() {
|
|
63
|
+
<div>
|
|
64
|
+
const a = 1;
|
|
65
|
+
<div>
|
|
66
|
+
const b = 1;
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
const b = 1;
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
const a = 2;
|
|
74
|
+
<div>
|
|
75
|
+
const b = 1;
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(App);
|
|
81
|
+
})
|
|
61
82
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, RippleMap } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('RippleMap', () => {
|
|
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('handles set with update and delete operations with a reactive $size var', () => {
|
|
24
|
+
component MapTest() {
|
|
25
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
26
|
+
let $value = map.get('a');
|
|
27
|
+
let $size = map.$size;
|
|
28
|
+
|
|
29
|
+
<button onClick={() => map.set('d', 4)}>{'set'}</button>
|
|
30
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
31
|
+
<button onClick={() => map.set('a', 5)}>{'update'}</button>
|
|
32
|
+
|
|
33
|
+
<pre>{map.get('d')}</pre>
|
|
34
|
+
<pre>{$size}</pre>
|
|
35
|
+
<pre>{$value}</pre>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render(MapTest);
|
|
39
|
+
|
|
40
|
+
const setButton = container.querySelectorAll('button')[0];
|
|
41
|
+
const deleteButton = container.querySelectorAll('button')[1];
|
|
42
|
+
const updateButton = container.querySelectorAll('button')[2];
|
|
43
|
+
|
|
44
|
+
setButton.click();
|
|
45
|
+
flushSync();
|
|
46
|
+
|
|
47
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('4');
|
|
48
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
|
|
49
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
|
|
50
|
+
|
|
51
|
+
deleteButton.click();
|
|
52
|
+
flushSync();
|
|
53
|
+
|
|
54
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
55
|
+
|
|
56
|
+
updateButton.click();
|
|
57
|
+
flushSync();
|
|
58
|
+
|
|
59
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles clear operation', () => {
|
|
63
|
+
component MapTest() {
|
|
64
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
65
|
+
|
|
66
|
+
<button onClick={() => map.clear()}>{'clear'}</button>
|
|
67
|
+
<pre>{map.$size}</pre>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(MapTest);
|
|
71
|
+
|
|
72
|
+
const clearButton = container.querySelector('button');
|
|
73
|
+
|
|
74
|
+
clearButton.click();
|
|
75
|
+
flushSync();
|
|
76
|
+
|
|
77
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles has operation and tracks reactive $has', () => {
|
|
81
|
+
component MapTest() {
|
|
82
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
83
|
+
let $has = map.has('b');
|
|
84
|
+
|
|
85
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
86
|
+
<pre>{$has}</pre>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(MapTest);
|
|
90
|
+
|
|
91
|
+
const deleteButton = container.querySelector('button');
|
|
92
|
+
expect(container.querySelector('pre').textContent).toBe('true');
|
|
93
|
+
|
|
94
|
+
deleteButton.click();
|
|
95
|
+
flushSync();
|
|
96
|
+
|
|
97
|
+
expect(container.querySelector('pre').textContent).toBe('false');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles reactivity of keys, values, and entries', () => {
|
|
101
|
+
component MapTest() {
|
|
102
|
+
let map = new RippleMap([['x', 10], ['y', 20]]);
|
|
103
|
+
let $keys = Array.from(map.keys());
|
|
104
|
+
let $values = Array.from(map.values());
|
|
105
|
+
let $entries = Array.from(map.entries());
|
|
106
|
+
|
|
107
|
+
<button onClick={() => map.delete('x')}>{'delete'}</button>
|
|
108
|
+
|
|
109
|
+
<pre>{JSON.stringify($keys)}</pre>
|
|
110
|
+
<pre>{JSON.stringify($values)}</pre>
|
|
111
|
+
<pre>{JSON.stringify($entries)}</pre>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render(MapTest);
|
|
115
|
+
|
|
116
|
+
const deleteButton = container.querySelector('button');
|
|
117
|
+
|
|
118
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
|
|
119
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[10,20]');
|
|
120
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["x",10],["y",20]]');
|
|
121
|
+
|
|
122
|
+
deleteButton.click();
|
|
123
|
+
flushSync();
|
|
124
|
+
|
|
125
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["y"]');
|
|
126
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[20]');
|
|
127
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["y",20]]');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('toJSON returns correct object', () => {
|
|
131
|
+
let map = new RippleMap([['foo', 1], ['bar', 2]]);
|
|
132
|
+
expect(JSON.stringify(map)).toEqual('[["foo",1],["bar",2]]');
|
|
133
|
+
});
|
|
134
|
+
});
|
package/tests/set.test.ripple
CHANGED
package/types/index.d.ts
CHANGED
|
File without changes
|