Skip to content

Commit b2d106b

Browse files
FoHoOVRich-Harris
andauthored
fix: reflect SvelteURLSearchParams changes to SvelteURL (#12285)
* separated url-search params and url classes and added more tests * making URL aware of SvelteURLSearchParams changes so they are in sync * added changeset * generated types * bail out if url.sp and SvelteSp are already in sync * sync search on searchParams change because of how node18 handles it * use ts-expect-error instead of ts-ignore * remove a bit of indirection * tweak * fix * regenerate types * short-circuit in both directions --------- Co-authored-by: Rich Harris <[email protected]>
1 parent d986094 commit b2d106b

File tree

7 files changed

+424
-145
lines changed

7 files changed

+424
-145
lines changed

.changeset/angry-birds-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: reflect SvelteURLSearchParams changes to SvelteURL

packages/svelte/src/reactivity/index-client.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
export { SvelteDate } from './date.js';
22
export { SvelteSet } from './set.js';
33
export { SvelteMap } from './map.js';
4-
export { SvelteURL, SvelteURLSearchParams } from './url.js';
4+
export { SvelteURL } from './url.js';
5+
export { SvelteURLSearchParams } from './url-search-params.js';
56

67
/** @deprecated Use `SvelteDate` instead */
78
export function Date() {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { source } from '../internal/client/reactivity/sources.js';
2+
import { get } from '../internal/client/runtime.js';
3+
import { get_current_url } from './url.js';
4+
import { increment } from './utils.js';
5+
6+
export const REPLACE = Symbol();
7+
8+
export class SvelteURLSearchParams extends URLSearchParams {
9+
#version = source(0);
10+
#url = get_current_url();
11+
12+
#updating = false;
13+
14+
#update_url() {
15+
if (!this.#url || this.#updating) return;
16+
this.#updating = true;
17+
18+
const search = this.toString();
19+
this.#url.search = search && `?${search}`;
20+
21+
this.#updating = false;
22+
}
23+
24+
/**
25+
* @param {URLSearchParams} params
26+
*/
27+
[REPLACE](params) {
28+
if (this.#updating) return;
29+
this.#updating = true;
30+
31+
for (const key of [...super.keys()]) {
32+
super.delete(key);
33+
}
34+
35+
for (const [key, value] of params) {
36+
super.append(key, value);
37+
}
38+
39+
increment(this.#version);
40+
this.#updating = false;
41+
}
42+
43+
/**
44+
* @param {string} name
45+
* @param {string} value
46+
* @returns {void}
47+
*/
48+
append(name, value) {
49+
super.append(name, value);
50+
this.#update_url();
51+
increment(this.#version);
52+
}
53+
54+
/**
55+
* @param {string} name
56+
* @param {string=} value
57+
* @returns {void}
58+
*/
59+
delete(name, value) {
60+
var has_value = super.has(name, value);
61+
super.delete(name, value);
62+
if (has_value) {
63+
this.#update_url();
64+
increment(this.#version);
65+
}
66+
}
67+
68+
/**
69+
* @param {string} name
70+
* @returns {string|null}
71+
*/
72+
get(name) {
73+
get(this.#version);
74+
return super.get(name);
75+
}
76+
77+
/**
78+
* @param {string} name
79+
* @returns {string[]}
80+
*/
81+
getAll(name) {
82+
get(this.#version);
83+
return super.getAll(name);
84+
}
85+
86+
/**
87+
* @param {string} name
88+
* @param {string=} value
89+
* @returns {boolean}
90+
*/
91+
has(name, value) {
92+
get(this.#version);
93+
return super.has(name, value);
94+
}
95+
96+
keys() {
97+
get(this.#version);
98+
return super.keys();
99+
}
100+
101+
/**
102+
* @param {string} name
103+
* @param {string} value
104+
* @returns {void}
105+
*/
106+
set(name, value) {
107+
var previous = super.getAll(name).join('');
108+
super.set(name, value);
109+
// can't use has(name, value), because for something like https://svelte.dev?foo=1&bar=2&foo=3
110+
// if you set `foo` to 1, then foo=3 gets deleted whilst `has("foo", "1")` returns true
111+
if (previous !== super.getAll(name).join('')) {
112+
this.#update_url();
113+
increment(this.#version);
114+
}
115+
}
116+
117+
sort() {
118+
super.sort();
119+
this.#update_url();
120+
increment(this.#version);
121+
}
122+
123+
toString() {
124+
get(this.#version);
125+
return super.toString();
126+
}
127+
128+
values() {
129+
get(this.#version);
130+
return super.values();
131+
}
132+
133+
entries() {
134+
get(this.#version);
135+
return super.entries();
136+
}
137+
138+
[Symbol.iterator]() {
139+
return this.entries();
140+
}
141+
142+
get size() {
143+
get(this.#version);
144+
return super.size;
145+
}
146+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { render_effect, effect_root } from '../internal/client/reactivity/effects.js';
2+
import { flushSync } from '../index-client.js';
3+
import { assert, test } from 'vitest';
4+
import { SvelteURLSearchParams } from './url-search-params';
5+
6+
test('new URLSearchParams', () => {
7+
const params = new SvelteURLSearchParams('a=b');
8+
const log: any = [];
9+
10+
const cleanup = effect_root(() => {
11+
render_effect(() => {
12+
log.push(params.toString());
13+
});
14+
});
15+
16+
flushSync(() => {
17+
params.set('a', 'c');
18+
});
19+
20+
flushSync(() => {
21+
// nothing should happen here
22+
params.set('a', 'c');
23+
});
24+
25+
assert.deepEqual(log, ['a=b', 'a=c']);
26+
27+
cleanup();
28+
});
29+
30+
test('URLSearchParams.set', () => {
31+
const params = new SvelteURLSearchParams();
32+
const log: any = [];
33+
34+
const cleanup = effect_root(() => {
35+
render_effect(() => {
36+
log.push(params.toString());
37+
});
38+
});
39+
40+
flushSync(() => {
41+
params.set('a', 'b');
42+
});
43+
44+
flushSync(() => {
45+
params.set('a', 'c');
46+
});
47+
48+
flushSync(() => {
49+
// nothing should happen here
50+
params.set('a', 'c');
51+
});
52+
53+
assert.deepEqual(log, ['', 'a=b', 'a=c']);
54+
55+
cleanup();
56+
});
57+
58+
test('URLSearchParams.append', () => {
59+
const params = new SvelteURLSearchParams();
60+
const log: any = [];
61+
62+
const cleanup = effect_root(() => {
63+
render_effect(() => {
64+
log.push(params.toString());
65+
});
66+
});
67+
68+
flushSync(() => {
69+
params.append('a', 'b');
70+
});
71+
72+
flushSync(() => {
73+
// nothing should happen here
74+
params.set('a', 'b');
75+
});
76+
77+
flushSync(() => {
78+
params.append('a', 'c');
79+
});
80+
81+
assert.deepEqual(log, ['', 'a=b', 'a=b&a=c']);
82+
83+
cleanup();
84+
});
85+
86+
test('URLSearchParams.delete', () => {
87+
const params = new SvelteURLSearchParams('a=b&c=d');
88+
const log: any = [];
89+
90+
const cleanup = effect_root(() => {
91+
render_effect(() => {
92+
log.push(params.toString());
93+
});
94+
});
95+
96+
flushSync(() => {
97+
params.delete('a');
98+
});
99+
100+
flushSync(() => {
101+
// nothing should happen here
102+
params.delete('a');
103+
});
104+
105+
flushSync(() => {
106+
params.set('a', 'b');
107+
});
108+
109+
assert.deepEqual(log, ['a=b&c=d', 'c=d', 'c=d&a=b']);
110+
111+
cleanup();
112+
});
113+
114+
test('URLSearchParams.get', () => {
115+
const params = new SvelteURLSearchParams('a=b&c=d');
116+
const log: any = [];
117+
118+
const cleanup = effect_root(() => {
119+
render_effect(() => {
120+
log.push(params.get('a'));
121+
});
122+
render_effect(() => {
123+
log.push(params.get('c'));
124+
});
125+
render_effect(() => {
126+
log.push(params.get('e'));
127+
});
128+
});
129+
130+
flushSync(() => {
131+
params.set('a', 'b');
132+
});
133+
134+
flushSync(() => {
135+
params.set('a', 'new-b');
136+
});
137+
138+
flushSync(() => {
139+
params.delete('a');
140+
});
141+
142+
assert.deepEqual(log, ['b', 'd', null, 'new-b', 'd', null, null, 'd', null]);
143+
144+
cleanup();
145+
});
146+
147+
test('URLSearchParams.getAll', () => {
148+
const params = new SvelteURLSearchParams('a=b&c=d');
149+
const log: any = [];
150+
151+
const cleanup = effect_root(() => {
152+
render_effect(() => {
153+
log.push(params.getAll('a'));
154+
});
155+
render_effect(() => {
156+
log.push(params.getAll('q'));
157+
});
158+
});
159+
160+
flushSync(() => {
161+
params.append('a', 'b1');
162+
});
163+
164+
flushSync(() => {
165+
params.append('q', 'z');
166+
});
167+
168+
assert.deepEqual(log, [
169+
// initial
170+
['b'],
171+
[],
172+
// first flush
173+
['b', 'b1'],
174+
[],
175+
// second flush
176+
['b', 'b1'],
177+
['z']
178+
]);
179+
180+
cleanup();
181+
});
182+
183+
test('URLSearchParams.toString', () => {
184+
const params = new SvelteURLSearchParams();
185+
const log: any = [];
186+
187+
const cleanup = effect_root(() => {
188+
render_effect(() => {
189+
log.push(params.toString());
190+
});
191+
});
192+
193+
flushSync(() => {
194+
params.set('a', 'b');
195+
});
196+
197+
flushSync(() => {
198+
params.append('a', 'c');
199+
});
200+
201+
assert.deepEqual(log, ['', 'a=b', 'a=b&a=c']);
202+
203+
cleanup();
204+
});
205+
206+
test('SvelteURLSearchParams instanceof URLSearchParams', () => {
207+
assert.ok(new SvelteURLSearchParams() instanceof URLSearchParams);
208+
});

0 commit comments

Comments
 (0)