Skip to content

Commit a8d6e85

Browse files
authored
Handle trailing slashes when prefixing routes (#970)
Handle trailing slashes when prefixing routes. We use the function pathJoin to make sure that we handle trailing slashes in the right way when prefixing routes. This is to prepare the router to prefix routes like * with a result: /:locale?/* Fixes #161830978
1 parent 01077a0 commit a8d6e85

File tree

6 files changed

+76
-29
lines changed

6 files changed

+76
-29
lines changed

src/components/ContentNode/Reference.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export default {
7070
} = this.$router.resolve(url) || {};
7171
7272
// Resolved internal URLs don't have the "not found" route.
73-
return name !== notFoundRouteName;
73+
return !name.startsWith(notFoundRouteName);
7474
},
7575
isSymbolReference() {
7676
return this.kind === 'symbol' && !this.hasInlineFormatting

src/routes.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,20 @@ import {
1616
import ServerError from 'theme/views/ServerError.vue';
1717
import NotFound from 'theme/views/NotFound.vue';
1818

19-
export default [
19+
export const fallbackRoutes = [
20+
{
21+
path: '*',
22+
name: notFoundRouteName,
23+
component: NotFound,
24+
},
25+
{
26+
path: '*', // purposefully unreachable without a forced navigation
27+
name: serverErrorRouteName,
28+
component: ServerError,
29+
},
30+
];
31+
32+
export const pagesRoutes = [
2033
{
2134
path: '/tutorials/:id',
2235
name: 'tutorials-overview',
@@ -38,14 +51,9 @@ export default [
3851
/* webpackChunkName: "documentation-topic" */ 'theme/views/DocumentationTopic.vue'
3952
),
4053
},
41-
{
42-
path: '*',
43-
name: notFoundRouteName,
44-
component: NotFound,
45-
},
46-
{
47-
path: '*', // purposefully unreachable without a forced navigation
48-
name: serverErrorRouteName,
49-
component: ServerError,
50-
},
54+
];
55+
56+
export default [
57+
...pagesRoutes,
58+
...fallbackRoutes,
5159
];

src/setup-utils/SwiftDocCRenderRouter.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,18 @@
99
*/
1010

1111
import Router from 'vue-router';
12-
import {
13-
notFoundRouteName,
14-
serverErrorRouteName,
15-
} from 'docc-render/constants/router';
1612
import {
1713
saveScrollOnReload,
1814
restoreScrollOnReload,
1915
scrollBehavior,
2016
} from 'docc-render/utils/router-utils';
21-
import routes from 'docc-render/routes';
17+
import routes, { fallbackRoutes } from 'docc-render/routes';
2218
import { baseUrl } from 'docc-render/utils/theme-settings';
2319
import { addPrefixedRoutes } from 'docc-render/utils/route-utils';
2420

2521
const defaultRoutes = [
26-
...routes,
27-
...addPrefixedRoutes(routes, [
28-
notFoundRouteName,
29-
serverErrorRouteName,
30-
]),
22+
...addPrefixedRoutes(routes),
23+
...fallbackRoutes,
3124
];
3225

3326
export default function createRouterInstance(routerConfig = {}) {

src/utils/route-utils.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* See https://swift.org/LICENSE.txt for license information
88
* See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
10+
import { pathJoin } from 'docc-render/utils/assets';
1011

1112
const localEnvs = [
1213
{ pathPrefix: '/:locale?', nameSuffix: '-locale' },
@@ -26,7 +27,7 @@ export function addPrefixedRoutes(routes, skipRoutes = [], envs = localEnvs) {
2627
.filter(route => !skipRoutes.includes(route.name))
2728
.map(route => ({
2829
...route,
29-
path: current.pathPrefix + route.path,
30+
path: pathJoin([current.pathPrefix, route.path]),
3031
name: route.name + current.nameSuffix,
3132
})),
3233
), []);

tests/unit/setup-utils/SwiftDocCRenderRouter.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ describe('SwiftDocCRenderRouter', () => {
118118

119119
const resolve = path => router.resolve(path).route;
120120

121-
it('resolves paths to the "tutorials-overview" route', () => {
122-
const route = 'tutorials-overview';
121+
it('resolves paths to the "tutorials-overview-locale" route', () => {
122+
const route = 'tutorials-overview-locale';
123123

124124
expect(resolve('/tutorials/foo').name).toBe(route);
125125
expect(resolve('/tutorials/bar').name).toBe(route);
@@ -140,8 +140,8 @@ describe('SwiftDocCRenderRouter', () => {
140140
expect(resolve('/zh-CN/tutorials/foo/bar').name).not.toBe(route);
141141
});
142142

143-
it('resolves paths to the "topic" route', () => {
144-
const route = 'topic';
143+
it('resolves paths to the "topic-locale" route', () => {
144+
const route = 'topic-locale';
145145
expect(resolve('/tutorials/foo/bar').name).toBe(route);
146146
expect(resolve('/tutorials/foobar/baz').name).toBe(route);
147147
expect(resolve('/tutorials/documentation/foo').name).toBe(route);
@@ -163,8 +163,8 @@ describe('SwiftDocCRenderRouter', () => {
163163
});
164164
});
165165

166-
it('resolves paths to the "documentation-topic" route', () => {
167-
const route = 'documentation-topic';
166+
it('resolves paths to the "documentation-topic-locale" route', () => {
167+
const route = 'documentation-topic-locale';
168168

169169
expect(resolve('/documentation/foo').name).toBe(route);
170170
expect(resolve('/documentation/bar').name).toBe(route);

tests/unit/utils/route-utils.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,49 @@ describe('route-utils', () => {
5050
{ meta: { b: 'b' }, name: 'b-foo', path: '/foo/bar/path/to/b' },
5151
]);
5252
});
53+
54+
it('handles routes with root path', () => {
55+
const rootRoutes = [
56+
{ name: 'home', path: '/', meta: { isHome: true } },
57+
];
58+
59+
const result = addPrefixedRoutes(rootRoutes);
60+
expect(result[0].path).toBe('/:locale?/');
61+
});
62+
63+
it('handles routes with trailing slashes', () => {
64+
const trailingSlashRoutes = [
65+
{ name: 'trailing', path: '/path/with/trailing/', meta: {} },
66+
];
67+
68+
const result = addPrefixedRoutes(trailingSlashRoutes);
69+
expect(result[0].path).toBe('/:locale?/path/with/trailing/');
70+
});
71+
72+
it('handles routes without trailing slashes', () => {
73+
const trailingSlashRoutes = [
74+
{ name: 'trailing', path: 'path/with/trailing', meta: {} },
75+
];
76+
77+
const result = addPrefixedRoutes(trailingSlashRoutes);
78+
expect(result[0].path).toBe('/:locale?/path/with/trailing');
79+
});
80+
81+
it('handles pathPrefix with trailing slash', () => {
82+
const envWithTrailingSlash = [
83+
{ pathPrefix: '/api/', nameSuffix: '-api' },
84+
];
85+
86+
const result = addPrefixedRoutes([routes[0]], [], envWithTrailingSlash);
87+
expect(result[0].path).toBe('/api/path/to/a');
88+
});
89+
90+
it('handles pathPrefix without leading slash', () => {
91+
const envWithoutLeadingSlash = [
92+
{ pathPrefix: 'api', nameSuffix: '-api' },
93+
];
94+
95+
const result = addPrefixedRoutes([routes[0]], [], envWithoutLeadingSlash);
96+
expect(result[0].path).toBe('api/path/to/a');
97+
});
5398
});

0 commit comments

Comments
 (0)