From b8b8a1e54ac6cd20587b856b5d86c946d200cacf Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Fri, 21 Mar 2025 13:44:49 +0000 Subject: [PATCH 01/11] breadcrumb component now accepts an object array with name and href information needed. Allows path with multiple slashes to be applied to a href --- src/components/Breadcrumbs.stories.tsx | 27 ++++-- src/components/Breadcrumbs.test.tsx | 119 +++++-------------------- src/components/Breadcrumbs.tsx | 28 +++--- 3 files changed, 56 insertions(+), 118 deletions(-) diff --git a/src/components/Breadcrumbs.stories.tsx b/src/components/Breadcrumbs.stories.tsx index b4b03d5..ae44bb6 100644 --- a/src/components/Breadcrumbs.stories.tsx +++ b/src/components/Breadcrumbs.stories.tsx @@ -12,31 +12,48 @@ type Story = StoryObj; export const Default: Story = { args: { - path: "/first/second/third/last/", + path: [ + { name: "first", href: "first" }, + { name: "second", href: "second/could/be/here" }, + { name: "third", href: "third" }, + { name: "last", href: "/" }, + ], }, }; export const ShortPath: Story = { args: { - path: "just one", + path: [{ name: "just one", href: "/" }], }, }; export const LongPath: Story = { args: { - path: "/first/the second/third/fourth/almost last/last one/", + path: [ + { name: "first", href: "first" }, + { name: "the second", href: "the/second" }, + { name: "third", href: "third" }, + { name: "fourth", href: "fourth/could/be/here" }, + { name: "almost last", href: "almost last" }, + { name: "last one", href: "/" }, + ], }, }; export const Empty: Story = { args: { - path: "", + path: [], }, }; export const ColorChange: Story = { args: { - path: ["first", "second", "third", "last"], + path: [ + { name: "first", href: "first" }, + { name: "second", href: "second/could/be/here" }, + { name: "third", href: "third" }, + { name: "last", href: "/" }, + ], rootProps: { sx: { backgroundColor: "blue" }, }, diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index 5d922f6..c196f8f 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -2,16 +2,12 @@ import { render, RenderResult } from "@testing-library/react"; import { Breadcrumbs, getCrumbs } from "./Breadcrumbs"; import "@testing-library/jest-dom"; +const defaultArrayObject = [ + { name: "first", href: "first/is/this" }, + { name: "second", href: "second" }, + { name: "last one", href: "last one" }, +]; describe("Breadcrumbs", () => { - const crumbFirst = "first", - crumbFirstTitle = "First", - crumbSecond = "second", - crumbSecondTitle = "Second", - crumbLast = "last one", - crumbLastTitle = "Last one", - defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`, - defaultArrayPath = [crumbFirst, crumbSecond, crumbLast]; - function testHomeExists(renderResult: RenderResult) { const { getByTestId } = renderResult; const homeIcon = getByTestId("HomeIcon"); @@ -20,48 +16,12 @@ describe("Breadcrumbs", () => { expect(homeIcon.parentElement).toHaveAttribute("href", "/"); } - function testCrumbsExist(renderResult: RenderResult) { - const { getAllByRole, getByRole, getByText, queryByRole } = renderResult; - - expect(getAllByRole("link")).toHaveLength(3); - - testHomeExists(renderResult); - - let crumb = getByRole("link", { name: crumbFirstTitle }); - expect(crumb).toBeInTheDocument(); - expect(crumb).toHaveAttribute("href", `/${crumbFirst}`); - - crumb = getByRole("link", { name: crumbSecondTitle }); - expect(crumb).toBeInTheDocument(); - expect(crumb).toHaveAttribute("href", `/${crumbFirst}/${crumbSecond}`); - - expect( - queryByRole("link", { name: crumbLastTitle }), - ).not.toBeInTheDocument(); - expect(getByText(crumbLastTitle)).toBeInTheDocument(); - } - it("should render without errors", () => { - render(); - }); - - it("should use a path as string", () => { - testCrumbsExist(render()); - }); - - it("should use a path as array", () => { - testCrumbsExist(render()); + render(); }); it("should show just home when an empty string", () => { - const renderResult = render(); - testHomeExists(renderResult); - expect(renderResult.getAllByRole("link")).toHaveLength(1); - }); - - it("should show just home when an empty array", () => { const renderResult = render(); - testHomeExists(renderResult); expect(renderResult.getAllByRole("link")).toHaveLength(1); }); @@ -83,56 +43,23 @@ describe("getCrumbs", () => { }, ]; - it("should match if path string", () => { - expect(getCrumbs("/first/second/last one")).toStrictEqual(correctCrumbs); - }); - - it("should match if last slash included", () => { - expect(getCrumbs("/first/second/last one/")).toStrictEqual(correctCrumbs); - }); - - it("should match if first slash excluded", () => { - expect(getCrumbs("first/second/last one")).toStrictEqual(correctCrumbs); - }); - - it("should match if first slash excluded and last slash included", () => { - expect(getCrumbs("first/second/last one")).toStrictEqual(correctCrumbs); - }); - - it("should match path string with multi separators", () => { - expect(getCrumbs("///first//second/last one")).toStrictEqual(correctCrumbs); - }); - - it("should return an empty array when an empty string is passed", () => { - expect(getCrumbs("")).toStrictEqual([]); - }); - - it("should return an empty array when spaces are passed", () => { - expect(getCrumbs(" ")).toStrictEqual([]); - }); - - it("should match if path array", () => { - expect(getCrumbs(["first", "second", "last one"])).toStrictEqual( - correctCrumbs, - ); - }); - - it("should match if path array with empty", () => { - expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual( - correctCrumbs, - ); - }); - - it("should match by removing empty item", () => { - expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual( - correctCrumbs, - ); - }); - - it("should match by removing spaces only", () => { - expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual( - correctCrumbs, - ); + it("should match with correct array object passed", () => { + expect( + getCrumbs([ + { + name: "first", + href: "/first", + }, + { + name: "second", + href: "/first/second", + }, + { + name: "last one", + href: "/first/second/last one", + }, + ]) + ).toStrictEqual(correctCrumbs); }); it("should return an empty array when an empty array is passed", () => { diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 6f3a5cf..bb70e2b 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -13,32 +13,26 @@ import { import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; -interface BreadcrumbsProps { - path: string | string[]; - rootProps?: PaperProps; - muiBreadcrumbsProps?: Mui_BreadcrumbsProps; -} - type CrumbData = { name: string; href: string; }; +interface BreadcrumbsProps { + path: Array; + rootProps?: PaperProps; + muiBreadcrumbsProps?: Mui_BreadcrumbsProps; +} + /** * Create CrumbData from crumb parts with links - * @param path A single string path, or an array of string parts + * @param pathData An array object that take in crumb names and hrefs */ -export function getCrumbs(path: string | string[]): CrumbData[] { - if (typeof path === "string") { - path = path.split("/"); - } - - const crumbs = path.filter((item) => item.trim() !== ""); - - return crumbs.map((crumb, i) => { +export function getCrumbs(pathData: Array): CrumbData[] { + return pathData.map((obj, i) => { return { - name: crumb.charAt(0).toUpperCase() + crumb.slice(1), - href: "/" + crumbs.slice(0, i + 1).join("/"), + name: obj.name.charAt(0).toUpperCase() + obj.name.slice(1), + href: obj.href, }; }); } From 7def5201c9d37a54b1c682dfbc8350ef12573983 Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Fri, 21 Mar 2025 15:06:56 +0000 Subject: [PATCH 02/11] removed unused use of i in map --- src/components/Breadcrumbs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index bb70e2b..1d21eff 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -29,7 +29,7 @@ interface BreadcrumbsProps { * @param pathData An array object that take in crumb names and hrefs */ export function getCrumbs(pathData: Array): CrumbData[] { - return pathData.map((obj, i) => { + return pathData.map((obj) => { return { name: obj.name.charAt(0).toUpperCase() + obj.name.slice(1), href: obj.href, From 3857d0868d94a25d7f765acbca6867b33cb3ee9e Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Wed, 26 Mar 2025 11:21:55 +0000 Subject: [PATCH 03/11] Reverted breadcrumb. Breadcrumb now takes either a string, string array or object array. Tests updated and comp 100% tested --- src/components/Breadcrumbs.stories.tsx | 24 +++---- src/components/Breadcrumbs.test.tsx | 95 +++++++++++++++----------- src/components/Breadcrumbs.tsx | 39 +++++++---- 3 files changed, 92 insertions(+), 66 deletions(-) diff --git a/src/components/Breadcrumbs.stories.tsx b/src/components/Breadcrumbs.stories.tsx index ae44bb6..ca849d5 100644 --- a/src/components/Breadcrumbs.stories.tsx +++ b/src/components/Breadcrumbs.stories.tsx @@ -12,30 +12,28 @@ type Story = StoryObj; export const Default: Story = { args: { - path: [ - { name: "first", href: "first" }, - { name: "second", href: "second/could/be/here" }, - { name: "third", href: "third" }, - { name: "last", href: "/" }, - ], + path: "first/second/third/last", }, }; export const ShortPath: Story = { args: { - path: [{ name: "just one", href: "/" }], + path: "just one", }, }; export const LongPath: Story = { + args: { + path: "/first/the second/third/fourth/almost last/last one/", + }, +}; + +export const DifferentLinkToPathName: Story = { args: { path: [ - { name: "first", href: "first" }, - { name: "the second", href: "the/second" }, - { name: "third", href: "third" }, - { name: "fourth", href: "fourth/could/be/here" }, - { name: "almost last", href: "almost last" }, - { name: "last one", href: "/" }, + { name: "first", href: "link" }, + { name: "second", href: "other link" }, + { name: "last", href: "/" }, ], }, }; diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index c196f8f..a900953 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -2,11 +2,19 @@ import { render, RenderResult } from "@testing-library/react"; import { Breadcrumbs, getCrumbs } from "./Breadcrumbs"; import "@testing-library/jest-dom"; -const defaultArrayObject = [ - { name: "first", href: "first/is/this" }, - { name: "second", href: "second" }, - { name: "last one", href: "last one" }, -]; +const crumbFirst = "first", + crumbFirstTitle = "First", + crumbSecond = "second", + crumbSecondTitle = "Second", + crumbLast = "last one", + crumbLastTitle = "Last one", + defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`, + defaultArrayPath = [crumbFirst, crumbSecond, crumbLast], + defaultArrayObject = [ + { name: `${crumbFirstTitle}`, href: `/${crumbFirst}` }, + { name: `${crumbSecondTitle}`, href: `/${crumbSecond}` }, + { name: `${crumbLastTitle}`, href: "/" }, + ]; describe("Breadcrumbs", () => { function testHomeExists(renderResult: RenderResult) { const { getByTestId } = renderResult; @@ -16,6 +24,27 @@ describe("Breadcrumbs", () => { expect(homeIcon.parentElement).toHaveAttribute("href", "/"); } + function testCrumbsExist(renderResult: RenderResult) { + const { getAllByRole, getByRole, getByText, queryByRole } = renderResult; + + expect(getAllByRole("link")).toHaveLength(3); + + testHomeExists(renderResult); + + let crumb = getByRole("link", { name: crumbFirstTitle }); + expect(crumb).toBeInTheDocument(); + expect(crumb).toHaveAttribute("href", `/${crumbFirst}`); + + crumb = getByRole("link", { name: crumbSecondTitle }); + expect(crumb).toBeInTheDocument(); + expect(crumb).toHaveAttribute("href", `/${crumbFirst}/${crumbSecond}`); + + expect( + queryByRole("link", { name: crumbLastTitle }) + ).not.toBeInTheDocument(); + expect(getByText(crumbLastTitle)).toBeInTheDocument(); + } + it("should render without errors", () => { render(); }); @@ -25,44 +54,30 @@ describe("Breadcrumbs", () => { testHomeExists(renderResult); expect(renderResult.getAllByRole("link")).toHaveLength(1); }); -}); -describe("getCrumbs", () => { - const correctCrumbs = [ - { - name: "First", - href: "/first", - }, - { - name: "Second", - href: "/first/second", - }, - { - name: "Last one", - href: "/first/second/last one", - }, - ]; + it("should use path as string", () => { + testCrumbsExist(render()); + }); - it("should match with correct array object passed", () => { - expect( - getCrumbs([ - { - name: "first", - href: "/first", - }, - { - name: "second", - href: "/first/second", - }, - { - name: "last one", - href: "/first/second/last one", - }, - ]) - ).toStrictEqual(correctCrumbs); + it("should use path as string array", () => { + testCrumbsExist(render()); }); - it("should return an empty array when an empty array is passed", () => { - expect(getCrumbs([])).toStrictEqual([]); + it("should use path as object array", () => { + const { getByRole, queryByRole, getByText } = render( + + ); + let crumb = getByRole("link", { name: crumbFirstTitle }); + expect(crumb).toBeInTheDocument(); + expect(crumb).toHaveAttribute("href", `/${crumbFirst}`); + + crumb = getByRole("link", { name: crumbSecondTitle }); + expect(crumb).toBeInTheDocument(); + expect(crumb).toHaveAttribute("href", `/${crumbSecond}`); + + expect( + queryByRole("link", { name: crumbLastTitle }) + ).not.toBeInTheDocument(); + expect(getByText(crumbLastTitle)).toBeInTheDocument(); }); }); diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 1d21eff..f0ad88b 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -13,27 +13,40 @@ import { import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; -type CrumbData = { - name: string; - href: string; -}; - interface BreadcrumbsProps { - path: Array; + path: string | string[] | object[]; rootProps?: PaperProps; muiBreadcrumbsProps?: Mui_BreadcrumbsProps; } +type CrumbData = { + name: string; + href: string; +}; + /** * Create CrumbData from crumb parts with links - * @param pathData An array object that take in crumb names and hrefs + * @param path A single string path, an array of string parts or and array of object parts */ -export function getCrumbs(pathData: Array): CrumbData[] { - return pathData.map((obj) => { - return { - name: obj.name.charAt(0).toUpperCase() + obj.name.slice(1), - href: obj.href, - }; +export function getCrumbs(path: string | string[] | object[]): CrumbData[] { + if (typeof path === "string") { + path = path.split("/"); + } + + const crumbs = path.filter((item) => item !== ""); + + return crumbs.map((crumb: any, i) => { + if (typeof crumb === "string") { + return { + name: crumb.charAt(0).toUpperCase() + crumb.slice(1), + href: "/" + crumbs.slice(0, i + 1).join("/"), + }; + } else { + return { + name: crumb["name"].charAt(0).toUpperCase() + crumb["name"].slice(1), + href: crumb["href"], + }; + } }); } From a72bb33b45b19733f3765fc4499b0d1a40f2a2eb Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Mon, 31 Mar 2025 15:16:45 +0100 Subject: [PATCH 04/11] lint error fix --- src/components/Breadcrumbs.test.tsx | 97 ++++++++++++++++++++++++++++- src/components/Breadcrumbs.tsx | 16 +++-- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index a900953..441a3db 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -40,7 +40,7 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbFirst}/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }) + queryByRole("link", { name: crumbLastTitle }), ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); } @@ -65,7 +65,7 @@ describe("Breadcrumbs", () => { it("should use path as object array", () => { const { getByRole, queryByRole, getByText } = render( - + , ); let crumb = getByRole("link", { name: crumbFirstTitle }); expect(crumb).toBeInTheDocument(); @@ -76,8 +76,99 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }) + queryByRole("link", { name: crumbLastTitle }), ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); }); }); + +describe("getCrumbs", () => { + const stringAndStringArrayCrumbs = [ + { + name: "First", + href: "/first", + }, + { + name: "Second", + href: "/first/second", + }, + { + name: "Last one", + href: "/first/second/last one", + }, + ]; + + const objectArrayCrumbs = [ + { name: "First", href: "first" }, + { name: "Second", href: "this is the second link" }, + { name: "Last", href: "/" }, + ]; + + it("should match if path string", () => { + expect(getCrumbs("/first/second/last one")).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should match if last slash included", () => { + expect(getCrumbs("/first/second/last one/")).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should match if first slash excluded and last slash included", () => { + expect(getCrumbs("first/second/last one")).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should match path string with multi separators", () => { + expect(getCrumbs("///first//second/last one")).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should return an empty array when an empty string is passed", () => { + expect(getCrumbs("")).toStrictEqual([]); + }); + + it("should return an empty array when spaces are passed", () => { + expect(getCrumbs(" ")).toStrictEqual([]); + }); + + it("should match if path array", () => { + expect(getCrumbs(["first", "second", "last one"])).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should match if path array with empty", () => { + expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should match by removing spaces only", () => { + expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual( + stringAndStringArrayCrumbs, + ); + }); + + it("should return an empty array when an empty array is passed", () => { + expect(getCrumbs([])).toStrictEqual([]); + }); + + it("should match if path is object array", () => { + expect( + getCrumbs([ + { name: "First", href: "first" }, + { name: "Second", href: "this is the second link" }, + { name: "Last", href: "/" }, + ]), + ).toStrictEqual(objectArrayCrumbs); + }); + + it("should return an empty array if empty object array passed", () => { + expect(getCrumbs([{}])).toStrictEqual([]); + }); +}); diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index f0ad88b..a783165 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -33,9 +33,16 @@ export function getCrumbs(path: string | string[] | object[]): CrumbData[] { path = path.split("/"); } - const crumbs = path.filter((item) => item !== ""); + const crumbs = path.filter((item) => { + if (typeof item === "object") { + return Object.entries(item).length > 0 ? item : undefined; + } else { + return item.trim() !== ""; + } + }); - return crumbs.map((crumb: any, i) => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + return crumbs.map((crumb: any, i: number): CrumbData => { if (typeof crumb === "string") { return { name: crumb.charAt(0).toUpperCase() + crumb.slice(1), @@ -43,8 +50,9 @@ export function getCrumbs(path: string | string[] | object[]): CrumbData[] { }; } else { return { - name: crumb["name"].charAt(0).toUpperCase() + crumb["name"].slice(1), - href: crumb["href"], + name: + crumb["name"].trim().charAt(0).toUpperCase() + crumb["name"].slice(1), + href: crumb["href"].trim(), }; } }); From e90eceae6c0651c46e1f583a219ebd8db14e72a8 Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Mon, 31 Mar 2025 16:41:31 +0100 Subject: [PATCH 05/11] resolved ts issue with any type --- src/components/Breadcrumbs.test.tsx | 26 +++++++++++--------------- src/components/Breadcrumbs.tsx | 21 ++++++++++----------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index 441a3db..671391b 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -40,7 +40,7 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbFirst}/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }), + queryByRole("link", { name: crumbLastTitle }) ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); } @@ -65,7 +65,7 @@ describe("Breadcrumbs", () => { it("should use path as object array", () => { const { getByRole, queryByRole, getByText } = render( - , + ); let crumb = getByRole("link", { name: crumbFirstTitle }); expect(crumb).toBeInTheDocument(); @@ -76,7 +76,7 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }), + queryByRole("link", { name: crumbLastTitle }) ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); }); @@ -106,25 +106,25 @@ describe("getCrumbs", () => { it("should match if path string", () => { expect(getCrumbs("/first/second/last one")).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); it("should match if last slash included", () => { expect(getCrumbs("/first/second/last one/")).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); it("should match if first slash excluded and last slash included", () => { expect(getCrumbs("first/second/last one")).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); it("should match path string with multi separators", () => { expect(getCrumbs("///first//second/last one")).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); @@ -138,19 +138,19 @@ describe("getCrumbs", () => { it("should match if path array", () => { expect(getCrumbs(["first", "second", "last one"])).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); it("should match if path array with empty", () => { expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); it("should match by removing spaces only", () => { expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual( - stringAndStringArrayCrumbs, + stringAndStringArrayCrumbs ); }); @@ -164,11 +164,7 @@ describe("getCrumbs", () => { { name: "First", href: "first" }, { name: "Second", href: "this is the second link" }, { name: "Last", href: "/" }, - ]), + ]) ).toStrictEqual(objectArrayCrumbs); }); - - it("should return an empty array if empty object array passed", () => { - expect(getCrumbs([{}])).toStrictEqual([]); - }); }); diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index a783165..ee7b189 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -13,22 +13,22 @@ import { import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; -interface BreadcrumbsProps { - path: string | string[] | object[]; - rootProps?: PaperProps; - muiBreadcrumbsProps?: Mui_BreadcrumbsProps; -} - type CrumbData = { name: string; href: string; }; +interface BreadcrumbsProps { + path: string | string[] | CrumbData[]; + rootProps?: PaperProps; + muiBreadcrumbsProps?: Mui_BreadcrumbsProps; +} /** * Create CrumbData from crumb parts with links - * @param path A single string path, an array of string parts or and array of object parts + * @param path A single string path, an array of string parts or and array of CrumbData parts */ -export function getCrumbs(path: string | string[] | object[]): CrumbData[] { +export function getCrumbs(path: string | string[] | CrumbData[]): CrumbData[] { + console.log(typeof path); if (typeof path === "string") { path = path.split("/"); } @@ -41,12 +41,11 @@ export function getCrumbs(path: string | string[] | object[]): CrumbData[] { } }); - /* eslint-disable @typescript-eslint/no-explicit-any */ - return crumbs.map((crumb: any, i: number): CrumbData => { + return crumbs.map((crumb: string | CrumbData, index: number) => { if (typeof crumb === "string") { return { name: crumb.charAt(0).toUpperCase() + crumb.slice(1), - href: "/" + crumbs.slice(0, i + 1).join("/"), + href: "/" + crumbs.slice(0, index + 1).join("/"), }; } else { return { From 92ba9b758bca36b65d936acfa6636181d30a935d Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Mon, 31 Mar 2025 16:42:14 +0100 Subject: [PATCH 06/11] removed console log --- src/components/Breadcrumbs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index ee7b189..a13f36b 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -28,7 +28,6 @@ interface BreadcrumbsProps { * @param path A single string path, an array of string parts or and array of CrumbData parts */ export function getCrumbs(path: string | string[] | CrumbData[]): CrumbData[] { - console.log(typeof path); if (typeof path === "string") { path = path.split("/"); } From bd38f7cb71f33a0e5e80b83a1b0d7acbf6647ef3 Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Mon, 31 Mar 2025 16:44:09 +0100 Subject: [PATCH 07/11] lint fix --- src/components/Breadcrumbs.test.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index 671391b..83a644d 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -40,7 +40,7 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbFirst}/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }) + queryByRole("link", { name: crumbLastTitle }), ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); } @@ -65,7 +65,7 @@ describe("Breadcrumbs", () => { it("should use path as object array", () => { const { getByRole, queryByRole, getByText } = render( - + , ); let crumb = getByRole("link", { name: crumbFirstTitle }); expect(crumb).toBeInTheDocument(); @@ -76,7 +76,7 @@ describe("Breadcrumbs", () => { expect(crumb).toHaveAttribute("href", `/${crumbSecond}`); expect( - queryByRole("link", { name: crumbLastTitle }) + queryByRole("link", { name: crumbLastTitle }), ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); }); @@ -106,25 +106,25 @@ describe("getCrumbs", () => { it("should match if path string", () => { expect(getCrumbs("/first/second/last one")).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); it("should match if last slash included", () => { expect(getCrumbs("/first/second/last one/")).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); it("should match if first slash excluded and last slash included", () => { expect(getCrumbs("first/second/last one")).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); it("should match path string with multi separators", () => { expect(getCrumbs("///first//second/last one")).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); @@ -138,19 +138,19 @@ describe("getCrumbs", () => { it("should match if path array", () => { expect(getCrumbs(["first", "second", "last one"])).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); it("should match if path array with empty", () => { expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); it("should match by removing spaces only", () => { expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual( - stringAndStringArrayCrumbs + stringAndStringArrayCrumbs, ); }); @@ -164,7 +164,7 @@ describe("getCrumbs", () => { { name: "First", href: "first" }, { name: "Second", href: "this is the second link" }, { name: "Last", href: "/" }, - ]) + ]), ).toStrictEqual(objectArrayCrumbs); }); }); From de287075e09bad7068422fd8cf54146a6fa01e6f Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Mon, 7 Apr 2025 10:13:06 +0100 Subject: [PATCH 08/11] CustomLink type created and used in Breadcrumbs comp. Plan to use in other components in the future. --- src/components/Breadcrumbs.tsx | 18 +++++++++--------- src/types/links.d.ts | 4 ++++ 2 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 src/types/links.d.ts diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index a13f36b..91ba74a 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -13,21 +13,21 @@ import { import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; -type CrumbData = { - name: string; - href: string; -}; +import { CustomLink } from "types/links"; + interface BreadcrumbsProps { - path: string | string[] | CrumbData[]; + path: string | string[] | CustomLink[]; rootProps?: PaperProps; muiBreadcrumbsProps?: Mui_BreadcrumbsProps; } /** * Create CrumbData from crumb parts with links - * @param path A single string path, an array of string parts or and array of CrumbData parts + * @param path A single string path, an array of string parts or and array of CustomLink parts */ -export function getCrumbs(path: string | string[] | CrumbData[]): CrumbData[] { +export function getCrumbs( + path: string | string[] | CustomLink[] +): CustomLink[] { if (typeof path === "string") { path = path.split("/"); } @@ -40,7 +40,7 @@ export function getCrumbs(path: string | string[] | CrumbData[]): CrumbData[] { } }); - return crumbs.map((crumb: string | CrumbData, index: number) => { + return crumbs.map((crumb: string | CustomLink, index: number) => { if (typeof crumb === "string") { return { name: crumb.charAt(0).toUpperCase() + crumb.slice(1), @@ -62,7 +62,7 @@ const Breadcrumbs = ({ muiBreadcrumbsProps, }: BreadcrumbsProps) => { const theme = useTheme(); - const crumbs: CrumbData[] = getCrumbs(path); + const crumbs: CustomLink[] = getCrumbs(path); return ( Date: Mon, 7 Apr 2025 10:20:54 +0100 Subject: [PATCH 09/11] lint fix --- src/components/Breadcrumbs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 91ba74a..b34671e 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -26,7 +26,7 @@ interface BreadcrumbsProps { * @param path A single string path, an array of string parts or and array of CustomLink parts */ export function getCrumbs( - path: string | string[] | CustomLink[] + path: string | string[] | CustomLink[], ): CustomLink[] { if (typeof path === "string") { path = path.split("/"); From 44bed3462ad9e6c0e629a5fd1006aa3b5936344d Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Tue, 8 Apr 2025 11:43:44 +0100 Subject: [PATCH 10/11] reverting some breadcrumb story changes and updating test names --- src/components/Breadcrumbs.stories.tsx | 9 ++------- src/components/Breadcrumbs.test.tsx | 6 ++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/Breadcrumbs.stories.tsx b/src/components/Breadcrumbs.stories.tsx index ca849d5..dfa78d1 100644 --- a/src/components/Breadcrumbs.stories.tsx +++ b/src/components/Breadcrumbs.stories.tsx @@ -12,7 +12,7 @@ type Story = StoryObj; export const Default: Story = { args: { - path: "first/second/third/last", + path: "/first/second/third/last/", }, }; @@ -46,12 +46,7 @@ export const Empty: Story = { export const ColorChange: Story = { args: { - path: [ - { name: "first", href: "first" }, - { name: "second", href: "second/could/be/here" }, - { name: "third", href: "third" }, - { name: "last", href: "/" }, - ], + path: ["first", "second", "third", "last"], rootProps: { sx: { backgroundColor: "blue" }, }, diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index 83a644d..d661f1d 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -50,6 +50,12 @@ describe("Breadcrumbs", () => { }); it("should show just home when an empty string", () => { + const renderResult = render(); + testHomeExists(renderResult); + expect(renderResult.getAllByRole("link")).toHaveLength(1); + }); + + it("should show just home when an empty array", () => { const renderResult = render(); testHomeExists(renderResult); expect(renderResult.getAllByRole("link")).toHaveLength(1); From 7480b0b2cdfb7bce46a743645eba31516e18b75a Mon Sep 17 00:00:00 2001 From: Rhys Adey Date: Tue, 8 Apr 2025 11:55:23 +0100 Subject: [PATCH 11/11] Applying MW suggestion --- src/components/Breadcrumbs.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index d661f1d..7510eba 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -1,6 +1,7 @@ import { render, RenderResult } from "@testing-library/react"; import { Breadcrumbs, getCrumbs } from "./Breadcrumbs"; import "@testing-library/jest-dom"; +import { CustomLink } from "types/links"; const crumbFirst = "first", crumbFirstTitle = "First", @@ -10,7 +11,7 @@ const crumbFirst = "first", crumbLastTitle = "Last one", defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`, defaultArrayPath = [crumbFirst, crumbSecond, crumbLast], - defaultArrayObject = [ + defaultArrayObject: CustomLink[] = [ { name: `${crumbFirstTitle}`, href: `/${crumbFirst}` }, { name: `${crumbSecondTitle}`, href: `/${crumbSecond}` }, { name: `${crumbLastTitle}`, href: "/" },