Skip to content

Commit cad3725

Browse files
committed
Allow building query strings independently of paths (again)
1 parent 33a13cc commit cad3725

File tree

4 files changed

+136
-42
lines changed

4 files changed

+136
-42
lines changed

src/entry/mithril.esm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import m from "../core.js"
22

33
import {Link, WithRouter} from "../std/router.js"
44
import {debouncer, throttler} from "../std/rate-limit.js"
5+
import {p, q} from "../std/path-query.js"
56
import init from "../std/init.js"
67
import lazy from "../std/lazy.js"
7-
import p from "../std/p.js"
88
import tracked from "../std/tracked.js"
99
import use from "../std/use.js"
1010
import withProgress from "../std/with-progress.js"
1111

1212
m.WithRouter = WithRouter
1313
m.Link = Link
1414
m.p = p
15+
m.q = q
1516
m.withProgress = withProgress
1617
m.lazy = lazy
1718
m.init = init

src/std/p.js renamed to src/std/path-query.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ var serializeQueryValue = (key, value) => {
1212
}
1313
}
1414

15+
var q = (params) => Object.entries(params).map(([k, v]) => serializeQueryValue(k, v)).join("&")
16+
1517
var invalidTemplateChars = /:([^\/\.-]+)(\.{3})?:/
1618

1719
// Returns `path` from `template` + `params`
@@ -44,11 +46,11 @@ var p = (template, params) => {
4446

4547
if (queryIndex >= 0) result += template.slice(queryIndex, queryEnd)
4648
if (newQueryIndex >= 0) result += (queryIndex < 0 ? "?" : "&") + resolved.slice(newQueryIndex, newQueryEnd)
47-
var querystring = Object.entries(query).map(([k, v]) => serializeQueryValue(k, v)).join("&")
49+
var querystring = q(query)
4850
if (querystring) result += (queryIndex < 0 && newQueryIndex < 0 ? "?" : "&") + querystring
4951
if (hashIndex >= 0) result += template.slice(hashIndex)
5052
if (newHashIndex >= 0) result += (hashIndex < 0 ? "" : "&") + resolved.slice(newHashIndex)
5153
return result
5254
}
5355

54-
export {p as default}
56+
export {p, q}

tests/std/p.js

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,202 @@
11
import o from "ospec"
22

3-
import p from "../../src/std/p.js"
3+
import m from "../../src/entry/mithril.esm.js"
44

55
o.spec("p", () => {
66
function test(prefix) {
77
o("returns path if no params", () => {
8-
var string = p(prefix + "/route/foo", undefined)
8+
var string = m.p(prefix + "/route/foo", undefined)
99

1010
o(string).equals(`${prefix}/route/foo`)
1111
})
1212
o("skips interpolation if no params", () => {
13-
var string = p(prefix + "/route/:id", undefined)
13+
var string = m.p(prefix + "/route/:id", undefined)
1414

1515
o(string).equals(`${prefix}/route/:id`)
1616
})
1717
o("appends query strings", () => {
18-
var string = p(prefix + "/route/foo", {a: "b", c: 1})
18+
var string = m.p(prefix + "/route/foo", {a: "b", c: 1})
1919

2020
o(string).equals(`${prefix}/route/foo?a=b&c=1`)
2121
})
2222
o("inserts template parameters at end", () => {
23-
var string = p(prefix + "/route/:id", {id: "1"})
23+
var string = m.p(prefix + "/route/:id", {id: "1"})
2424

2525
o(string).equals(`${prefix}/route/1`)
2626
})
2727
o("inserts template parameters at beginning", () => {
28-
var string = p(prefix + "/:id/foo", {id: "1"})
28+
var string = m.p(prefix + "/:id/foo", {id: "1"})
2929

3030
o(string).equals(`${prefix}/1/foo`)
3131
})
3232
o("inserts template parameters at middle", () => {
33-
var string = p(prefix + "/route/:id/foo", {id: "1"})
33+
var string = m.p(prefix + "/route/:id/foo", {id: "1"})
3434

3535
o(string).equals(`${prefix}/route/1/foo`)
3636
})
3737
o("inserts variadic paths", () => {
38-
var string = p(prefix + "/route/:foo...", {foo: "id/1"})
38+
var string = m.p(prefix + "/route/:foo...", {foo: "id/1"})
3939

4040
o(string).equals(`${prefix}/route/id/1`)
4141
})
4242
o("inserts variadic paths with initial slashes", () => {
43-
var string = p(prefix + "/route/:foo...", {foo: "/id/1"})
43+
var string = m.p(prefix + "/route/:foo...", {foo: "/id/1"})
4444

4545
o(string).equals(`${prefix}/route//id/1`)
4646
})
4747
o("skips template parameters at end if param missing", () => {
48-
var string = p(prefix + "/route/:id", {param: 1})
48+
var string = m.p(prefix + "/route/:id", {param: 1})
4949

5050
o(string).equals(`${prefix}/route/:id?param=1`)
5151
})
5252
o("skips template parameters at beginning if param missing", () => {
53-
var string = p(prefix + "/:id/foo", {param: 1})
53+
var string = m.p(prefix + "/:id/foo", {param: 1})
5454

5555
o(string).equals(`${prefix}/:id/foo?param=1`)
5656
})
5757
o("skips template parameters at middle if param missing", () => {
58-
var string = p(prefix + "/route/:id/foo", {param: 1})
58+
var string = m.p(prefix + "/route/:id/foo", {param: 1})
5959

6060
o(string).equals(`${prefix}/route/:id/foo?param=1`)
6161
})
6262
o("skips variadic template parameters if param missing", () => {
63-
var string = p(prefix + "/route/:foo...", {param: "/id/1"})
63+
var string = m.p(prefix + "/route/:foo...", {param: "/id/1"})
6464

6565
o(string).equals(`${prefix}/route/:foo...?param=%2Fid%2F1`)
6666
})
6767
o("handles escaped values", () => {
68-
var data = p(prefix + "/route/:foo", {"foo": ";:@&=+$,/?%#"})
68+
var data = m.p(prefix + "/route/:foo", {"foo": ";:@&=+$,/?%#"})
6969

7070
o(data).equals(`${prefix}/route/%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23`)
7171
})
7272
o("handles unicode", () => {
73-
var data = p(prefix + "/route/:ö", {"ö": "ö"})
73+
var data = m.p(prefix + "/route/:ö", {"ö": "ö"})
7474

7575
o(data).equals(`${prefix}/route/%C3%B6`)
7676
})
7777
o("handles zero", () => {
78-
var string = p(prefix + "/route/:a", {a: 0})
78+
var string = m.p(prefix + "/route/:a", {a: 0})
7979

8080
o(string).equals(`${prefix}/route/0`)
8181
})
8282
o("handles false", () => {
83-
var string = p(prefix + "/route/:a", {a: false})
83+
var string = m.p(prefix + "/route/:a", {a: false})
8484

8585
o(string).equals(`${prefix}/route/false`)
8686
})
8787
o("handles dashes", () => {
88-
var string = p(prefix + "/:lang-:region/route", {
88+
var string = m.p(prefix + "/:lang-:region/route", {
8989
lang: "en",
9090
region: "US"
9191
})
9292

9393
o(string).equals(`${prefix}/en-US/route`)
9494
})
9595
o("handles dots", () => {
96-
var string = p(prefix + "/:file.:ext/view", {
96+
var string = m.p(prefix + "/:file.:ext/view", {
9797
file: "image",
9898
ext: "png"
9999
})
100100

101101
o(string).equals(`${prefix}/image.png/view`)
102102
})
103103
o("merges query strings", () => {
104-
var string = p(prefix + "/item?a=1&b=2", {c: 3})
104+
var string = m.p(prefix + "/item?a=1&b=2", {c: 3})
105105

106106
o(string).equals(`${prefix}/item?a=1&b=2&c=3`)
107107
})
108108
o("merges query strings with other parameters", () => {
109-
var string = p(prefix + "/item/:id?a=1&b=2", {id: "foo", c: 3})
109+
var string = m.p(prefix + "/item/:id?a=1&b=2", {id: "foo", c: 3})
110110

111111
o(string).equals(`${prefix}/item/foo?a=1&b=2&c=3`)
112112
})
113113
o("consumes template parameters without modifying query string", () => {
114-
var string = p(prefix + "/item/:id?a=1&b=2", {id: "foo"})
114+
var string = m.p(prefix + "/item/:id?a=1&b=2", {id: "foo"})
115115

116116
o(string).equals(`${prefix}/item/foo?a=1&b=2`)
117117
})
118118
o("handles flat object in query string", () => {
119-
var string = p(prefix, {a: "b", c: 1})
119+
var string = m.p(prefix, {a: "b", c: 1})
120120

121121
o(string).equals(`${prefix}?a=b&c=1`)
122122
})
123123
o("handles escaped values in query string", () => {
124-
var data = p(prefix, {";:@&=+$,/?%#": ";:@&=+$,/?%#"})
124+
var data = m.p(prefix, {";:@&=+$,/?%#": ";:@&=+$,/?%#"})
125125

126126
o(data).equals(`${prefix}?%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23=%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23`)
127127
})
128128
o("handles unicode in query string", () => {
129-
var data = p(prefix, {"ö": "ö"})
129+
var data = m.p(prefix, {"ö": "ö"})
130130

131131
o(data).equals(`${prefix}?%C3%B6=%C3%B6`)
132132
})
133133
o("handles nested object in query string", () => {
134-
var string = p(prefix, {a: {b: 1, c: 2}})
134+
var string = m.p(prefix, {a: {b: 1, c: 2}})
135135

136136
o(string).equals(`${prefix}?a%5Bb%5D=1&a%5Bc%5D=2`)
137137
})
138138
o("handles deep nested object in query string", () => {
139-
var string = p(prefix, {a: {b: {c: 1, d: 2}}})
139+
var string = m.p(prefix, {a: {b: {c: 1, d: 2}}})
140140

141141
o(string).equals(`${prefix}?a%5Bb%5D%5Bc%5D=1&a%5Bb%5D%5Bd%5D=2`)
142142
})
143143
o("handles nested array in query string", () => {
144-
var string = p(prefix, {a: ["x", "y"]})
144+
var string = m.p(prefix, {a: ["x", "y"]})
145145

146146
o(string).equals(`${prefix}?a%5B%5D=x&a%5B%5D=y`)
147147
})
148148
o("handles array w/ dupe values in query string", () => {
149-
var string = p(prefix, {a: ["x", "x"]})
149+
var string = m.p(prefix, {a: ["x", "x"]})
150150

151151
o(string).equals(`${prefix}?a%5B%5D=x&a%5B%5D=x`)
152152
})
153153
o("handles deep nested array in query string", () => {
154-
var string = p(prefix, {a: [["x", "y"]]})
154+
var string = m.p(prefix, {a: [["x", "y"]]})
155155

156156
o(string).equals(`${prefix}?a%5B%5D%5B%5D=x&a%5B%5D%5B%5D=y`)
157157
})
158158
o("handles deep nested array in object in query string", () => {
159-
var string = p(prefix, {a: {b: ["x", "y"]}})
159+
var string = m.p(prefix, {a: {b: ["x", "y"]}})
160160

161161
o(string).equals(`${prefix}?a%5Bb%5D%5B%5D=x&a%5Bb%5D%5B%5D=y`)
162162
})
163163
o("handles deep nested object in array in query string", () => {
164-
var string = p(prefix, {a: [{b: 1, c: 2}]})
164+
var string = m.p(prefix, {a: [{b: 1, c: 2}]})
165165

166166
o(string).equals(`${prefix}?a%5B%5D%5Bb%5D=1&a%5B%5D%5Bc%5D=2`)
167167
})
168168
o("handles date in query string", () => {
169-
var string = p(prefix, {a: new Date(0)})
169+
var string = m.p(prefix, {a: new Date(0)})
170170

171171
o(string).equals(`${prefix}?a=${encodeURIComponent(new Date(0).toString())}`)
172172
})
173173
o("handles zero in query string", () => {
174-
var string = p(prefix, {a: 0})
174+
var string = m.p(prefix, {a: 0})
175175

176176
o(string).equals(`${prefix}?a=0`)
177177
})
178178
o("retains empty string literally", () => {
179-
var string = p(prefix, {a: ""})
179+
var string = m.p(prefix, {a: ""})
180180

181181
o(string).equals(`${prefix}?a=`)
182182
})
183183
o("drops `null` from query string", () => {
184-
var string = p(prefix, {a: null})
184+
var string = m.p(prefix, {a: null})
185185

186186
o(string).equals(prefix)
187187
})
188188
o("drops `undefined` from query string", () => {
189-
var string = p(prefix, {a: undefined})
189+
var string = m.p(prefix, {a: undefined})
190190

191191
o(string).equals(prefix)
192192
})
193193
o("turns `true` into value-less string in query string", () => {
194-
var string = p(prefix, {a: true})
194+
var string = m.p(prefix, {a: true})
195195

196196
o(string).equals(`${prefix}?a`)
197197
})
198198
o("drops `false` from query string", () => {
199-
var string = p(prefix, {a: false})
199+
var string = m.p(prefix, {a: false})
200200

201201
o(string).equals(prefix)
202202
})

tests/std/q.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import o from "ospec"
2+
3+
import m from "../../src/entry/mithril.esm.js"
4+
5+
o.spec("q", () => {
6+
o("handles flat object", () => {
7+
var string = m.q({a: "b", c: 1})
8+
9+
o(string).equals("a=b&c=1")
10+
})
11+
o("handles escaped values", () => {
12+
var data = m.q({";:@&=+$,/?%#": ";:@&=+$,/?%#"})
13+
14+
o(data).equals("%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23=%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23")
15+
})
16+
o("handles unicode", () => {
17+
var data = m.q({"ö": "ö"})
18+
19+
o(data).equals("%C3%B6=%C3%B6")
20+
})
21+
o("handles nested object in query string", () => {
22+
var string = m.q({a: {b: 1, c: 2}})
23+
24+
o(string).equals("a%5Bb%5D=1&a%5Bc%5D=2")
25+
})
26+
o("handles deep nested object in query string", () => {
27+
var string = m.q({a: {b: {c: 1, d: 2}}})
28+
29+
o(string).equals("a%5Bb%5D%5Bc%5D=1&a%5Bb%5D%5Bd%5D=2")
30+
})
31+
o("handles nested array in query string", () => {
32+
var string = m.q({a: ["x", "y"]})
33+
34+
o(string).equals("a%5B%5D=x&a%5B%5D=y")
35+
})
36+
o("handles array w/ dupe values in query string", () => {
37+
var string = m.q({a: ["x", "x"]})
38+
39+
o(string).equals("a%5B%5D=x&a%5B%5D=x")
40+
})
41+
o("handles deep nested array in query string", () => {
42+
var string = m.q({a: [["x", "y"]]})
43+
44+
o(string).equals("a%5B%5D%5B%5D=x&a%5B%5D%5B%5D=y")
45+
})
46+
o("handles deep nested array in object in query string", () => {
47+
var string = m.q({a: {b: ["x", "y"]}})
48+
49+
o(string).equals("a%5Bb%5D%5B%5D=x&a%5Bb%5D%5B%5D=y")
50+
})
51+
o("handles deep nested object in array in query string", () => {
52+
var string = m.q({a: [{b: 1, c: 2}]})
53+
54+
o(string).equals("a%5B%5D%5Bb%5D=1&a%5B%5D%5Bc%5D=2")
55+
})
56+
o("handles date in query string", () => {
57+
var string = m.q({a: new Date(0)})
58+
59+
o(string).equals(`a=${encodeURIComponent(new Date(0).toString())}`)
60+
})
61+
o("handles zero in query string", () => {
62+
var string = m.q({a: 0})
63+
64+
o(string).equals("a=0")
65+
})
66+
o("retains empty string literally", () => {
67+
var string = m.q({a: ""})
68+
69+
o(string).equals("a=")
70+
})
71+
o("drops `null` from query string", () => {
72+
var string = m.q({a: null})
73+
74+
o(string).equals("")
75+
})
76+
o("drops `undefined` from query string", () => {
77+
var string = m.q({a: undefined})
78+
79+
o(string).equals("")
80+
})
81+
o("turns `true` into value-less string in query string", () => {
82+
var string = m.q({a: true})
83+
84+
o(string).equals("a")
85+
})
86+
o("drops `false` from query string", () => {
87+
var string = m.q({a: false})
88+
89+
o(string).equals("")
90+
})
91+
})

0 commit comments

Comments
 (0)