ripple 0.2.116 → 0.2.119
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 +16 -16
- package/src/compiler/index.js +20 -1
- package/src/compiler/phases/3-transform/segments.js +99 -57
- package/src/compiler/phases/3-transform/server/index.js +21 -11
- package/src/runtime/index-client.js +1 -1
- package/src/runtime/index-server.js +24 -0
- package/src/runtime/internal/client/runtime.js +10 -0
- package/src/runtime/internal/client/utils.js +0 -8
- package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
- package/tests/client/_etc.test.ripple +5 -0
- package/tests/client/array/array.copy-within.test.ripple +120 -0
- package/tests/client/array/array.derived.test.ripple +495 -0
- package/tests/client/array/array.iteration.test.ripple +115 -0
- package/tests/client/array/array.mutations.test.ripple +385 -0
- package/tests/client/array/array.static.test.ripple +237 -0
- package/tests/client/array/array.to-methods.test.ripple +93 -0
- package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
- package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
- package/tests/client/basic/basic.attributes.test.ripple +474 -0
- package/tests/client/basic/basic.collections.test.ripple +94 -0
- package/tests/client/basic/basic.components.test.ripple +225 -0
- package/tests/client/basic/basic.errors.test.ripple +126 -0
- package/tests/client/basic/basic.events.test.ripple +222 -0
- package/tests/client/basic/basic.reactivity.test.ripple +476 -0
- package/tests/client/basic/basic.rendering.test.ripple +204 -0
- package/tests/client/basic/basic.styling.test.ripple +63 -0
- package/tests/client/basic/basic.utilities.test.ripple +25 -0
- package/tests/client/boundaries.test.ripple +2 -21
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
- package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
- package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
- package/tests/client/compiler/compiler.basic.test.ripple +203 -0
- package/tests/client/compiler/compiler.regex.test.ripple +87 -0
- package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
- package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
- package/tests/client/composite/composite.generics.test.ripple +211 -0
- package/tests/client/composite/composite.props.test.ripple +106 -0
- package/tests/client/composite/composite.reactivity.test.ripple +184 -0
- package/tests/client/composite/composite.render.test.ripple +84 -0
- package/tests/client/computed-properties.test.ripple +2 -21
- package/tests/client/context.test.ripple +5 -22
- package/tests/client/date.test.ripple +1 -20
- package/tests/client/dynamic-elements.test.ripple +16 -24
- package/tests/client/for.test.ripple +4 -23
- package/tests/client/head.test.ripple +11 -23
- package/tests/client/html.test.ripple +1 -20
- package/tests/client/input-value.test.ripple +11 -31
- package/tests/client/map.test.ripple +3 -22
- package/tests/client/media-query.test.ripple +10 -23
- package/tests/client/object.test.ripple +5 -24
- package/tests/client/portal.test.ripple +2 -19
- package/tests/client/ref.test.ripple +8 -26
- package/tests/client/set.test.ripple +3 -22
- package/tests/client/svg.test.ripple +1 -22
- package/tests/client/switch.test.ripple +6 -25
- package/tests/client/tracked-expression.test.ripple +2 -21
- package/tests/client/typescript-generics.test.ripple +0 -21
- package/tests/client/url/url.derived.test.ripple +83 -0
- package/tests/client/url/url.parsing.test.ripple +165 -0
- package/tests/client/url/url.partial-removal.test.ripple +198 -0
- package/tests/client/url/url.reactivity.test.ripple +449 -0
- package/tests/client/url/url.serialization.test.ripple +50 -0
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
- package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
- package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
- package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
- package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
- package/tests/client.d.ts +12 -0
- package/tests/server/if.test.ripple +66 -0
- package/tests/setup-client.js +28 -0
- package/tsconfig.json +4 -2
- package/types/index.d.ts +5 -1
- package/LICENSE +0 -21
- package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
- package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
- package/tests/client/array.test.ripple +0 -1455
- package/tests/client/basic.test.ripple +0 -1892
- package/tests/client/compiler.test.ripple +0 -541
- package/tests/client/composite.test.ripple +0 -692
- package/tests/client/url-search-params.test.ripple +0 -912
- package/tests/client/url.test.ripple +0 -954
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { track, flushSync } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('basic client > attribute rendering', () => {
|
|
5
|
+
it('render static attributes', () => {
|
|
6
|
+
component Basic() {
|
|
7
|
+
<div class='foo' id='bar' style='color: red;'>{'Hello World'}</div>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
render(Basic);
|
|
11
|
+
|
|
12
|
+
expect(container).toMatchSnapshot();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('render dynamic class attribute', () => {
|
|
16
|
+
component Basic() {
|
|
17
|
+
let active = track(false);
|
|
18
|
+
|
|
19
|
+
<button onClick={() => { @active = !@active }}>{'Toggle'}</button>
|
|
20
|
+
<div class={@active ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
.active {
|
|
24
|
+
color: green;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
render(Basic);
|
|
30
|
+
|
|
31
|
+
const button = container.querySelector('button');
|
|
32
|
+
const div = container.querySelector('div');
|
|
33
|
+
|
|
34
|
+
expect(Array.from(div.classList).some(className => className.startsWith('ripple-'))).toBe(true);
|
|
35
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
36
|
+
|
|
37
|
+
button.click();
|
|
38
|
+
flushSync();
|
|
39
|
+
expect(div.classList.contains('active')).toBe(true);
|
|
40
|
+
|
|
41
|
+
button.click();
|
|
42
|
+
flushSync();
|
|
43
|
+
|
|
44
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('render class attribute with array, nested array, nested object', () => {
|
|
48
|
+
component Basic() {
|
|
49
|
+
<div class={[
|
|
50
|
+
'foo',
|
|
51
|
+
'bar',
|
|
52
|
+
true && 'baz',
|
|
53
|
+
false && 'aaa',
|
|
54
|
+
null && 'bbb',
|
|
55
|
+
[
|
|
56
|
+
'ccc',
|
|
57
|
+
'ddd',
|
|
58
|
+
{ eee: true, fff: false }
|
|
59
|
+
]
|
|
60
|
+
]}>
|
|
61
|
+
{'Class Array'}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.foo {
|
|
66
|
+
color: red;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
render(Basic);
|
|
72
|
+
|
|
73
|
+
const div = container.querySelector('div');
|
|
74
|
+
|
|
75
|
+
expect(Array.from(div.classList).some(className => className.startsWith('ripple-'))).toBe(true);
|
|
76
|
+
expect(div.classList.contains('foo')).toBe(true);
|
|
77
|
+
expect(div.classList.contains('bar')).toBe(true);
|
|
78
|
+
expect(div.classList.contains('baz')).toBe(true);
|
|
79
|
+
expect(div.classList.contains('aaa')).toBe(false);
|
|
80
|
+
expect(div.classList.contains('bbb')).toBe(false);
|
|
81
|
+
expect(div.classList.contains('ccc')).toBe(true);
|
|
82
|
+
expect(div.classList.contains('ddd')).toBe(true);
|
|
83
|
+
expect(div.classList.contains('eee')).toBe(true);
|
|
84
|
+
expect(div.classList.contains('fff')).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('render dynamic class object', () => {
|
|
88
|
+
component Basic() {
|
|
89
|
+
let active = track(false);
|
|
90
|
+
|
|
91
|
+
<button onClick={() => { @active = !@active }}>{'Toggle'}</button>
|
|
92
|
+
<div class={{ active: @active, inactive: !@active }}>{'Dynamic Class'}</div>
|
|
93
|
+
|
|
94
|
+
<style>
|
|
95
|
+
.active {
|
|
96
|
+
color: green;
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
render(Basic);
|
|
102
|
+
|
|
103
|
+
const button = container.querySelector('button');
|
|
104
|
+
const div = container.querySelector('div');
|
|
105
|
+
|
|
106
|
+
expect(Array.from(div.classList).some(className => className.startsWith('ripple-'))).toBe(true);
|
|
107
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
108
|
+
expect(div.classList.contains('active')).toBe(false);
|
|
109
|
+
|
|
110
|
+
button.click();
|
|
111
|
+
flushSync();
|
|
112
|
+
expect(div.classList.contains('inactive')).toBe(false);
|
|
113
|
+
expect(div.classList.contains('active')).toBe(true);
|
|
114
|
+
|
|
115
|
+
button.click();
|
|
116
|
+
flushSync();
|
|
117
|
+
|
|
118
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
119
|
+
expect(div.classList.contains('active')).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('render dynamic id attribute', () => {
|
|
123
|
+
component Basic() {
|
|
124
|
+
let count = track(0);
|
|
125
|
+
|
|
126
|
+
<button onClick={() => { @count++ }}>{'Increment'}</button>
|
|
127
|
+
<div id={`item-${@count}`}>{'Dynamic ID'}</div>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
render(Basic);
|
|
131
|
+
|
|
132
|
+
const button = container.querySelector('button');
|
|
133
|
+
const div = container.querySelector('div');
|
|
134
|
+
|
|
135
|
+
expect(div.id).toBe('item-0');
|
|
136
|
+
|
|
137
|
+
button.click();
|
|
138
|
+
flushSync();
|
|
139
|
+
|
|
140
|
+
expect(div.id).toBe('item-1');
|
|
141
|
+
|
|
142
|
+
button.click();
|
|
143
|
+
flushSync();
|
|
144
|
+
|
|
145
|
+
expect(div.id).toBe('item-2');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('render dynamic style attribute', () => {
|
|
149
|
+
component Basic() {
|
|
150
|
+
let color = track('red');
|
|
151
|
+
|
|
152
|
+
<button onClick={() => { @color = @color === 'red' ? 'blue' : 'red' }}>{'Change Color'}</button>
|
|
153
|
+
<div style={`color: ${@color}; font-weight: bold;`}>{'Dynamic Style'}</div>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
render(Basic);
|
|
157
|
+
|
|
158
|
+
const button = container.querySelector('button');
|
|
159
|
+
const div = container.querySelector('div');
|
|
160
|
+
|
|
161
|
+
expect(div.style.color).toBe('red');
|
|
162
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
163
|
+
|
|
164
|
+
button.click();
|
|
165
|
+
flushSync();
|
|
166
|
+
|
|
167
|
+
expect(div.style.color).toBe('blue');
|
|
168
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('render style attribute as dynamic object', () => {
|
|
172
|
+
component Basic() {
|
|
173
|
+
let color = track('red');
|
|
174
|
+
|
|
175
|
+
<button onClick={() => { @color = @color === 'red' ? 'blue' : 'red' }}>{'Change Color'}</button>
|
|
176
|
+
<div style={{
|
|
177
|
+
color: @color,
|
|
178
|
+
fontWeight: 'bold',
|
|
179
|
+
}}>{'Dynamic Style'}</div>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
render(Basic);
|
|
183
|
+
|
|
184
|
+
const button = container.querySelector('button');
|
|
185
|
+
const div = container.querySelector('div');
|
|
186
|
+
|
|
187
|
+
expect(div.style.color).toBe('red');
|
|
188
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
189
|
+
|
|
190
|
+
button.click();
|
|
191
|
+
flushSync();
|
|
192
|
+
|
|
193
|
+
expect(div.style.color).toBe('blue');
|
|
194
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('render tracked variable as style attribute', () => {
|
|
198
|
+
component Basic() {
|
|
199
|
+
let style = track({ color: 'red', fontWeight: 'bold' });
|
|
200
|
+
|
|
201
|
+
function toggleColor() {
|
|
202
|
+
@style = { ...@style, color: @style.color === 'red' ? 'blue' : 'red' };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
<button onClick={toggleColor}>{'Change Color'}</button>
|
|
206
|
+
<div style={@style}>{'Dynamic Style'}</div>
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render(Basic);
|
|
210
|
+
|
|
211
|
+
const button = container.querySelector('button');
|
|
212
|
+
const div = container.querySelector('div');
|
|
213
|
+
|
|
214
|
+
expect(div.style.color).toBe('red');
|
|
215
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
216
|
+
|
|
217
|
+
button.click();
|
|
218
|
+
flushSync();
|
|
219
|
+
|
|
220
|
+
expect(div.style.color).toBe('blue');
|
|
221
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('render tracked object as style attribute', () => {
|
|
225
|
+
component Basic() {
|
|
226
|
+
let style = #{ color: 'red', fontWeight: 'bold' };
|
|
227
|
+
|
|
228
|
+
function toggleColor() {
|
|
229
|
+
@style.color = @style.color === 'red' ? 'blue' : 'red';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
<button onClick={toggleColor}>{'Change Color'}</button>
|
|
233
|
+
<div style={{ ...@style }}>{'Dynamic Style'}</div>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
render(Basic);
|
|
237
|
+
|
|
238
|
+
const button = container.querySelector('button');
|
|
239
|
+
const div = container.querySelector('div');
|
|
240
|
+
|
|
241
|
+
expect(div.style.color).toBe('red');
|
|
242
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
243
|
+
|
|
244
|
+
button.click();
|
|
245
|
+
flushSync();
|
|
246
|
+
|
|
247
|
+
expect(div.style.color).toBe('blue');
|
|
248
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('render spread attributes with style and class', () => {
|
|
252
|
+
component Basic() {
|
|
253
|
+
const attributes = {
|
|
254
|
+
style: { color: 'red', fontWeight: 'bold' },
|
|
255
|
+
class: ['foo', false && 'bar'],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
<div {...attributes}>{'Attributes with style and class'}</div>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
render(Basic);
|
|
262
|
+
|
|
263
|
+
const div = container.querySelector('div');
|
|
264
|
+
|
|
265
|
+
expect(div.style.color).toBe('red');
|
|
266
|
+
expect(div.style.fontWeight).toBe('bold');
|
|
267
|
+
|
|
268
|
+
expect(div.classList.contains('foo')).toBe(true);
|
|
269
|
+
expect(div.classList.contains('bar')).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('render spread props without duplication', () => {
|
|
273
|
+
component App() {
|
|
274
|
+
const checkBoxProp = {name:'car'}
|
|
275
|
+
|
|
276
|
+
<div>
|
|
277
|
+
<input {...checkBoxProp} type="checkbox" id="vehicle1" value="Bike" />
|
|
278
|
+
</div>
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
render(App);
|
|
282
|
+
|
|
283
|
+
const input = container.querySelector('input');
|
|
284
|
+
const html = container.innerHTML;
|
|
285
|
+
|
|
286
|
+
expect(input.getAttribute('name')).toBe('car');
|
|
287
|
+
expect(input.getAttribute('type')).toBe('checkbox');
|
|
288
|
+
expect(input.getAttribute('id')).toBe('vehicle1');
|
|
289
|
+
expect(input.getAttribute('value')).toBe('Bike');
|
|
290
|
+
|
|
291
|
+
expect(html).not.toContain('type="checkbox"type="checkbox"');
|
|
292
|
+
expect(html).not.toContain('value="Bike"value="Bike"');
|
|
293
|
+
|
|
294
|
+
expect(container).toMatchSnapshot();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('render dynamic boolean attributes', () => {
|
|
298
|
+
component Basic() {
|
|
299
|
+
let disabled = track(false);
|
|
300
|
+
let checked = track(false);
|
|
301
|
+
|
|
302
|
+
<button onClick={() => {
|
|
303
|
+
@disabled = !@disabled;
|
|
304
|
+
@checked = !@checked;
|
|
305
|
+
}}>{'Toggle'}</button>
|
|
306
|
+
<input type='checkbox' disabled={@disabled} checked={@checked} />
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
render(Basic);
|
|
310
|
+
|
|
311
|
+
const button = container.querySelector('button');
|
|
312
|
+
const input = container.querySelector('input');
|
|
313
|
+
|
|
314
|
+
expect(input.disabled).toBe(false);
|
|
315
|
+
expect(input.checked).toBe(false);
|
|
316
|
+
|
|
317
|
+
button.click();
|
|
318
|
+
flushSync();
|
|
319
|
+
|
|
320
|
+
expect(input.disabled).toBe(true);
|
|
321
|
+
expect(input.checked).toBe(true);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('render multiple dynamic attributes', () => {
|
|
325
|
+
component Basic() {
|
|
326
|
+
let theme = track('light');
|
|
327
|
+
let size = track('medium');
|
|
328
|
+
|
|
329
|
+
<button
|
|
330
|
+
onClick={() => {
|
|
331
|
+
@theme = @theme === 'light' ? 'dark' : 'light';
|
|
332
|
+
@size = @size === 'medium' ? 'large' : 'medium';
|
|
333
|
+
}}
|
|
334
|
+
>{'Toggle Theme & Size'}</button>
|
|
335
|
+
<div class={`theme-${@theme} size-${@size}`} data-theme={@theme} data-size={@size}>{'Multiple Dynamic Attributes'}</div>
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
render(Basic);
|
|
339
|
+
|
|
340
|
+
const button = container.querySelector('button');
|
|
341
|
+
const div = container.querySelector('div');
|
|
342
|
+
|
|
343
|
+
expect(div.className).toBe('theme-light size-medium');
|
|
344
|
+
expect(div.getAttribute('data-theme')).toBe('light');
|
|
345
|
+
expect(div.getAttribute('data-size')).toBe('medium');
|
|
346
|
+
|
|
347
|
+
button.click();
|
|
348
|
+
flushSync();
|
|
349
|
+
|
|
350
|
+
expect(div.className).toBe('theme-dark size-large');
|
|
351
|
+
expect(div.getAttribute('data-theme')).toBe('dark');
|
|
352
|
+
expect(div.getAttribute('data-size')).toBe('large');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('render conditional attributes', () => {
|
|
356
|
+
component Basic() {
|
|
357
|
+
let showTitle = track(false);
|
|
358
|
+
let showAria = track(false);
|
|
359
|
+
|
|
360
|
+
<button onClick={() => {
|
|
361
|
+
@showTitle = !@showTitle;
|
|
362
|
+
@showAria = !@showAria;
|
|
363
|
+
}}>{'Toggle Attributes'}</button>
|
|
364
|
+
<div
|
|
365
|
+
title={@showTitle ? 'This is a title' : null}
|
|
366
|
+
aria-label={@showAria ? 'Accessible label' : null}
|
|
367
|
+
>{'Conditional Attributes'}</div>
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
render(Basic);
|
|
371
|
+
|
|
372
|
+
const button = container.querySelector('button');
|
|
373
|
+
const div = container.querySelector('div');
|
|
374
|
+
|
|
375
|
+
expect(div.hasAttribute('title')).toBe(false);
|
|
376
|
+
expect(div.hasAttribute('aria-label')).toBe(false);
|
|
377
|
+
|
|
378
|
+
button.click();
|
|
379
|
+
flushSync();
|
|
380
|
+
|
|
381
|
+
expect(div.getAttribute('title')).toBe('This is a title');
|
|
382
|
+
expect(div.getAttribute('aria-label')).toBe('Accessible label');
|
|
383
|
+
|
|
384
|
+
button.click();
|
|
385
|
+
flushSync();
|
|
386
|
+
|
|
387
|
+
expect(div.hasAttribute('title')).toBe(false);
|
|
388
|
+
expect(div.hasAttribute('aria-label')).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('render spread attributes', () => {
|
|
392
|
+
component Basic() {
|
|
393
|
+
let attrs = track({
|
|
394
|
+
class: 'initial',
|
|
395
|
+
id: 'test-1'
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
<button
|
|
399
|
+
onClick={() => {
|
|
400
|
+
@attrs = {
|
|
401
|
+
class: 'updated',
|
|
402
|
+
id: 'test-2',
|
|
403
|
+
'data-extra': 'value'
|
|
404
|
+
};
|
|
405
|
+
}}
|
|
406
|
+
>{'Update Attributes'}</button>
|
|
407
|
+
<div {...@attrs}>{'Spread Attributes'}</div>
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
render(Basic);
|
|
411
|
+
|
|
412
|
+
const button = container.querySelector('button');
|
|
413
|
+
const div = container.querySelector('div');
|
|
414
|
+
|
|
415
|
+
expect(div.className).toBe('initial');
|
|
416
|
+
expect(div.id).toBe('test-1');
|
|
417
|
+
expect(div.hasAttribute('data-extra')).toBe(false);
|
|
418
|
+
|
|
419
|
+
button.click();
|
|
420
|
+
flushSync();
|
|
421
|
+
|
|
422
|
+
expect(div.className).toBe('updated');
|
|
423
|
+
expect(div.id).toBe('test-2');
|
|
424
|
+
expect(div.getAttribute('data-extra')).toBe('value');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('renders with reactive attributes with nested reactive attributes', () => {
|
|
428
|
+
component App() {
|
|
429
|
+
let value = track('parent-class');
|
|
430
|
+
|
|
431
|
+
<p class={@value}>{'Colored parent value'}</p>
|
|
432
|
+
|
|
433
|
+
<div>
|
|
434
|
+
let nested = track('nested-class');
|
|
435
|
+
|
|
436
|
+
<p class={@nested}>{'Colored nested value'}</p>
|
|
437
|
+
</div>
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
render(App);
|
|
441
|
+
|
|
442
|
+
const paragraphs = container.querySelectorAll('p');
|
|
443
|
+
|
|
444
|
+
expect(paragraphs[0].className).toBe('parent-class');
|
|
445
|
+
expect(paragraphs[1].className).toBe('nested-class');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('handles boolean attributes with no prop value provides', () => {
|
|
449
|
+
component App() {
|
|
450
|
+
<div class="container">
|
|
451
|
+
<button onClick={() => console.log("clicked!")} disabled>{"Button"}</button>
|
|
452
|
+
<input type="checkbox" checked />
|
|
453
|
+
</div>
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
render(App);
|
|
457
|
+
expect(container).toMatchSnapshot();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('handles boolean props correctly', () => {
|
|
461
|
+
component App() {
|
|
462
|
+
<div data-disabled />
|
|
463
|
+
|
|
464
|
+
<Child isDisabled />
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
component Child({ isDisabled }) {
|
|
468
|
+
<input disabled={isDisabled} />
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
render(App);
|
|
472
|
+
expect(container).toMatchSnapshot();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { track, flushSync } from 'ripple';
|
|
2
|
+
import { TRACKED_ARRAY } from '../../../src/runtime/internal/client/constants.js';
|
|
3
|
+
|
|
4
|
+
describe('basic client > collections', () => {
|
|
5
|
+
it('renders with simple reactive objects', () => {
|
|
6
|
+
component Basic() {
|
|
7
|
+
let user = track({
|
|
8
|
+
name: 'John',
|
|
9
|
+
age: 25
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
<div class='name'>{@user.name}</div>
|
|
13
|
+
<div class='age'>{@user.age}</div>
|
|
14
|
+
<button onClick={() => {
|
|
15
|
+
@user = {...@user, name: 'Jane', age: 30}
|
|
16
|
+
}}>{'Update User'}</button>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
render(Basic);
|
|
20
|
+
|
|
21
|
+
const nameDiv = container.querySelector('.name');
|
|
22
|
+
const ageDiv = container.querySelector('.age');
|
|
23
|
+
const button = container.querySelector('button');
|
|
24
|
+
|
|
25
|
+
expect(nameDiv.textContent).toBe('John');
|
|
26
|
+
expect(ageDiv.textContent).toBe('25');
|
|
27
|
+
|
|
28
|
+
button.click();
|
|
29
|
+
flushSync();
|
|
30
|
+
|
|
31
|
+
expect(nameDiv.textContent).toBe('Jane');
|
|
32
|
+
expect(ageDiv.textContent).toBe('30');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders with nested reactive objects', () => {
|
|
36
|
+
component Basic() {
|
|
37
|
+
let user = track({
|
|
38
|
+
name: track('John'),
|
|
39
|
+
age: track(25)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
<div class='name'>{@user.@name}</div>
|
|
43
|
+
<div class='age'>{@user.@age}</div>
|
|
44
|
+
<button onClick={() => {
|
|
45
|
+
@user.@name = 'Jane';
|
|
46
|
+
@user.@age = 30;
|
|
47
|
+
}}>{'Update User'}</button>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render(Basic);
|
|
51
|
+
|
|
52
|
+
const nameDiv = container.querySelector('.name');
|
|
53
|
+
const ageDiv = container.querySelector('.age');
|
|
54
|
+
const button = container.querySelector('button');
|
|
55
|
+
|
|
56
|
+
expect(nameDiv.textContent).toBe('John');
|
|
57
|
+
expect(ageDiv.textContent).toBe('25');
|
|
58
|
+
|
|
59
|
+
button.click();
|
|
60
|
+
flushSync();
|
|
61
|
+
|
|
62
|
+
expect(nameDiv.textContent).toBe('Jane');
|
|
63
|
+
expect(ageDiv.textContent).toBe('30');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('works as a reactive TrackedArray when constructed using # syntactic sugar', () => {
|
|
67
|
+
component App() {
|
|
68
|
+
const array = #[1, 2, 3];
|
|
69
|
+
|
|
70
|
+
<pre>{String(array[3])}</pre>
|
|
71
|
+
<pre>{array[0]}</pre>
|
|
72
|
+
<pre>{TRACKED_ARRAY in array}</pre>
|
|
73
|
+
|
|
74
|
+
<button onClick={() => { array.push(array.length + 1); array[0] = array[0] + 1 }}>{'Add'}</button>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render(App);
|
|
78
|
+
|
|
79
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
80
|
+
const pre2 = container.querySelectorAll('pre')[1];
|
|
81
|
+
const pre3 = container.querySelectorAll('pre')[2];
|
|
82
|
+
const button = container.querySelector('button');
|
|
83
|
+
|
|
84
|
+
expect(pre1.textContent).toBe('undefined');
|
|
85
|
+
expect(pre2.textContent).toBe('1');
|
|
86
|
+
expect(pre3.textContent).toBe('true');
|
|
87
|
+
|
|
88
|
+
button.click();
|
|
89
|
+
flushSync();
|
|
90
|
+
|
|
91
|
+
expect(pre1.textContent).toBe('4');
|
|
92
|
+
expect(pre2.textContent).toBe('2');
|
|
93
|
+
});
|
|
94
|
+
});
|