Skip to content

Commit 2447ecb

Browse files
committed
[added] formatPattern util method
1 parent aef8747 commit 2447ecb

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

modules/URLUtils.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import qs from 'qs';
2+
import invariant from 'invariant';
23

34
export var parseQueryString = qs.parse;
45

@@ -134,3 +135,52 @@ export function matchPattern(pattern, pathname) {
134135
export function getParamNames(pattern) {
135136
return compilePattern(pattern).paramNames;
136137
}
138+
139+
/**
140+
* Returns a version of the given pattern with params interpolated. Throws
141+
* if there is a dynamic segment of the pattern for which there is no param.
142+
*/
143+
export function formatPattern(pattern, params) {
144+
params = params || {};
145+
146+
var { tokens } = compilePattern(pattern);
147+
var parenCount = 0, pathname = '', splatIndex = 0;
148+
149+
var token, paramName, paramValue;
150+
for (var i = 0, len = tokens.length; i < len; ++i) {
151+
token = tokens[i];
152+
153+
if (token === '*') {
154+
paramValue = Array.isArray(params.splat) ? params.splat[splatIndex++] : params.splat;
155+
156+
invariant(
157+
paramValue != null || parenCount > 0,
158+
'Missing splat #%s for path "%s"',
159+
splatIndex, pattern
160+
);
161+
162+
if (paramValue != null)
163+
pathname += paramValue;
164+
} else if (token === '(') {
165+
parenCount += 1;
166+
} else if (token === ')') {
167+
parenCount -= 1;
168+
} else if (token.charAt(0) === ':') {
169+
paramName = token.substring(1);
170+
paramValue = params[paramName];
171+
172+
invariant(
173+
paramValue != null || parenCount > 0,
174+
'Missing "%s" parameter for path "%s"',
175+
paramName, pattern
176+
);
177+
178+
if (paramValue != null)
179+
pathname += paramValue;
180+
} else {
181+
pathname += token;
182+
}
183+
}
184+
185+
return pathname.replace(/\/+/g, '/');
186+
}

modules/__tests__/URLUtils-test.js

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import expect from 'expect';
2-
import { getPathname, getQueryString } from '../URLUtils';
2+
import { getPathname, getQueryString, formatPattern } from '../URLUtils';
33

44
describe('getPathname', function () {
55
it('returns the pathname portion of a path', function () {
@@ -16,3 +16,101 @@ describe('getQueryString', function () {
1616
describe('matchPattern', function () {
1717
it('ignores trailing slashes');
1818
});
19+
20+
describe('formatPattern', function () {
21+
describe('when a pattern does not have dynamic segments', function () {
22+
var pattern = 'a/b/c';
23+
24+
it('returns the pattern', function () {
25+
expect(formatPattern(pattern, {})).toEqual(pattern);
26+
});
27+
});
28+
29+
describe('when a pattern has dynamic segments', function () {
30+
var pattern = 'comments/:id/edit';
31+
32+
describe('and a param is missing', function () {
33+
it('throws an Error', function () {
34+
expect(function () {
35+
formatPattern(pattern, {});
36+
}).toThrow(Error);
37+
});
38+
});
39+
40+
describe('and a param is optional', function () {
41+
var pattern = 'comments/(:id)/edit';
42+
43+
it('returns the correct path when param is supplied', function () {
44+
expect(formatPattern(pattern, { id:'123' })).toEqual('comments/123/edit');
45+
});
46+
47+
it('returns the correct path when param is not supplied', function () {
48+
expect(formatPattern(pattern, {})).toEqual('comments/edit');
49+
});
50+
});
51+
52+
describe('and a param and forward slash are optional', function () {
53+
var pattern = 'comments(/:id)/edit';
54+
55+
it('returns the correct path when param is supplied', function () {
56+
expect(formatPattern(pattern, { id:'123' })).toEqual('comments/123/edit');
57+
});
58+
59+
it('returns the correct path when param is not supplied', function () {
60+
expect(formatPattern(pattern, {})).toEqual('comments/edit');
61+
});
62+
});
63+
64+
describe('and all params are present', function () {
65+
it('returns the correct path', function () {
66+
expect(formatPattern(pattern, { id: 'abc' })).toEqual('comments/abc/edit');
67+
});
68+
69+
it('returns the correct path when the value is 0', function () {
70+
expect(formatPattern(pattern, { id: 0 })).toEqual('comments/0/edit');
71+
});
72+
});
73+
74+
describe('and some params have special URL encoding', function () {
75+
it('returns the correct path', function () {
76+
expect(formatPattern(pattern, { id: 'one, two' })).toEqual('comments/one, two/edit');
77+
});
78+
});
79+
80+
describe('and a param has a forward slash', function () {
81+
it('preserves the forward slash', function () {
82+
expect(formatPattern(pattern, { id: 'the/id' })).toEqual('comments/the/id/edit');
83+
});
84+
});
85+
86+
describe('and some params contain dots', function () {
87+
it('returns the correct path', function () {
88+
expect(formatPattern(pattern, { id: 'alt.black.helicopter' })).toEqual('comments/alt.black.helicopter/edit');
89+
});
90+
});
91+
});
92+
93+
describe('when a pattern has one splat', function () {
94+
it('returns the correct path', function () {
95+
expect(formatPattern('/a/*/d', { splat: 'b/c' })).toEqual('/a/b/c/d');
96+
});
97+
});
98+
99+
describe('when a pattern has multiple splats', function () {
100+
it('returns the correct path', function () {
101+
expect(formatPattern('/a/*/c/*', { splat: [ 'b', 'd' ] })).toEqual('/a/b/c/d');
102+
});
103+
104+
it('complains if not given enough splat values', function () {
105+
expect(function () {
106+
formatPattern('/a/*/c/*', { splat: [ 'b' ] });
107+
}).toThrow(Error);
108+
});
109+
});
110+
111+
describe('when a pattern has dots', function () {
112+
it('returns the correct path', function () {
113+
expect(formatPattern('/foo.bar.baz')).toEqual('/foo.bar.baz');
114+
});
115+
});
116+
});

0 commit comments

Comments
 (0)