Skip to content

Breadcrumb to accept Array<object> #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 8, 2025
12 changes: 11 additions & 1 deletion src/components/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ export const LongPath: Story = {
},
};

export const DifferentLinkToPathName: Story = {
args: {
path: [
{ name: "first", href: "link" },
{ name: "second", href: "other link" },
{ name: "last", href: "/" },
],
},
};

export const Empty: Story = {
args: {
path: "",
path: [],
},
};

Expand Down
112 changes: 74 additions & 38 deletions src/components/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
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",
crumbSecond = "second",
crumbSecondTitle = "Second",
crumbLast = "last one",
crumbLastTitle = "Last one",
defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`,
defaultArrayPath = [crumbFirst, crumbSecond, crumbLast],
defaultArrayObject: CustomLink[] = [
{ name: `${crumbFirstTitle}`, href: `/${crumbFirst}` },
{ name: `${crumbSecondTitle}`, href: `/${crumbSecond}` },
{ name: `${crumbLastTitle}`, href: "/" },
];
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");
Expand Down Expand Up @@ -42,15 +47,7 @@ describe("Breadcrumbs", () => {
}

it("should render without errors", () => {
render(<Breadcrumbs path={defaultStringPath} />);
});

it("should use a path as string", () => {
testCrumbsExist(render(<Breadcrumbs path={defaultStringPath} />));
});

it("should use a path as array", () => {
testCrumbsExist(render(<Breadcrumbs path={defaultArrayPath} />));
render(<Breadcrumbs path={defaultArrayObject} />);
});

it("should show just home when an empty string", () => {
Expand All @@ -61,14 +58,39 @@ describe("Breadcrumbs", () => {

it("should show just home when an empty array", () => {
const renderResult = render(<Breadcrumbs path={[]} />);

testHomeExists(renderResult);
expect(renderResult.getAllByRole("link")).toHaveLength(1);
});

it("should use path as string", () => {
testCrumbsExist(render(<Breadcrumbs path={defaultStringPath} />));
});

it("should use path as string array", () => {
testCrumbsExist(render(<Breadcrumbs path={defaultArrayPath} />));
});

it("should use path as object array", () => {
const { getByRole, queryByRole, getByText } = render(
<Breadcrumbs path={defaultArrayObject} />,
);
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();
});
});

describe("getCrumbs", () => {
const correctCrumbs = [
const stringAndStringArrayCrumbs = [
{
name: "First",
href: "/first",
Expand All @@ -83,24 +105,34 @@ describe("getCrumbs", () => {
},
];

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(correctCrumbs);
expect(getCrumbs("/first/second/last one")).toStrictEqual(
stringAndStringArrayCrumbs,
);
});

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);
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(correctCrumbs);
expect(getCrumbs("first/second/last one")).toStrictEqual(
stringAndStringArrayCrumbs,
);
});

it("should match path string with multi separators", () => {
expect(getCrumbs("///first//second/last one")).toStrictEqual(correctCrumbs);
expect(getCrumbs("///first//second/last one")).toStrictEqual(
stringAndStringArrayCrumbs,
);
});

it("should return an empty array when an empty string is passed", () => {
Expand All @@ -113,29 +145,33 @@ describe("getCrumbs", () => {

it("should match if path array", () => {
expect(getCrumbs(["first", "second", "last one"])).toStrictEqual(
correctCrumbs,
stringAndStringArrayCrumbs,
);
});

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,
stringAndStringArrayCrumbs,
);
});

it("should match by removing spaces only", () => {
expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual(
correctCrumbs,
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);
});
});
43 changes: 28 additions & 15 deletions src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,46 @@ import {
import HomeIcon from "@mui/icons-material/Home";
import NavigateNextIcon from "@mui/icons-material/NavigateNext";

import { CustomLink } from "types/links";

interface BreadcrumbsProps {
path: string | string[];
path: string | string[] | CustomLink[];
rootProps?: PaperProps;
muiBreadcrumbsProps?: Mui_BreadcrumbsProps;
}

type CrumbData = {
name: string;
href: string;
};

/**
* Create CrumbData from crumb parts with links
* @param path A single string path, or an array of string 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[] {
export function getCrumbs(
path: string | string[] | CustomLink[],
): CustomLink[] {
if (typeof path === "string") {
path = path.split("/");
}

const crumbs = path.filter((item) => item.trim() !== "");
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, i) => {
return {
name: crumb.charAt(0).toUpperCase() + crumb.slice(1),
href: "/" + crumbs.slice(0, i + 1).join("/"),
};
return crumbs.map((crumb: string | CustomLink, index: number) => {
if (typeof crumb === "string") {
return {
name: crumb.charAt(0).toUpperCase() + crumb.slice(1),
href: "/" + crumbs.slice(0, index + 1).join("/"),
};
} else {
return {
name:
crumb["name"].trim().charAt(0).toUpperCase() + crumb["name"].slice(1),
href: crumb["href"].trim(),
};
}
});
}

Expand All @@ -49,7 +62,7 @@ const Breadcrumbs = ({
muiBreadcrumbsProps,
}: BreadcrumbsProps) => {
const theme = useTheme();
const crumbs: CrumbData[] = getCrumbs(path);
const crumbs: CustomLink[] = getCrumbs(path);

return (
<Paper
Expand Down
4 changes: 4 additions & 0 deletions src/types/links.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type CustomLink = {
name: string;
href: string;
}