ripple 0.2.151 → 0.2.153
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/README.md +3 -3
- package/package.json +5 -5
- package/src/compiler/phases/1-parse/index.js +1 -1
- package/src/compiler/phases/3-transform/client/index.js +37 -16
- package/src/compiler/phases/3-transform/server/index.js +43 -25
- package/src/runtime/internal/client/events.js +5 -1
- package/src/runtime/internal/client/index.js +2 -1
- package/src/runtime/internal/client/render.js +18 -15
- package/src/runtime/internal/client/runtime.js +78 -10
- package/src/runtime/internal/server/index.js +51 -11
- package/src/server/index.js +1 -1
- package/tests/client/array/array.derived.test.ripple +61 -33
- package/tests/client/array/array.iteration.test.ripple +3 -1
- package/tests/client/array/array.mutations.test.ripple +19 -15
- package/tests/client/array/array.static.test.ripple +115 -104
- package/tests/client/array/array.to-methods.test.ripple +3 -3
- package/tests/client/basic/basic.attributes.test.ripple +110 -57
- package/tests/client/basic/basic.collections.test.ripple +41 -22
- package/tests/client/basic/basic.errors.test.ripple +12 -6
- package/tests/client/basic/basic.events.test.ripple +51 -33
- package/tests/client/basic/basic.reactivity.test.ripple +120 -56
- package/tests/client/basic/basic.rendering.test.ripple +49 -19
- package/tests/client/basic/basic.styling.test.ripple +2 -2
- package/tests/client/basic/basic.utilities.test.ripple +1 -1
- package/tests/client/boundaries.test.ripple +70 -58
- package/tests/client/compiler/compiler.assignments.test.ripple +32 -4
- package/tests/client/compiler/compiler.attributes.test.ripple +46 -46
- package/tests/client/compiler/compiler.basic.test.ripple +18 -15
- package/tests/client/compiler/compiler.tracked-access.test.ripple +53 -42
- package/tests/client/compiler/compiler.typescript.test.ripple +1 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +6 -6
- package/tests/client/composite/composite.generics.test.ripple +39 -36
- package/tests/client/composite/composite.props.test.ripple +4 -3
- package/tests/client/composite/composite.reactivity.test.ripple +112 -27
- package/tests/client/composite/composite.render.test.ripple +9 -8
- package/tests/client/computed-properties.test.ripple +24 -24
- package/tests/client/context.test.ripple +11 -9
- package/tests/client/date.test.ripple +3 -1
- package/tests/client/dynamic-elements.test.ripple +103 -78
- package/tests/client/for.test.ripple +27 -17
- package/tests/client/head.test.ripple +42 -6
- package/tests/client/html.test.ripple +42 -32
- package/tests/client/input-value.test.ripple +4 -4
- package/tests/client/map.test.ripple +140 -141
- package/tests/client/media-query.test.ripple +31 -31
- package/tests/client/object.test.ripple +148 -112
- package/tests/client/portal.test.ripple +29 -15
- package/tests/client/ref.test.ripple +9 -3
- package/tests/client/set.test.ripple +111 -111
- package/tests/client/tracked-expression.test.ripple +16 -17
- package/tests/client/url/url.derived.test.ripple +19 -9
- package/tests/client/url/url.parsing.test.ripple +24 -8
- package/tests/client/url/url.partial-removal.test.ripple +12 -4
- package/tests/client/url/url.reactivity.test.ripple +63 -25
- package/tests/client/url/url.serialization.test.ripple +18 -6
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +10 -6
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +3 -1
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +26 -14
- package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +3 -1
- package/tests/server/await.test.ripple +23 -22
- package/tests/server/basic.test.ripple +1 -1
- package/tests/server/compiler.test.ripple +3 -7
- package/tests/server/composite.test.ripple +38 -36
- package/tests/server/for.test.ripple +9 -5
- package/tests/server/if.test.ripple +1 -1
- package/tests/server/streaming-ssr.test.ripple +67 -0
- package/types/server.d.ts +5 -4
|
@@ -24,7 +24,7 @@ describe('use value()', () => {
|
|
|
24
24
|
expect(logs).toEqual(['text changed', '', 'text changed', 'Hello']);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
it('should update value on input with a predefined value', () => {
|
|
28
28
|
const logs: string[] = [];
|
|
29
29
|
|
|
30
30
|
component App() {
|
|
@@ -83,9 +83,9 @@ describe('use value()', () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
<select {ref bindValue(select)}>
|
|
86
|
-
<option value="1">{
|
|
87
|
-
<option value="2">{
|
|
88
|
-
<option value="3">{
|
|
86
|
+
<option value="1">{'One'}</option>
|
|
87
|
+
<option value="2">{'Two'}</option>
|
|
88
|
+
<option value="3">{'Three'}</option>
|
|
89
89
|
</select>
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -1,203 +1,202 @@
|
|
|
1
1
|
import { flushSync, TrackedMap, track } from 'ripple';
|
|
2
2
|
|
|
3
3
|
describe('TrackedMap', () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
it('handles set with update and delete operations with a reactive size var', () => {
|
|
5
|
+
component MapTest() {
|
|
6
|
+
let map = new TrackedMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
7
|
+
let value = track(() => map.get('a'));
|
|
8
|
+
let size = track(() => map.size);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<button onClick={() => map.set('d', 4)}>{'set'}</button>
|
|
11
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
12
|
+
<button onClick={() => map.set('a', 5)}>{'update'}</button>
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
<pre>{map.get('d')}</pre>
|
|
15
|
+
<pre>{@size}</pre>
|
|
16
|
+
<pre>{@value}</pre>
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
render(MapTest);
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const setButton = container.querySelectorAll('button')[0];
|
|
22
|
+
const deleteButton = container.querySelectorAll('button')[1];
|
|
23
|
+
const updateButton = container.querySelectorAll('button')[2];
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
setButton.click();
|
|
26
|
+
flushSync();
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('4');
|
|
29
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
|
|
30
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
deleteButton.click();
|
|
33
|
+
flushSync();
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
updateButton.click();
|
|
38
|
+
flushSync();
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
|
|
41
|
+
});
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
it('handles clear operation', () => {
|
|
44
|
+
component MapTest() {
|
|
45
|
+
let map = new TrackedMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
<button onClick={() => map.clear()}>{'clear'}</button>
|
|
48
|
+
<pre>{map.size}</pre>
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
render(MapTest);
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
const clearButton = container.querySelector('button');
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
clearButton.click();
|
|
56
|
+
flushSync();
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
59
|
+
});
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
it('handles has operation and tracks reactive $has', () => {
|
|
62
|
+
component MapTest() {
|
|
63
|
+
let map = new TrackedMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
64
|
+
let has = track(() => map.has('b'));
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
67
|
+
<pre>{@has}</pre>
|
|
68
|
+
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
render(MapTest);
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const deleteButton = container.querySelector('button');
|
|
73
|
+
expect(container.querySelector('pre').textContent).toBe('true');
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
deleteButton.click();
|
|
76
|
+
flushSync();
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
expect(container.querySelector('pre').textContent).toBe('false');
|
|
79
|
+
});
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
it('handles reactivity of keys, values, and entries', () => {
|
|
82
|
+
component MapTest() {
|
|
83
|
+
let map = new TrackedMap([['x', 10], ['y', 20]]);
|
|
84
|
+
let keys = track(() => Array.from(map.keys()));
|
|
85
|
+
let values = track(() => Array.from(map.values()));
|
|
86
|
+
let entries = track(() => Array.from(map.entries()));
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
<button onClick={() => map.delete('x')}>{'delete'}</button>
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
<pre>{JSON.stringify(@keys)}</pre>
|
|
91
|
+
<pre>{JSON.stringify(@values)}</pre>
|
|
92
|
+
<pre>{JSON.stringify(@entries)}</pre>
|
|
93
|
+
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
render(MapTest);
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
const deleteButton = container.querySelector('button');
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
|
|
100
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[10,20]');
|
|
101
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["x",10],["y",20]]');
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
deleteButton.click();
|
|
104
|
+
flushSync();
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["y"]');
|
|
107
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[20]');
|
|
108
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["y",20]]');
|
|
109
|
+
});
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
it('toJSON returns correct object', () => {
|
|
112
|
+
component MapTest() {
|
|
113
|
+
let map = new TrackedMap([['foo', 1], ['bar', 2]]);
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
<pre>{JSON.stringify(map)}</pre>
|
|
116
|
+
}
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
render(MapTest);
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[["foo",1],["bar",2]]');
|
|
121
|
+
});
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
it('creates empty TrackedMap using #Map() shorthand syntax', () => {
|
|
124
|
+
component MapTest() {
|
|
125
|
+
let map = #Map();
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
<button onClick={() => map.set('a', 1)}>{'add'}</button>
|
|
128
|
+
<pre>{map.size}</pre>
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
render(MapTest);
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
const addButton = container.querySelector('button');
|
|
136
|
+
addButton.click();
|
|
137
|
+
flushSync();
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
140
|
+
});
|
|
141
141
|
|
|
142
|
+
it('creates TrackedMap with initial entries using #Map() shorthand syntax', () => {
|
|
143
|
+
component MapTest() {
|
|
144
|
+
let map = #Map([['a', 1], ['b', 2], ['c', 3]]);
|
|
145
|
+
let value = track(() => map.get('b'));
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
<button onClick={() => map.set('b', 10)}>{'update'}</button>
|
|
148
|
+
<pre>{map.size}</pre>
|
|
149
|
+
<pre>{@value}</pre>
|
|
150
|
+
}
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
<pre>{map.size}</pre>
|
|
150
|
-
<pre>{@value}</pre>
|
|
151
|
-
}
|
|
152
|
+
render(MapTest);
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('3');
|
|
155
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
const updateButton = container.querySelector('button');
|
|
158
|
+
updateButton.click();
|
|
159
|
+
flushSync();
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
flushSync();
|
|
161
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('10');
|
|
162
|
+
});
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
it('handles all operations with #Map() shorthand syntax', () => {
|
|
165
|
+
component MapTest() {
|
|
166
|
+
let map = #Map([['x', 100], ['y', 200]]);
|
|
167
|
+
let keys = track(() => Array.from(map.keys()));
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let keys = track(() => Array.from(map.keys()));
|
|
169
|
+
<button onClick={() => map.set('z', 300)}>{'add'}</button>
|
|
170
|
+
<button onClick={() => map.delete('x')}>{'delete'}</button>
|
|
171
|
+
<button onClick={() => map.clear()}>{'clear'}</button>
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
<pre>{JSON.stringify(@keys)}</pre>
|
|
174
|
+
<pre>{map.size}</pre>
|
|
175
|
+
}
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
<pre>{map.size}</pre>
|
|
176
|
-
}
|
|
177
|
+
render(MapTest);
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
const [addButton, deleteButton, clearButton] = container.querySelectorAll('button');
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
|
|
182
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
181
183
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
addButton.click();
|
|
185
|
+
flushSync();
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y","z"]');
|
|
188
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
deleteButton.click();
|
|
191
|
+
flushSync();
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["y","z"]');
|
|
194
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
193
195
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
clearButton.click();
|
|
197
|
+
flushSync();
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
201
|
-
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
202
|
-
});
|
|
199
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
200
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
201
|
+
});
|
|
203
202
|
});
|
|
@@ -3,43 +3,45 @@ import { flushSync, MediaQuery, track } from 'ripple';
|
|
|
3
3
|
type Callback = (event: MediaQueryListEvent) => void;
|
|
4
4
|
|
|
5
5
|
function setupMatchMedia() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
let listeners = new Set<Callback>();
|
|
7
|
+
|
|
8
|
+
// A mock implementation of matchMedia
|
|
9
|
+
const mockMatchMedia = vi.fn().mockImplementation((query) => {
|
|
10
|
+
return {
|
|
11
|
+
media: query,
|
|
12
|
+
matches: false, // default value
|
|
13
|
+
addEventListener: (type: string, cb: Callback) => {
|
|
14
|
+
if (type === 'change') listeners.add(cb);
|
|
15
|
+
},
|
|
16
|
+
removeEventListener: (type: string, cb: Callback) => {
|
|
17
|
+
if (type === 'change') listeners.delete(cb);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/** @param {function(MediaQueryListEvent): void} cb */
|
|
21
|
+
addListener: (cb: Callback) => listeners.add(cb),
|
|
22
|
+
|
|
23
|
+
/** @param {function(MediaQueryListEvent): void} cb */
|
|
24
|
+
removeListener: (cb: Callback) => listeners.delete(cb),
|
|
25
|
+
dispatch: (event: MediaQueryListEvent) => {
|
|
26
|
+
listeners.forEach((cb) => cb(event));
|
|
27
|
+
},
|
|
26
28
|
listenersCount: () => listeners.size,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
};
|
|
30
|
+
});
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
33
|
+
writable: true,
|
|
34
|
+
value: mockMatchMedia,
|
|
35
|
+
});
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
return { mockMatchMedia, listeners };
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
describe('MediaQuery', () => {
|
|
39
41
|
let mm: ReturnType<typeof setupMatchMedia>;
|
|
40
42
|
|
|
41
43
|
beforeEach(() => {
|
|
42
|
-
|
|
44
|
+
mm = setupMatchMedia();
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
it('should be reactive if matchMedia changes', () => {
|
|
@@ -62,7 +64,6 @@ describe('MediaQuery', () => {
|
|
|
62
64
|
const p = container.querySelector('p');
|
|
63
65
|
expect(p.textContent).toBe('false');
|
|
64
66
|
|
|
65
|
-
|
|
66
67
|
mm.mockMatchMedia.mock.results[0].value.matches = true;
|
|
67
68
|
mm.mockMatchMedia.mock.results[0].value.dispatch(event);
|
|
68
69
|
flushSync();
|
|
@@ -78,7 +79,6 @@ describe('MediaQuery', () => {
|
|
|
78
79
|
expect(p.textContent).toBe('false');
|
|
79
80
|
});
|
|
80
81
|
|
|
81
|
-
|
|
82
82
|
it('should have cleared event listeners after unmount', async () => {
|
|
83
83
|
const media = '(min-width: 600px)';
|
|
84
84
|
|
|
@@ -110,7 +110,7 @@ describe('MediaQuery', () => {
|
|
|
110
110
|
flushSync();
|
|
111
111
|
|
|
112
112
|
// wait for microtask queue to flush
|
|
113
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
113
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
114
114
|
|
|
115
115
|
expect(mm.mockMatchMedia.mock.results[0].value.listenersCount()).toBe(0);
|
|
116
116
|
});
|