Skip to content

Commit

Permalink
Implement util functions to match internal routes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
arcticicestudio committed Dec 6, 2018
1 parent 208c9ac commit cb9000e
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 4 deletions.
18 changes: 18 additions & 0 deletions src/config/routes/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ const ROOT = pathSeparator;
*/
const BLOG = "blog";

/**
* The route name of the "community" page.
*
* @constant {string}
* @since 0.3.0
*/
const COMMUNITY = "community";

/**
* The route name of the "docs" page.
*
Expand All @@ -67,10 +75,20 @@ const DOCS = "docs";
*/
const LANDING = "landing";

/**
* The route name of the port projects page.
*
* @constant {string}
* @since 0.3.0
*/
const PORTS = "ports";

module.exports = {
BASE_PUBLIC_URL,
BLOG,
COMMUNITY,
DOCS,
LANDING,
PORTS,
ROOT
};
24 changes: 21 additions & 3 deletions src/config/routes/mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @since 0.1.0
*/

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

/**
* The root route mapping.
Expand All @@ -32,6 +32,14 @@ const ROUTE_ROOT = ROOT;
*/
const ROUTE_BLOG = ROUTE_ROOT + BLOG;

/**
* The route mapping for the "community" page.
*
* @constant {string}
* @since 0.3.0
*/
const ROUTE_COMMUNITY = ROUTE_ROOT + COMMUNITY;

/**
* The route mapping for the "docs" page.
*
Expand All @@ -49,9 +57,19 @@ const ROUTE_DOCS = ROUTE_ROOT + DOCS;
*/
const ROUTE_LANDING = ROUTE_ROOT + LANDING;

/**
* The route mapping for the port projects page.
*
* @constant {string}
* @since 0.3.0
*/
const ROUTE_PORTS = ROUTE_ROOT + PORTS;

module.exports = {
ROUTE_ROOT,
ROUTE_BLOG,
ROUTE_COMMUNITY,
ROUTE_DOCS,
ROUTE_LANDING
ROUTE_LANDING,
ROUTE_PORTS,
ROUTE_ROOT
};
4 changes: 3 additions & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* @since 0.2.0
*/

import isRouteInternal from "./isRouteInternal";
import isRoutePartiallyMatch from "./isRoutePartiallyMatch";
import { readSessionCache, writeSessionCache } from "./sessionCache";

export { readSessionCache, writeSessionCache };
export { isRouteInternal, isRoutePartiallyMatch, readSessionCache, writeSessionCache };
24 changes: 24 additions & 0 deletions src/utils/isRouteInternal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <development@arcticicestudio.com>
* Copyright (C) 2018-present Sven Greb <development@svengreb.de>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

/* eslint-disable no-useless-escape */

/**
* Validates if the given route is internal.
* Matches exactly one slash or hash, anything else is external including relative routes starting with two slahes.
* The hash allows to link to anchors within the same document.
*
* @method isRouteInternal
* @param {string} route The route to validate.
* @return {Boolean} `true` if the given route is internal, `false` otherwise.
* @since 0.3.0
*/
const isRouteInternal = route => /^[\/#](?!\/)/.test(route);

export default isRouteInternal;
37 changes: 37 additions & 0 deletions src/utils/isRoutePartiallyMatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <development@arcticicestudio.com>
* Copyright (C) 2018-present Sven Greb <development@svengreb.de>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import isRouteInternal from "./isRouteInternal";

/**
* Validates if the given path element partially matches the route.
*
* @method isRoutePartiallyMatch
* @param {string} route The route to check.
* @param {string} pathElement The path element to check against the route.
* @return {Boolean} `true` if the given path element is partially matching, `false` otherwise.
* @since 0.3.0
*/
const isRoutePartiallyMatch = (route, pathElement) => {
/* Don't match exact and external routes. */
if (route === pathElement) return false;
if (!isRouteInternal(pathElement)) return false;

/* Split into path elements and filter out leading and pending slashes. */
const routeTokens = route.split("/").filter(t => t.length);
const pathElementTokens = pathElement.split("/").filter(t => t.length);

const isMatch = pathElementTokens.every((t, idx) => routeTokens[idx] === t);
/* Prevent false-positive match by only allowing the path element as exact root when current route is not the root. */
const isPathElementExactRoot = pathElement === "/" && route !== "/";

return isPathElementExactRoot ? false : isMatch;
};

export default isRoutePartiallyMatch;
41 changes: 41 additions & 0 deletions test/utils/isRouteInternal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <development@arcticicestudio.com>
* Copyright (C) 2018-present Sven Greb <development@svengreb.de>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import { isRouteInternal } from "utils";
import { ROUTE_BLOG, ROUTE_DOCS, ROUTE_COMMUNITY, ROUTE_PORTS, ROUTE_ROOT } from "config/routes/mappings";
import { metadataNordDocs } from "data/project";

describe("internal routes are", () => {
test("matching", () => {
[
"#",
ROUTE_ROOT,
`${ROUTE_ROOT}#`,
`${ROUTE_ROOT}?port=atom`,
ROUTE_BLOG,
ROUTE_DOCS,
ROUTE_COMMUNITY,
ROUTE_PORTS
].forEach(route => expect(isRouteInternal(route)).toBeTruthy());
});

test("not matching", () => {
[
`${metadataNordDocs.homepage}`,
`${metadataNordDocs.repository.url}`,
"https://github.com/arcticicestudio",
"https://www.nordtheme.com",
"https://nordtheme.com",
"https://nordtheme.com",
"//nordtheme.com",
"file:///etc/hosts",
"mailto:support@nordtheme.com"
].forEach(route => expect(isRouteInternal(route)).toBeFalsy());
});
});
47 changes: 47 additions & 0 deletions test/utils/isRoutePartiallyMatch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018-present Arctic Ice Studio <development@arcticicestudio.com>
* Copyright (C) 2018-present Sven Greb <development@svengreb.de>
*
* Project: Nord Docs
* Repository: https://github.com/arcticicestudio/nord-docs
* License: MIT
*/

import { isRoutePartiallyMatch } from "utils";
import { ROUTE_BLOG, ROUTE_DOCS, ROUTE_COMMUNITY, ROUTE_PORTS, ROUTE_ROOT } from "config/routes/mappings";
import { metadataNordDocs } from "data/project";

describe("partial routes are", () => {
test("matching", () => {
[
{ route: `${ROUTE_BLOG}/2018/12/06/snow-winter`, pathElement: ROUTE_BLOG },
{ route: `${ROUTE_DOCS}${ROUTE_PORTS}/vim`, pathElement: ROUTE_DOCS },
{ route: `${ROUTE_COMMUNITY}/slack`, pathElement: ROUTE_COMMUNITY }
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeTruthy());
});

test("not matching", () => {
[
{ route: ROUTE_ROOT, pathElement: ROUTE_ROOT },
{ route: ROUTE_ROOT, pathElement: ROUTE_BLOG },
{ route: `${ROUTE_BLOG}/2018`, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` },
{ route: `${ROUTE_BLOG}/2018/12/06/snow-winter`, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` }
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
});

test("not matching exact and external routes", () => {
[
{ route: ROUTE_ROOT, pathElement: ROUTE_ROOT },
{ route: metadataNordDocs.homepage, pathElement: ROUTE_DOCS },
{ route: metadataNordDocs.repository.url, pathElement: ROUTE_COMMUNITY },
{ route: "https://www.nordtheme.com", pathElement: ROUTE_ROOT }
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
});
});

test("prevents false-positive match by only allowing the path element as exact root when current route is not the root", () => {
[
{ route: ROUTE_ROOT, pathElement: ROUTE_BLOG },
{ route: ROUTE_ROOT, pathElement: `${ROUTE_BLOG}/2018/12/06/snow-winter` }
].forEach(({ route, pathElement }) => expect(isRoutePartiallyMatch(route, pathElement)).toBeFalsy());
});

0 comments on commit cb9000e

Please sign in to comment.