From bc9f4a2f70fdd243fc0b333fe49396ab50e47a9d Mon Sep 17 00:00:00 2001 From: Vordgi Date: Fri, 7 Jun 2024 23:20:12 +0400 Subject: [PATCH 1/4] get-79 update get-params logic and add new options --- package/src/get-params.ts | 70 +++++++++++++++++++++++++-------------- package/src/utils.ts | 37 +++++++++++++++++++-- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/package/src/get-params.ts b/package/src/get-params.ts index ad42c07..daf8345 100644 --- a/package/src/get-params.ts +++ b/package/src/get-params.ts @@ -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; }; diff --git a/package/src/utils.ts b/package/src/utils.ts index bb41fdc..4d7c367 100644 --- a/package/src/utils.ts +++ b/package/src/utils.ts @@ -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[]) => { @@ -73,3 +76,31 @@ export const normalizeInterceptingRoutes = (pageParts: string[]) => { return normilizedParts.reverse(); }; + +export const INVALID_PARSE = Symbol("Invalid Parse"); + +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 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) { + return INVALID_PARSE; + } + + const query = parseSegments(pagePathInterceptedParts, pathnameParts); + return query; +}; From 06b50381f34b5d21824d87a8acf105f0d9674de5 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Fri, 7 Jun 2024 23:21:03 +0400 Subject: [PATCH 2/4] get-79 add tests for parseParams --- tests/base/app/utils-unit.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/base/app/utils-unit.spec.ts diff --git a/tests/base/app/utils-unit.spec.ts b/tests/base/app/utils-unit.spec.ts new file mode 100644 index 0000000..ca53d63 --- /dev/null +++ b/tests/base/app/utils-unit.spec.ts @@ -0,0 +1,15 @@ +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("/", "/[[...segments]]/page")).toEqual({}); + expect(parseParams("/intercepted/", "/example/multiple/(..)(..)intercepted/page")).toEqual({}); + }); +}); From 3b9cba30a6edaccd55aa69c1226ddaf68c74adc5 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sat, 8 Jun 2024 09:49:26 +0400 Subject: [PATCH 3/4] get-79 improve paths comparison in get-params --- package/src/get-params.ts | 2 +- package/src/utils.ts | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/package/src/get-params.ts b/package/src/get-params.ts index daf8345..4be27f9 100644 --- a/package/src/get-params.ts +++ b/package/src/get-params.ts @@ -15,7 +15,7 @@ type GetParamsOptions = { pagePaths?: string[]; }; -export const getParams = (options: GetParamsOptions) => { +export const getParams = (options: GetParamsOptions = {}) => { serverGetterInClientComponentError("getParams"); const store = staticGenerationAsyncStorage.getStore(); diff --git a/package/src/utils.ts b/package/src/utils.ts index 4d7c367..72792b1 100644 --- a/package/src/utils.ts +++ b/package/src/utils.ts @@ -79,6 +79,17 @@ export const normalizeInterceptingRoutes = (pageParts: string[]) => { 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); @@ -87,15 +98,7 @@ export const parseParams = (urlPathname: string, pagePath: string) => { const pathnameParts = cleanUrlPathname.split("/").slice(0, -1); 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; + const isCorrectMatched = isNotFoundPage || isSamePaths(pathnameParts, pagePathInterceptedParts); if (!isCorrectMatched) { return INVALID_PARSE; From 55f1742de21dc2a9611ab3b003ae2da085f2c110 Mon Sep 17 00:00:00 2001 From: Vordgi Date: Sat, 8 Jun 2024 09:50:01 +0400 Subject: [PATCH 4/4] get-79 add additional tests for get-params --- tests/base/app/not-found.spec.ts | 28 ++++++++++++++++++++++++++++ tests/base/app/not-found.tsx | 14 ++++++++++++++ tests/base/app/utils-unit.spec.ts | 3 +++ 3 files changed, 45 insertions(+) create mode 100644 tests/base/app/not-found.spec.ts create mode 100644 tests/base/app/not-found.tsx diff --git a/tests/base/app/not-found.spec.ts b/tests/base/app/not-found.spec.ts new file mode 100644 index 0000000..0715576 --- /dev/null +++ b/tests/base/app/not-found.spec.ts @@ -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(); + }); +}); diff --git a/tests/base/app/not-found.tsx b/tests/base/app/not-found.tsx new file mode 100644 index 0000000..513c28c --- /dev/null +++ b/tests/base/app/not-found.tsx @@ -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 ( +
+

{JSON.stringify(params)}

+
+ ); +} diff --git a/tests/base/app/utils-unit.spec.ts b/tests/base/app/utils-unit.spec.ts index ca53d63..6f0fb17 100644 --- a/tests/base/app/utils-unit.spec.ts +++ b/tests/base/app/utils-unit.spec.ts @@ -9,6 +9,9 @@ test.describe("Test utils", () => { 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({}); });