@fpkit/acss 3.1.1 → 3.2.0
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/libs/{chunk-2NRIP6RB.cjs → chunk-2C3YLBWP.cjs} +3 -3
- package/libs/{chunk-NWJDAHP6.cjs → chunk-2GJHKWEK.cjs} +3 -3
- package/libs/{chunk-FVROL3V5.js → chunk-2JCDEC32.js} +3 -3
- package/libs/{chunk-IRLFZ3OL.js → chunk-3XJC4XUG.js} +2 -2
- package/libs/{chunk-L6PRDL6F.cjs → chunk-5CJPTDK3.cjs} +3 -3
- package/libs/{chunk-E4OSROCA.cjs → chunk-5QSNJQVH.cjs} +3 -3
- package/libs/{chunk-O3JIHC5M.cjs → chunk-6BUJZ4DJ.cjs} +3 -3
- package/libs/{chunk-WXBFBWYF.cjs → chunk-AFINOD2L.cjs} +3 -3
- package/libs/{chunk-HRRHPLER.js → chunk-AWZLSWDO.js} +2 -2
- package/libs/chunk-DDSXKOUB.js +7 -0
- package/libs/chunk-DDSXKOUB.js.map +1 -0
- package/libs/{chunk-CWRNJA4P.js → chunk-DIJBIOFE.js} +3 -3
- package/libs/chunk-EJ6KYBFE.cjs +13 -0
- package/libs/chunk-EJ6KYBFE.cjs.map +1 -0
- package/libs/{chunk-GUJSMQ3V.cjs → chunk-EKJYOCLY.cjs} +3 -3
- package/libs/{chunk-X5RKCLDC.cjs → chunk-F64GE6RG.cjs} +4 -4
- package/libs/{chunk-5RAWNUVD.js → chunk-IBUTNPTQ.js} +2 -2
- package/libs/chunk-IWP4VJEP.cjs +18 -0
- package/libs/chunk-IWP4VJEP.cjs.map +1 -0
- package/libs/{chunk-ZFJ4U45S.js → chunk-KDMX3FAW.js} +2 -2
- package/libs/{chunk-DYFAUAB7.cjs → chunk-LXODKKA3.cjs} +4 -4
- package/libs/{chunk-MPTMPBFT.js → chunk-M7JLT62Q.js} +2 -2
- package/libs/{chunk-IQ76HGVP.js → chunk-MBWI67UT.js} +2 -2
- package/libs/{chunk-O5XAJ7BY.cjs → chunk-NCGVF2QS.cjs} +4 -4
- package/libs/{chunk-W2UIN7EV.cjs → chunk-NPWHQVYB.cjs} +3 -3
- package/libs/{chunk-43TK2ICH.js → chunk-PMWL5XZ4.js} +3 -3
- package/libs/{chunk-KVKQLRJG.js → chunk-TF3GQKOY.js} +2 -2
- package/libs/{chunk-IEB64SWY.js → chunk-U5VA34SU.js} +2 -2
- package/libs/chunk-UGMP72J2.js +8 -0
- package/libs/chunk-UGMP72J2.js.map +1 -0
- package/libs/{chunk-EE3ZWSBY.cjs → chunk-URBGDUFN.cjs} +6 -6
- package/libs/{chunk-TPIB3RQP.js → chunk-ZF6Y7W57.js} +5 -5
- package/libs/component-props-50e69975.d.ts +66 -0
- package/libs/components/box/box.css +1 -0
- package/libs/components/box/box.css.map +1 -0
- package/libs/components/box/box.min.css +3 -0
- package/libs/components/breadcrumbs/breadcrumb.cjs +6 -6
- package/libs/components/breadcrumbs/breadcrumb.js +3 -3
- package/libs/components/button.cjs +4 -4
- package/libs/components/button.d.cts +10 -3
- package/libs/components/button.d.ts +10 -3
- package/libs/components/button.js +2 -2
- package/libs/components/card.cjs +7 -7
- package/libs/components/card.d.cts +13 -85
- package/libs/components/card.d.ts +13 -85
- package/libs/components/card.js +2 -2
- 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/cluster/cluster.css +1 -0
- package/libs/components/cluster/cluster.css.map +1 -0
- package/libs/components/cluster/cluster.min.css +3 -0
- package/libs/components/dialog/dialog.cjs +7 -7
- package/libs/components/dialog/dialog.js +5 -5
- package/libs/components/form/fields.cjs +4 -4
- package/libs/components/form/fields.js +2 -2
- package/libs/components/form/textarea.cjs +4 -4
- package/libs/components/form/textarea.js +2 -2
- package/libs/components/grid/grid.css +1 -0
- package/libs/components/grid/grid.css.map +1 -0
- package/libs/components/grid/grid.min.css +3 -0
- package/libs/components/heading/heading.cjs +3 -3
- package/libs/components/heading/heading.js +2 -2
- package/libs/components/icons/icon.cjs +4 -4
- package/libs/components/icons/icon.d.cts +2 -2
- package/libs/components/icons/icon.d.ts +2 -2
- package/libs/components/icons/icon.js +2 -2
- package/libs/components/link/link.cjs +6 -6
- package/libs/components/link/link.js +2 -2
- package/libs/components/list/list.cjs +5 -5
- package/libs/components/list/list.js +2 -2
- package/libs/components/modal.cjs +4 -4
- package/libs/components/modal.d.cts +1 -1
- package/libs/components/modal.d.ts +1 -1
- package/libs/components/modal.js +3 -3
- package/libs/components/nav/nav.cjs +7 -7
- package/libs/components/nav/nav.js +3 -3
- package/libs/components/stack/stack.css +1 -0
- package/libs/components/stack/stack.css.map +1 -0
- package/libs/components/stack/stack.min.css +3 -0
- package/libs/components/tables/table.d.cts +1 -1
- package/libs/components/tables/table.d.ts +1 -1
- package/libs/components/text/text.cjs +5 -5
- package/libs/components/text/text.js +2 -2
- package/libs/hooks.cjs +4 -4
- package/libs/hooks.js +3 -3
- package/libs/{icons-287fce3a.d.ts → icons-df8e744f.d.ts} +1 -1
- package/libs/icons.cjs +3 -3
- package/libs/icons.d.cts +2 -2
- package/libs/icons.d.ts +2 -2
- package/libs/icons.js +2 -2
- package/libs/index.cjs +64 -63
- 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 +923 -4
- package/libs/index.d.ts +923 -4
- package/libs/index.js +28 -28
- package/libs/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/alert/STYLES.mdx +790 -0
- package/src/components/badge/STYLES.mdx +610 -0
- package/src/components/box/README.mdx +401 -0
- package/src/components/box/STYLES.mdx +360 -0
- package/src/components/box/box.scss +245 -0
- package/src/components/box/box.stories.tsx +395 -0
- package/src/components/box/box.test.tsx +425 -0
- package/src/components/box/box.tsx +170 -0
- package/src/components/box/box.types.ts +166 -0
- package/src/components/breadcrumbs/STYLES.mdx +99 -0
- package/src/components/buttons/STYLES.mdx +766 -0
- package/src/components/cards/STYLES.mdx +835 -0
- package/src/components/cards/card.scss +29 -21
- package/src/components/cards/card.tsx +13 -3
- package/src/components/cards/card.types.ts +13 -0
- package/src/components/cluster/README.mdx +595 -0
- package/src/components/cluster/STYLES.mdx +626 -0
- package/src/components/cluster/cluster.scss +86 -0
- package/src/components/cluster/cluster.stories.tsx +385 -0
- package/src/components/cluster/cluster.test.tsx +655 -0
- package/src/components/cluster/cluster.tsx +94 -0
- package/src/components/cluster/cluster.types.ts +75 -0
- package/src/components/details/STYLES.mdx +445 -0
- package/src/components/dialog/STYLES.mdx +888 -0
- package/src/components/flexbox/STYLES.mdx +1 -1
- package/src/components/form/STYLES.mdx +821 -0
- package/src/components/grid/README.mdx +709 -0
- package/src/components/grid/STYLES.mdx +785 -0
- package/src/components/grid/grid.scss +287 -0
- package/src/components/grid/grid.stories.tsx +486 -0
- package/src/components/grid/grid.test.tsx +981 -0
- package/src/components/grid/grid.tsx +222 -0
- package/src/components/grid/grid.types.ts +344 -0
- package/src/components/icons/STYLES.mdx +56 -0
- package/src/components/images/STYLES.mdx +75 -0
- package/src/components/layout/STYLES.mdx +556 -0
- package/src/components/link/STYLES.mdx +75 -0
- package/src/components/list/STYLES.mdx +631 -0
- package/src/components/nav/STYLES.mdx +460 -0
- package/src/components/progress/STYLES.mdx +64 -0
- package/src/components/stack/README.mdx +400 -0
- package/src/components/stack/STYLES.mdx +414 -0
- package/src/components/stack/stack.scss +109 -0
- package/src/components/stack/stack.stories.tsx +559 -0
- package/src/components/stack/stack.test.tsx +426 -0
- package/src/components/stack/stack.tsx +141 -0
- package/src/components/stack/stack.types.ts +133 -0
- package/src/components/tag/STYLES.mdx +105 -0
- package/src/components/text-to-speech/STYLES.mdx +80 -0
- package/src/components/ui.tsx +3 -3
- package/src/index.scss +5 -1
- package/src/index.ts +305 -12
- package/src/sass/GLOBALS-STYLES.md +631 -0
- package/src/sass/_globals.scss +45 -24
- package/src/styles/box/box.css +220 -0
- package/src/styles/box/box.css.map +1 -0
- package/src/styles/cards/card.css +22 -17
- package/src/styles/cards/card.css.map +1 -1
- package/src/styles/cluster/cluster.css +71 -0
- package/src/styles/cluster/cluster.css.map +1 -0
- package/src/styles/grid/grid.css +238 -0
- package/src/styles/grid/grid.css.map +1 -0
- package/src/styles/index.css +667 -49
- package/src/styles/index.css.map +1 -1
- package/src/styles/stack/stack.css +86 -0
- package/src/styles/stack/stack.css.map +1 -0
- package/src/types/component-props.ts +42 -14
- package/src/types/layout-primitives.ts +48 -0
- package/src/types/shared.ts +10 -26
- package/libs/chunk-ENTCUJ3A.cjs +0 -13
- package/libs/chunk-ENTCUJ3A.cjs.map +0 -1
- package/libs/chunk-HHLNOC5T.js +0 -7
- package/libs/chunk-HHLNOC5T.js.map +0 -1
- package/libs/chunk-KK47SYZI.js +0 -8
- package/libs/chunk-KK47SYZI.js.map +0 -1
- package/libs/chunk-W5TKWBFC.cjs +0 -18
- package/libs/chunk-W5TKWBFC.cjs.map +0 -1
- package/libs/component-props-67d978a2.d.ts +0 -38
- /package/libs/{chunk-2NRIP6RB.cjs.map → chunk-2C3YLBWP.cjs.map} +0 -0
- /package/libs/{chunk-NWJDAHP6.cjs.map → chunk-2GJHKWEK.cjs.map} +0 -0
- /package/libs/{chunk-FVROL3V5.js.map → chunk-2JCDEC32.js.map} +0 -0
- /package/libs/{chunk-IRLFZ3OL.js.map → chunk-3XJC4XUG.js.map} +0 -0
- /package/libs/{chunk-L6PRDL6F.cjs.map → chunk-5CJPTDK3.cjs.map} +0 -0
- /package/libs/{chunk-E4OSROCA.cjs.map → chunk-5QSNJQVH.cjs.map} +0 -0
- /package/libs/{chunk-O3JIHC5M.cjs.map → chunk-6BUJZ4DJ.cjs.map} +0 -0
- /package/libs/{chunk-WXBFBWYF.cjs.map → chunk-AFINOD2L.cjs.map} +0 -0
- /package/libs/{chunk-HRRHPLER.js.map → chunk-AWZLSWDO.js.map} +0 -0
- /package/libs/{chunk-CWRNJA4P.js.map → chunk-DIJBIOFE.js.map} +0 -0
- /package/libs/{chunk-GUJSMQ3V.cjs.map → chunk-EKJYOCLY.cjs.map} +0 -0
- /package/libs/{chunk-X5RKCLDC.cjs.map → chunk-F64GE6RG.cjs.map} +0 -0
- /package/libs/{chunk-5RAWNUVD.js.map → chunk-IBUTNPTQ.js.map} +0 -0
- /package/libs/{chunk-ZFJ4U45S.js.map → chunk-KDMX3FAW.js.map} +0 -0
- /package/libs/{chunk-DYFAUAB7.cjs.map → chunk-LXODKKA3.cjs.map} +0 -0
- /package/libs/{chunk-MPTMPBFT.js.map → chunk-M7JLT62Q.js.map} +0 -0
- /package/libs/{chunk-IQ76HGVP.js.map → chunk-MBWI67UT.js.map} +0 -0
- /package/libs/{chunk-O5XAJ7BY.cjs.map → chunk-NCGVF2QS.cjs.map} +0 -0
- /package/libs/{chunk-W2UIN7EV.cjs.map → chunk-NPWHQVYB.cjs.map} +0 -0
- /package/libs/{chunk-43TK2ICH.js.map → chunk-PMWL5XZ4.js.map} +0 -0
- /package/libs/{chunk-KVKQLRJG.js.map → chunk-TF3GQKOY.js.map} +0 -0
- /package/libs/{chunk-IEB64SWY.js.map → chunk-U5VA34SU.js.map} +0 -0
- /package/libs/{chunk-EE3ZWSBY.cjs.map → chunk-URBGDUFN.cjs.map} +0 -0
- /package/libs/{chunk-TPIB3RQP.js.map → chunk-ZF6Y7W57.js.map} +0 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import { Cluster } from "./cluster";
|
|
5
|
+
import type { ClusterProps } from "./cluster.types";
|
|
6
|
+
|
|
7
|
+
describe("Cluster Component", () => {
|
|
8
|
+
describe("Rendering", () => {
|
|
9
|
+
it("should render with default props", () => {
|
|
10
|
+
render(<Cluster data-testid="cluster">Content</Cluster>);
|
|
11
|
+
const cluster = screen.getByTestId("cluster");
|
|
12
|
+
expect(cluster).toBeInTheDocument();
|
|
13
|
+
expect(cluster.tagName).toBe("DIV");
|
|
14
|
+
expect(cluster).toHaveClass("cluster");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should render children correctly", () => {
|
|
18
|
+
render(
|
|
19
|
+
<Cluster>
|
|
20
|
+
<span>Tag 1</span>
|
|
21
|
+
<span>Tag 2</span>
|
|
22
|
+
<span>Tag 3</span>
|
|
23
|
+
</Cluster>
|
|
24
|
+
);
|
|
25
|
+
expect(screen.getByText("Tag 1")).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText("Tag 2")).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText("Tag 3")).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should render with text content", () => {
|
|
31
|
+
render(<Cluster>Plain text content</Cluster>);
|
|
32
|
+
expect(screen.getByText("Plain text content")).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should render with multiple children types", () => {
|
|
36
|
+
render(
|
|
37
|
+
<Cluster>
|
|
38
|
+
<span>Span</span>
|
|
39
|
+
<button type="button">Button</button>
|
|
40
|
+
<a href="#test">Link</a>
|
|
41
|
+
</Cluster>
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByText("Span")).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByText("Button")).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText("Link")).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("Polymorphic Rendering (as prop)", () => {
|
|
50
|
+
it("should render as div by default", () => {
|
|
51
|
+
render(<Cluster data-testid="cluster">Content</Cluster>);
|
|
52
|
+
expect(screen.getByTestId("cluster").tagName).toBe("DIV");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should render as ul when as='ul'", () => {
|
|
56
|
+
render(
|
|
57
|
+
<Cluster as="ul" data-testid="cluster">
|
|
58
|
+
<li>Item 1</li>
|
|
59
|
+
<li>Item 2</li>
|
|
60
|
+
</Cluster>
|
|
61
|
+
);
|
|
62
|
+
expect(screen.getByTestId("cluster").tagName).toBe("UL");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should render as ol when as='ol'", () => {
|
|
66
|
+
render(
|
|
67
|
+
<Cluster as="ol" data-testid="cluster">
|
|
68
|
+
<li>Item 1</li>
|
|
69
|
+
<li>Item 2</li>
|
|
70
|
+
</Cluster>
|
|
71
|
+
);
|
|
72
|
+
expect(screen.getByTestId("cluster").tagName).toBe("OL");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should render as nav when as='nav'", () => {
|
|
76
|
+
render(
|
|
77
|
+
<Cluster as="nav" data-testid="cluster">
|
|
78
|
+
<a href="#1">Link 1</a>
|
|
79
|
+
<a href="#2">Link 2</a>
|
|
80
|
+
</Cluster>
|
|
81
|
+
);
|
|
82
|
+
expect(screen.getByTestId("cluster").tagName).toBe("NAV");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should render as section when as='section'", () => {
|
|
86
|
+
render(
|
|
87
|
+
<Cluster as="section" data-testid="cluster">
|
|
88
|
+
Content
|
|
89
|
+
</Cluster>
|
|
90
|
+
);
|
|
91
|
+
expect(screen.getByTestId("cluster").tagName).toBe("SECTION");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("Gap Prop", () => {
|
|
96
|
+
it("should apply gap='0' class", () => {
|
|
97
|
+
render(
|
|
98
|
+
<Cluster gap="0" data-testid="cluster">
|
|
99
|
+
Content
|
|
100
|
+
</Cluster>
|
|
101
|
+
);
|
|
102
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-0");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should apply gap='xs' class", () => {
|
|
106
|
+
render(
|
|
107
|
+
<Cluster gap="xs" data-testid="cluster">
|
|
108
|
+
Content
|
|
109
|
+
</Cluster>
|
|
110
|
+
);
|
|
111
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xs");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should apply gap='sm' class", () => {
|
|
115
|
+
render(
|
|
116
|
+
<Cluster gap="sm" data-testid="cluster">
|
|
117
|
+
Content
|
|
118
|
+
</Cluster>
|
|
119
|
+
);
|
|
120
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-sm");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should apply gap='md' class", () => {
|
|
124
|
+
render(
|
|
125
|
+
<Cluster gap="md" data-testid="cluster">
|
|
126
|
+
Content
|
|
127
|
+
</Cluster>
|
|
128
|
+
);
|
|
129
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-md");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should apply gap='lg' class", () => {
|
|
133
|
+
render(
|
|
134
|
+
<Cluster gap="lg" data-testid="cluster">
|
|
135
|
+
Content
|
|
136
|
+
</Cluster>
|
|
137
|
+
);
|
|
138
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-lg");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should apply gap='xl' class", () => {
|
|
142
|
+
render(
|
|
143
|
+
<Cluster gap="xl" data-testid="cluster">
|
|
144
|
+
Content
|
|
145
|
+
</Cluster>
|
|
146
|
+
);
|
|
147
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xl");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should work without gap prop", () => {
|
|
151
|
+
render(<Cluster data-testid="cluster">Content</Cluster>);
|
|
152
|
+
const cluster = screen.getByTestId("cluster");
|
|
153
|
+
expect(cluster).toHaveClass("cluster");
|
|
154
|
+
expect(cluster).not.toHaveClass("cluster-gap-0");
|
|
155
|
+
expect(cluster).not.toHaveClass("cluster-gap-xs");
|
|
156
|
+
expect(cluster).not.toHaveClass("cluster-gap-sm");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("Justify Prop (Horizontal Alignment)", () => {
|
|
161
|
+
it("should apply justify='start' class", () => {
|
|
162
|
+
render(
|
|
163
|
+
<Cluster justify="start" data-testid="cluster">
|
|
164
|
+
Content
|
|
165
|
+
</Cluster>
|
|
166
|
+
);
|
|
167
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-start");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should apply justify='center' class", () => {
|
|
171
|
+
render(
|
|
172
|
+
<Cluster justify="center" data-testid="cluster">
|
|
173
|
+
Content
|
|
174
|
+
</Cluster>
|
|
175
|
+
);
|
|
176
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-center");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should apply justify='end' class", () => {
|
|
180
|
+
render(
|
|
181
|
+
<Cluster justify="end" data-testid="cluster">
|
|
182
|
+
Content
|
|
183
|
+
</Cluster>
|
|
184
|
+
);
|
|
185
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-end");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should apply justify='between' class", () => {
|
|
189
|
+
render(
|
|
190
|
+
<Cluster justify="between" data-testid="cluster">
|
|
191
|
+
Content
|
|
192
|
+
</Cluster>
|
|
193
|
+
);
|
|
194
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-justify-between");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should work without justify prop", () => {
|
|
198
|
+
render(<Cluster data-testid="cluster">Content</Cluster>);
|
|
199
|
+
const cluster = screen.getByTestId("cluster");
|
|
200
|
+
expect(cluster).not.toHaveClass("cluster-justify-start");
|
|
201
|
+
expect(cluster).not.toHaveClass("cluster-justify-center");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("Align Prop (Vertical Alignment)", () => {
|
|
206
|
+
it("should apply align='start' class", () => {
|
|
207
|
+
render(
|
|
208
|
+
<Cluster align="start" data-testid="cluster">
|
|
209
|
+
Content
|
|
210
|
+
</Cluster>
|
|
211
|
+
);
|
|
212
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-start");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should apply align='center' class", () => {
|
|
216
|
+
render(
|
|
217
|
+
<Cluster align="center" data-testid="cluster">
|
|
218
|
+
Content
|
|
219
|
+
</Cluster>
|
|
220
|
+
);
|
|
221
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-center");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should apply align='end' class", () => {
|
|
225
|
+
render(
|
|
226
|
+
<Cluster align="end" data-testid="cluster">
|
|
227
|
+
Content
|
|
228
|
+
</Cluster>
|
|
229
|
+
);
|
|
230
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-end");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should apply align='baseline' class", () => {
|
|
234
|
+
render(
|
|
235
|
+
<Cluster align="baseline" data-testid="cluster">
|
|
236
|
+
Content
|
|
237
|
+
</Cluster>
|
|
238
|
+
);
|
|
239
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-align-baseline");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should work without align prop", () => {
|
|
243
|
+
render(<Cluster data-testid="cluster">Content</Cluster>);
|
|
244
|
+
const cluster = screen.getByTestId("cluster");
|
|
245
|
+
expect(cluster).not.toHaveClass("cluster-align-start");
|
|
246
|
+
expect(cluster).not.toHaveClass("cluster-align-center");
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("Combined Props", () => {
|
|
251
|
+
it("should apply gap and justify together", () => {
|
|
252
|
+
render(
|
|
253
|
+
<Cluster gap="md" justify="center" data-testid="cluster">
|
|
254
|
+
Content
|
|
255
|
+
</Cluster>
|
|
256
|
+
);
|
|
257
|
+
const cluster = screen.getByTestId("cluster");
|
|
258
|
+
expect(cluster).toHaveClass("cluster");
|
|
259
|
+
expect(cluster).toHaveClass("cluster-gap-md");
|
|
260
|
+
expect(cluster).toHaveClass("cluster-justify-center");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should apply gap and align together", () => {
|
|
264
|
+
render(
|
|
265
|
+
<Cluster gap="lg" align="baseline" data-testid="cluster">
|
|
266
|
+
Content
|
|
267
|
+
</Cluster>
|
|
268
|
+
);
|
|
269
|
+
const cluster = screen.getByTestId("cluster");
|
|
270
|
+
expect(cluster).toHaveClass("cluster");
|
|
271
|
+
expect(cluster).toHaveClass("cluster-gap-lg");
|
|
272
|
+
expect(cluster).toHaveClass("cluster-align-baseline");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should apply all props together", () => {
|
|
276
|
+
render(
|
|
277
|
+
<Cluster gap="sm" justify="between" align="center" data-testid="cluster">
|
|
278
|
+
Content
|
|
279
|
+
</Cluster>
|
|
280
|
+
);
|
|
281
|
+
const cluster = screen.getByTestId("cluster");
|
|
282
|
+
expect(cluster).toHaveClass("cluster");
|
|
283
|
+
expect(cluster).toHaveClass("cluster-gap-sm");
|
|
284
|
+
expect(cluster).toHaveClass("cluster-justify-between");
|
|
285
|
+
expect(cluster).toHaveClass("cluster-align-center");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should combine all props with polymorphic as prop", () => {
|
|
289
|
+
render(
|
|
290
|
+
<Cluster as="nav" gap="md" justify="center" align="baseline" data-testid="cluster">
|
|
291
|
+
<a href="#1">Link</a>
|
|
292
|
+
</Cluster>
|
|
293
|
+
);
|
|
294
|
+
const cluster = screen.getByTestId("cluster");
|
|
295
|
+
expect(cluster.tagName).toBe("NAV");
|
|
296
|
+
expect(cluster).toHaveClass("cluster");
|
|
297
|
+
expect(cluster).toHaveClass("cluster-gap-md");
|
|
298
|
+
expect(cluster).toHaveClass("cluster-justify-center");
|
|
299
|
+
expect(cluster).toHaveClass("cluster-align-baseline");
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe("ClassName and Classes Props", () => {
|
|
304
|
+
it("should merge className prop with utility classes", () => {
|
|
305
|
+
render(
|
|
306
|
+
<Cluster className="custom-class" data-testid="cluster">
|
|
307
|
+
Content
|
|
308
|
+
</Cluster>
|
|
309
|
+
);
|
|
310
|
+
const cluster = screen.getByTestId("cluster");
|
|
311
|
+
expect(cluster).toHaveClass("cluster");
|
|
312
|
+
expect(cluster).toHaveClass("custom-class");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should merge classes prop with utility classes", () => {
|
|
316
|
+
render(
|
|
317
|
+
<Cluster classes="another-class" data-testid="cluster">
|
|
318
|
+
Content
|
|
319
|
+
</Cluster>
|
|
320
|
+
);
|
|
321
|
+
const cluster = screen.getByTestId("cluster");
|
|
322
|
+
expect(cluster).toHaveClass("cluster");
|
|
323
|
+
expect(cluster).toHaveClass("another-class");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("should merge both className and classes", () => {
|
|
327
|
+
render(
|
|
328
|
+
<Cluster className="custom" classes="another" data-testid="cluster">
|
|
329
|
+
Content
|
|
330
|
+
</Cluster>
|
|
331
|
+
);
|
|
332
|
+
const cluster = screen.getByTestId("cluster");
|
|
333
|
+
expect(cluster).toHaveClass("cluster");
|
|
334
|
+
expect(cluster).toHaveClass("custom");
|
|
335
|
+
expect(cluster).toHaveClass("another");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("should merge custom classes with all utility classes", () => {
|
|
339
|
+
render(
|
|
340
|
+
<Cluster
|
|
341
|
+
gap="md"
|
|
342
|
+
justify="center"
|
|
343
|
+
align="baseline"
|
|
344
|
+
className="custom"
|
|
345
|
+
classes="utility"
|
|
346
|
+
data-testid="cluster"
|
|
347
|
+
>
|
|
348
|
+
Content
|
|
349
|
+
</Cluster>
|
|
350
|
+
);
|
|
351
|
+
const cluster = screen.getByTestId("cluster");
|
|
352
|
+
expect(cluster).toHaveClass("cluster");
|
|
353
|
+
expect(cluster).toHaveClass("cluster-gap-md");
|
|
354
|
+
expect(cluster).toHaveClass("cluster-justify-center");
|
|
355
|
+
expect(cluster).toHaveClass("cluster-align-baseline");
|
|
356
|
+
expect(cluster).toHaveClass("custom");
|
|
357
|
+
expect(cluster).toHaveClass("utility");
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe("Inline Styles", () => {
|
|
362
|
+
it("should apply inline styles via styles prop", () => {
|
|
363
|
+
render(
|
|
364
|
+
<Cluster styles={{ maxWidth: "500px" }} data-testid="cluster">
|
|
365
|
+
Content
|
|
366
|
+
</Cluster>
|
|
367
|
+
);
|
|
368
|
+
const cluster = screen.getByTestId("cluster");
|
|
369
|
+
expect(cluster).toHaveStyle({ maxWidth: "500px" });
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should apply inline styles via style prop", () => {
|
|
373
|
+
render(
|
|
374
|
+
<Cluster style={{ border: "1px solid red" }} data-testid="cluster">
|
|
375
|
+
Content
|
|
376
|
+
</Cluster>
|
|
377
|
+
);
|
|
378
|
+
const cluster = screen.getByTestId("cluster");
|
|
379
|
+
expect(cluster).toHaveStyle({ border: "1px solid red" });
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("should apply CSS custom properties", () => {
|
|
383
|
+
render(
|
|
384
|
+
<Cluster styles={{ "--custom-color": "blue" } as React.CSSProperties} data-testid="cluster">
|
|
385
|
+
Content
|
|
386
|
+
</Cluster>
|
|
387
|
+
);
|
|
388
|
+
const cluster = screen.getByTestId("cluster");
|
|
389
|
+
expect(cluster).toHaveStyle({ "--custom-color": "blue" });
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe("Ref Forwarding", () => {
|
|
394
|
+
it("should forward ref to the underlying element", () => {
|
|
395
|
+
const ref = { current: null as HTMLDivElement | null };
|
|
396
|
+
render(
|
|
397
|
+
<Cluster ref={ref} data-testid="cluster">
|
|
398
|
+
Content
|
|
399
|
+
</Cluster>
|
|
400
|
+
);
|
|
401
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
402
|
+
expect(ref.current).toBe(screen.getByTestId("cluster"));
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("should forward ref with polymorphic as prop", () => {
|
|
406
|
+
const ref = { current: null as HTMLElement | null };
|
|
407
|
+
render(
|
|
408
|
+
<Cluster as="nav" ref={ref} data-testid="cluster">
|
|
409
|
+
Content
|
|
410
|
+
</Cluster>
|
|
411
|
+
);
|
|
412
|
+
expect(ref.current).toBeInstanceOf(HTMLElement);
|
|
413
|
+
expect(ref.current?.tagName).toBe("NAV");
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
describe("ARIA and Accessibility Attributes", () => {
|
|
418
|
+
it("should accept aria-label", () => {
|
|
419
|
+
render(
|
|
420
|
+
<Cluster aria-label="Tag cloud" data-testid="cluster">
|
|
421
|
+
Content
|
|
422
|
+
</Cluster>
|
|
423
|
+
);
|
|
424
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("aria-label", "Tag cloud");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should accept aria-labelledby", () => {
|
|
428
|
+
render(
|
|
429
|
+
<>
|
|
430
|
+
<h2 id="cluster-title">Filter Tags</h2>
|
|
431
|
+
<Cluster aria-labelledby="cluster-title" data-testid="cluster">
|
|
432
|
+
<span>Tag 1</span>
|
|
433
|
+
</Cluster>
|
|
434
|
+
</>
|
|
435
|
+
);
|
|
436
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("aria-labelledby", "cluster-title");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should accept aria-describedby", () => {
|
|
440
|
+
render(
|
|
441
|
+
<>
|
|
442
|
+
<p id="cluster-desc">Select tags to filter results</p>
|
|
443
|
+
<Cluster aria-describedby="cluster-desc" data-testid="cluster">
|
|
444
|
+
<span>Tag 1</span>
|
|
445
|
+
</Cluster>
|
|
446
|
+
</>
|
|
447
|
+
);
|
|
448
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("aria-describedby", "cluster-desc");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("should accept role attribute", () => {
|
|
452
|
+
render(
|
|
453
|
+
<Cluster role="group" data-testid="cluster">
|
|
454
|
+
Content
|
|
455
|
+
</Cluster>
|
|
456
|
+
);
|
|
457
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("role", "group");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("should accept data attributes", () => {
|
|
461
|
+
render(
|
|
462
|
+
<Cluster data-testid="cluster" data-component="tag-cloud">
|
|
463
|
+
Content
|
|
464
|
+
</Cluster>
|
|
465
|
+
);
|
|
466
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("data-component", "tag-cloud");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("should accept id attribute", () => {
|
|
470
|
+
render(
|
|
471
|
+
<Cluster id="tags-cluster" data-testid="cluster">
|
|
472
|
+
Content
|
|
473
|
+
</Cluster>
|
|
474
|
+
);
|
|
475
|
+
expect(screen.getByTestId("cluster")).toHaveAttribute("id", "tags-cluster");
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe("Edge Cases", () => {
|
|
480
|
+
it("should handle empty children", () => {
|
|
481
|
+
render(<Cluster data-testid="cluster" />);
|
|
482
|
+
expect(screen.getByTestId("cluster")).toBeInTheDocument();
|
|
483
|
+
expect(screen.getByTestId("cluster")).toBeEmptyDOMElement();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("should handle null children", () => {
|
|
487
|
+
render(<Cluster data-testid="cluster">{null}</Cluster>);
|
|
488
|
+
expect(screen.getByTestId("cluster")).toBeInTheDocument();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should handle undefined children", () => {
|
|
492
|
+
render(<Cluster data-testid="cluster">{undefined}</Cluster>);
|
|
493
|
+
expect(screen.getByTestId("cluster")).toBeInTheDocument();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("should handle false children (conditional rendering)", () => {
|
|
497
|
+
const shouldShow = false;
|
|
498
|
+
render(<Cluster data-testid="cluster">{shouldShow && <span>Hidden</span>}</Cluster>);
|
|
499
|
+
expect(screen.getByTestId("cluster")).toBeInTheDocument();
|
|
500
|
+
expect(screen.queryByText("Hidden")).not.toBeInTheDocument();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should handle fragments", () => {
|
|
504
|
+
render(
|
|
505
|
+
<Cluster>
|
|
506
|
+
<>
|
|
507
|
+
<span>Item 1</span>
|
|
508
|
+
<span>Item 2</span>
|
|
509
|
+
</>
|
|
510
|
+
</Cluster>
|
|
511
|
+
);
|
|
512
|
+
expect(screen.getByText("Item 1")).toBeInTheDocument();
|
|
513
|
+
expect(screen.getByText("Item 2")).toBeInTheDocument();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("should handle mixed content types", () => {
|
|
517
|
+
render(
|
|
518
|
+
<Cluster>
|
|
519
|
+
Text node
|
|
520
|
+
<span>Element</span>
|
|
521
|
+
{null}
|
|
522
|
+
{undefined}
|
|
523
|
+
{false}
|
|
524
|
+
<button type="button">Button</button>
|
|
525
|
+
</Cluster>
|
|
526
|
+
);
|
|
527
|
+
expect(screen.getByText("Text node")).toBeInTheDocument();
|
|
528
|
+
expect(screen.getByText("Element")).toBeInTheDocument();
|
|
529
|
+
expect(screen.getByText("Button")).toBeInTheDocument();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe("Real-World Usage Patterns", () => {
|
|
534
|
+
it("should render tag cloud correctly", () => {
|
|
535
|
+
const tags = ["React", "TypeScript", "CSS", "Accessibility"];
|
|
536
|
+
render(
|
|
537
|
+
<Cluster gap="sm" justify="center" data-testid="cluster">
|
|
538
|
+
{tags.map((tag) => (
|
|
539
|
+
<span key={tag} className="tag">
|
|
540
|
+
{tag}
|
|
541
|
+
</span>
|
|
542
|
+
))}
|
|
543
|
+
</Cluster>
|
|
544
|
+
);
|
|
545
|
+
const cluster = screen.getByTestId("cluster");
|
|
546
|
+
expect(cluster).toHaveClass("cluster-gap-sm");
|
|
547
|
+
expect(cluster).toHaveClass("cluster-justify-center");
|
|
548
|
+
tags.forEach((tag) => {
|
|
549
|
+
expect(screen.getByText(tag)).toBeInTheDocument();
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("should render button group correctly", () => {
|
|
554
|
+
render(
|
|
555
|
+
<Cluster gap="md" data-testid="cluster">
|
|
556
|
+
<button type="button">Action 1</button>
|
|
557
|
+
<button type="button">Action 2</button>
|
|
558
|
+
<button type="button">Action 3</button>
|
|
559
|
+
</Cluster>
|
|
560
|
+
);
|
|
561
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-md");
|
|
562
|
+
expect(screen.getByText("Action 1")).toBeInTheDocument();
|
|
563
|
+
expect(screen.getByText("Action 2")).toBeInTheDocument();
|
|
564
|
+
expect(screen.getByText("Action 3")).toBeInTheDocument();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it("should render navigation links with baseline alignment", () => {
|
|
568
|
+
render(
|
|
569
|
+
<Cluster as="nav" gap="lg" align="baseline" justify="center" data-testid="cluster">
|
|
570
|
+
<a href="#home">Home</a>
|
|
571
|
+
<a href="#about">About</a>
|
|
572
|
+
<a href="#contact">Contact</a>
|
|
573
|
+
</Cluster>
|
|
574
|
+
);
|
|
575
|
+
const cluster = screen.getByTestId("cluster");
|
|
576
|
+
expect(cluster.tagName).toBe("NAV");
|
|
577
|
+
expect(cluster).toHaveClass("cluster-gap-lg");
|
|
578
|
+
expect(cluster).toHaveClass("cluster-align-baseline");
|
|
579
|
+
expect(cluster).toHaveClass("cluster-justify-center");
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should render filter pills with semantic list", () => {
|
|
583
|
+
render(
|
|
584
|
+
<Cluster as="ul" gap="sm" styles={{ listStyle: "none" }} data-testid="cluster">
|
|
585
|
+
<li>
|
|
586
|
+
<button type="button">All</button>
|
|
587
|
+
</li>
|
|
588
|
+
<li>
|
|
589
|
+
<button type="button">Active</button>
|
|
590
|
+
</li>
|
|
591
|
+
<li>
|
|
592
|
+
<button type="button">Completed</button>
|
|
593
|
+
</li>
|
|
594
|
+
</Cluster>
|
|
595
|
+
);
|
|
596
|
+
const cluster = screen.getByTestId("cluster");
|
|
597
|
+
expect(cluster.tagName).toBe("UL");
|
|
598
|
+
expect(cluster).toHaveClass("cluster-gap-sm");
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("should render badge cluster with zero gap", () => {
|
|
602
|
+
render(
|
|
603
|
+
<Cluster gap="xs" data-testid="cluster">
|
|
604
|
+
<span className="badge">Active</span>
|
|
605
|
+
<span className="badge">New</span>
|
|
606
|
+
<span className="badge">Beta</span>
|
|
607
|
+
</Cluster>
|
|
608
|
+
);
|
|
609
|
+
expect(screen.getByTestId("cluster")).toHaveClass("cluster-gap-xs");
|
|
610
|
+
expect(screen.getByText("Active")).toBeInTheDocument();
|
|
611
|
+
expect(screen.getByText("New")).toBeInTheDocument();
|
|
612
|
+
expect(screen.getByText("Beta")).toBeInTheDocument();
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
describe("TypeScript Type Safety", () => {
|
|
617
|
+
it("should accept valid ClusterProps", () => {
|
|
618
|
+
const props: ClusterProps = {
|
|
619
|
+
gap: "md",
|
|
620
|
+
justify: "center",
|
|
621
|
+
align: "baseline",
|
|
622
|
+
as: "nav",
|
|
623
|
+
className: "custom",
|
|
624
|
+
children: <span>Content</span>,
|
|
625
|
+
};
|
|
626
|
+
render(<Cluster {...props} data-testid="cluster" />);
|
|
627
|
+
expect(screen.getByTestId("cluster")).toBeInTheDocument();
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it("should accept all SpacingScale values", () => {
|
|
631
|
+
const spacingScales: ClusterProps["gap"][] = ["0", "xs", "sm", "md", "lg", "xl"];
|
|
632
|
+
spacingScales.forEach((gap, index) => {
|
|
633
|
+
render(
|
|
634
|
+
<Cluster gap={gap} data-testid={`cluster-${index}`}>
|
|
635
|
+
Content
|
|
636
|
+
</Cluster>
|
|
637
|
+
);
|
|
638
|
+
expect(screen.getByTestId(`cluster-${index}`)).toHaveClass(`cluster-gap-${gap}`);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it("should accept all ClusterElement values", () => {
|
|
643
|
+
const elements: ClusterProps["as"][] = ["div", "ul", "ol", "nav", "section"];
|
|
644
|
+
elements.forEach((element, index) => {
|
|
645
|
+
if (!element) return;
|
|
646
|
+
render(
|
|
647
|
+
<Cluster as={element} data-testid={`cluster-${index}`}>
|
|
648
|
+
Content
|
|
649
|
+
</Cluster>
|
|
650
|
+
);
|
|
651
|
+
expect(screen.getByTestId(`cluster-${index}`).tagName).toBe(element.toUpperCase());
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
});
|