GitFOSS
test(routing): add test suite to cover buildRouteLink helper function
+ 132
- 48
@@ -1,5 +1,6 @@
 .DS_Store
 .vscode/
+coverage/
 dist/
 node_modules/
 

@@ -1,4 +1,5 @@
 .DS_Store
+coverage/
 dist/
 node_modules/
 

@@ -37,7 +37,7 @@ export enum AppRoute {
   REPOSITORY_PULL_REQUEST_CREATE_ACTION = "repository.pull_request.create.action",
   REPOSITORY_PULL_REQUEST_DELETE_ACTION = "repository.pull_request.delete.action",
   REPOSITORY_PULL_REQUEST_DETAILS = "repository.pull_request.details",
-  REPOSITORY_PULL_REQUEST_MERGE_ACTION = "/:orgSlug/:repoSlug/pulls/:pullUid/merge",
+  REPOSITORY_PULL_REQUEST_MERGE_ACTION = "repository.pull_request.merge.action",
   REPOSITORY_PULL_REQUEST_UPDATE_ACTION = "repository.pull_request.update.action",
   REPOSITORY_PULL_REQUESTS = "repository.pull_requests",
   REPOSITORY_SHOW_OBJECT = "repository.show_object",

new file
app/utils/shared/buildRouteLink.test.ts
@@ -0,0 +1,63 @@
+import { AppRoute } from "../../routes.defs";
+import { default as buildRouteLink } from "./buildRouteLink";
+
+describe("buildRouteLink", () => {
+  it(`should return path from route without transform when it gets called with a null as routeParams argument`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.HOME;
+    // When
+    const link = buildRouteLink(routeName, null);
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/"`);
+  });
+  it(`should return path from route without transform when it gets called with an empty object as routeParams argument`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.HOME;
+    // When
+    const link = buildRouteLink(routeName, {});
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/"`);
+  });
+  it(`should return path from route with params values when it gets called with an object containing values as routeParams argument`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.ORGANIZATION_DETAILS;
+    // When
+    const link = buildRouteLink(routeName, {
+      orgSlug: "my-super-org",
+    });
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/my-super-org"`);
+  });
+  it(`should return path from route with params values when it gets called with an object containing values and magic "*" value as routeParams argument`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.REPOSITORY_BROWSER_WITH_PATH;
+    // When
+    const link = buildRouteLink(routeName, {
+      orgSlug: "my-super-org",
+      repoSlug: "my-super-repo",
+      currentRef: "main",
+      "*": "app.manifest.json",
+    });
+    // Then
+    expect(link).toMatchInlineSnapshot(
+      `"/my-super-org/my-super-repo/main/tree/app.manifest.json"`
+    );
+  });
+  it(`should return path from route with url encoded params values when it gets called with an object containing values as routeParams argument`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.REPOSITORY_BROWSER_WITH_PATH;
+    // When
+    const link = buildRouteLink(routeName, {
+      orgSlug: "my-super-org",
+      repoSlug: "my-super-repo",
+      currentRef: "main",
+      "*": "app/services/auth/isExistingEmailAddress.ts",
+    });
+    // Then
+    expect(link).toMatchInlineSnapshot(
+      `"/my-super-org/my-super-repo/main/tree/app%2Fservices%2Fauth%2FisExistingEmailAddress.ts"`
+    );
+  });
+});
+
+describe("buildPathLink", () => {});

app/utils/shared/buildRouteLink.ts
@@ -7,7 +7,15 @@ export default function buildRouteLink<P extends AppRoute>(
     : {} | null
 ): typeof AppRoutePaths[P] {
   const path = AppRoutePaths[route];
+  return buildPathLink(path, routeParams);
+}
 
+export function buildPathLink<P extends AppRoute>(
+  path: string,
+  routeParams: "params" extends keyof AppRoutesParams[P]
+    ? AppRoutesParams[P]["params"]
+    : {} | null
+): string {
   if (
     routeParams == null ||
     (routeParams != null &&

...
@@ -17,52 +25,33 @@ export default function buildRouteLink<P extends AppRoute>(
     return path;
   }
 
-  const paramsEntries =
-    typeof routeParams === "object" ? Object.entries(routeParams as never) : [];
-
-  let pathParts = path.split("/");
-  let linkBuilder = [] as string[];
-
-  paramsEntries.forEach(([k, v]) => {
-    const keyRegExp = new RegExp(`:${k}`, "g");
-    pathParts = pathParts.map((part) => {
-      if (Array.isArray(part.match(keyRegExp))) {
-        return part.replace(keyRegExp, `${String(v)}`);
-      } else if (k === "*" && part === "*") {
-        return String(v);
-      }
-      return part;
-    });
-  });
-
-  pathParts.map((part) => linkBuilder.push(part));
-  return linkBuilder.join("/");
-}
-
-export function buildPathLink<P extends AppRoute>(
-  path: string,
-  routeParams: "params" extends keyof AppRoutesParams[P]
-    ? AppRoutesParams[P]["params"]
-    : undefined
-): string {
-  const paramsEntries =
-    typeof routeParams === "object" ? Object.entries(routeParams as never) : [];
+  const paramsEntries = Object.entries(routeParams as never);
 
   let pathParts = path.split("/");
   let linkBuilder = [] as string[];
 
-  paramsEntries.forEach(([k, v]) => {
-    const keyRegExp = new RegExp(`:${k}`, "g");
-    pathParts = pathParts.map((part) => {
-      if (Array.isArray(part.match(keyRegExp))) {
-        return part.replace(keyRegExp, `${String(v)}`);
-      } else if (k === "*" && part === "*") {
-        return String(v);
-      }
-      return part;
-    });
+  pathParts.forEach((part) => {
+    if (part.trim() === "") {
+      linkBuilder.push("");
+    } else if (
+      "*" in routeParams &&
+      (routeParams as any)["*"] != null &&
+      part === "*"
+    ) {
+      linkBuilder.push((routeParams as any)["*"]);
+    } else if (part.includes(":")) {
+      paramsEntries.forEach(([k, v]) => {
+        if (k == null || k.trim() === "" || k === "*") return;
+        const keyRegExp = new RegExp(`:${k}`, "g");
+        if (Array.isArray(part.match(keyRegExp))) {
+          linkBuilder.push(part.replace(keyRegExp, `${String(v)}`));
+        }
+      });
+    } else {
+      linkBuilder.push(part);
+    }
   });
 
-  pathParts.map((part) => linkBuilder.push(part));
-  return linkBuilder.join("/");
+  const link = linkBuilder.map((param) => encodeURIComponent(param)).join("/");
+  return link;
 }

@@ -75,6 +75,7 @@
     "jest": "^27.5.1",
     "npm-run-all": "^4.1.5",
     "prisma": "^4.4.0",
+    "ts-jest": "^27.1.5",
     "ts-node-dev": "^2.0.0",
     "tslib": "^2.4.0",
     "typescript": "^4.6.2"

@@ -1373,6 +1373,12 @@ browserslist@^4.20.2:
     node-releases "^2.0.5"
     update-browserslist-db "^1.0.4"
 
+bs-logger@0.x:
+  version "0.2.6"
+  resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+  dependencies:
+    fast-json-stable-stringify "2.x"
+
 bser@2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"

...
@@ -2074,7 +2080,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
 
-fast-json-stable-stringify@^2.0.0:
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
 

...
@@ -3216,7 +3222,7 @@ jest-snapshot@^27.5.1:
     pretty-format "^27.5.1"
     semver "^7.3.2"
 
-jest-util@^27.5.1:
+jest-util@^27.0.0, jest-util@^27.5.1:
   version "27.5.1"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9"
   dependencies:

...
@@ -3352,7 +3358,7 @@ json-schema-traverse@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
 
-json5@^2.2.1:
+json5@2.x, json5@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
 

...
@@ -3562,6 +3568,10 @@ lodash.isstring@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
 
+lodash.memoize@4.x:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+
 lodash.once@^4.0.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"

...
@@ -3623,7 +3633,7 @@ make-dir@^3.0.0:
   dependencies:
     semver "^6.0.0"
 
-make-error@^1.1.1:
+make-error@1.x, make-error@^1.1.1:
   version "1.3.6"
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
 

...
@@ -4427,6 +4437,12 @@ semver-store@^0.3.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
 
+semver@7.x:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  dependencies:
+    lru-cache "^6.0.0"
+
 semver@^6.0.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"

...
@@ -4850,6 +4866,19 @@ tree-kill@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
 
+ts-jest@^27.1.5:
+  version "27.1.5"
+  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297"
+  dependencies:
+    bs-logger "0.x"
+    fast-json-stable-stringify "2.x"
+    jest-util "^27.0.0"
+    json5 "2.x"
+    lodash.memoize "4.x"
+    make-error "1.x"
+    semver "7.x"
+    yargs-parser "20.x"
+
 ts-node-dev@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065"

...
@@ -5134,7 +5163,7 @@ yallist@^2.0.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
 
-yargs-parser@^20.2.2:
+yargs-parser@20.x, yargs-parser@^20.2.2:
   version "20.2.9"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"