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,29 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mount, flushSync, TrackedObject, track } from 'ripple';
|
|
1
|
+
import { flushSync, TrackedObject } from 'ripple';
|
|
3
2
|
import { TRACKED_OBJECT } from '../../src/runtime/internal/client/constants.js';
|
|
4
3
|
|
|
5
4
|
describe('TrackedObject', () => {
|
|
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
5
|
it('makes new properties reactive', () => {
|
|
25
6
|
component ObjectTest() {
|
|
26
|
-
const obj = new TrackedObject({});
|
|
7
|
+
const obj = new TrackedObject<{ a: number; }>({});
|
|
27
8
|
|
|
28
9
|
obj.a = 0;
|
|
29
10
|
|
|
@@ -67,7 +48,7 @@ describe('TrackedObject', () => {
|
|
|
67
48
|
|
|
68
49
|
it('checks if property exists via the has trap', () => {
|
|
69
50
|
component ObjectTest() {
|
|
70
|
-
const obj = new TrackedObject({b: 1});
|
|
51
|
+
const obj = new TrackedObject<{ a: number; b: number; }>({b: 1});
|
|
71
52
|
|
|
72
53
|
obj.a = 0;
|
|
73
54
|
|
|
@@ -83,7 +64,7 @@ describe('TrackedObject', () => {
|
|
|
83
64
|
|
|
84
65
|
it('deletes properties via the delete trap', () => {
|
|
85
66
|
component ObjectTest() {
|
|
86
|
-
const obj = new TrackedObject({a: 0, b: 1});
|
|
67
|
+
const obj = new TrackedObject<{ a?: number; b: number; }>({a: 0, b: 1});
|
|
87
68
|
|
|
88
69
|
<pre>{String(obj.a)}</pre>
|
|
89
70
|
<button onClick={() => { delete obj.a; }}>{'Delete A'}</button>
|
|
@@ -104,7 +85,7 @@ describe('TrackedObject', () => {
|
|
|
104
85
|
|
|
105
86
|
it('checks if non-existent property is reactive when added later', () => {
|
|
106
87
|
component ObjectTest() {
|
|
107
|
-
const obj = new TrackedObject({});
|
|
88
|
+
const obj = new TrackedObject<{ a?: number; }>({});
|
|
108
89
|
|
|
109
90
|
<pre>{String(obj.a)} </pre>
|
|
110
91
|
<button onClick={() => { obj.a = 1; }}>{'Add A'}</button>
|
|
@@ -1,24 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mount, Portal, track, flushSync } from 'ripple';
|
|
1
|
+
import { Portal, track, flushSync } from 'ripple';
|
|
3
2
|
|
|
4
3
|
describe('Portal', () => {
|
|
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
4
|
afterEach(() => {
|
|
19
|
-
// Remove container
|
|
20
|
-
document.body.removeChild(container);
|
|
21
|
-
|
|
22
5
|
// Clean up any leftover portal content from document.body
|
|
23
6
|
const portals = document.body.querySelectorAll('.test-portal');
|
|
24
7
|
portals.forEach(el => el.remove());
|
|
@@ -164,4 +147,4 @@ describe('Portal', () => {
|
|
|
164
147
|
|
|
165
148
|
expect(portalElement.textContent).toContain('Count: 1');
|
|
166
149
|
});
|
|
167
|
-
});
|
|
150
|
+
});
|
|
@@ -1,50 +1,32 @@
|
|
|
1
|
-
import { describe, it, expect
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { flushSync, TrackedArray, track, createRefKey } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('refs', () => {
|
|
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
5
|
it('capture a <div>', () => {
|
|
24
|
-
let div;
|
|
6
|
+
let div: HTMLDivElement | undefined;
|
|
25
7
|
|
|
26
8
|
component Component() {
|
|
27
|
-
<div {ref (node) => { div = node; }}>{'Hello World'}</div>
|
|
9
|
+
<div {ref (node: HTMLDivElement) => { div = node; }}>{'Hello World'}</div>
|
|
28
10
|
}
|
|
29
11
|
render(Component);
|
|
30
12
|
flushSync();
|
|
31
|
-
expect(div
|
|
13
|
+
expect(div?.textContent).toBe('Hello World');
|
|
32
14
|
});
|
|
33
15
|
|
|
34
16
|
it('works with spreading from composite component', () => {
|
|
35
|
-
let _node;
|
|
17
|
+
let _node: Child | undefined;
|
|
36
18
|
|
|
37
19
|
component Component() {
|
|
38
20
|
let items = TrackedArray.from([1, 2, 3]);
|
|
39
21
|
|
|
40
|
-
function componentRef(node) {
|
|
22
|
+
function componentRef(node: Child) {
|
|
41
23
|
_node = node;
|
|
42
24
|
}
|
|
43
25
|
|
|
44
26
|
<Child {ref componentRef} {items} />
|
|
45
27
|
}
|
|
46
28
|
|
|
47
|
-
component Child(props) {
|
|
29
|
+
component Child(props: { items: TrackedArray<number> }) {
|
|
48
30
|
const { items, ...rest } = props;
|
|
49
31
|
<pre {...rest}>{JSON.stringify(items)}</pre>
|
|
50
32
|
<pre>{items.length}</pre>
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mount, flushSync, TrackedSet, track } from 'ripple';
|
|
1
|
+
import { flushSync, TrackedSet, track } from 'ripple';
|
|
3
2
|
|
|
4
3
|
describe('TrackedSet', () => {
|
|
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
4
|
it('handles add and delete operations', () => {
|
|
24
5
|
component SetTest() {
|
|
25
6
|
let items = new TrackedSet([1, 2, 3]);
|
|
@@ -29,7 +10,7 @@ describe('TrackedSet', () => {
|
|
|
29
10
|
<Child items={items} />
|
|
30
11
|
}
|
|
31
12
|
|
|
32
|
-
component Child({ items }) {
|
|
13
|
+
component Child({ items }: { items: TrackedSet<number> }) {
|
|
33
14
|
<pre>{JSON.stringify(items)}</pre>
|
|
34
15
|
<pre>{items.size}</pre>
|
|
35
16
|
}
|
|
@@ -60,7 +41,7 @@ describe('TrackedSet', () => {
|
|
|
60
41
|
<Child items={items} />
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
component Child({ items }) {
|
|
44
|
+
component Child({ items }: { items: TrackedSet<number> }) {
|
|
64
45
|
<pre>{JSON.stringify(items)}</pre>
|
|
65
46
|
<pre>{items.size}</pre>
|
|
66
47
|
}
|
|
@@ -96,4 +77,85 @@ describe('TrackedSet', () => {
|
|
|
96
77
|
|
|
97
78
|
expect(container.querySelectorAll('pre')[0].textContent).toBe('false');
|
|
98
79
|
});
|
|
80
|
+
|
|
81
|
+
it('creates empty TrackedSet using #Set() shorthand syntax', () => {
|
|
82
|
+
component SetTest() {
|
|
83
|
+
let items = #Set();
|
|
84
|
+
|
|
85
|
+
<button onClick={() => items.add(1)}>{'add'}</button>
|
|
86
|
+
<pre>{items.size}</pre>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(SetTest);
|
|
90
|
+
|
|
91
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
92
|
+
|
|
93
|
+
const addButton = container.querySelector('button');
|
|
94
|
+
addButton.click();
|
|
95
|
+
flushSync();
|
|
96
|
+
|
|
97
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('creates TrackedSet with initial values using #Set() shorthand syntax', () => {
|
|
101
|
+
component SetTest() {
|
|
102
|
+
let items = #Set([1, 2, 3, 4]);
|
|
103
|
+
let hasValue = track(() => items.has(3));
|
|
104
|
+
|
|
105
|
+
<button onClick={() => items.delete(3)}>{'delete'}</button>
|
|
106
|
+
<pre>{items.size}</pre>
|
|
107
|
+
<pre>{@hasValue}</pre>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
render(SetTest);
|
|
111
|
+
|
|
112
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('4');
|
|
113
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('true');
|
|
114
|
+
|
|
115
|
+
const deleteButton = container.querySelector('button');
|
|
116
|
+
deleteButton.click();
|
|
117
|
+
flushSync();
|
|
118
|
+
|
|
119
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('3');
|
|
120
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('false');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('handles all operations with #Set() shorthand syntax', () => {
|
|
124
|
+
component SetTest() {
|
|
125
|
+
let items = #Set([10, 20, 30]);
|
|
126
|
+
let values = track(() => Array.from(items.values()));
|
|
127
|
+
|
|
128
|
+
<button onClick={() => items.add(40)}>{'add'}</button>
|
|
129
|
+
<button onClick={() => items.delete(20)}>{'delete'}</button>
|
|
130
|
+
<button onClick={() => items.clear()}>{'clear'}</button>
|
|
131
|
+
|
|
132
|
+
<pre>{JSON.stringify(@values)}</pre>
|
|
133
|
+
<pre>{items.size}</pre>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render(SetTest);
|
|
137
|
+
|
|
138
|
+
const [addButton, deleteButton, clearButton] = container.querySelectorAll('button');
|
|
139
|
+
|
|
140
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30]');
|
|
141
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
142
|
+
|
|
143
|
+
addButton.click();
|
|
144
|
+
flushSync();
|
|
145
|
+
|
|
146
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,20,30,40]');
|
|
147
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
|
|
148
|
+
|
|
149
|
+
deleteButton.click();
|
|
150
|
+
flushSync();
|
|
151
|
+
|
|
152
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[10,30,40]');
|
|
153
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
154
|
+
|
|
155
|
+
clearButton.click();
|
|
156
|
+
flushSync();
|
|
157
|
+
|
|
158
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
159
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
160
|
+
});
|
|
99
161
|
});
|
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mount, flushSync } from 'ripple';
|
|
3
|
-
|
|
4
1
|
describe('SVG namespace handling', () => {
|
|
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
2
|
it('should render static SVG elements with correct namespace', () => {
|
|
24
3
|
component App() {
|
|
25
4
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor">
|
|
@@ -143,7 +122,7 @@ describe('SVG namespace handling', () => {
|
|
|
143
122
|
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
144
123
|
expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
145
124
|
expect(rects.length).toBe(2);
|
|
146
|
-
|
|
125
|
+
|
|
147
126
|
rects.forEach(rect => {
|
|
148
127
|
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
149
128
|
});
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mount, flushSync, track } from 'ripple';
|
|
1
|
+
import { flushSync, track } from 'ripple';
|
|
3
2
|
|
|
4
3
|
describe('switch statements', () => {
|
|
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
4
|
it('renders simple switch with literal cases', () => {
|
|
24
5
|
component App() {
|
|
25
6
|
let value = track('b');
|
|
@@ -115,19 +96,19 @@ describe('switch statements', () => {
|
|
|
115
96
|
|
|
116
97
|
switch (@status) {
|
|
117
98
|
case 'active':
|
|
118
|
-
message = 'Currently active.';
|
|
99
|
+
@message = 'Currently active.';
|
|
119
100
|
<div>{'Status: ' + @message}</div>
|
|
120
101
|
break;
|
|
121
102
|
case 'pending':
|
|
122
|
-
message = 'Waiting for completion.';
|
|
103
|
+
@message = 'Waiting for completion.';
|
|
123
104
|
<div>{'Status: ' + @message}</div>
|
|
124
105
|
break;
|
|
125
106
|
case 'completed':
|
|
126
|
-
message = 'Task finished!';
|
|
107
|
+
@message = 'Task finished!';
|
|
127
108
|
<div class="success">{'Status: ' + @message}</div>
|
|
128
109
|
break;
|
|
129
110
|
default:
|
|
130
|
-
message = 'An error occurred.';
|
|
111
|
+
@message = 'An error occurred.';
|
|
131
112
|
<div class="error">{'Status: ' + @message}</div>
|
|
132
113
|
}
|
|
133
114
|
}
|
|
@@ -149,4 +130,4 @@ describe('switch statements', () => {
|
|
|
149
130
|
expect(container.textContent).toBe('PendingCompletedErrorStatus: An error occurred.');
|
|
150
131
|
expect(container.querySelector('.error')).toBeTruthy();
|
|
151
132
|
});
|
|
152
|
-
});
|
|
133
|
+
});
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mount, flushSync, TrackedSet, track } from 'ripple';
|
|
1
|
+
import { track } from 'ripple';
|
|
3
2
|
|
|
4
3
|
describe('TrackedExpression tests', () => {
|
|
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
4
|
it('should handle the syntax correctly', () => {
|
|
24
5
|
component App() {
|
|
25
6
|
let count = track(0);
|
|
@@ -43,4 +24,4 @@ describe('TrackedExpression tests', () => {
|
|
|
43
24
|
render(App);
|
|
44
25
|
expect(container).toMatchSnapshot();
|
|
45
26
|
});
|
|
46
|
-
});
|
|
27
|
+
});
|
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { mount } from 'ripple';
|
|
3
|
-
|
|
4
1
|
describe('generic patterns', () => {
|
|
5
|
-
let container;
|
|
6
|
-
|
|
7
|
-
function render(component) {
|
|
8
|
-
mount(component, {
|
|
9
|
-
target: container
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
container = document.createElement('div');
|
|
15
|
-
document.body.appendChild(container);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
document.body.removeChild(container);
|
|
20
|
-
container = null;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
2
|
it('tests simple generic function', () => {
|
|
24
3
|
component App() {
|
|
25
4
|
const e = new Map<string, Promise<number>>();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { track, flushSync, TrackedURL } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURL > derived', () => {
|
|
4
|
+
it('handles reactive computed properties based on URL', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const url = new TrackedURL('https://example.com/users/123?tab=profile');
|
|
7
|
+
let userId = track(() => url.pathname.split('/').pop());
|
|
8
|
+
let activeTab = track(() => url.searchParams.get('tab'));
|
|
9
|
+
|
|
10
|
+
<button onClick={() => url.pathname = '/users/456'}>{'Change User'}</button>
|
|
11
|
+
<button onClick={() => url.searchParams.set('tab', 'settings')}>{'Change Tab'}</button>
|
|
12
|
+
<pre>{`User ID: ${@userId}`}</pre>
|
|
13
|
+
<pre>{`Active Tab: ${@activeTab}`}</pre>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render(URLTest);
|
|
17
|
+
|
|
18
|
+
const changeUserBtn = container.querySelectorAll('button')[0];
|
|
19
|
+
const changeTabBtn = container.querySelectorAll('button')[1];
|
|
20
|
+
|
|
21
|
+
// Initial state
|
|
22
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('User ID: 123');
|
|
23
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Active Tab: profile');
|
|
24
|
+
|
|
25
|
+
// Change user
|
|
26
|
+
changeUserBtn.click();
|
|
27
|
+
flushSync();
|
|
28
|
+
|
|
29
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('User ID: 456');
|
|
30
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Active Tab: profile');
|
|
31
|
+
|
|
32
|
+
// Change tab
|
|
33
|
+
changeTabBtn.click();
|
|
34
|
+
flushSync();
|
|
35
|
+
|
|
36
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('User ID: 456');
|
|
37
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Active Tab: settings');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('maintains reactivity across multiple components', () => {
|
|
41
|
+
component ParentTest() {
|
|
42
|
+
const url = new TrackedURL('https://example.com/path?count=0');
|
|
43
|
+
|
|
44
|
+
<ChildA url={url} />
|
|
45
|
+
<ChildB url={url} />
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
component ChildA({ url }: { url: TrackedURL }) {
|
|
49
|
+
<button onClick={() => {
|
|
50
|
+
const current = parseInt(url.searchParams.get('count') || '0', 10);
|
|
51
|
+
url.searchParams.set('count', String(current + 1));
|
|
52
|
+
}}>{'Increment Count'}</button>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
component ChildB({ url }: { url: TrackedURL }) {
|
|
56
|
+
let count = track(() => url.searchParams.get('count'));
|
|
57
|
+
|
|
58
|
+
<pre>{url.href}</pre>
|
|
59
|
+
<pre>{@count}</pre>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render(ParentTest);
|
|
63
|
+
|
|
64
|
+
const button = container.querySelector('button');
|
|
65
|
+
|
|
66
|
+
// Initial state
|
|
67
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/path?count=0');
|
|
68
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
69
|
+
|
|
70
|
+
// Increment from child
|
|
71
|
+
button.click();
|
|
72
|
+
flushSync();
|
|
73
|
+
|
|
74
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/path?count=1');
|
|
75
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
76
|
+
|
|
77
|
+
button.click();
|
|
78
|
+
flushSync();
|
|
79
|
+
|
|
80
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/path?count=2');
|
|
81
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { flushSync, TrackedURL } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURL > parsing', () => {
|
|
4
|
+
it('creates URL from string with reactivity', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const url = new TrackedURL('https://example.com:8080/path?foo=bar#section');
|
|
7
|
+
|
|
8
|
+
<pre>{url.href}</pre>
|
|
9
|
+
<pre>{url.protocol}</pre>
|
|
10
|
+
<pre>{url.hostname}</pre>
|
|
11
|
+
<pre>{url.port}</pre>
|
|
12
|
+
<pre>{url.pathname}</pre>
|
|
13
|
+
<pre>{url.search}</pre>
|
|
14
|
+
<pre>{url.hash}</pre>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render(URLTest);
|
|
18
|
+
|
|
19
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com:8080/path?foo=bar#section');
|
|
20
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('https:');
|
|
21
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('example.com');
|
|
22
|
+
expect(container.querySelectorAll('pre')[3].textContent).toBe('8080');
|
|
23
|
+
expect(container.querySelectorAll('pre')[4].textContent).toBe('/path');
|
|
24
|
+
expect(container.querySelectorAll('pre')[5].textContent).toBe('?foo=bar');
|
|
25
|
+
expect(container.querySelectorAll('pre')[6].textContent).toBe('#section');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('creates URL from string with base URL', () => {
|
|
29
|
+
component URLTest() {
|
|
30
|
+
const url = new TrackedURL('/path?query=value', 'https://example.com');
|
|
31
|
+
|
|
32
|
+
<pre>{url.href}</pre>
|
|
33
|
+
<pre>{url.origin}</pre>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render(URLTest);
|
|
37
|
+
|
|
38
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/path?query=value');
|
|
39
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('https://example.com');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('handles URL encoding correctly', () => {
|
|
43
|
+
component URLTest() {
|
|
44
|
+
const url = new TrackedURL('https://example.com/path with spaces?key=value with spaces');
|
|
45
|
+
|
|
46
|
+
<pre>{url.pathname}</pre>
|
|
47
|
+
<pre>{url.search}</pre>
|
|
48
|
+
<pre>{url.href}</pre>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
render(URLTest);
|
|
52
|
+
|
|
53
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('/path%20with%20spaces');
|
|
54
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('?key=value%20with%20spaces');
|
|
55
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('https://example.com/path%20with%20spaces?key=value%20with%20spaces');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handles URL with file protocol', () => {
|
|
59
|
+
component URLTest() {
|
|
60
|
+
const url = new TrackedURL('file:///Users/username/documents/file.txt');
|
|
61
|
+
|
|
62
|
+
<pre>{url.protocol}</pre>
|
|
63
|
+
<pre>{url.pathname}</pre>
|
|
64
|
+
<pre>{url.href}</pre>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render(URLTest);
|
|
68
|
+
|
|
69
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('file:');
|
|
70
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('/Users/username/documents/file.txt');
|
|
71
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('file:///Users/username/documents/file.txt');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('handles URL with IPv4 address', () => {
|
|
75
|
+
component URLTest() {
|
|
76
|
+
const url = new TrackedURL('https://192.168.1.1:8080/path');
|
|
77
|
+
|
|
78
|
+
<button onClick={() => url.hostname = '10.0.0.1'}>{'Change IP'}</button>
|
|
79
|
+
<pre>{url.href}</pre>
|
|
80
|
+
<pre>{url.hostname}</pre>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
render(URLTest);
|
|
84
|
+
|
|
85
|
+
const button = container.querySelector('button');
|
|
86
|
+
|
|
87
|
+
// Initial state
|
|
88
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://192.168.1.1:8080/path');
|
|
89
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('192.168.1.1');
|
|
90
|
+
|
|
91
|
+
// Change IP
|
|
92
|
+
button.click();
|
|
93
|
+
flushSync();
|
|
94
|
+
|
|
95
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://10.0.0.1:8080/path');
|
|
96
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('10.0.0.1');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('handles URL with localhost', () => {
|
|
100
|
+
component URLTest() {
|
|
101
|
+
const url = new TrackedURL('http://localhost:3000/api/data');
|
|
102
|
+
|
|
103
|
+
<button onClick={() => url.port = '8080'}>{'Change Port'}</button>
|
|
104
|
+
<pre>{url.href}</pre>
|
|
105
|
+
<pre>{url.hostname}</pre>
|
|
106
|
+
<pre>{url.port}</pre>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render(URLTest);
|
|
110
|
+
|
|
111
|
+
const button = container.querySelector('button');
|
|
112
|
+
|
|
113
|
+
// Initial state
|
|
114
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('http://localhost:3000/api/data');
|
|
115
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('localhost');
|
|
116
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('3000');
|
|
117
|
+
|
|
118
|
+
// Change port
|
|
119
|
+
button.click();
|
|
120
|
+
flushSync();
|
|
121
|
+
|
|
122
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('http://localhost:8080/api/data');
|
|
123
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('localhost');
|
|
124
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('8080');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles URL with multiple path segments', () => {
|
|
128
|
+
component URLTest() {
|
|
129
|
+
const url = new TrackedURL('https://example.com/api/v1/users/123/profile');
|
|
130
|
+
|
|
131
|
+
<button onClick={() => url.pathname = '/api/v2/users/456/settings'}>{'Change Path'}</button>
|
|
132
|
+
<pre>{url.pathname}</pre>
|
|
133
|
+
<pre>{url.href}</pre>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render(URLTest);
|
|
137
|
+
|
|
138
|
+
const button = container.querySelector('button');
|
|
139
|
+
|
|
140
|
+
// Initial state
|
|
141
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('/api/v1/users/123/profile');
|
|
142
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('https://example.com/api/v1/users/123/profile');
|
|
143
|
+
|
|
144
|
+
// Change path
|
|
145
|
+
button.click();
|
|
146
|
+
flushSync();
|
|
147
|
+
|
|
148
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('/api/v2/users/456/settings');
|
|
149
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('https://example.com/api/v2/users/456/settings');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('handles relative URL paths correctly', () => {
|
|
153
|
+
component URLTest() {
|
|
154
|
+
const url = new TrackedURL('../sibling/path', 'https://example.com/parent/current');
|
|
155
|
+
|
|
156
|
+
<pre>{url.href}</pre>
|
|
157
|
+
<pre>{url.pathname}</pre>
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
render(URLTest);
|
|
161
|
+
|
|
162
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/sibling/path');
|
|
163
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('/sibling/path');
|
|
164
|
+
});
|
|
165
|
+
});
|