Skip to content

Commit a4b8408

Browse files
phurytwtimdorr
authored andcommitted
Add exact and strict support to renderRoutes (remix-run#4826)
* Add exact and strict support to renderRoutes * Add unit tests for renderRoutes
1 parent 6e62f42 commit a4b8408

3 files changed

Lines changed: 353 additions & 1 deletion

File tree

packages/react-router-config/modules/__tests__/integration-test.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,94 @@ describe('integration', () => {
9999
expect(branch[0].match).toEqual(rendered[0])
100100
expect(branch[1].match).toEqual(rendered[1])
101101
})
102+
103+
104+
105+
it('generates the same matches in renderRoutes and matchRoutes with routes using exact', () => {
106+
const rendered = []
107+
108+
const Comp = ({ match, route: { routes } }) => (
109+
rendered.push(match),
110+
renderRoutes(routes)
111+
)
112+
113+
const routes = [
114+
// should skip
115+
{
116+
path: '/pepper/habanero',
117+
component: Comp,
118+
exact: true
119+
},
120+
// should match
121+
{
122+
path: '/pepper',
123+
component: Comp,
124+
exact: true
125+
}
126+
]
127+
128+
const pathname = '/pepper'
129+
const branch = matchRoutes(routes, pathname)
130+
renderToString(
131+
<StaticRouter location={pathname} context={{}}>
132+
{renderRoutes(routes)}
133+
</StaticRouter>
134+
)
135+
expect(branch.length).toEqual(1)
136+
expect(rendered.length).toEqual(1)
137+
expect(branch[0].match).toEqual(rendered[0])
138+
})
139+
140+
141+
142+
it('generates the same matches in renderRoutes and matchRoutes with routes using exact + strict', () => {
143+
const rendered = []
144+
145+
const Comp = ({ match, route: { routes } }) => (
146+
rendered.push(match),
147+
renderRoutes(routes)
148+
)
149+
150+
const routes = [
151+
// should match
152+
{
153+
path: '/pepper/',
154+
component: Comp,
155+
strict: true,
156+
exact: true,
157+
routes: [
158+
// should skip
159+
{
160+
path: '/pepper',
161+
component: Comp,
162+
strict: true,
163+
exact: true
164+
}
165+
]
166+
}
167+
]
168+
169+
let pathname = '/pepper'
170+
let branch = matchRoutes(routes, pathname)
171+
renderToString(
172+
<StaticRouter location={pathname} context={{}}>
173+
{renderRoutes(routes)}
174+
</StaticRouter>
175+
)
176+
expect(branch.length).toEqual(0)
177+
expect(rendered.length).toEqual(0)
178+
179+
pathname = '/pepper/'
180+
branch = matchRoutes(routes, pathname)
181+
renderToString(
182+
<StaticRouter location={pathname} context={{}}>
183+
{renderRoutes(routes)}
184+
</StaticRouter>
185+
)
186+
187+
expect(branch.length).toEqual(1)
188+
expect(rendered.length).toEqual(1)
189+
expect(branch[0].match).toEqual(rendered[0])
190+
})
102191
})
103192

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import renderRoutes from '../renderRoutes'
2+
import expect from 'expect'
3+
import React from 'react'
4+
import { renderToString } from 'react-dom/server'
5+
import StaticRouter from 'react-router/StaticRouter'
6+
7+
describe('renderRoutes', () => {
8+
let rendered
9+
const Comp = ({ match, route, route: { routes } }) => (
10+
rendered.push(route),
11+
renderRoutes(routes)
12+
)
13+
14+
beforeEach(() => {
15+
rendered = []
16+
})
17+
18+
it('renders pathless routes', () => {
19+
const routeToMatch = {
20+
component: Comp
21+
}
22+
const routes = [routeToMatch]
23+
24+
renderToString(
25+
<StaticRouter location='/path' context={{}}>
26+
{renderRoutes(routes)}
27+
</StaticRouter>
28+
)
29+
expect(rendered.length).toEqual(1)
30+
expect(rendered[0]).toMatch(routeToMatch)
31+
})
32+
33+
describe('Switch usage', () => {
34+
it('renders the first matched route', () => {
35+
const routeToMatch = {
36+
component: Comp,
37+
path: '/'
38+
}
39+
const routes = [routeToMatch, {
40+
component: Comp,
41+
}]
42+
43+
renderToString(
44+
<StaticRouter location='/' context={{}}>
45+
{renderRoutes(routes)}
46+
</StaticRouter>
47+
)
48+
expect(rendered.length).toEqual(1)
49+
expect(rendered[0]).toMatch(routeToMatch)
50+
})
51+
52+
it('renders the first matched route in nested routes', () => {
53+
const childRouteToMatch = {
54+
component: Comp,
55+
path: '/',
56+
}
57+
const routeToMatch = {
58+
component: Comp,
59+
path: '/',
60+
routes: [childRouteToMatch, {
61+
component: Comp
62+
}]
63+
}
64+
const routes = [routeToMatch, {
65+
component: Comp,
66+
}]
67+
68+
renderToString(
69+
<StaticRouter location='/' context={{}}>
70+
{renderRoutes(routes)}
71+
</StaticRouter>
72+
)
73+
expect(rendered.length).toEqual(2)
74+
expect(rendered[0]).toMatch(routeToMatch)
75+
expect(rendered[1]).toMatch(childRouteToMatch)
76+
})
77+
})
78+
79+
describe('routes with exact', () => {
80+
it('renders the exact route', () => {
81+
const routeToMatch = {
82+
component: Comp,
83+
path: '/path/child',
84+
exact: true,
85+
routes: [{
86+
component: Comp
87+
}]
88+
}
89+
const routes = [{
90+
component: Comp,
91+
path: '/path',
92+
exact: true
93+
}, routeToMatch]
94+
95+
renderToString(
96+
<StaticRouter location='/path/child' context={{}}>
97+
{renderRoutes(routes)}
98+
</StaticRouter>
99+
)
100+
expect(rendered.length).toEqual(2)
101+
expect(rendered[0]).toEqual(routeToMatch)
102+
expect(rendered[1]).toEqual({ component: Comp })
103+
})
104+
105+
it('skips exact route and does not render it and any of its child routes', () => {
106+
const routes = [{
107+
component: Comp,
108+
path: '/path',
109+
exact: true,
110+
routes: [{
111+
component: Comp
112+
}, {
113+
component: Comp
114+
}]
115+
}]
116+
117+
renderToString(
118+
<StaticRouter location='/path/child' context={{}}>
119+
{renderRoutes(routes)}
120+
</StaticRouter>
121+
)
122+
renderToString(
123+
<StaticRouter location='/' context={{}}>
124+
{renderRoutes(routes)}
125+
</StaticRouter>
126+
)
127+
expect(rendered.length).toEqual(0)
128+
})
129+
130+
it('renders the matched exact route but not its child routes if they do not match', () => {
131+
const routes = [{
132+
// should render
133+
component: Comp,
134+
path: '/path',
135+
exact: true,
136+
routes: [{
137+
// should skip
138+
component: Comp,
139+
path: '/path/child',
140+
exact: true
141+
}, {
142+
// should render
143+
component: Comp
144+
}]
145+
}]
146+
147+
renderToString(
148+
<StaticRouter location='/path/child/grandchild' context={{}}>
149+
{renderRoutes(routes)}
150+
</StaticRouter>
151+
)
152+
renderToString(
153+
<StaticRouter location='/path' context={{}}>
154+
{renderRoutes(routes)}
155+
</StaticRouter>
156+
)
157+
expect(rendered.length).toEqual(2)
158+
expect(rendered[0]).toEqual(routes[0])
159+
expect(rendered[1]).toEqual(routes[0].routes[1])
160+
})
161+
})
162+
163+
describe('routes with exact + strict', () => {
164+
it('renders the exact strict route', () => {
165+
const routeToMatch = {
166+
component: Comp,
167+
path: '/path/',
168+
exact: true,
169+
strict: true
170+
}
171+
const routes = [{
172+
// should skip
173+
component: Comp,
174+
path: '/path',
175+
exact: true,
176+
strict: true
177+
// should render
178+
}, routeToMatch]
179+
180+
renderToString(
181+
<StaticRouter location='/path/' context={{}}>
182+
{renderRoutes(routes)}
183+
</StaticRouter>
184+
)
185+
expect(rendered.length).toEqual(1)
186+
expect(rendered[0]).toEqual(routeToMatch)
187+
})
188+
189+
it('skips exact strict route and does not render it and any of its child routes', () => {
190+
const routes = [{
191+
component: Comp,
192+
path: '/path/',
193+
exact: true,
194+
strict: true,
195+
routes: [{
196+
component: Comp
197+
}, {
198+
component: Comp
199+
}]
200+
}]
201+
202+
renderToString(
203+
<StaticRouter location='/path/child' context={{}}>
204+
{renderRoutes(routes)}
205+
</StaticRouter>
206+
)
207+
renderToString(
208+
<StaticRouter location='/' context={{}}>
209+
{renderRoutes(routes)}
210+
</StaticRouter>
211+
)
212+
renderToString(
213+
<StaticRouter location='/path' context={{}}>
214+
{renderRoutes(routes)}
215+
</StaticRouter>
216+
)
217+
expect(rendered.length).toEqual(0)
218+
})
219+
220+
it('renders the matched exact strict route but not its child routes if they do not match', () => {
221+
const routes = [{
222+
// should skip
223+
component: Comp,
224+
path: '/path',
225+
exact: true,
226+
strict: true
227+
}, {
228+
// should render
229+
component: Comp,
230+
path: '/path/',
231+
exact: true,
232+
strict: true,
233+
routes: [{
234+
// should skip
235+
component: Comp,
236+
exact: true,
237+
strict: true,
238+
path: '/path'
239+
}, {
240+
// should render
241+
component: Comp,
242+
exact: true,
243+
strict: true,
244+
path: '/path/'
245+
}]
246+
}]
247+
248+
renderToString(
249+
<StaticRouter location='/path/child/grandchild' context={{}}>
250+
{renderRoutes(routes)}
251+
</StaticRouter>
252+
)
253+
renderToString(
254+
<StaticRouter location='/path/' context={{}}>
255+
{renderRoutes(routes)}
256+
</StaticRouter>
257+
)
258+
expect(rendered.length).toEqual(2)
259+
expect(rendered[0]).toEqual(routes[1])
260+
expect(rendered[1]).toEqual(routes[1].routes[1])
261+
})
262+
})
263+
})

packages/react-router-config/modules/renderRoutes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Route from 'react-router/Route'
55
const renderRoutes = (routes) => routes ? (
66
<Switch>
77
{routes.map((route, i) => (
8-
<Route key={i} path={route.path} render={(props) => (
8+
<Route key={i} path={route.path} exact={route.exact} strict={route.strict} render={(props) => (
99
<route.component {...props} route={route}/>
1010
)}/>
1111
))}

0 commit comments

Comments
 (0)