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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { flushSync, TrackedURL } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURL > serialization', () => {
|
|
4
|
+
it('handles toString method', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const url = new TrackedURL('https://example.com/path?foo=bar#section');
|
|
7
|
+
|
|
8
|
+
<button onClick={() => url.pathname = '/newpath'}>{'Change Pathname'}</button>
|
|
9
|
+
<pre>{url.toString()}</pre>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render(URLTest);
|
|
13
|
+
|
|
14
|
+
const button = container.querySelector('button');
|
|
15
|
+
|
|
16
|
+
// Initial state
|
|
17
|
+
expect(container.querySelector('pre').textContent).toBe('https://example.com/path?foo=bar#section');
|
|
18
|
+
|
|
19
|
+
// Change pathname
|
|
20
|
+
button.click();
|
|
21
|
+
flushSync();
|
|
22
|
+
|
|
23
|
+
expect(container.querySelector('pre').textContent).toBe('https://example.com/newpath?foo=bar#section');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('handles toJSON method', () => {
|
|
27
|
+
component URLTest() {
|
|
28
|
+
const url = new TrackedURL('https://example.com/path?foo=bar');
|
|
29
|
+
|
|
30
|
+
<button onClick={() => url.pathname = '/api'}>{'Change Pathname'}</button>
|
|
31
|
+
<pre>{url.toJSON()}</pre>
|
|
32
|
+
<pre>{JSON.stringify({ url: url.toJSON() })}</pre>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render(URLTest);
|
|
36
|
+
|
|
37
|
+
const button = container.querySelector('button');
|
|
38
|
+
|
|
39
|
+
// Initial state
|
|
40
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/path?foo=bar');
|
|
41
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('{"url":"https://example.com/path?foo=bar"}');
|
|
42
|
+
|
|
43
|
+
// Change pathname
|
|
44
|
+
button.click();
|
|
45
|
+
flushSync();
|
|
46
|
+
|
|
47
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/api?foo=bar');
|
|
48
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('{"url":"https://example.com/api?foo=bar"}');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { flushSync, track, TrackedURL, TrackedURLSearchParams } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURLSearchParams > derived', () => {
|
|
4
|
+
it('handles reactive computed properties based on search params', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const params = new TrackedURLSearchParams('page=1&limit=10');
|
|
7
|
+
let page = track(() => parseInt(params.get('page') || '1', 10));
|
|
8
|
+
let limit = track(() => parseInt(params.get('limit') || '10', 10));
|
|
9
|
+
let offset = track(() => (@page - 1) * @limit);
|
|
10
|
+
|
|
11
|
+
<button onClick={() => params.set('page', '2')}>{'next page'}</button>
|
|
12
|
+
<button onClick={() => params.set('page', '1')}>{'first page'}</button>
|
|
13
|
+
<pre>{`Page: ${@page}`}</pre>
|
|
14
|
+
<pre>{`Limit: ${@limit}`}</pre>
|
|
15
|
+
<pre>{`Offset: ${@offset}`}</pre>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
render(URLTest);
|
|
19
|
+
|
|
20
|
+
const nextButton = container.querySelectorAll('button')[0];
|
|
21
|
+
const firstButton = container.querySelectorAll('button')[1];
|
|
22
|
+
|
|
23
|
+
// Initial state
|
|
24
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('Page: 1');
|
|
25
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Limit: 10');
|
|
26
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('Offset: 0');
|
|
27
|
+
|
|
28
|
+
// Test next page
|
|
29
|
+
nextButton.click();
|
|
30
|
+
flushSync();
|
|
31
|
+
|
|
32
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('Page: 2');
|
|
33
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Limit: 10');
|
|
34
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('Offset: 10');
|
|
35
|
+
|
|
36
|
+
// Test first page
|
|
37
|
+
firstButton.click();
|
|
38
|
+
flushSync();
|
|
39
|
+
|
|
40
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('Page: 1');
|
|
41
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('Limit: 10');
|
|
42
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('Offset: 0');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('maintains reactivity across multiple components', () => {
|
|
46
|
+
component ParentTest() {
|
|
47
|
+
const params = new TrackedURLSearchParams('count=0');
|
|
48
|
+
|
|
49
|
+
<ChildA params={params} />
|
|
50
|
+
<ChildB params={params} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
component ChildA({ params }: { params: TrackedURLSearchParams }) {
|
|
54
|
+
<button onClick={() => {
|
|
55
|
+
const current = parseInt(params.get('count') || '0', 10);
|
|
56
|
+
params.set('count', String(current + 1));
|
|
57
|
+
}}>{'increment'}</button>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
component ChildB({ params }: { params: TrackedURLSearchParams }) {
|
|
61
|
+
let count = track(() => params.get('count'));
|
|
62
|
+
|
|
63
|
+
<pre>{@count}</pre>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
render(ParentTest);
|
|
67
|
+
|
|
68
|
+
const button = container.querySelector('button');
|
|
69
|
+
|
|
70
|
+
// Initial state
|
|
71
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
72
|
+
|
|
73
|
+
// Test increment from child component
|
|
74
|
+
button.click();
|
|
75
|
+
flushSync();
|
|
76
|
+
|
|
77
|
+
expect(container.querySelector('pre').textContent).toBe('1');
|
|
78
|
+
|
|
79
|
+
button.click();
|
|
80
|
+
flushSync();
|
|
81
|
+
|
|
82
|
+
expect(container.querySelector('pre').textContent).toBe('2');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TrackedURLSearchParams } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURLSearchParams > initialization', () => {
|
|
4
|
+
it('creates empty URLSearchParams with reactivity', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const params = new TrackedURLSearchParams();
|
|
7
|
+
|
|
8
|
+
<pre>{params.toString()}</pre>
|
|
9
|
+
<pre>{params.size}</pre>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
render(URLTest);
|
|
13
|
+
|
|
14
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('');
|
|
15
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('creates URLSearchParams from string with reactivity', () => {
|
|
19
|
+
component URLTest() {
|
|
20
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
21
|
+
|
|
22
|
+
<pre>{params.toString()}</pre>
|
|
23
|
+
<pre>{params.size}</pre>
|
|
24
|
+
<pre>{params.get('foo')}</pre>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render(URLTest);
|
|
28
|
+
|
|
29
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&baz=qux');
|
|
30
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
31
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('bar');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('creates URLSearchParams from object with reactivity', () => {
|
|
35
|
+
component URLTest() {
|
|
36
|
+
const params = new TrackedURLSearchParams({ foo: 'bar', baz: 'qux' });
|
|
37
|
+
|
|
38
|
+
<pre>{params.toString()}</pre>
|
|
39
|
+
<pre>{params.size}</pre>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render(URLTest);
|
|
43
|
+
|
|
44
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&baz=qux');
|
|
45
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('handles URL-encoded characters correctly', () => {
|
|
49
|
+
component URLTest() {
|
|
50
|
+
const params = new TrackedURLSearchParams('name=John+Doe&email=john%40example.com');
|
|
51
|
+
|
|
52
|
+
<pre>{params.get('name')}</pre>
|
|
53
|
+
<pre>{params.get('email')}</pre>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render(URLTest);
|
|
57
|
+
|
|
58
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('John Doe');
|
|
59
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('john@example.com');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { flushSync, track, TrackedURLSearchParams } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURLSearchParams > iteration', () => {
|
|
4
|
+
it('handles keys method with reactivity', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
7
|
+
let keys = track(() => Array.from(params.keys()));
|
|
8
|
+
|
|
9
|
+
<button onClick={() => params.append('new', 'value')}>{'add param'}</button>
|
|
10
|
+
<pre>{JSON.stringify(@keys)}</pre>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
render(URLTest);
|
|
14
|
+
|
|
15
|
+
const button = container.querySelector('button');
|
|
16
|
+
|
|
17
|
+
// Initial state
|
|
18
|
+
expect(container.querySelector('pre').textContent).toBe('["foo","baz"]');
|
|
19
|
+
|
|
20
|
+
// Test add
|
|
21
|
+
button.click();
|
|
22
|
+
flushSync();
|
|
23
|
+
|
|
24
|
+
expect(container.querySelector('pre').textContent).toBe('["foo","baz","new"]');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('handles values method with reactivity', () => {
|
|
28
|
+
component URLTest() {
|
|
29
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
30
|
+
let values = track(() => Array.from(params.values()));
|
|
31
|
+
|
|
32
|
+
<button onClick={() => params.set('foo', 'updated')}>{'update foo'}</button>
|
|
33
|
+
<pre>{JSON.stringify(@values)}</pre>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render(URLTest);
|
|
37
|
+
|
|
38
|
+
const button = container.querySelector('button');
|
|
39
|
+
|
|
40
|
+
// Initial state
|
|
41
|
+
expect(container.querySelector('pre').textContent).toBe('["bar","qux"]');
|
|
42
|
+
|
|
43
|
+
// Test update
|
|
44
|
+
button.click();
|
|
45
|
+
flushSync();
|
|
46
|
+
|
|
47
|
+
expect(container.querySelector('pre').textContent).toBe('["updated","qux"]');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles entries method with reactivity', () => {
|
|
51
|
+
component URLTest() {
|
|
52
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
53
|
+
let entries = track(() => Array.from(params.entries()));
|
|
54
|
+
|
|
55
|
+
<button onClick={() => params.append('new', 'value')}>{'add param'}</button>
|
|
56
|
+
<pre>{JSON.stringify(@entries)}</pre>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render(URLTest);
|
|
60
|
+
|
|
61
|
+
const button = container.querySelector('button');
|
|
62
|
+
|
|
63
|
+
// Initial state
|
|
64
|
+
expect(container.querySelector('pre').textContent).toBe('[["foo","bar"],["baz","qux"]]');
|
|
65
|
+
|
|
66
|
+
// Test add
|
|
67
|
+
button.click();
|
|
68
|
+
flushSync();
|
|
69
|
+
|
|
70
|
+
expect(container.querySelector('pre').textContent).toBe('[["foo","bar"],["baz","qux"],["new","value"]]');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles Symbol.iterator with reactivity', () => {
|
|
74
|
+
component URLTest() {
|
|
75
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
76
|
+
let entries = track(() => Array.from(params));
|
|
77
|
+
|
|
78
|
+
<button onClick={() => params.delete('foo')}>{'delete foo'}</button>
|
|
79
|
+
<pre>{JSON.stringify(@entries)}</pre>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
render(URLTest);
|
|
83
|
+
|
|
84
|
+
const button = container.querySelector('button');
|
|
85
|
+
|
|
86
|
+
// Initial state
|
|
87
|
+
expect(container.querySelector('pre').textContent).toBe('[["foo","bar"],["baz","qux"]]');
|
|
88
|
+
|
|
89
|
+
// Test delete
|
|
90
|
+
button.click();
|
|
91
|
+
flushSync();
|
|
92
|
+
|
|
93
|
+
expect(container.querySelector('pre').textContent).toBe('[["baz","qux"]]');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('handles iteration with for...of', () => {
|
|
97
|
+
component URLTest() {
|
|
98
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
99
|
+
|
|
100
|
+
<button onClick={() => params.append('new', 'value')}>{'add param'}</button>
|
|
101
|
+
|
|
102
|
+
for (const [key, value] of params) {
|
|
103
|
+
<pre>{`${key}=${value}`}</pre>
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
render(URLTest);
|
|
108
|
+
|
|
109
|
+
const button = container.querySelector('button');
|
|
110
|
+
|
|
111
|
+
// Initial state
|
|
112
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
113
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('baz=qux');
|
|
114
|
+
|
|
115
|
+
// Test add
|
|
116
|
+
button.click();
|
|
117
|
+
flushSync();
|
|
118
|
+
|
|
119
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
120
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('baz=qux');
|
|
121
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('new=value');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('handles forEach iteration', () => {
|
|
125
|
+
component URLTest() {
|
|
126
|
+
const params = new TrackedURLSearchParams('a=1&b=2&c=3');
|
|
127
|
+
let sum = track(() => {
|
|
128
|
+
let total = 0;
|
|
129
|
+
// Access the params reactively through entries
|
|
130
|
+
for (const [key, value] of params.entries()) {
|
|
131
|
+
total += parseInt(value, 10);
|
|
132
|
+
}
|
|
133
|
+
return total;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
<button onClick={() => params.append('d', '4')}>{'add d=4'}</button>
|
|
137
|
+
<pre>{@sum}</pre>
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
render(URLTest);
|
|
141
|
+
|
|
142
|
+
const button = container.querySelector('button');
|
|
143
|
+
|
|
144
|
+
// Initial state: 1 + 2 + 3 = 6
|
|
145
|
+
expect(container.querySelector('pre').textContent).toBe('6');
|
|
146
|
+
|
|
147
|
+
// Add d=4, sum should be 10
|
|
148
|
+
button.click();
|
|
149
|
+
flushSync();
|
|
150
|
+
|
|
151
|
+
expect(container.querySelector('pre').textContent).toBe('10');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { flushSync, track, TrackedURL, TrackedURLSearchParams } from 'ripple';
|
|
2
|
+
|
|
3
|
+
describe('TrackedURLSearchParams > mutation', () => {
|
|
4
|
+
it('handles append operation with reactivity', () => {
|
|
5
|
+
component URLTest() {
|
|
6
|
+
const params = new TrackedURLSearchParams('foo=bar');
|
|
7
|
+
|
|
8
|
+
<button onClick={() => params.append('baz', 'qux')}>{'append'}</button>
|
|
9
|
+
<pre>{params.toString()}</pre>
|
|
10
|
+
<pre>{params.size}</pre>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
render(URLTest);
|
|
14
|
+
|
|
15
|
+
const button = container.querySelector('button');
|
|
16
|
+
|
|
17
|
+
// Initial state
|
|
18
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
19
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
20
|
+
|
|
21
|
+
// Test append
|
|
22
|
+
button.click();
|
|
23
|
+
flushSync();
|
|
24
|
+
|
|
25
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&baz=qux');
|
|
26
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('handles append with multiple values for same key', () => {
|
|
30
|
+
component URLTest() {
|
|
31
|
+
const params = new TrackedURLSearchParams('foo=bar');
|
|
32
|
+
let allFoo = track(() => params.getAll('foo'));
|
|
33
|
+
|
|
34
|
+
<button onClick={() => params.append('foo', 'baz')}>{'append foo'}</button>
|
|
35
|
+
<pre>{params.toString()}</pre>
|
|
36
|
+
<pre>{JSON.stringify(@allFoo)}</pre>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
render(URLTest);
|
|
40
|
+
|
|
41
|
+
const button = container.querySelector('button');
|
|
42
|
+
|
|
43
|
+
// Initial state
|
|
44
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
45
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('["bar"]');
|
|
46
|
+
|
|
47
|
+
// Test append
|
|
48
|
+
button.click();
|
|
49
|
+
flushSync();
|
|
50
|
+
|
|
51
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&foo=baz');
|
|
52
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('["bar","baz"]');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('handles delete operation with reactivity', () => {
|
|
56
|
+
component URLTest() {
|
|
57
|
+
const params = new TrackedURLSearchParams('foo=bar&baz=qux');
|
|
58
|
+
|
|
59
|
+
<button onClick={() => params.delete('foo')}>{'delete foo'}</button>
|
|
60
|
+
<pre>{params.toString()}</pre>
|
|
61
|
+
<pre>{params.size}</pre>
|
|
62
|
+
<pre>{params.has('foo').toString()}</pre>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
render(URLTest);
|
|
66
|
+
|
|
67
|
+
const button = container.querySelector('button');
|
|
68
|
+
|
|
69
|
+
// Initial state
|
|
70
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&baz=qux');
|
|
71
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
72
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('true');
|
|
73
|
+
|
|
74
|
+
// Test delete
|
|
75
|
+
button.click();
|
|
76
|
+
flushSync();
|
|
77
|
+
|
|
78
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('baz=qux');
|
|
79
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
80
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('false');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('handles delete with specific value', () => {
|
|
84
|
+
component URLTest() {
|
|
85
|
+
const params = new TrackedURLSearchParams('foo=bar&foo=baz&foo=qux');
|
|
86
|
+
|
|
87
|
+
<button onClick={() => params.delete('foo', 'baz')}>{'delete foo=baz'}</button>
|
|
88
|
+
<pre>{params.toString()}</pre>
|
|
89
|
+
<pre>{params.size}</pre>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render(URLTest);
|
|
93
|
+
|
|
94
|
+
const button = container.querySelector('button');
|
|
95
|
+
|
|
96
|
+
// Initial state
|
|
97
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&foo=baz&foo=qux');
|
|
98
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
99
|
+
|
|
100
|
+
// Test delete specific value
|
|
101
|
+
button.click();
|
|
102
|
+
flushSync();
|
|
103
|
+
|
|
104
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&foo=qux');
|
|
105
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('handles delete when key does not exist', () => {
|
|
109
|
+
component URLTest() {
|
|
110
|
+
const params = new TrackedURLSearchParams('foo=bar');
|
|
111
|
+
let reactiveSize = track(() => params.size);
|
|
112
|
+
|
|
113
|
+
<button onClick={() => params.delete('nonexistent')}>{'delete nonexistent'}</button>
|
|
114
|
+
<pre>{params.toString()}</pre>
|
|
115
|
+
<pre>{@reactiveSize}</pre>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
render(URLTest);
|
|
119
|
+
|
|
120
|
+
const button = container.querySelector('button');
|
|
121
|
+
|
|
122
|
+
// Initial state
|
|
123
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
124
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
125
|
+
|
|
126
|
+
// Test delete nonexistent - should not trigger reactivity
|
|
127
|
+
button.click();
|
|
128
|
+
flushSync();
|
|
129
|
+
|
|
130
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
131
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles set operation with reactivity', () => {
|
|
135
|
+
component URLTest() {
|
|
136
|
+
const params = new TrackedURLSearchParams('foo=bar');
|
|
137
|
+
|
|
138
|
+
<button onClick={() => params.set('foo', 'updated')}>{'update foo'}</button>
|
|
139
|
+
<button onClick={() => params.set('baz', 'qux')}>{'add baz'}</button>
|
|
140
|
+
<pre>{params.toString()}</pre>
|
|
141
|
+
<pre>{params.size}</pre>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
render(URLTest);
|
|
145
|
+
|
|
146
|
+
const updateButton = container.querySelectorAll('button')[0];
|
|
147
|
+
const addButton = container.querySelectorAll('button')[1];
|
|
148
|
+
|
|
149
|
+
// Initial state
|
|
150
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
151
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
152
|
+
|
|
153
|
+
// Test update
|
|
154
|
+
updateButton.click();
|
|
155
|
+
flushSync();
|
|
156
|
+
|
|
157
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=updated');
|
|
158
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
159
|
+
|
|
160
|
+
// Test add new key
|
|
161
|
+
addButton.click();
|
|
162
|
+
flushSync();
|
|
163
|
+
|
|
164
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=updated&baz=qux');
|
|
165
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('handles set with multiple existing values', () => {
|
|
169
|
+
component URLTest() {
|
|
170
|
+
const params = new TrackedURLSearchParams('foo=bar&foo=baz&foo=qux');
|
|
171
|
+
let allFoo = track(() => params.getAll('foo'));
|
|
172
|
+
|
|
173
|
+
<button onClick={() => params.set('foo', 'single')}>{'set foo'}</button>
|
|
174
|
+
<pre>{params.toString()}</pre>
|
|
175
|
+
<pre>{JSON.stringify(@allFoo)}</pre>
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
render(URLTest);
|
|
179
|
+
|
|
180
|
+
const button = container.querySelector('button');
|
|
181
|
+
|
|
182
|
+
// Initial state
|
|
183
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar&foo=baz&foo=qux');
|
|
184
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('["bar","baz","qux"]');
|
|
185
|
+
|
|
186
|
+
// Test set - should replace all values
|
|
187
|
+
button.click();
|
|
188
|
+
flushSync();
|
|
189
|
+
|
|
190
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=single');
|
|
191
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('["single"]');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('handles set when value is the same', () => {
|
|
195
|
+
component URLTest() {
|
|
196
|
+
const params = new TrackedURLSearchParams('foo=bar');
|
|
197
|
+
let reactiveString = track(() => params.toString());
|
|
198
|
+
|
|
199
|
+
<button onClick={() => params.set('foo', 'bar')}>{'set same value'}</button>
|
|
200
|
+
<pre>{@reactiveString}</pre>
|
|
201
|
+
<pre>{params.size}</pre>
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
render(URLTest);
|
|
205
|
+
|
|
206
|
+
const button = container.querySelector('button');
|
|
207
|
+
|
|
208
|
+
// Test set same value - should not trigger reactivity changes
|
|
209
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
210
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
211
|
+
|
|
212
|
+
button.click();
|
|
213
|
+
flushSync();
|
|
214
|
+
|
|
215
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('foo=bar');
|
|
216
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('handles sort operation with reactivity', () => {
|
|
220
|
+
component URLTest() {
|
|
221
|
+
const params = new TrackedURLSearchParams('z=last&a=first&m=middle');
|
|
222
|
+
|
|
223
|
+
<button onClick={() => params.sort()}>{'sort'}</button>
|
|
224
|
+
<pre>{params.toString()}</pre>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
render(URLTest);
|
|
228
|
+
|
|
229
|
+
const button = container.querySelector('button');
|
|
230
|
+
|
|
231
|
+
// Initial state
|
|
232
|
+
expect(container.querySelector('pre').textContent).toBe('z=last&a=first&m=middle');
|
|
233
|
+
|
|
234
|
+
// Test sort
|
|
235
|
+
button.click();
|
|
236
|
+
flushSync();
|
|
237
|
+
|
|
238
|
+
expect(container.querySelector('pre').textContent).toBe('a=first&m=middle&z=last');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('handles clearing all params via delete', () => {
|
|
242
|
+
component URLTest() {
|
|
243
|
+
const url = new TrackedURL('https://example.com?foo=bar&baz=qux');
|
|
244
|
+
const params = url.searchParams;
|
|
245
|
+
|
|
246
|
+
<button onClick={() => {
|
|
247
|
+
params.delete('foo');
|
|
248
|
+
params.delete('baz');
|
|
249
|
+
}}>{'clear all'}</button>
|
|
250
|
+
<pre>{url.href}</pre>
|
|
251
|
+
<pre>{params.size}</pre>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
render(URLTest);
|
|
255
|
+
|
|
256
|
+
const button = container.querySelector('button');
|
|
257
|
+
|
|
258
|
+
// Initial state
|
|
259
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/?foo=bar&baz=qux');
|
|
260
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
261
|
+
|
|
262
|
+
// Test clear all
|
|
263
|
+
button.click();
|
|
264
|
+
flushSync();
|
|
265
|
+
|
|
266
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('https://example.com/');
|
|
267
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('handles multiple operations in sequence', () => {
|
|
271
|
+
component URLTest() {
|
|
272
|
+
const params = new TrackedURLSearchParams();
|
|
273
|
+
|
|
274
|
+
<button onClick={() => {
|
|
275
|
+
params.append('a', '1');
|
|
276
|
+
params.append('b', '2');
|
|
277
|
+
params.set('a', '10');
|
|
278
|
+
params.delete('b');
|
|
279
|
+
params.append('c', '3');
|
|
280
|
+
params.sort();
|
|
281
|
+
}}>{'complex operations'}</button>
|
|
282
|
+
<pre>{params.toString()}</pre>
|
|
283
|
+
<pre>{params.size}</pre>
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
render(URLTest);
|
|
287
|
+
|
|
288
|
+
const button = container.querySelector('button');
|
|
289
|
+
|
|
290
|
+
// Initial state
|
|
291
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('');
|
|
292
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
293
|
+
|
|
294
|
+
// Test complex operations
|
|
295
|
+
button.click();
|
|
296
|
+
flushSync();
|
|
297
|
+
|
|
298
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('a=10&c=3');
|
|
299
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('handles duplicate keys with different values', () => {
|
|
303
|
+
component URLTest() {
|
|
304
|
+
const params = new TrackedURLSearchParams();
|
|
305
|
+
let tags = track(() => params.getAll('tag'));
|
|
306
|
+
|
|
307
|
+
<button onClick={() => params.append('tag', 'javascript')}>{'add js'}</button>
|
|
308
|
+
<button onClick={() => params.append('tag', 'typescript')}>{'add ts'}</button>
|
|
309
|
+
<button onClick={() => params.append('tag', 'ripple')}>{'add ripple'}</button>
|
|
310
|
+
<pre>{JSON.stringify(@tags)}</pre>
|
|
311
|
+
<pre>{params.size}</pre>
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
render(URLTest);
|
|
315
|
+
|
|
316
|
+
const jsButton = container.querySelectorAll('button')[0];
|
|
317
|
+
const tsButton = container.querySelectorAll('button')[1];
|
|
318
|
+
const rippleButton = container.querySelectorAll('button')[2];
|
|
319
|
+
|
|
320
|
+
// Initial state
|
|
321
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
|
|
322
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
|
|
323
|
+
|
|
324
|
+
// Add tags sequentially
|
|
325
|
+
jsButton.click();
|
|
326
|
+
flushSync();
|
|
327
|
+
|
|
328
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["javascript"]');
|
|
329
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('1');
|
|
330
|
+
|
|
331
|
+
tsButton.click();
|
|
332
|
+
flushSync();
|
|
333
|
+
|
|
334
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["javascript","typescript"]');
|
|
335
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
|
|
336
|
+
|
|
337
|
+
rippleButton.click();
|
|
338
|
+
flushSync();
|
|
339
|
+
|
|
340
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["javascript","typescript","ripple"]');
|
|
341
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
342
|
+
});
|
|
343
|
+
});
|