ripple 0.2.115 → 0.2.118
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/1-parse/index.js +79 -0
- package/src/compiler/phases/3-transform/client/index.js +54 -8
- package/src/compiler/phases/3-transform/segments.js +107 -60
- package/src/compiler/phases/3-transform/server/index.js +21 -11
- package/src/compiler/types/index.d.ts +16 -0
- package/src/runtime/index-client.js +19 -185
- package/src/runtime/index-server.js +24 -0
- package/src/runtime/internal/client/bindings.js +443 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/runtime.js +10 -0
- package/src/runtime/internal/client/utils.js +0 -8
- package/src/runtime/map.js +11 -1
- package/src/runtime/set.js +11 -1
- 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 +82 -20
- 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 +84 -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 +92 -46
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`composite
|
|
3
|
+
exports[`composite > render > correct handles passing through component props and children 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<!---->
|
|
6
6
|
<div>
|
|
@@ -22,7 +22,7 @@ exports[`composite components > correct handles passing through component props
|
|
|
22
22
|
</div>
|
|
23
23
|
`;
|
|
24
24
|
|
|
25
|
-
exports[`composite
|
|
25
|
+
exports[`composite > render > render simple text as children 1`] = `
|
|
26
26
|
<div>
|
|
27
27
|
<!---->
|
|
28
28
|
<button
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { track, flushSync } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('composite > dynamic components', () => {
|
|
4
|
+
it('supports rendering compositie components using <@component> syntax', () => {
|
|
5
|
+
component App() {
|
|
6
|
+
component basic() {
|
|
7
|
+
<div>{'Basic Component'}</div>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const tracked_basic = track(() => basic);
|
|
11
|
+
|
|
12
|
+
<@tracked_basic />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
render(App);
|
|
16
|
+
flushSync();
|
|
17
|
+
|
|
18
|
+
expect(container.textContent).toBe('Basic Component');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('supports rendering composite components using <object.@component> syntax', () => {
|
|
22
|
+
component App() {
|
|
23
|
+
component basic() {
|
|
24
|
+
<div>{'Basic Component'}</div>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const tracked_basic = track(() => basic);
|
|
28
|
+
|
|
29
|
+
const obj = {
|
|
30
|
+
tracked_basic,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
<obj.@tracked_basic />
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render(App);
|
|
37
|
+
flushSync();
|
|
38
|
+
|
|
39
|
+
expect(container.textContent).toBe('Basic Component');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('supports rendering composite components using <@object.@component> syntax', () => {
|
|
43
|
+
component App() {
|
|
44
|
+
component basic() {
|
|
45
|
+
<div>{'Basic Component'}</div>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tracked_basic = track(() => basic);
|
|
49
|
+
|
|
50
|
+
const obj = {
|
|
51
|
+
tracked_basic,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tracked_object = track(obj);
|
|
55
|
+
|
|
56
|
+
<@tracked_object.@tracked_basic />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render(App);
|
|
60
|
+
flushSync();
|
|
61
|
+
|
|
62
|
+
expect(container.textContent).toBe('Basic Component');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('handles dynamic component switching', () => {
|
|
66
|
+
component Child1() {
|
|
67
|
+
<div>{"I am child 1"}</div>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
component Child2() {
|
|
71
|
+
<div>{"I am child 2"}</div>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
component App() {
|
|
75
|
+
let thing = track(() => Child1)
|
|
76
|
+
|
|
77
|
+
<div id="container">
|
|
78
|
+
<@thing />
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<button onClick={() => @thing = @thing === Child1 ? Child2 : Child1}>{"Change Child"}</button>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
render(App);
|
|
85
|
+
flushSync();
|
|
86
|
+
|
|
87
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 1');
|
|
88
|
+
|
|
89
|
+
const button = container.querySelector('button');
|
|
90
|
+
button.click();
|
|
91
|
+
flushSync();
|
|
92
|
+
|
|
93
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 2');
|
|
94
|
+
|
|
95
|
+
button.click();
|
|
96
|
+
flushSync();
|
|
97
|
+
|
|
98
|
+
expect(container.querySelector('#container').textContent).toBe('I am child 1');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { TrackedArray, TrackedMap } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('composite > generics', () => {
|
|
4
|
+
it('handles advanced generic ambiguity and edge cases', () => {
|
|
5
|
+
component App() {
|
|
6
|
+
// Ambiguous generics vs JSX / less-than parsing scenarios
|
|
7
|
+
|
|
8
|
+
// 7. Generic following optional chaining
|
|
9
|
+
const maybe = {
|
|
10
|
+
factory<T>() {
|
|
11
|
+
return {
|
|
12
|
+
make<U>() {
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const g = maybe?.factory<number>()?.make<boolean>();
|
|
19
|
+
|
|
20
|
+
// 8. Comparison operator (ensure '<' here NOT misparsed as generics)
|
|
21
|
+
let x = 10, y = 20;
|
|
22
|
+
const h = x < y ? 'lt' : 'ge';
|
|
23
|
+
|
|
24
|
+
// 9. Chained comparisons with intervening generics
|
|
25
|
+
class Box<T> {
|
|
26
|
+
value: T;
|
|
27
|
+
constructor(value?: T) {
|
|
28
|
+
this.value = value;
|
|
29
|
+
}
|
|
30
|
+
open<U>() {
|
|
31
|
+
return new Box<U>();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const limit = 100;
|
|
35
|
+
const i = new Box<number>().value < limit ? 'ok' : 'no';
|
|
36
|
+
|
|
37
|
+
// 10. JSX / Element should still work
|
|
38
|
+
<div class="still-works">
|
|
39
|
+
<span>{'Test'}</span>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
// 11. Generic function call vs Element: Identifier followed by generic args
|
|
43
|
+
function identity<T>(value: T): T {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
const j = identity<number>(42);
|
|
47
|
+
|
|
48
|
+
// 12. Member + generic call immediately followed by another call
|
|
49
|
+
class Factory {
|
|
50
|
+
create<T>() {
|
|
51
|
+
return (value: T) => value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const factory = new Factory();
|
|
55
|
+
const k = factory.create<number>()(123);
|
|
56
|
+
|
|
57
|
+
// 13. Multiple generic segments in chain
|
|
58
|
+
function foo<T>() {
|
|
59
|
+
return {
|
|
60
|
+
bar<U>() {
|
|
61
|
+
return {
|
|
62
|
+
baz<V>() {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const l = foo<number>().bar<string>().baz<boolean>();
|
|
70
|
+
|
|
71
|
+
// 14. Generic with constraint + default
|
|
72
|
+
type Extractor<T extends { id: number } = { id: number }> = (v: T) => number;
|
|
73
|
+
const m: Extractor = (v) => v.id;
|
|
74
|
+
|
|
75
|
+
// 15. Generic in angle after "new" + trailing call
|
|
76
|
+
class Wrapper<T> {
|
|
77
|
+
value: T;
|
|
78
|
+
constructor() {
|
|
79
|
+
this.value = null as unknown as T;
|
|
80
|
+
}
|
|
81
|
+
unwrap<U>() {
|
|
82
|
+
return null as unknown as U;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const n = new Wrapper<number>().unwrap<string>();
|
|
86
|
+
|
|
87
|
+
// 16. Angle brackets inside type assertion vs generic call
|
|
88
|
+
function getUnknown(): unknown {
|
|
89
|
+
return new Map<string, number>([['a', 1]]);
|
|
90
|
+
}
|
|
91
|
+
getUnknown.factory = function<T>() {
|
|
92
|
+
return {
|
|
93
|
+
make<U>() {
|
|
94
|
+
return 2;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const raw = getUnknown();
|
|
99
|
+
const o = (raw as Map<string, number>).get('a');
|
|
100
|
+
|
|
101
|
+
// 17. Generic with comma + trailing less-than comparison on next token
|
|
102
|
+
class Pair<T1, T2> {
|
|
103
|
+
first: T1;
|
|
104
|
+
second: T2;
|
|
105
|
+
constructor() {
|
|
106
|
+
this.first = null as unknown as T1;
|
|
107
|
+
this.second = null as unknown as T2;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const p = new Pair<number, string>();
|
|
111
|
+
const q = 1 < 2 ? p : null;
|
|
112
|
+
|
|
113
|
+
// 18. Nested generics with line breaks resembling JSX indentation
|
|
114
|
+
interface Node<T> {
|
|
115
|
+
value: T;
|
|
116
|
+
}
|
|
117
|
+
interface Edge<W> {
|
|
118
|
+
weight: W;
|
|
119
|
+
}
|
|
120
|
+
class Graph<N, E> {
|
|
121
|
+
nodes: N[];
|
|
122
|
+
edges: E[];
|
|
123
|
+
constructor() {
|
|
124
|
+
this.nodes = [];
|
|
125
|
+
this.edges = [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const r = new Graph<
|
|
129
|
+
Node<string>,
|
|
130
|
+
Edge<number>
|
|
131
|
+
>();
|
|
132
|
+
|
|
133
|
+
// 19. Ternary containing generics in both branches
|
|
134
|
+
let flag = true;
|
|
135
|
+
const s = flag ? new Box<number>() : new Box<string>();
|
|
136
|
+
|
|
137
|
+
// 20. Generic inside template expression
|
|
138
|
+
const t = `length=${new TrackedArray<number>().length}`;
|
|
139
|
+
|
|
140
|
+
// 21. Optional chaining + generic + property access
|
|
141
|
+
const registry = new TrackedMap<string, number>();
|
|
142
|
+
const u = registry.get<number>('id')?.toString();
|
|
143
|
+
|
|
144
|
+
// 22. Generic call used as callee for another call
|
|
145
|
+
function make<T>() {
|
|
146
|
+
return (value: T) => value;
|
|
147
|
+
}
|
|
148
|
+
const v = make<number>()(10);
|
|
149
|
+
|
|
150
|
+
// 23. Generic followed by tagged template (ensure not confused with JSX)
|
|
151
|
+
function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
|
|
152
|
+
return values[0];
|
|
153
|
+
}
|
|
154
|
+
const tagResult = tagFn<number>`value`;
|
|
155
|
+
|
|
156
|
+
// 24. Sequence mixing: (a < b) + generic call in same statement
|
|
157
|
+
function compute<T>(x: T, y: T): T {
|
|
158
|
+
return y;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const w = (x < y) && compute<number>(x, y);
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
// Additional component focusing on edge crankers
|
|
165
|
+
|
|
166
|
+
// 28. Generic after parenthesized new expression
|
|
167
|
+
const aa = (new Box<number>()).open<string>();
|
|
168
|
+
|
|
169
|
+
// 29. Generic chain right after closing paren of IIFE
|
|
170
|
+
class Builder<Kind> {
|
|
171
|
+
finalize<Result>() {
|
|
172
|
+
return {
|
|
173
|
+
result: null as unknown as Result
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const builder = new Builder<Number>();
|
|
178
|
+
const result = ((function(){ return builder; })() as Builder<Number>).finalize<boolean>();
|
|
179
|
+
|
|
180
|
+
// 30. Angle bracket start of conditional expression line
|
|
181
|
+
function adjust<T>(value: T): T {
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
const val =
|
|
185
|
+
new Wrapper<number>()
|
|
186
|
+
.value < 100
|
|
187
|
+
? adjust<number>(10)
|
|
188
|
+
: adjust<number>(20);
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
// 32. Generic with comments inside angle list
|
|
192
|
+
class Mapper<Key, Value> {
|
|
193
|
+
map: Map<Key, Value>;
|
|
194
|
+
constructor() {
|
|
195
|
+
this.map = new Map<Key, Value>();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const gg = new Mapper<
|
|
199
|
+
// key type
|
|
200
|
+
string,
|
|
201
|
+
/* value type */
|
|
202
|
+
number
|
|
203
|
+
>();
|
|
204
|
+
|
|
205
|
+
// 33. Map of generic instance as key
|
|
206
|
+
const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render(App);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { track, effect, flushSync } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('composite > props', () => {
|
|
4
|
+
it('correctly handles default prop values', () => {
|
|
5
|
+
component Child({ foo = 456 }) {
|
|
6
|
+
<div>{foo}</div>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
component App() {
|
|
10
|
+
let foo = track(123);
|
|
11
|
+
|
|
12
|
+
<Child />
|
|
13
|
+
<Child {@foo} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render(App);
|
|
17
|
+
|
|
18
|
+
expect(container.querySelectorAll('div')[0].textContent).toBe('456');
|
|
19
|
+
expect(container.querySelectorAll('div')[1].textContent).toBe('123');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('correctly handles default prop values #2', () => {
|
|
23
|
+
component Child({ foo = 456 }) {
|
|
24
|
+
<div>{foo}</div>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
component App() {
|
|
28
|
+
let foo = 123;
|
|
29
|
+
|
|
30
|
+
<Child />
|
|
31
|
+
<Child {foo} />
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(App);
|
|
35
|
+
|
|
36
|
+
expect(container.querySelectorAll('div')[0].textContent).toBe('456');
|
|
37
|
+
expect(container.querySelectorAll('div')[1].textContent).toBe('123');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('correctly handles no props', () => {
|
|
41
|
+
component Child(props: { foo?: Tracked<number> }) {
|
|
42
|
+
<div>{props.@foo}</div>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
component App() {
|
|
46
|
+
let foo = track(123);
|
|
47
|
+
|
|
48
|
+
<Child />
|
|
49
|
+
<Child {foo} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render(App);
|
|
53
|
+
|
|
54
|
+
expect(container.querySelectorAll('div')[0].textContent).toBe('');
|
|
55
|
+
expect(container.querySelectorAll('div')[1].textContent).toBe('123');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('correctly handles no props #2', () => {
|
|
59
|
+
component Child({ foo }) {
|
|
60
|
+
<div>{foo}</div>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
component App() {
|
|
64
|
+
let foo = track(123);
|
|
65
|
+
|
|
66
|
+
<Child />
|
|
67
|
+
<Child {@foo} />
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(App);
|
|
71
|
+
|
|
72
|
+
expect(container.querySelectorAll('div')[0].textContent).toBe('');
|
|
73
|
+
expect(container.querySelectorAll('div')[1].textContent).toBe('123');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('mutating a tracked value prop should work as intended', () => {
|
|
77
|
+
const logs: number[] = [];
|
|
78
|
+
|
|
79
|
+
component Counter({count}) {
|
|
80
|
+
effect(() => {
|
|
81
|
+
logs.push(@count);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
<button onClick={() => @count = @count + 1}>{'+'}</button>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
component App() {
|
|
88
|
+
const count = track(0);
|
|
89
|
+
|
|
90
|
+
<div>
|
|
91
|
+
<Counter count={count} />
|
|
92
|
+
</div>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
render(App);
|
|
96
|
+
flushSync();
|
|
97
|
+
|
|
98
|
+
expect(logs).toEqual([0]);
|
|
99
|
+
|
|
100
|
+
const button = container.querySelector('button');
|
|
101
|
+
button.click();
|
|
102
|
+
flushSync();
|
|
103
|
+
|
|
104
|
+
expect(logs).toEqual([0, 1]);
|
|
105
|
+
})
|
|
106
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { track, flushSync, effect } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('composite > reactivity', () => {
|
|
4
|
+
it('renders composite components with object state', () => {
|
|
5
|
+
component Button({ obj }: { obj: { count: Tracked<number> } }) {
|
|
6
|
+
<button class='count2' onClick={() => {
|
|
7
|
+
obj.@count++;
|
|
8
|
+
}}>{obj.@count}</button>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
component App() {
|
|
12
|
+
<div>
|
|
13
|
+
let obj = {
|
|
14
|
+
count: track(0)
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
<span class='count'>{obj.@count}</span>
|
|
18
|
+
<Button obj={obj} />
|
|
19
|
+
</div>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
render(App);
|
|
23
|
+
|
|
24
|
+
const button = container.querySelector('button');
|
|
25
|
+
|
|
26
|
+
button.click();
|
|
27
|
+
flushSync();
|
|
28
|
+
|
|
29
|
+
expect(container.querySelector('.count').textContent).toBe('1');
|
|
30
|
+
expect(container.querySelector('.count2').textContent).toBe('1');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders composite components with object state wrapped in an if statement', () => {
|
|
34
|
+
component Button({ obj }: { obj: { count: Tracked<number> } }) {
|
|
35
|
+
<button class='count2' onClick={() => {
|
|
36
|
+
obj.@count++;
|
|
37
|
+
}}>{obj.@count}</button>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
component OtherComponent({ obj }: { obj: { count: Tracked<number> } }) {
|
|
41
|
+
<div class='count3'>{obj.@count}</div>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
component App() {
|
|
45
|
+
<div>
|
|
46
|
+
let obj = {
|
|
47
|
+
count: track(0)
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
<span class='count'>{obj.@count}</span>
|
|
51
|
+
<span>{' '}</span>
|
|
52
|
+
if (obj) {
|
|
53
|
+
<Button obj={obj} />
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (obj) {
|
|
57
|
+
<OtherComponent obj={obj} />
|
|
58
|
+
}
|
|
59
|
+
</div>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render(App);
|
|
63
|
+
|
|
64
|
+
const button = container.querySelector('button');
|
|
65
|
+
|
|
66
|
+
button.click();
|
|
67
|
+
flushSync();
|
|
68
|
+
|
|
69
|
+
expect(container.querySelector('.count').textContent).toBe('1');
|
|
70
|
+
expect(container.querySelector('.count2').textContent).toBe('1');
|
|
71
|
+
expect(container.querySelector('.count3').textContent).toBe('1');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('parents and children have isolated state', () => {
|
|
75
|
+
component Button(props: { count: number }) {
|
|
76
|
+
let count = track(() => props.count);
|
|
77
|
+
<button onClick={() => { @count++; } }>{"child: " + @count}</button>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
component App() {
|
|
81
|
+
<div>
|
|
82
|
+
let count = track(0);
|
|
83
|
+
|
|
84
|
+
<button onClick={() => { @count++; } }>{"parent: " + @count}</button>
|
|
85
|
+
<Button {@count} />
|
|
86
|
+
</div>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(App);
|
|
90
|
+
|
|
91
|
+
const buttons = container.querySelectorAll('button');
|
|
92
|
+
|
|
93
|
+
expect(buttons[0].textContent).toBe('parent: 0');
|
|
94
|
+
expect(buttons[1].textContent).toBe('child: 0');
|
|
95
|
+
|
|
96
|
+
buttons[0].click();
|
|
97
|
+
flushSync();
|
|
98
|
+
|
|
99
|
+
expect(buttons[0].textContent).toBe('parent: 1');
|
|
100
|
+
expect(buttons[1].textContent).toBe('child: 1');
|
|
101
|
+
|
|
102
|
+
buttons[1].click();
|
|
103
|
+
flushSync();
|
|
104
|
+
|
|
105
|
+
expect(buttons[0].textContent).toBe('parent: 1');
|
|
106
|
+
expect(buttons[1].textContent).toBe('child: 2');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('parents and children have isolated connected state (destructured props)', () => {
|
|
110
|
+
component Button({count}: { count: number }) {
|
|
111
|
+
let local_count = track(() => count);
|
|
112
|
+
<button onClick={() => { @local_count++; } }>{"child: " + @local_count}</button>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
component App() {
|
|
116
|
+
<div>
|
|
117
|
+
let count = track(0);
|
|
118
|
+
|
|
119
|
+
<button onClick={() => { @count++; } }>{"parent: " + @count}</button>
|
|
120
|
+
<Button {@count} />
|
|
121
|
+
</div>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render(App);
|
|
125
|
+
|
|
126
|
+
const buttons = container.querySelectorAll('button');
|
|
127
|
+
|
|
128
|
+
expect(buttons[0].textContent).toBe('parent: 0');
|
|
129
|
+
expect(buttons[1].textContent).toBe('child: 0');
|
|
130
|
+
|
|
131
|
+
buttons[0].click();
|
|
132
|
+
flushSync();
|
|
133
|
+
|
|
134
|
+
expect(buttons[0].textContent).toBe('parent: 1');
|
|
135
|
+
expect(buttons[1].textContent).toBe('child: 1');
|
|
136
|
+
|
|
137
|
+
buttons[1].click();
|
|
138
|
+
flushSync();
|
|
139
|
+
|
|
140
|
+
expect(buttons[0].textContent).toBe('parent: 1');
|
|
141
|
+
expect(buttons[1].textContent).toBe('child: 2');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('handles spreading of props', () => {
|
|
145
|
+
let logs: string[] = [];
|
|
146
|
+
|
|
147
|
+
component App() {
|
|
148
|
+
const a = track(1);
|
|
149
|
+
const b = track(2);
|
|
150
|
+
const c = track(3);
|
|
151
|
+
|
|
152
|
+
const obj = track(() => ({
|
|
153
|
+
@a,
|
|
154
|
+
@b,
|
|
155
|
+
@c,
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
<Child {...@obj} />
|
|
159
|
+
|
|
160
|
+
<button onClick={() => { @a++; @b++; @c++; }}>{"Increment all"}</button>
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
component Child({ a, b, c }: { a: number; b: number; c: number }) {
|
|
164
|
+
effect(() => {
|
|
165
|
+
logs.push(`Child effect: ${a}, ${b}, ${c}`);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
<div>{a + ' ' + b + ' ' + c}</div>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
render(App);
|
|
172
|
+
flushSync();
|
|
173
|
+
|
|
174
|
+
expect(container.querySelector('div').textContent).toBe('1 2 3');
|
|
175
|
+
expect(logs).toEqual(['Child effect: 1, 2, 3']);
|
|
176
|
+
|
|
177
|
+
const button = container.querySelector('button');
|
|
178
|
+
button.click();
|
|
179
|
+
flushSync();
|
|
180
|
+
|
|
181
|
+
expect(container.querySelector('div').textContent).toBe('2 3 4');
|
|
182
|
+
expect(logs).toEqual(['Child effect: 1, 2, 3', 'Child effect: 2, 3, 4']);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { track, flushSync, TrackedArray } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('composite > render', () => {
|
|
4
|
+
it('renders composite components', () => {
|
|
5
|
+
component Button({ count }: { count: number }) {
|
|
6
|
+
<div>{count}</div>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
component App() {
|
|
10
|
+
let count = track(0);
|
|
11
|
+
|
|
12
|
+
<button onClick={() => @count++}>{'Increment'}</button>
|
|
13
|
+
<Button {@count} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render(App);
|
|
17
|
+
|
|
18
|
+
const button = container.querySelector('button');
|
|
19
|
+
|
|
20
|
+
button.click();
|
|
21
|
+
flushSync();
|
|
22
|
+
|
|
23
|
+
expect(container.querySelector('div').textContent).toBe('1');
|
|
24
|
+
|
|
25
|
+
button.click();
|
|
26
|
+
flushSync();
|
|
27
|
+
|
|
28
|
+
expect(container.querySelector('div').textContent).toBe('2');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('correct handles passing through component props and children', () => {
|
|
32
|
+
component Button({ A, B, children }: { A: () => void; B: () => void; children: () => void }) {
|
|
33
|
+
<div>
|
|
34
|
+
<A />
|
|
35
|
+
<children />
|
|
36
|
+
<B />
|
|
37
|
+
</div>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
component App() {
|
|
41
|
+
<Button>
|
|
42
|
+
component A() {
|
|
43
|
+
<div>{"I am A"}</div>
|
|
44
|
+
}
|
|
45
|
+
<div>{"other text"}</div>
|
|
46
|
+
component B() {
|
|
47
|
+
<div>{"I am B"}</div>
|
|
48
|
+
}
|
|
49
|
+
</Button>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render(App);
|
|
53
|
+
|
|
54
|
+
expect(container).toMatchSnapshot();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('render simple text as children', () => {
|
|
58
|
+
component App() {
|
|
59
|
+
let name = 'Click Me';
|
|
60
|
+
|
|
61
|
+
<Child
|
|
62
|
+
class="my-button"
|
|
63
|
+
>{name}</Child>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
component Child({children, ...rest}: {children: string; class: string}) {
|
|
67
|
+
<button {...rest}><children /></button>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(App);
|
|
71
|
+
expect(container).toMatchSnapshot();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('handles generics', () => {
|
|
75
|
+
component ArrayTest() {
|
|
76
|
+
let items = new TrackedArray<number>();
|
|
77
|
+
items.push.apply(items, [1, 2, 3, 4, 5]);
|
|
78
|
+
|
|
79
|
+
<pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
render(ArrayTest);
|
|
83
|
+
});
|
|
84
|
+
});
|