Skip to content

Commit cb9000e

Browse files
Implement util functions to match internal routes
To handle the logic of conditionally rendering a Gatsby Link or a basic `<a>`, based on the passed target URL (internal & external), this commit implements the `isRouteInternal` and `isRoutePartiallyMatch` functions to evaluate the passed target URL. Associated epic: GH-69 GH-70
1 parent 208c9ac commit cb9000e

File tree

7 files changed

+191
-4
lines changed

7 files changed

+191
-4
lines changed

src/config/routes/constants.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ const ROOT = pathSeparator;
5050
*/
5151
const BLOG = "blog";
5252

53+
/**
54+
* The route name of the "community" page.
55+
*
56+
* @constant {string}
57+
* @since 0.3.0
58+
*/
59+
const COMMUNITY = "community";
60+
5361
/**
5462
* The route name of the "docs" page.
5563
*
@@ -67,10 +75,20 @@ const DOCS = "docs";
6775
*/
6876
const LANDING = "landing";
6977

78+
/**
79+
* The route name of the port projects page.
80+
*
81+
* @constant {string}
82+
* @since 0.3.0
83+
*/
84+
const PORTS = "ports";
85+
7086
module.exports = {
7187
BASE_PUBLIC_URL,
7288
BLOG,
89+
COMMUNITY,
7390
DOCS,
7491
LANDING,
92+
PORTS,
7593
ROOT
7694
};

src/config/routes/mappings.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* @since 0.1.0
1515
*/
1616

17-
const { ROOT, BLOG, DOCS, LANDING } = require("./constants");
17+
const { BLOG, COMMUNITY, DOCS, LANDING, PORTS, ROOT } = require("./constants");
1818

1919
/**
2020
* The root route mapping.
@@ -32,6 +32,14 @@ const ROUTE_ROOT = ROOT;
3232
*/
3333
const ROUTE_BLOG = ROUTE_ROOT + BLOG;
3434

35+
/**
36+
* The route mapping for the "community" page.
37+
*
38+
* @constant {string}
39+
* @since 0.3.0
40+
*/
41+
const ROUTE_COMMUNITY = ROUTE_ROOT + COMMUNITY;
42+
3543
/**
3644
* The route mapping for the "docs" page.
3745
*
@@ -49,9 +57,19 @@ const ROUTE_DOCS = ROUTE_ROOT + DOCS;
4957
*/
5058
const ROUTE_LANDING = ROUTE_ROOT + LANDING;
5159

60+
/**
61+
* The route mapping for the port projects page.
62+
*
63+
* @constant {string}
64+
* @since 0.3.0
65+
*/
66+
const ROUTE_PORTS = ROUTE_ROOT + PORTS;
67+
5268
module.exports = {
53-
ROUTE_ROOT,
5469
ROUTE_BLOG,
70+
ROUTE_COMMUNITY,
5571
ROUTE_DOCS,
56-
ROUTE_LANDING
72+
ROUTE_LANDING,
73+
ROUTE_PORTS,
74+
ROUTE_ROOT
5775
};

src/utils/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* @since 0.2.0
1515
*/
1616

17+
import isRouteInternal from "./isRouteInternal";
18+
import isRoutePartiallyMatch from "./isRoutePartiallyMatch";
1719
import { readSessionCache, writeSessionCache } from "./sessionCache";
1820

19-
export { readSessionCache, writeSessionCache };
21+
export { isRouteInternal, isRoutePartiallyMatch, readSessionCache, writeSessionCache };

src/utils/isRouteInternal.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
/* eslint-disable no-useless-escape */
11+
12+
/**
13+
* Validates if the given route is internal.
14+
* Matches exactly one slash or hash, anything else is external including relative routes starting with two slahes.
15+
* The hash allows to link to anchors within the same document.
16+
*
17+
* @method isRouteInternal
18+
* @param {string} route The route to validate.
19+
* @return {Boolean} `true` if the given route is internal, `false` otherwise.
20+
* @since 0.3.0
21+
*/
22+
const isRouteInternal = route => /^[\/#](?!\/)/.test(route);
23+
24+
export default isRouteInternal;

src/utils/isRoutePartiallyMatch.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
import isRouteInternal from "./isRouteInternal";
11+
12+
/**
13+
* Validates if the given path element partially matches the route.
14+
*
15+
* @method isRoutePartiallyMatch
16+
* @param {string} route The route to check.
17+
* @param {string} pathElement The path element to check against the route.
18+
* @return {Boolean} `true` if the given path element is partially matching, `false` otherwise.
19+
* @since 0.3.0
20+
*/
21+
const isRoutePartiallyMatch = (route, pathElement) => {
22+
/* Don't match exact and external routes. */
23+
if (route === pathElement) return false;
24+
if (!isRouteInternal(pathElement)) return false;
25+
26+
/* Split into path elements and filter out leading and pending slashes. */
27+
const routeTokens = route.split("/").filter(t => t.length);
28+
const pathElementTokens = pathElement.split("/").filter(t => t.length);
29+
30+
const isMatch = pathElementTokens.every((t, idx) => routeTokens[idx] === t);
31+
/* Prevent false-positive match by only allowing the path element as exact root when current route is not the root. */
32+
const isPathElementExactRoot = pathElement === "/" && route !== "/";
33+
34+
return isPathElementExactRoot ? false : isMatch;
35+
};
36+
37+
export default isRoutePartiallyMatch;

test/utils/isRouteInternal.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
import { isRouteInternal } from "utils";
11+
import { ROUTE_BLOG, ROUTE_DOCS, ROUTE_COMMUNITY, ROUTE_PORTS, ROUTE_ROOT } from "config/routes/mappings";
12+
import { metadataNordDocs } from "data/project";
13+
14+
describe("internal routes are", () => {
15+
test("matching", () => {
16+
[
17+
"#",
18+
ROUTE_ROOT,
19+
`${ROUTE_ROOT}#`,
20+
`${ROUTE_ROOT}?port=atom`,
21+
ROUTE_BLOG,
22+
ROUTE_DOCS,
23+
ROUTE_COMMUNITY,
24+
ROUTE_PORTS
25+
].forEach(route => expect(isRouteInternal(route)).toBeTruthy());
26+
});
27+
28+
test("not matching", () => {
29+
[
30+
`${metadataNordDocs.homepage}`,
31+
`${metadataNordDocs.repository.url}`,
32+
"https://github.com/arcticicestudio",
33+
"https://www.nordtheme.com",
34+
"https://nordtheme.com",
35+
"https://nordtheme.com",
36+
"//nordtheme.com",
37+
"file:///etc/hosts",
38+
39+
].forEach(route => expect(isRouteInternal(route)).toBeFalsy());
40+
});
41+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (C) 2018-present Arctic Ice Studio <[email protected]>
3+
* Copyright (C) 2018-present Sven Greb <[email protected]>
4+
*
5+
* Project: Nord Docs
6+
* Repository: https://github.com/arcticicestudio/nord-docs
7+
* License: MIT
8+
*/
9+
10+
import { isRoutePartiallyMatch } from "utils";
11+
import { ROUTE_BLOG, ROUTE_DOCS, ROUTE_COMMUNITY, ROUTE_PORTS, ROUTE_ROOT } from "config/routes/mappings";
12+
import { metadataNordDocs } from "data/project";
13+
14+
describe("partial routes are", () => {
15+
test("matching", () => {
16+
[
17+
{ route: `${ROUTE_BLOG}/2018/12/06/snow-winter`, pathElement: ROUTE_BLOG },
18+
{ route: `${ROUTE_DOCS}${ROUTE_PORTS}/vim`, pathElement: ROUTE_DOCS },
19+
{ route: `${ROUTE_COMMUNITY}/slack`, pathElement: ROUTE_COMMUNITY }
20+
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeTruthy());
21+
});
22+
23+
test("not matching", () => {
24+
[
25+
{ route: ROUTE_ROOT, pathElement: ROUTE_ROOT },
26+
{ route: ROUTE_ROOT, pathElement: ROUTE_BLOG },
27+
{ route: `${ROUTE_BLOG}/2018`, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` },
28+
{ route: `${ROUTE_BLOG}/2018/12/06/snow-winter`, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` }
29+
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
30+
});
31+
32+
test("not matching exact and external routes", () => {
33+
[
34+
{ route: ROUTE_ROOT, pathElement: ROUTE_ROOT },
35+
{ route: metadataNordDocs.homepage, pathElement: ROUTE_DOCS },
36+
{ route: metadataNordDocs.repository.url, pathElement: ROUTE_COMMUNITY },
37+
{ route: "https://www.nordtheme.com", pathElement: ROUTE_ROOT }
38+
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
39+
});
40+
});
41+
42+
test("prevents false-positive match by only allowing the path element as exact root when current route is not the root", () => {
43+
[
44+
{ route: ROUTE_ROOT, pathElement: ROUTE_BLOG },
45+
{ route: ROUTE_ROOT, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` }
46+
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
47+
});

0 commit comments

Comments
 (0)