Skip to content

Commit b61d940

Browse files
authored
Breadcrumb to accept Array<object> (#38)
* breadcrumb component now accepts an object array with name and href information needed. Allows path with multiple slashes to be applied to a href * removed unused use of i in map * Reverted breadcrumb. Breadcrumb now takes either a string, string array or object array. Tests updated and comp 100% tested * lint error fix * resolved ts issue with any type * removed console log * lint fix * CustomLink type created and used in Breadcrumbs comp. Plan to use in other components in the future. * lint fix * reverting some breadcrumb story changes and updating test names * Applying MW suggestion
1 parent 4edfd4f commit b61d940

File tree

4 files changed

+117
-54
lines changed

4 files changed

+117
-54
lines changed

src/components/Breadcrumbs.stories.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,19 @@ export const LongPath: Story = {
2828
},
2929
};
3030

31+
export const DifferentLinkToPathName: Story = {
32+
args: {
33+
path: [
34+
{ name: "first", href: "link" },
35+
{ name: "second", href: "other link" },
36+
{ name: "last", href: "/" },
37+
],
38+
},
39+
};
40+
3141
export const Empty: Story = {
3242
args: {
33-
path: "",
43+
path: [],
3444
},
3545
};
3646

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { render, RenderResult } from "@testing-library/react";
22
import { Breadcrumbs, getCrumbs } from "./Breadcrumbs";
33
import "@testing-library/jest-dom";
4-
4+
import { CustomLink } from "types/links";
5+
6+
const crumbFirst = "first",
7+
crumbFirstTitle = "First",
8+
crumbSecond = "second",
9+
crumbSecondTitle = "Second",
10+
crumbLast = "last one",
11+
crumbLastTitle = "Last one",
12+
defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`,
13+
defaultArrayPath = [crumbFirst, crumbSecond, crumbLast],
14+
defaultArrayObject: CustomLink[] = [
15+
{ name: `${crumbFirstTitle}`, href: `/${crumbFirst}` },
16+
{ name: `${crumbSecondTitle}`, href: `/${crumbSecond}` },
17+
{ name: `${crumbLastTitle}`, href: "/" },
18+
];
519
describe("Breadcrumbs", () => {
6-
const crumbFirst = "first",
7-
crumbFirstTitle = "First",
8-
crumbSecond = "second",
9-
crumbSecondTitle = "Second",
10-
crumbLast = "last one",
11-
crumbLastTitle = "Last one",
12-
defaultStringPath = `/${crumbFirst}/${crumbSecond}/${crumbLast}`,
13-
defaultArrayPath = [crumbFirst, crumbSecond, crumbLast];
14-
1520
function testHomeExists(renderResult: RenderResult) {
1621
const { getByTestId } = renderResult;
1722
const homeIcon = getByTestId("HomeIcon");
@@ -42,15 +47,7 @@ describe("Breadcrumbs", () => {
4247
}
4348

4449
it("should render without errors", () => {
45-
render(<Breadcrumbs path={defaultStringPath} />);
46-
});
47-
48-
it("should use a path as string", () => {
49-
testCrumbsExist(render(<Breadcrumbs path={defaultStringPath} />));
50-
});
51-
52-
it("should use a path as array", () => {
53-
testCrumbsExist(render(<Breadcrumbs path={defaultArrayPath} />));
50+
render(<Breadcrumbs path={defaultArrayObject} />);
5451
});
5552

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

6259
it("should show just home when an empty array", () => {
6360
const renderResult = render(<Breadcrumbs path={[]} />);
64-
6561
testHomeExists(renderResult);
6662
expect(renderResult.getAllByRole("link")).toHaveLength(1);
6763
});
64+
65+
it("should use path as string", () => {
66+
testCrumbsExist(render(<Breadcrumbs path={defaultStringPath} />));
67+
});
68+
69+
it("should use path as string array", () => {
70+
testCrumbsExist(render(<Breadcrumbs path={defaultArrayPath} />));
71+
});
72+
73+
it("should use path as object array", () => {
74+
const { getByRole, queryByRole, getByText } = render(
75+
<Breadcrumbs path={defaultArrayObject} />,
76+
);
77+
let crumb = getByRole("link", { name: crumbFirstTitle });
78+
expect(crumb).toBeInTheDocument();
79+
expect(crumb).toHaveAttribute("href", `/${crumbFirst}`);
80+
81+
crumb = getByRole("link", { name: crumbSecondTitle });
82+
expect(crumb).toBeInTheDocument();
83+
expect(crumb).toHaveAttribute("href", `/${crumbSecond}`);
84+
85+
expect(
86+
queryByRole("link", { name: crumbLastTitle }),
87+
).not.toBeInTheDocument();
88+
expect(getByText(crumbLastTitle)).toBeInTheDocument();
89+
});
6890
});
6991

7092
describe("getCrumbs", () => {
71-
const correctCrumbs = [
93+
const stringAndStringArrayCrumbs = [
7294
{
7395
name: "First",
7496
href: "/first",
@@ -83,24 +105,34 @@ describe("getCrumbs", () => {
83105
},
84106
];
85107

108+
const objectArrayCrumbs = [
109+
{ name: "First", href: "first" },
110+
{ name: "Second", href: "this is the second link" },
111+
{ name: "Last", href: "/" },
112+
];
113+
86114
it("should match if path string", () => {
87-
expect(getCrumbs("/first/second/last one")).toStrictEqual(correctCrumbs);
115+
expect(getCrumbs("/first/second/last one")).toStrictEqual(
116+
stringAndStringArrayCrumbs,
117+
);
88118
});
89119

90120
it("should match if last slash included", () => {
91-
expect(getCrumbs("/first/second/last one/")).toStrictEqual(correctCrumbs);
92-
});
93-
94-
it("should match if first slash excluded", () => {
95-
expect(getCrumbs("first/second/last one")).toStrictEqual(correctCrumbs);
121+
expect(getCrumbs("/first/second/last one/")).toStrictEqual(
122+
stringAndStringArrayCrumbs,
123+
);
96124
});
97125

98126
it("should match if first slash excluded and last slash included", () => {
99-
expect(getCrumbs("first/second/last one")).toStrictEqual(correctCrumbs);
127+
expect(getCrumbs("first/second/last one")).toStrictEqual(
128+
stringAndStringArrayCrumbs,
129+
);
100130
});
101131

102132
it("should match path string with multi separators", () => {
103-
expect(getCrumbs("///first//second/last one")).toStrictEqual(correctCrumbs);
133+
expect(getCrumbs("///first//second/last one")).toStrictEqual(
134+
stringAndStringArrayCrumbs,
135+
);
104136
});
105137

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

114146
it("should match if path array", () => {
115147
expect(getCrumbs(["first", "second", "last one"])).toStrictEqual(
116-
correctCrumbs,
148+
stringAndStringArrayCrumbs,
117149
);
118150
});
119151

120152
it("should match if path array with empty", () => {
121153
expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual(
122-
correctCrumbs,
123-
);
124-
});
125-
126-
it("should match by removing empty item", () => {
127-
expect(getCrumbs(["first", "second", "last one", ""])).toStrictEqual(
128-
correctCrumbs,
154+
stringAndStringArrayCrumbs,
129155
);
130156
});
131157

132158
it("should match by removing spaces only", () => {
133159
expect(getCrumbs(["first", "second", "last one", " "])).toStrictEqual(
134-
correctCrumbs,
160+
stringAndStringArrayCrumbs,
135161
);
136162
});
137163

138164
it("should return an empty array when an empty array is passed", () => {
139165
expect(getCrumbs([])).toStrictEqual([]);
140166
});
167+
168+
it("should match if path is object array", () => {
169+
expect(
170+
getCrumbs([
171+
{ name: "First", href: "first" },
172+
{ name: "Second", href: "this is the second link" },
173+
{ name: "Last", href: "/" },
174+
]),
175+
).toStrictEqual(objectArrayCrumbs);
176+
});
141177
});

src/components/Breadcrumbs.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,46 @@ import {
1313
import HomeIcon from "@mui/icons-material/Home";
1414
import NavigateNextIcon from "@mui/icons-material/NavigateNext";
1515

16+
import { CustomLink } from "types/links";
17+
1618
interface BreadcrumbsProps {
17-
path: string | string[];
19+
path: string | string[] | CustomLink[];
1820
rootProps?: PaperProps;
1921
muiBreadcrumbsProps?: Mui_BreadcrumbsProps;
2022
}
2123

22-
type CrumbData = {
23-
name: string;
24-
href: string;
25-
};
26-
2724
/**
2825
* Create CrumbData from crumb parts with links
29-
* @param path A single string path, or an array of string parts
26+
* @param path A single string path, an array of string parts or and array of CustomLink parts
3027
*/
31-
export function getCrumbs(path: string | string[]): CrumbData[] {
28+
export function getCrumbs(
29+
path: string | string[] | CustomLink[],
30+
): CustomLink[] {
3231
if (typeof path === "string") {
3332
path = path.split("/");
3433
}
3534

36-
const crumbs = path.filter((item) => item.trim() !== "");
35+
const crumbs = path.filter((item) => {
36+
if (typeof item === "object") {
37+
return Object.entries(item).length > 0 ? item : undefined;
38+
} else {
39+
return item.trim() !== "";
40+
}
41+
});
3742

38-
return crumbs.map((crumb, i) => {
39-
return {
40-
name: crumb.charAt(0).toUpperCase() + crumb.slice(1),
41-
href: "/" + crumbs.slice(0, i + 1).join("/"),
42-
};
43+
return crumbs.map((crumb: string | CustomLink, index: number) => {
44+
if (typeof crumb === "string") {
45+
return {
46+
name: crumb.charAt(0).toUpperCase() + crumb.slice(1),
47+
href: "/" + crumbs.slice(0, index + 1).join("/"),
48+
};
49+
} else {
50+
return {
51+
name:
52+
crumb["name"].trim().charAt(0).toUpperCase() + crumb["name"].slice(1),
53+
href: crumb["href"].trim(),
54+
};
55+
}
4356
});
4457
}
4558

@@ -49,7 +62,7 @@ const Breadcrumbs = ({
4962
muiBreadcrumbsProps,
5063
}: BreadcrumbsProps) => {
5164
const theme = useTheme();
52-
const crumbs: CrumbData[] = getCrumbs(path);
65+
const crumbs: CustomLink[] = getCrumbs(path);
5366

5467
return (
5568
<Paper

src/types/links.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type CustomLink = {
2+
name: string;
3+
href: string;
4+
}

0 commit comments

Comments
 (0)