Skip to content

Commit

Permalink
Merge pull request #80 from vordgi/get-79
Browse files Browse the repository at this point in the history
get-79 update get-params logic and add new options
  • Loading branch information
vordgi authored Jun 8, 2024
2 parents 7e4a4db + 55f1742 commit 208e216
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 27 deletions.
70 changes: 46 additions & 24 deletions package/src/get-params.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
import { staticGenerationAsyncStorage } from "next/dist/client/components/static-generation-async-storage.external";
import { normalizeInterceptingRoutes, normalizePagePath, normalizePathname, parseSegments } from "./utils";
import { serverGetterInClientComponentError } from "./server-getter-in-client-component-error";
import { INVALID_PARSE, parseParams } from "./utils";

type GetParamsOptions = {
/**
* In case of error or segments difference
* getter will return null
* (_f.e. `/_not-found` could be at `/it/removed-page` and it would not have any params_)
*/
ignoreDifferenceError?: true;
/** Custom pathname (f.e. `["/example/custom"]`). Usable for rewritten pages in SSR or custom functions */
pathname?: string;
/** Custom pagePaths list (f.e. `["/example/[slug]/page"]`). Usable for rewritten pages in SSR or custom functions */
pagePaths?: string[];
};

export const getParams = () => {
export const getParams = (options: GetParamsOptions = {}) => {
serverGetterInClientComponentError("getParams");

const store = staticGenerationAsyncStorage.getStore();

if (!store) return {};

const { ignoreDifferenceError, pagePaths, pathname } = options;
const { urlPathname, pagePath = "/" } = store;

const cleanUrlPathname = normalizePathname(urlPathname);
const cleanPagePath = normalizePagePath(pagePath);
const pagePathParts = cleanPagePath.split("/");
const pagePathInterceptedParts = normalizeInterceptingRoutes(pagePathParts);
const pathnameParts = cleanUrlPathname.split("/");

const isNotFoundPage = pagePath.match(/\/_not-found\/?$/);
const isValidCatchALl =
cleanPagePath.match(/\/\[\.\.\.[^\]]+\]/) && pathnameParts.length >= pagePathInterceptedParts.length;
const isValidOptionalCatchALl =
cleanPagePath.match(/\/\[\[\.\.\.[^\]]+\]\]/) && pathnameParts.length >= pagePathInterceptedParts.length - 1;
const isCorrectMatched =
isNotFoundPage ||
pagePathInterceptedParts.length === pathnameParts.length ||
isValidCatchALl ||
isValidOptionalCatchALl;

if (!isCorrectMatched) {
const targetUrlPathname = pathname || urlPathname;

let isInvalid = false;
try {
if (pagePaths) {
for (const userPagePath of pagePaths) {
const params = parseParams(targetUrlPathname, userPagePath);
if (params === INVALID_PARSE) {
isInvalid ||= true;
continue;
}
return params;
}
} else {
const params = parseParams(targetUrlPathname, pagePath);
if (params === INVALID_PARSE) {
isInvalid ||= true;
} else {
return params;
}
}
} catch {
isInvalid = true;
}
if (isInvalid && !ignoreDifferenceError) {
const createIssueUrl = new URL("https://github.com/vordgi/nimpl-getters/issues/new");
createIssueUrl.searchParams.set("title", "Error parsing segments in get-params");
createIssueUrl.searchParams.set("body", `urlPathname: \`${urlPathname}\`;\n\npagePath: \`${pagePath}\`;`);
createIssueUrl.searchParams.set(
"body",
`urlPathname: \`${urlPathname}\`;\n\npagePath(s): \`${pagePaths || pagePath}\`;`,
);
createIssueUrl.searchParams.append("labels", "bug");
throw new Error(`Something went wrong. Please create an issue on Github: ${createIssueUrl}`);
}

const query = parseSegments(pagePathInterceptedParts, pathnameParts);
return query;
return null;
};
40 changes: 37 additions & 3 deletions package/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ export const normalizePathname = (pathname: string) => {
const cleanPathname = pathname && new URL(pathname, "http://n").pathname;
const pathnameWithoutTrailingSlash = cleanPathname?.replace(/^(\/.*)\/$/, "$1");
const pathnameWithoutFileType = pathnameWithoutTrailingSlash?.replace(/\/_not-found$/, "");
return pathnameWithoutFileType || "/";
return pathnameWithoutFileType.endsWith("/") ? pathnameWithoutFileType : pathnameWithoutFileType + "/";
};

export const normalizePagePath = (pagePath: string) => {
const cleanPagePath = pagePath && new URL(pagePath, "http://n").pathname;
const pagePathWithoutFileType = cleanPagePath?.replace(/(\/page|\/_not-found)$/, "");
const pagePathWithoutFileType = cleanPagePath?.replace(/(\/page|\/_not-found)$/, "/");
const pagePathWithoutGroups = pagePathWithoutFileType
.split("/")
.filter(Boolean)
.filter((segment) => !segment.match(/^(\([^)]+\)$|^\@.+$)/g));

return pagePathWithoutGroups.join("/") || "/";
if (pagePathWithoutGroups.length === 0) return "/";

return "/" + pagePathWithoutGroups.join("/") + "/";
};

export const parseSegments = (pagePathParts: string[], pathnameParts: string[]) => {
Expand Down Expand Up @@ -73,3 +76,34 @@ export const normalizeInterceptingRoutes = (pageParts: string[]) => {

return normilizedParts.reverse();
};

export const INVALID_PARSE = Symbol("Invalid Parse");

const isSamePaths = (urlPathnameParts: string[], pagePathParts: string[]) => {
for (let i = 0; i < pagePathParts.length; i++) {
const urlPathnamePart = urlPathnameParts[i];
const pagePathPart = pagePathParts[i];
if (pagePathPart.match(/\[\.\.\.[^\]]+\]/)) return true;
if (pagePathPart.match(/\[[^\]]+\]/)) continue;
if (urlPathnamePart !== pagePathPart) return false;
}
return urlPathnameParts.length === pagePathParts.length;
};

export const parseParams = (urlPathname: string, pagePath: string) => {
const cleanUrlPathname = normalizePathname(urlPathname);
const cleanPagePath = normalizePagePath(pagePath);
const pagePathParts = cleanPagePath.split("/").slice(0, -1);
const pagePathInterceptedParts = normalizeInterceptingRoutes(pagePathParts);
const pathnameParts = cleanUrlPathname.split("/").slice(0, -1);

const isNotFoundPage = pagePath.match(/\/_not-found\/?$/);
const isCorrectMatched = isNotFoundPage || isSamePaths(pathnameParts, pagePathInterceptedParts);

if (!isCorrectMatched) {
return INVALID_PARSE;
}

const query = parseSegments(pagePathInterceptedParts, pathnameParts);
return query;
};
28 changes: 28 additions & 0 deletions tests/base/app/not-found.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expect } from "@playwright/test";

test.describe("get-params with custom pagePaths", async () => {
test(`should return correct params for /specific/ pages`, async ({ page }) => {
await page.goto("/specific/de/unknown-page");
page.waitForSelector("#not-found");

const params = await page.$("#get-params");
const paramsRow = await params?.textContent();
expect(paramsRow && JSON.parse(paramsRow)).toEqual({ locale: "de", subpaths: ["unknown-page"] });
});
test(`should return correct params for base pages`, async ({ page }) => {
await page.goto("/it/base/unknown-page");
page.waitForSelector("#not-found");

const params = await page.$("#get-params");
const paramsRow = await params?.textContent();
expect(paramsRow && JSON.parse(paramsRow)).toEqual({ locale: "it", subpaths: ["unknown-page"] });
});
test(`should return correct params for non defined pagePaths`, async ({ page }) => {
await page.goto("/it/invalid/unknown-page");
page.waitForSelector("#not-found");

const params = await page.$("#get-params");
const paramsRow = await params?.textContent();
expect(paramsRow && JSON.parse(paramsRow)).toBeNull();
});
});
14 changes: 14 additions & 0 deletions tests/base/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getParams } from "@nimpl/getters/get-params";

export default function NotFound() {
const params = getParams({
ignoreDifferenceError: true,
pagePaths: ["/specific/[locale]/[...subpaths]/page", "/[locale]/base/[...subpaths]/page"],
});

return (
<div id="not-found">
<p id="get-params">{JSON.stringify(params)}</p>
</div>
);
}
18 changes: 18 additions & 0 deletions tests/base/app/utils-unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { test, expect } from "@playwright/test";
import { parseParams, INVALID_PARSE } from "@nimpl/getters/utils";

test.describe("Test utils", () => {
test(`test parseParams`, () => {
expect(parseParams("/", "/page")).toEqual({});
expect(parseParams("/", "/[slug]/page")).toBe(INVALID_PARSE);
expect(parseParams("/example/", "/[slug]/page")).toEqual({ slug: "example" });
expect(parseParams("/example/multiple/", "/[slug]/page")).toBe(INVALID_PARSE);
expect(parseParams("/example/multiple/", "/[slug]/(group)/multiple/page")).toEqual({ slug: "example" });
expect(parseParams("/example/multiple/", "/[...segments]/page")).toEqual({ segments: ["example", "multiple"] });
expect(parseParams("/example/multiple/", "/[[...segments]]/page")).toEqual({
segments: ["example", "multiple"],
});
expect(parseParams("/", "/[[...segments]]/page")).toEqual({});
expect(parseParams("/intercepted/", "/example/multiple/(..)(..)intercepted/page")).toEqual({});
});
});

0 comments on commit 208e216

Please sign in to comment.