@fpkit/acss 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +514 -18
- package/libs/chunk-23ANBDCR.js +8 -0
- package/libs/chunk-23ANBDCR.js.map +1 -0
- package/libs/chunk-2LTJ7HHX.cjs +18 -0
- package/libs/chunk-2LTJ7HHX.cjs.map +1 -0
- package/libs/chunk-2Y7W75TT.js +9 -0
- package/libs/chunk-2Y7W75TT.js.map +1 -0
- package/libs/chunk-3MKLDCKQ.cjs +31 -0
- package/libs/chunk-3MKLDCKQ.cjs.map +1 -0
- package/libs/chunk-5M57K4SW.js +8 -0
- package/libs/chunk-5M57K4SW.js.map +1 -0
- package/libs/chunk-5S4ORA4C.cjs +15 -0
- package/libs/chunk-5S4ORA4C.cjs.map +1 -0
- package/libs/chunk-772NRB75.js +9 -0
- package/libs/chunk-772NRB75.js.map +1 -0
- package/libs/chunk-AHDJGCG5.cjs +15 -0
- package/libs/chunk-AHDJGCG5.cjs.map +1 -0
- package/libs/chunk-B7F5FS6D.cjs +16 -0
- package/libs/chunk-B7F5FS6D.cjs.map +1 -0
- package/libs/chunk-BHRQBJRY.js +8 -0
- package/libs/chunk-BHRQBJRY.js.map +1 -0
- package/libs/chunk-D4YLRWAO.cjs +18 -0
- package/libs/chunk-D4YLRWAO.cjs.map +1 -0
- package/libs/chunk-ETFLFC2S.js +10 -0
- package/libs/chunk-ETFLFC2S.js.map +1 -0
- package/libs/chunk-G55UJ53G.cjs +16 -0
- package/libs/chunk-G55UJ53G.cjs.map +1 -0
- package/libs/chunk-GZ4QFPRY.js +9 -0
- package/libs/chunk-GZ4QFPRY.js.map +1 -0
- package/libs/chunk-IYUN2EW3.cjs +15 -0
- package/libs/chunk-IYUN2EW3.cjs.map +1 -0
- package/libs/chunk-J32EZPYD.cjs +15 -0
- package/libs/chunk-J32EZPYD.cjs.map +1 -0
- package/libs/chunk-JJ43O4Y5.js +8 -0
- package/libs/chunk-JJ43O4Y5.js.map +1 -0
- package/libs/chunk-KUKIVRC2.js +7 -0
- package/libs/chunk-KUKIVRC2.js.map +1 -0
- package/libs/chunk-L75OQKEI.cjs +13 -0
- package/libs/chunk-L75OQKEI.cjs.map +1 -0
- package/libs/chunk-LT5KZ2QW.cjs +22 -0
- package/libs/chunk-LT5KZ2QW.cjs.map +1 -0
- package/libs/chunk-M5RRNTVX.cjs +15 -0
- package/libs/chunk-M5RRNTVX.cjs.map +1 -0
- package/libs/chunk-NGTJDDFO.js +8 -0
- package/libs/chunk-NGTJDDFO.js.map +1 -0
- package/libs/chunk-OK5QEIMD.cjs +17 -0
- package/libs/chunk-OK5QEIMD.cjs.map +1 -0
- package/libs/chunk-P2DC76ZZ.cjs +18 -0
- package/libs/chunk-P2DC76ZZ.cjs.map +1 -0
- package/libs/chunk-P7TTEYCD.js +7 -0
- package/libs/chunk-P7TTEYCD.js.map +1 -0
- package/libs/chunk-PQ2K3BM6.cjs +17 -0
- package/libs/chunk-PQ2K3BM6.cjs.map +1 -0
- package/libs/chunk-QLZWHAMK.js +8 -0
- package/libs/chunk-QLZWHAMK.js.map +1 -0
- package/libs/chunk-RIVUMPOG.js +8 -0
- package/libs/chunk-RIVUMPOG.js.map +1 -0
- package/libs/chunk-ROZI23GS.cjs +15 -0
- package/libs/chunk-ROZI23GS.cjs.map +1 -0
- package/libs/chunk-S7BABR7Z.cjs +13 -0
- package/libs/chunk-S7BABR7Z.cjs.map +1 -0
- package/libs/chunk-SMYRLO3E.js +8 -0
- package/libs/chunk-SMYRLO3E.js.map +1 -0
- package/libs/chunk-TYRCEX2L.js +8 -0
- package/libs/chunk-TYRCEX2L.js.map +1 -0
- package/libs/chunk-VUH3FXGJ.js +11 -0
- package/libs/chunk-VUH3FXGJ.js.map +1 -0
- package/libs/chunk-XBA562WW.js +8 -0
- package/libs/chunk-XBA562WW.js.map +1 -0
- package/libs/chunk-XTQKWY7W.cjs +32 -0
- package/libs/chunk-XTQKWY7W.cjs.map +1 -0
- package/libs/chunk-ZANSFMTD.js +9 -0
- package/libs/chunk-ZANSFMTD.js.map +1 -0
- package/libs/component-props-a8a2f97e.d.ts +38 -0
- package/libs/components/alert/alert.css +1 -1
- package/libs/components/alert/alert.css.map +1 -1
- package/libs/components/alert/alert.min.css +2 -2
- package/libs/components/badge/badge.css +1 -1
- package/libs/components/badge/badge.css.map +1 -1
- package/libs/components/badge/badge.min.css +2 -2
- package/libs/components/breadcrumbs/breadcrumb.cjs +24 -0
- package/libs/components/breadcrumbs/breadcrumb.cjs.map +1 -0
- package/libs/components/breadcrumbs/breadcrumb.d.cts +290 -0
- package/libs/components/breadcrumbs/breadcrumb.d.ts +290 -0
- package/libs/components/breadcrumbs/breadcrumb.js +5 -0
- package/libs/components/breadcrumbs/breadcrumb.js.map +1 -0
- package/libs/components/button.cjs +19 -0
- package/libs/components/button.cjs.map +1 -0
- package/libs/components/button.d.cts +16 -0
- package/libs/components/button.d.ts +16 -0
- package/libs/components/button.js +4 -0
- package/libs/components/button.js.map +1 -0
- package/libs/components/buttons/button.css +1 -1
- package/libs/components/buttons/button.css.map +1 -1
- package/libs/components/buttons/button.min.css +2 -2
- package/libs/components/card.cjs +31 -0
- package/libs/components/card.cjs.map +1 -0
- package/libs/components/card.d.cts +302 -0
- package/libs/components/card.d.ts +302 -0
- package/libs/components/card.js +4 -0
- package/libs/components/card.js.map +1 -0
- package/libs/components/cards/card.css +1 -1
- package/libs/components/cards/card.css.map +1 -1
- package/libs/components/cards/card.min.css +2 -2
- package/libs/components/details/details.css +1 -1
- package/libs/components/details/details.css.map +1 -1
- package/libs/components/details/details.min.css +2 -2
- package/libs/components/dialog/dialog.cjs +22 -0
- package/libs/components/dialog/dialog.cjs.map +1 -0
- package/libs/components/dialog/dialog.css +1 -1
- package/libs/components/dialog/dialog.css.map +1 -1
- package/libs/components/dialog/dialog.d.cts +105 -0
- package/libs/components/dialog/dialog.d.ts +105 -0
- package/libs/components/dialog/dialog.js +7 -0
- package/libs/components/dialog/dialog.js.map +1 -0
- package/libs/components/dialog/dialog.min.css +2 -2
- package/libs/components/form/fields.cjs +19 -0
- package/libs/components/form/fields.cjs.map +1 -0
- package/libs/components/form/fields.d.cts +24 -0
- package/libs/components/form/fields.d.ts +24 -0
- package/libs/components/form/fields.js +4 -0
- package/libs/components/form/fields.js.map +1 -0
- package/libs/components/form/inputs.cjs +19 -0
- package/libs/components/form/inputs.cjs.map +1 -0
- package/libs/components/form/inputs.d.cts +2 -0
- package/libs/components/form/inputs.d.ts +2 -0
- package/libs/components/form/inputs.js +4 -0
- package/libs/components/form/inputs.js.map +1 -0
- package/libs/components/form/textarea.cjs +19 -0
- package/libs/components/form/textarea.cjs.map +1 -0
- package/libs/components/form/textarea.d.cts +29 -0
- package/libs/components/form/textarea.d.ts +29 -0
- package/libs/components/form/textarea.js +4 -0
- package/libs/components/form/textarea.js.map +1 -0
- package/libs/components/heading/heading.cjs +10 -0
- package/libs/components/heading/heading.cjs.map +1 -0
- package/libs/components/heading/heading.d.cts +3 -0
- package/libs/components/heading/heading.d.ts +3 -0
- package/libs/components/heading/heading.js +4 -0
- package/libs/components/heading/heading.js.map +1 -0
- package/libs/components/icons/icon.cjs +19 -0
- package/libs/components/icons/icon.cjs.map +1 -0
- package/libs/{icons-31ace3de.d.ts → components/icons/icon.d.cts} +151 -61
- package/libs/components/icons/icon.d.ts +445 -0
- package/libs/components/icons/icon.js +4 -0
- package/libs/components/icons/icon.js.map +1 -0
- package/libs/components/images/img.css +1 -1
- package/libs/components/images/img.css.map +1 -1
- package/libs/components/images/img.min.css +2 -2
- package/libs/components/link/link.cjs +19 -0
- package/libs/components/link/link.cjs.map +1 -0
- package/libs/components/link/link.d.cts +19 -0
- package/libs/components/link/link.d.ts +19 -0
- package/libs/components/link/link.js +4 -0
- package/libs/components/link/link.js.map +1 -0
- package/libs/components/list/list.cjs +23 -0
- package/libs/components/list/list.cjs.map +1 -0
- package/libs/components/list/list.d.cts +39 -0
- package/libs/components/list/list.d.ts +39 -0
- package/libs/components/list/list.js +4 -0
- package/libs/components/list/list.js.map +1 -0
- package/libs/components/modal.cjs +14 -0
- package/libs/components/modal.cjs.map +1 -0
- package/libs/components/modal.d.cts +35 -0
- package/libs/components/modal.d.ts +35 -0
- package/libs/components/modal.js +5 -0
- package/libs/components/modal.js.map +1 -0
- package/libs/components/nav/nav.cjs +28 -0
- package/libs/components/nav/nav.cjs.map +1 -0
- package/libs/components/nav/nav.d.cts +44 -0
- package/libs/components/nav/nav.d.ts +44 -0
- package/libs/components/nav/nav.js +5 -0
- package/libs/components/nav/nav.js.map +1 -0
- package/libs/components/popover/popover.cjs +23 -0
- package/libs/components/popover/popover.cjs.map +1 -0
- package/libs/components/popover/popover.d.cts +40 -0
- package/libs/components/popover/popover.d.ts +40 -0
- package/libs/components/popover/popover.js +4 -0
- package/libs/components/popover/popover.js.map +1 -0
- package/libs/components/tables/table.cjs +21 -0
- package/libs/components/tables/table.cjs.map +1 -0
- package/libs/components/tables/table.d.cts +36 -0
- package/libs/components/tables/table.d.ts +36 -0
- package/libs/components/tables/table.js +4 -0
- package/libs/components/tables/table.js.map +1 -0
- package/libs/components/text/text.cjs +23 -0
- package/libs/components/text/text.cjs.map +1 -0
- package/libs/components/text/text.d.cts +30 -0
- package/libs/components/text/text.d.ts +30 -0
- package/libs/components/text/text.js +4 -0
- package/libs/components/text/text.js.map +1 -0
- package/libs/heading-3648c538.d.ts +250 -0
- package/libs/hooks.cjs +7 -0
- package/libs/hooks.d.cts +5 -0
- package/libs/hooks.d.ts +5 -0
- package/libs/hooks.js +3 -0
- package/libs/icons.cjs +3 -2
- package/libs/icons.d.cts +3 -1
- package/libs/icons.d.ts +3 -1
- package/libs/icons.js +2 -1
- package/libs/index.cjs +174 -62
- package/libs/index.cjs.map +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +529 -446
- package/libs/index.d.ts +529 -446
- package/libs/index.js +36 -7
- package/libs/index.js.map +1 -1
- package/libs/inputs-f3a216db.d.ts +45 -0
- package/libs/ui-645f95b5.d.ts +285 -0
- package/package.json +2 -2
- package/src/components/README-UI.mdx +416 -0
- package/src/components/alert/ACCESSIBILITY.md +319 -0
- package/src/components/alert/README.mdx +475 -19
- package/src/components/alert/alert.scss +113 -6
- package/src/components/alert/alert.stories.tsx +372 -0
- package/src/components/alert/alert.test.tsx +762 -0
- package/src/components/alert/alert.tsx +331 -66
- package/src/components/alert/views/alert-actions.tsx +13 -0
- package/src/components/alert/views/alert-content.tsx +17 -0
- package/src/components/alert/views/alert-icon.tsx +53 -0
- package/src/components/alert/views/alert-screen-reader-text.tsx +30 -0
- package/src/components/alert/views/alert-title.tsx +23 -0
- package/src/components/alert/views/alert-view.tsx +158 -0
- package/src/components/alert/views/index.ts +12 -0
- package/src/components/badge/badge.mdx +186 -49
- package/src/components/badge/badge.scss +20 -2
- package/src/components/badge/badge.stories.tsx +160 -14
- package/src/components/badge/badge.test.tsx +179 -0
- package/src/components/badge/badge.tsx +97 -4
- package/src/components/breadcrumbs/README.mdx +364 -45
- package/src/components/breadcrumbs/__snapshots__/breadcrumb.test.tsx.snap +152 -0
- package/src/components/breadcrumbs/breadcrumb.stories.tsx +7 -3
- package/src/components/breadcrumbs/breadcrumb.test.tsx +490 -0
- package/src/components/breadcrumbs/breadcrumb.tsx +427 -170
- package/src/components/button.ts +2 -0
- package/src/components/buttons/button.scss +34 -31
- package/src/components/buttons/button.stories.tsx +35 -0
- package/src/components/card.ts +2 -0
- package/src/components/cards/README.mdx +657 -0
- package/src/components/cards/card.scss +22 -0
- package/src/components/cards/card.stories.tsx +167 -5
- package/src/components/cards/card.test.tsx +360 -20
- package/src/components/cards/card.tsx +200 -79
- package/src/components/cards/card.types.ts +135 -0
- package/src/components/cards/card.utils.ts +79 -0
- package/src/components/details/ACCESSIBILITY-REVIEW-LIVE.md +1050 -0
- package/src/components/details/ACCESSIBILITY-REVIEW.md +502 -0
- package/src/components/details/README.mdx +437 -69
- package/src/components/details/details.scss +16 -0
- package/src/components/details/details.test.tsx +385 -0
- package/src/components/details/details.tsx +101 -69
- package/src/components/details/details.types.ts +76 -0
- package/src/components/dialog/README.mdx +513 -110
- package/src/components/dialog/dialog-modal.tsx +79 -56
- package/src/components/dialog/dialog.scss +53 -3
- package/src/components/dialog/dialog.stories.tsx +10 -7
- package/src/components/dialog/dialog.test.tsx +450 -0
- package/src/components/dialog/dialog.tsx +69 -59
- package/src/components/dialog/dialog.types.ts +133 -0
- package/src/components/dialog/views/dialog-footer.tsx +54 -11
- package/src/components/dialog/views/dialog-header.tsx +20 -15
- package/src/components/heading/heading.stories.tsx +44 -4
- package/src/components/heading/heading.tsx +89 -23
- package/src/components/icons/README.mdx +332 -0
- package/src/components/icons/icon.stories.tsx +74 -1
- package/src/components/icons/icon.tsx +89 -1
- package/src/components/icons/types.ts +47 -0
- package/src/components/images/README.mdx +340 -24
- package/src/components/images/img.scss +19 -3
- package/src/components/images/img.stories.tsx +424 -15
- package/src/components/images/img.test.tsx +354 -25
- package/src/components/images/img.tsx +186 -63
- package/src/components/images/img.types.ts +211 -0
- package/src/components/modal.ts +1 -0
- package/src/components/title/MIGRATION.md +199 -0
- package/src/components/title/README.md +326 -0
- package/src/components/title/README.mdx +452 -0
- package/src/components/title/title.stories.tsx +393 -0
- package/src/components/title/title.test.tsx +251 -0
- package/src/components/title/title.tsx +219 -0
- package/src/components/ui.stories.tsx +894 -0
- package/src/components/ui.test.tsx +559 -0
- package/src/components/ui.tsx +266 -15
- package/src/components/word-count/README.md +240 -0
- package/src/hooks.ts +1 -0
- package/src/index.ts +51 -19
- package/src/sass/_properties.scss +1 -0
- package/src/styles/alert/alert.css +94 -4
- package/src/styles/alert/alert.css.map +1 -1
- package/src/styles/badge/badge.css +20 -2
- package/src/styles/badge/badge.css.map +1 -1
- package/src/styles/buttons/button.css +31 -31
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/cards/card.css +16 -0
- package/src/styles/cards/card.css.map +1 -1
- package/src/styles/details/details.css +19 -0
- package/src/styles/details/details.css.map +1 -1
- package/src/styles/dialog/dialog.css +43 -2
- package/src/styles/dialog/dialog.css.map +1 -1
- package/src/styles/images/img.css +15 -3
- package/src/styles/images/img.css.map +1 -1
- package/src/styles/index.css +240 -43
- package/src/styles/index.css.map +1 -1
- package/src/test/setup.d.ts +9 -0
- package/src/test/setup.ts +53 -1
- package/libs/chunk-PWVRDQ3R.js +0 -8
- package/libs/chunk-PWVRDQ3R.js.map +0 -1
- package/libs/chunk-SVS4MX3U.cjs +0 -31
- package/libs/chunk-SVS4MX3U.cjs.map +0 -1
- package/src/components/cards/README.md +0 -80
- package/src/components/dialog/hooks/useClickOutside.ts +0 -33
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { Breadcrumb, CustomRoute } from "./breadcrumb";
|
|
5
|
+
|
|
6
|
+
describe("Breadcrumb", () => {
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// CORE FUNCTIONALITY TESTS
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
describe("Core Functionality", () => {
|
|
12
|
+
it("renders a navigation element with breadcrumb list", () => {
|
|
13
|
+
render(<Breadcrumb currentRoute="/products/shirts" />);
|
|
14
|
+
const nav = screen.getByRole("navigation");
|
|
15
|
+
expect(nav).toBeInTheDocument();
|
|
16
|
+
|
|
17
|
+
const list = nav.querySelector("ol");
|
|
18
|
+
expect(list).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns null when currentRoute is undefined", () => {
|
|
22
|
+
const { container } = render(<Breadcrumb currentRoute={undefined} />);
|
|
23
|
+
expect(container.firstChild).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns null when currentRoute is empty string", () => {
|
|
27
|
+
const { container } = render(<Breadcrumb currentRoute="" />);
|
|
28
|
+
expect(container.firstChild).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("renders home/start route link correctly", () => {
|
|
32
|
+
render(<Breadcrumb currentRoute="/products" />);
|
|
33
|
+
const homeLink = screen.getByRole("link", { name: "Home" });
|
|
34
|
+
expect(homeLink).toBeInTheDocument();
|
|
35
|
+
expect(homeLink).toHaveAttribute("href", "/");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("parses path segments correctly from currentRoute", () => {
|
|
39
|
+
render(<Breadcrumb currentRoute="/products/shirts/blue-shirt" />);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByText("products")).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText("shirts")).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByText("blue-shirt")).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("handles paths with leading slashes", () => {
|
|
47
|
+
render(<Breadcrumb currentRoute="/products" />);
|
|
48
|
+
expect(screen.getByText("products")).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("handles paths with trailing slashes", () => {
|
|
52
|
+
render(<Breadcrumb currentRoute="/products/shirts/" />);
|
|
53
|
+
expect(screen.getByText("products")).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByText("shirts")).toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles encoded URI components correctly", () => {
|
|
58
|
+
render(<Breadcrumb currentRoute="/products/learning%20in%20public" />);
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText("products")).toBeInTheDocument();
|
|
61
|
+
|
|
62
|
+
// Text is truncated, so use aria-label to verify full decoded text
|
|
63
|
+
const element = screen.getByText(/learning in pub/);
|
|
64
|
+
expect(element).toHaveAttribute("aria-label", "learning in public");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// CUSTOM ROUTES TESTS
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
describe("Custom Routes", () => {
|
|
73
|
+
const customRoutes: CustomRoute[] = [
|
|
74
|
+
{
|
|
75
|
+
path: "products",
|
|
76
|
+
name: "All Products",
|
|
77
|
+
url: "/products",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
path: "shirts",
|
|
81
|
+
name: "Shirts & Tops",
|
|
82
|
+
url: "/products/shirts",
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
it("maps path segments to custom route names", () => {
|
|
87
|
+
render(
|
|
88
|
+
<Breadcrumb
|
|
89
|
+
currentRoute="/products/shirts"
|
|
90
|
+
routes={customRoutes}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText("All Products")).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByText("Shirts & Tops")).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("uses custom URLs when provided", () => {
|
|
99
|
+
render(
|
|
100
|
+
<Breadcrumb
|
|
101
|
+
currentRoute="/products/shirts"
|
|
102
|
+
routes={customRoutes}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const productsLink = screen.getByRole("link", { name: "All Products" });
|
|
107
|
+
expect(productsLink).toHaveAttribute("href", "/products");
|
|
108
|
+
|
|
109
|
+
const shirtsSpan = screen.getByText("Shirts & Tops");
|
|
110
|
+
expect(shirtsSpan).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("falls back to path segment when no custom route found", () => {
|
|
114
|
+
render(
|
|
115
|
+
<Breadcrumb
|
|
116
|
+
currentRoute="/products/pants"
|
|
117
|
+
routes={customRoutes}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(screen.getByText("All Products")).toBeInTheDocument();
|
|
122
|
+
expect(screen.getByText("pants")).toBeInTheDocument(); // No custom route
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("handles partial custom route mappings", () => {
|
|
126
|
+
const partialRoutes: CustomRoute[] = [
|
|
127
|
+
{
|
|
128
|
+
path: "products",
|
|
129
|
+
name: "Products",
|
|
130
|
+
url: "/products",
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
render(
|
|
135
|
+
<Breadcrumb
|
|
136
|
+
currentRoute="/products/shirts/item-123"
|
|
137
|
+
routes={partialRoutes}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(screen.getByText("Products")).toBeInTheDocument();
|
|
142
|
+
expect(screen.getByText("shirts")).toBeInTheDocument();
|
|
143
|
+
expect(screen.getByText("item-123")).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// ACCESSIBILITY TESTS
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
describe("Accessibility", () => {
|
|
152
|
+
it("renders semantic nav element with proper aria-label", () => {
|
|
153
|
+
render(
|
|
154
|
+
<Breadcrumb
|
|
155
|
+
currentRoute="/products"
|
|
156
|
+
ariaLabel="Page navigation"
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const nav = screen.getByRole("navigation", { name: "Page navigation" });
|
|
161
|
+
expect(nav).toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("uses default aria-label of 'Breadcrumb' when not provided", () => {
|
|
165
|
+
render(<Breadcrumb currentRoute="/products" />);
|
|
166
|
+
|
|
167
|
+
const nav = screen.getByRole("navigation", { name: "Breadcrumb" });
|
|
168
|
+
expect(nav).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("uses ordered list (ol) for breadcrumb list", () => {
|
|
172
|
+
render(<Breadcrumb currentRoute="/products/shirts" />);
|
|
173
|
+
|
|
174
|
+
const nav = screen.getByRole("navigation");
|
|
175
|
+
const list = nav.querySelector("ol");
|
|
176
|
+
expect(list).toBeInTheDocument();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("marks last segment with aria-current='page'", () => {
|
|
180
|
+
render(<Breadcrumb currentRoute="/products/shirts" />);
|
|
181
|
+
|
|
182
|
+
const currentPage = screen.getByText("shirts");
|
|
183
|
+
expect(currentPage).toHaveAttribute("aria-current", "page");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("hides spacers from screen readers with aria-hidden", () => {
|
|
187
|
+
render(<Breadcrumb currentRoute="/products/shirts" />);
|
|
188
|
+
|
|
189
|
+
const nav = screen.getByRole("navigation");
|
|
190
|
+
const spacers = nav.querySelectorAll('[aria-hidden="true"]');
|
|
191
|
+
|
|
192
|
+
// Should have at least 2 spacers (one for each segment)
|
|
193
|
+
expect(spacers.length).toBeGreaterThan(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("does not render anchor tags with href='#'", () => {
|
|
197
|
+
render(<Breadcrumb currentRoute="/products/shirts" />);
|
|
198
|
+
|
|
199
|
+
const nav = screen.getByRole("navigation");
|
|
200
|
+
const invalidLinks = nav.querySelectorAll('a[href="#"]');
|
|
201
|
+
|
|
202
|
+
expect(invalidLinks.length).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("provides full text in aria-label when truncated", () => {
|
|
206
|
+
const longName = "this-is-a-very-long-product-name";
|
|
207
|
+
render(
|
|
208
|
+
<Breadcrumb
|
|
209
|
+
currentRoute={`/products/${longName}`}
|
|
210
|
+
truncateLength={10}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const truncatedElement = screen.getByText(/this-is-a-/);
|
|
215
|
+
expect(truncatedElement).toHaveAttribute("aria-label", longName);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("does not add aria-label when text is not truncated", () => {
|
|
219
|
+
render(
|
|
220
|
+
<Breadcrumb
|
|
221
|
+
currentRoute="/products/shirt"
|
|
222
|
+
truncateLength={20}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const element = screen.getByText("shirt");
|
|
227
|
+
expect(element).not.toHaveAttribute("aria-label");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// TRUNCATION TESTS
|
|
233
|
+
// ============================================================================
|
|
234
|
+
|
|
235
|
+
describe("Text Truncation", () => {
|
|
236
|
+
it("truncates text beyond default truncateLength (15)", () => {
|
|
237
|
+
const longName = "verylongproductname";
|
|
238
|
+
render(<Breadcrumb currentRoute={`/products/${longName}`} />);
|
|
239
|
+
|
|
240
|
+
// Should be truncated to 15 chars + "..."
|
|
241
|
+
expect(screen.getByText("verylongproduct...")).toBeInTheDocument();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("respects custom truncateLength prop", () => {
|
|
245
|
+
const longName = "verylongname";
|
|
246
|
+
render(
|
|
247
|
+
<Breadcrumb
|
|
248
|
+
currentRoute={`/products/${longName}`}
|
|
249
|
+
truncateLength={5}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(screen.getByText("veryl...")).toBeInTheDocument();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("does not truncate text shorter than truncateLength", () => {
|
|
257
|
+
render(
|
|
258
|
+
<Breadcrumb
|
|
259
|
+
currentRoute="/products/shirt"
|
|
260
|
+
truncateLength={15}
|
|
261
|
+
/>
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(screen.getByText("shirt")).toBeInTheDocument();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("truncates both intermediate and current page segments", () => {
|
|
268
|
+
render(
|
|
269
|
+
<Breadcrumb
|
|
270
|
+
currentRoute="/verylongfirstsegment/verylongsecondsegment"
|
|
271
|
+
truncateLength={10}
|
|
272
|
+
/>
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
expect(screen.getByText("verylongfi...")).toBeInTheDocument();
|
|
276
|
+
expect(screen.getByText("verylongse...")).toBeInTheDocument();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// EDGE CASES TESTS
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
describe("Edge Cases", () => {
|
|
285
|
+
it("skips last segment if length <= 3 characters", () => {
|
|
286
|
+
render(<Breadcrumb currentRoute="/products/shirts/abc" />);
|
|
287
|
+
|
|
288
|
+
// "abc" should be skipped (last segment with length 3)
|
|
289
|
+
expect(screen.queryByText("abc")).not.toBeInTheDocument();
|
|
290
|
+
expect(screen.getByText("products")).toBeInTheDocument();
|
|
291
|
+
expect(screen.getByText("shirts")).toBeInTheDocument();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("skips duplicate consecutive segments", () => {
|
|
295
|
+
const { container } = render(
|
|
296
|
+
<Breadcrumb currentRoute="/products/products" />
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const nav = container.querySelector("nav");
|
|
300
|
+
const productLinks = nav?.querySelectorAll('a[href="products"]');
|
|
301
|
+
|
|
302
|
+
// Should only have one "products" link (first occurrence)
|
|
303
|
+
expect(productLinks?.length).toBeLessThanOrEqual(1);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("handles single segment path", () => {
|
|
307
|
+
render(<Breadcrumb currentRoute="/about" />);
|
|
308
|
+
|
|
309
|
+
expect(screen.getByRole("link", { name: "Home" })).toBeInTheDocument();
|
|
310
|
+
expect(screen.getByText("about")).toBeInTheDocument();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("handles deep nesting (many segments)", () => {
|
|
314
|
+
render(
|
|
315
|
+
<Breadcrumb currentRoute="/level1/level2/level3/level4/level5" />
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(screen.getByText("level1")).toBeInTheDocument();
|
|
319
|
+
expect(screen.getByText("level2")).toBeInTheDocument();
|
|
320
|
+
expect(screen.getByText("level3")).toBeInTheDocument();
|
|
321
|
+
expect(screen.getByText("level4")).toBeInTheDocument();
|
|
322
|
+
expect(screen.getByText("level5")).toBeInTheDocument();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("handles special characters in segments", () => {
|
|
326
|
+
render(<Breadcrumb currentRoute="/products/t-shirts/blue" />);
|
|
327
|
+
|
|
328
|
+
expect(screen.getByText("t-shirts")).toBeInTheDocument();
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// ============================================================================
|
|
333
|
+
// INTEGRATION TESTS
|
|
334
|
+
// ============================================================================
|
|
335
|
+
|
|
336
|
+
describe("Props Integration", () => {
|
|
337
|
+
it("spreads linkProps to Link components", () => {
|
|
338
|
+
const handleClick = vi.fn();
|
|
339
|
+
|
|
340
|
+
render(
|
|
341
|
+
<Breadcrumb
|
|
342
|
+
currentRoute="/products/shirts"
|
|
343
|
+
linkProps={{
|
|
344
|
+
onClick: handleClick,
|
|
345
|
+
"data-testid": "breadcrumb-link",
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const links = screen.getAllByTestId("breadcrumb-link");
|
|
351
|
+
expect(links.length).toBeGreaterThan(0);
|
|
352
|
+
|
|
353
|
+
// Click first link
|
|
354
|
+
links[0].click();
|
|
355
|
+
expect(handleClick).toHaveBeenCalled();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("uses custom startRoute and startRouteUrl", () => {
|
|
359
|
+
render(
|
|
360
|
+
<Breadcrumb
|
|
361
|
+
currentRoute="/products"
|
|
362
|
+
startRoute="Dashboard"
|
|
363
|
+
startRouteUrl="/dashboard"
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const startLink = screen.getByRole("link", { name: "Dashboard" });
|
|
368
|
+
expect(startLink).toBeInTheDocument();
|
|
369
|
+
expect(startLink).toHaveAttribute("href", "/dashboard");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("renders custom spacer element", () => {
|
|
373
|
+
render(
|
|
374
|
+
<Breadcrumb
|
|
375
|
+
currentRoute="/products/shirts"
|
|
376
|
+
spacer={<span data-testid="custom-spacer">→</span>}
|
|
377
|
+
/>
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const spacers = screen.getAllByTestId("custom-spacer");
|
|
381
|
+
expect(spacers.length).toBeGreaterThan(0);
|
|
382
|
+
expect(spacers[0]).toHaveTextContent("→");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("applies custom id to nav element", () => {
|
|
386
|
+
render(
|
|
387
|
+
<Breadcrumb
|
|
388
|
+
currentRoute="/products"
|
|
389
|
+
id="custom-breadcrumb"
|
|
390
|
+
/>
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const nav = screen.getByRole("navigation");
|
|
394
|
+
expect(nav).toHaveAttribute("id", "custom-breadcrumb");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("applies custom className via classes prop", () => {
|
|
398
|
+
render(
|
|
399
|
+
<Breadcrumb
|
|
400
|
+
currentRoute="/products"
|
|
401
|
+
classes="custom-breadcrumb-class"
|
|
402
|
+
/>
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const nav = screen.getByRole("navigation");
|
|
406
|
+
expect(nav).toHaveClass("custom-breadcrumb-class");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("applies custom inline styles", () => {
|
|
410
|
+
render(
|
|
411
|
+
<Breadcrumb
|
|
412
|
+
currentRoute="/products"
|
|
413
|
+
styles={{ padding: "1rem", backgroundColor: "lightgray" }}
|
|
414
|
+
/>
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const nav = screen.getByRole("navigation");
|
|
418
|
+
expect(nav).toHaveStyle({ padding: "1rem" });
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// ============================================================================
|
|
423
|
+
// SUB-COMPONENT EXPORTS TESTS
|
|
424
|
+
// ============================================================================
|
|
425
|
+
|
|
426
|
+
describe("Sub-component Exports", () => {
|
|
427
|
+
it("exports Nav sub-component", () => {
|
|
428
|
+
expect(Breadcrumb.Nav).toBeDefined();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("exports List sub-component", () => {
|
|
432
|
+
expect(Breadcrumb.List).toBeDefined();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("exports Item sub-component", () => {
|
|
436
|
+
expect(Breadcrumb.Item).toBeDefined();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("allows custom composition with sub-components", () => {
|
|
440
|
+
render(
|
|
441
|
+
<Breadcrumb.Nav aria-label="Custom breadcrumb">
|
|
442
|
+
<Breadcrumb.Item>
|
|
443
|
+
<a href="/">Home</a>
|
|
444
|
+
</Breadcrumb.Item>
|
|
445
|
+
<Breadcrumb.Item>
|
|
446
|
+
<span>Current Page</span>
|
|
447
|
+
</Breadcrumb.Item>
|
|
448
|
+
</Breadcrumb.Nav>
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
expect(screen.getByRole("navigation")).toBeInTheDocument();
|
|
452
|
+
expect(screen.getByText("Home")).toBeInTheDocument();
|
|
453
|
+
expect(screen.getByText("Current Page")).toBeInTheDocument();
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// SNAPSHOT TESTS
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
describe("Snapshot Tests", () => {
|
|
462
|
+
it("matches snapshot for simple breadcrumb", () => {
|
|
463
|
+
const { container } = render(
|
|
464
|
+
<Breadcrumb currentRoute="/products/shirts" />
|
|
465
|
+
);
|
|
466
|
+
expect(container).toMatchSnapshot();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("matches snapshot with custom routes", () => {
|
|
470
|
+
const routes: CustomRoute[] = [
|
|
471
|
+
{ path: "products", name: "All Products", url: "/products" },
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
const { container } = render(
|
|
475
|
+
<Breadcrumb currentRoute="/products/shirts" routes={routes} />
|
|
476
|
+
);
|
|
477
|
+
expect(container).toMatchSnapshot();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("matches snapshot with truncation", () => {
|
|
481
|
+
const { container } = render(
|
|
482
|
+
<Breadcrumb
|
|
483
|
+
currentRoute="/products/verylongproductname"
|
|
484
|
+
truncateLength={10}
|
|
485
|
+
/>
|
|
486
|
+
);
|
|
487
|
+
expect(container).toMatchSnapshot();
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|