@hilum/ui 0.1.1 → 0.1.3
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 +23 -4
- package/dist/chunk-J2QQDUF4.js +204 -0
- package/dist/chunk-J2QQDUF4.js.map +1 -0
- package/dist/create-theme.d.ts +27 -0
- package/dist/create-theme.js +172 -0
- package/dist/create-theme.js.map +1 -0
- package/dist/index.d.ts +21 -8
- package/dist/index.js +393 -381
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +59 -50
- package/dist/tokens.d.ts +46 -38
- package/dist/tokens.js +1 -191
- package/dist/tokens.js.map +1 -1
- package/llms.txt +1514 -0
- package/package.json +11 -7
- package/src/components/accordion.tsx +5 -5
- package/src/components/action-panel.tsx +6 -6
- package/src/components/activity-feed.tsx +4 -4
- package/src/components/alert-dialog.tsx +3 -3
- package/src/components/alert.tsx +4 -4
- package/src/components/avatar-stack.tsx +2 -2
- package/src/components/avatar.tsx +2 -2
- package/src/components/badge.tsx +6 -5
- package/src/components/breadcrumb.tsx +5 -5
- package/src/components/button-group.tsx +4 -4
- package/src/components/button.tsx +6 -5
- package/src/components/calendar.tsx +7 -7
- package/src/components/card-heading.tsx +3 -3
- package/src/components/card.tsx +3 -3
- package/src/components/chart.tsx +11 -11
- package/src/components/checkbox.tsx +1 -1
- package/src/components/color-input.tsx +7 -7
- package/src/components/color-picker.tsx +5 -5
- package/src/components/combobox.tsx +10 -10
- package/src/components/command-palette.tsx +11 -11
- package/src/components/command.tsx +13 -13
- package/src/components/context-menu.tsx +15 -16
- package/src/components/data-table.tsx +6 -6
- package/src/components/date-picker.tsx +9 -4
- package/src/components/description-list.tsx +8 -8
- package/src/components/dialog.tsx +3 -3
- package/src/components/drawer.tsx +5 -5
- package/src/components/dropdown-menu.tsx +15 -16
- package/src/components/empty-state.tsx +3 -3
- package/src/components/field.tsx +13 -6
- package/src/components/grid-list.tsx +12 -12
- package/src/components/hover-card.tsx +1 -1
- package/src/components/input-group.tsx +8 -8
- package/src/components/input-number.tsx +7 -7
- package/src/components/input-otp.tsx +3 -3
- package/src/components/input.tsx +5 -4
- package/src/components/kbd.tsx +1 -1
- package/src/components/label.tsx +1 -1
- package/src/components/menubar.tsx +21 -22
- package/src/components/native-select.tsx +4 -4
- package/src/components/navigation-menu.tsx +10 -10
- package/src/components/notification.tsx +10 -10
- package/src/components/page-heading.tsx +8 -8
- package/src/components/pagination.tsx +1 -1
- package/src/components/progress.tsx +1 -1
- package/src/components/property-row.tsx +1 -1
- package/src/components/radio-card.tsx +6 -6
- package/src/components/radio-group.tsx +3 -3
- package/src/components/resizable.tsx +3 -3
- package/src/components/scroll-area.tsx +1 -1
- package/src/components/section-heading.tsx +3 -3
- package/src/components/select.tsx +11 -11
- package/src/components/separator.tsx +1 -1
- package/src/components/sheet.tsx +3 -3
- package/src/components/sidebar.tsx +19 -19
- package/src/components/skeleton.tsx +1 -1
- package/src/components/slider.tsx +1 -1
- package/src/components/sonner.tsx +5 -5
- package/src/components/stacked-list.tsx +4 -4
- package/src/components/stat-card.tsx +6 -6
- package/src/components/steps.tsx +13 -12
- package/src/components/switch.tsx +1 -1
- package/src/components/table.tsx +9 -9
- package/src/components/tabs.tsx +2 -2
- package/src/components/textarea.tsx +4 -4
- package/src/components/toggle.tsx +8 -8
- package/src/components/tooltip.tsx +1 -1
- package/src/index.ts +3 -2
- package/src/tokens/tokens.ts +54 -40
- package/src/components/__tests__/data.test.tsx +0 -180
- package/src/components/__tests__/disclosure.test.tsx +0 -198
- package/src/components/__tests__/display.test.tsx +0 -262
- package/src/components/__tests__/feedback.test.tsx +0 -138
- package/src/components/__tests__/form.test.tsx +0 -297
- package/src/components/__tests__/layout.test.tsx +0 -388
- package/src/components/__tests__/overlay.test.tsx +0 -90
- package/src/components/__tests__/primitives.test.tsx +0 -248
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import { userEvent } from "@testing-library/user-event";
|
|
4
|
-
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "../accordion";
|
|
5
|
-
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../tabs";
|
|
6
|
-
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "../collapsible";
|
|
7
|
-
import { Toggle } from "../toggle";
|
|
8
|
-
import { ToggleGroup, ToggleGroupItem } from "../toggle-group";
|
|
9
|
-
|
|
10
|
-
/* ------------------------------------------------------------------ */
|
|
11
|
-
/* Accordion */
|
|
12
|
-
/* ------------------------------------------------------------------ */
|
|
13
|
-
|
|
14
|
-
describe("Accordion", () => {
|
|
15
|
-
function TestAccordion() {
|
|
16
|
-
return (
|
|
17
|
-
<Accordion type="single" collapsible>
|
|
18
|
-
<AccordionItem value="a">
|
|
19
|
-
<AccordionTrigger>Section A</AccordionTrigger>
|
|
20
|
-
<AccordionContent>Content A</AccordionContent>
|
|
21
|
-
</AccordionItem>
|
|
22
|
-
<AccordionItem value="b">
|
|
23
|
-
<AccordionTrigger>Section B</AccordionTrigger>
|
|
24
|
-
<AccordionContent>Content B</AccordionContent>
|
|
25
|
-
</AccordionItem>
|
|
26
|
-
</Accordion>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
it("renders all trigger labels", () => {
|
|
31
|
-
render(<TestAccordion />);
|
|
32
|
-
expect(screen.getByText("Section A")).toBeInTheDocument();
|
|
33
|
-
expect(screen.getByText("Section B")).toBeInTheDocument();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("expands item on trigger click", async () => {
|
|
37
|
-
const user = userEvent.setup();
|
|
38
|
-
render(<TestAccordion />);
|
|
39
|
-
const triggerA = screen.getByText("Section A");
|
|
40
|
-
await user.click(triggerA);
|
|
41
|
-
expect(screen.getByText("Content A")).toBeVisible();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("renders multiple type without error", () => {
|
|
45
|
-
render(
|
|
46
|
-
<Accordion type="multiple">
|
|
47
|
-
<AccordionItem value="x">
|
|
48
|
-
<AccordionTrigger>X</AccordionTrigger>
|
|
49
|
-
<AccordionContent>X content</AccordionContent>
|
|
50
|
-
</AccordionItem>
|
|
51
|
-
</Accordion>,
|
|
52
|
-
);
|
|
53
|
-
expect(screen.getByText("X")).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
/* ------------------------------------------------------------------ */
|
|
58
|
-
/* Tabs */
|
|
59
|
-
/* ------------------------------------------------------------------ */
|
|
60
|
-
|
|
61
|
-
describe("Tabs", () => {
|
|
62
|
-
function TestTabs() {
|
|
63
|
-
return (
|
|
64
|
-
<Tabs defaultValue="tab1">
|
|
65
|
-
<TabsList>
|
|
66
|
-
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
|
|
67
|
-
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
|
|
68
|
-
</TabsList>
|
|
69
|
-
<TabsContent value="tab1">Panel 1</TabsContent>
|
|
70
|
-
<TabsContent value="tab2">Panel 2</TabsContent>
|
|
71
|
-
</Tabs>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
it("renders tab triggers", () => {
|
|
76
|
-
render(<TestTabs />);
|
|
77
|
-
expect(screen.getByRole("tab", { name: "Tab 1" })).toBeInTheDocument();
|
|
78
|
-
expect(screen.getByRole("tab", { name: "Tab 2" })).toBeInTheDocument();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("shows the default tab panel", () => {
|
|
82
|
-
render(<TestTabs />);
|
|
83
|
-
expect(screen.getByText("Panel 1")).toBeInTheDocument();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("switches to second tab on click", async () => {
|
|
87
|
-
const user = userEvent.setup();
|
|
88
|
-
render(<TestTabs />);
|
|
89
|
-
await user.click(screen.getByRole("tab", { name: "Tab 2" }));
|
|
90
|
-
expect(screen.getByText("Panel 2")).toBeInTheDocument();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
/* ------------------------------------------------------------------ */
|
|
95
|
-
/* Collapsible */
|
|
96
|
-
/* ------------------------------------------------------------------ */
|
|
97
|
-
|
|
98
|
-
describe("Collapsible", () => {
|
|
99
|
-
it("renders trigger and content", () => {
|
|
100
|
-
render(
|
|
101
|
-
<Collapsible>
|
|
102
|
-
<CollapsibleTrigger>Toggle</CollapsibleTrigger>
|
|
103
|
-
<CollapsibleContent>Hidden content</CollapsibleContent>
|
|
104
|
-
</Collapsible>,
|
|
105
|
-
);
|
|
106
|
-
expect(screen.getByText("Toggle")).toBeInTheDocument();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("renders as open by default when defaultOpen is set", () => {
|
|
110
|
-
render(
|
|
111
|
-
<Collapsible defaultOpen>
|
|
112
|
-
<CollapsibleTrigger>Show</CollapsibleTrigger>
|
|
113
|
-
<CollapsibleContent>Visible</CollapsibleContent>
|
|
114
|
-
</Collapsible>,
|
|
115
|
-
);
|
|
116
|
-
expect(screen.getByText("Visible")).toBeInTheDocument();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
/* ------------------------------------------------------------------ */
|
|
121
|
-
/* Toggle */
|
|
122
|
-
/* ------------------------------------------------------------------ */
|
|
123
|
-
|
|
124
|
-
describe("Toggle", () => {
|
|
125
|
-
it("renders as a button", () => {
|
|
126
|
-
render(<Toggle>Bold</Toggle>);
|
|
127
|
-
expect(screen.getByRole("button", { name: "Bold" })).toBeInTheDocument();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("renders all variants without error", () => {
|
|
131
|
-
const variants = ["default", "outline", "brand"] as const;
|
|
132
|
-
for (const variant of variants) {
|
|
133
|
-
const { unmount } = render(<Toggle variant={variant}>V</Toggle>);
|
|
134
|
-
expect(screen.getByRole("button")).toBeInTheDocument();
|
|
135
|
-
unmount();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("renders all sizes without error", () => {
|
|
140
|
-
const sizes = ["sm", "default", "lg", "icon", "icon-sm", "icon-lg"] as const;
|
|
141
|
-
for (const size of sizes) {
|
|
142
|
-
const { unmount } = render(<Toggle size={size}>S</Toggle>);
|
|
143
|
-
expect(screen.getByRole("button")).toBeInTheDocument();
|
|
144
|
-
unmount();
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("toggles pressed state on click", async () => {
|
|
149
|
-
const user = userEvent.setup();
|
|
150
|
-
render(<Toggle>B</Toggle>);
|
|
151
|
-
const btn = screen.getByRole("button");
|
|
152
|
-
expect(btn).toHaveAttribute("data-state", "off");
|
|
153
|
-
await user.click(btn);
|
|
154
|
-
expect(btn).toHaveAttribute("data-state", "on");
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
/* ------------------------------------------------------------------ */
|
|
159
|
-
/* ToggleGroup */
|
|
160
|
-
/* ------------------------------------------------------------------ */
|
|
161
|
-
|
|
162
|
-
describe("ToggleGroup", () => {
|
|
163
|
-
// Radix ToggleGroup type="single" uses role="radio" per the ARIA toggle button pattern.
|
|
164
|
-
// type="multiple" uses role="button".
|
|
165
|
-
|
|
166
|
-
it("renders items (type=single → role=radio)", () => {
|
|
167
|
-
render(
|
|
168
|
-
<ToggleGroup type="single">
|
|
169
|
-
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
|
170
|
-
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
|
171
|
-
</ToggleGroup>,
|
|
172
|
-
);
|
|
173
|
-
expect(screen.getByText("Bold")).toBeInTheDocument();
|
|
174
|
-
expect(screen.getByText("Italic")).toBeInTheDocument();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("activates item on click (type=single)", async () => {
|
|
178
|
-
const user = userEvent.setup();
|
|
179
|
-
render(
|
|
180
|
-
<ToggleGroup type="single">
|
|
181
|
-
<ToggleGroupItem value="a">Format A</ToggleGroupItem>
|
|
182
|
-
</ToggleGroup>,
|
|
183
|
-
);
|
|
184
|
-
const item = screen.getByRole("radio", { name: "Format A" });
|
|
185
|
-
await user.click(item);
|
|
186
|
-
expect(item).toHaveAttribute("data-state", "on");
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("supports multiple selection (type=multiple → role=button)", () => {
|
|
190
|
-
render(
|
|
191
|
-
<ToggleGroup type="multiple">
|
|
192
|
-
<ToggleGroupItem value="x">X</ToggleGroupItem>
|
|
193
|
-
<ToggleGroupItem value="y">Y</ToggleGroupItem>
|
|
194
|
-
</ToggleGroup>,
|
|
195
|
-
);
|
|
196
|
-
expect(screen.getAllByRole("button")).toHaveLength(2);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import {
|
|
4
|
-
Card,
|
|
5
|
-
CardHeader,
|
|
6
|
-
CardTitle,
|
|
7
|
-
CardDescription,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardFooter,
|
|
10
|
-
CardMedia,
|
|
11
|
-
} from "../card";
|
|
12
|
-
import { Avatar, AvatarFallback, AvatarImage, AvatarWithStatus } from "../avatar";
|
|
13
|
-
import { AvatarStack } from "../avatar-stack";
|
|
14
|
-
import { StatCard } from "../stat-card";
|
|
15
|
-
import { Notification } from "../notification";
|
|
16
|
-
import { MediaObject } from "../media-object";
|
|
17
|
-
|
|
18
|
-
/* ------------------------------------------------------------------ */
|
|
19
|
-
/* Card */
|
|
20
|
-
/* ------------------------------------------------------------------ */
|
|
21
|
-
|
|
22
|
-
describe("Card", () => {
|
|
23
|
-
it("renders children", () => {
|
|
24
|
-
render(<Card>Content</Card>);
|
|
25
|
-
expect(screen.getByText("Content")).toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("carries data-slot attribute", () => {
|
|
29
|
-
const { container } = render(<Card>Content</Card>);
|
|
30
|
-
expect(container.firstChild).toHaveAttribute("data-slot", "card");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("renders all variants without error", () => {
|
|
34
|
-
const variants = ["default", "muted", "ghost"] as const;
|
|
35
|
-
for (const variant of variants) {
|
|
36
|
-
const { unmount } = render(<Card variant={variant}>Card</Card>);
|
|
37
|
-
expect(screen.getByText("Card")).toBeInTheDocument();
|
|
38
|
-
unmount();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("composes sub-components correctly", () => {
|
|
43
|
-
render(
|
|
44
|
-
<Card>
|
|
45
|
-
<CardHeader>
|
|
46
|
-
<CardTitle>Title</CardTitle>
|
|
47
|
-
<CardDescription>Description</CardDescription>
|
|
48
|
-
</CardHeader>
|
|
49
|
-
<CardContent>Body</CardContent>
|
|
50
|
-
<CardFooter>Footer</CardFooter>
|
|
51
|
-
</Card>,
|
|
52
|
-
);
|
|
53
|
-
expect(screen.getByText("Title")).toBeInTheDocument();
|
|
54
|
-
expect(screen.getByText("Description")).toBeInTheDocument();
|
|
55
|
-
expect(screen.getByText("Body")).toBeInTheDocument();
|
|
56
|
-
expect(screen.getByText("Footer")).toBeInTheDocument();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("renders CardMedia", () => {
|
|
60
|
-
const { container } = render(<CardMedia data-testid="media" />);
|
|
61
|
-
expect(container.querySelector('[data-slot="card-media"]')).toBeInTheDocument();
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/* ------------------------------------------------------------------ */
|
|
66
|
-
/* Avatar */
|
|
67
|
-
/* ------------------------------------------------------------------ */
|
|
68
|
-
|
|
69
|
-
describe("Avatar", () => {
|
|
70
|
-
it("renders with fallback text", () => {
|
|
71
|
-
render(
|
|
72
|
-
<Avatar>
|
|
73
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
74
|
-
</Avatar>,
|
|
75
|
-
);
|
|
76
|
-
expect(screen.getByText("JD")).toBeInTheDocument();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("renders all sizes without error", () => {
|
|
80
|
-
const sizes = ["xs", "sm", "md", "lg", "xl"] as const;
|
|
81
|
-
for (const size of sizes) {
|
|
82
|
-
const { unmount } = render(
|
|
83
|
-
<Avatar size={size}>
|
|
84
|
-
<AvatarFallback>X</AvatarFallback>
|
|
85
|
-
</Avatar>,
|
|
86
|
-
);
|
|
87
|
-
expect(screen.getByText("X")).toBeInTheDocument();
|
|
88
|
-
unmount();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("renders AvatarImage with alt text", () => {
|
|
93
|
-
render(
|
|
94
|
-
<Avatar>
|
|
95
|
-
<AvatarImage src="https://example.com/avatar.jpg" alt="User avatar" />
|
|
96
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
97
|
-
</Avatar>,
|
|
98
|
-
);
|
|
99
|
-
// AvatarFallback shows when image fails — just check it's in the DOM
|
|
100
|
-
expect(screen.getByText("JD")).toBeInTheDocument();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe("AvatarWithStatus", () => {
|
|
105
|
-
it("renders children", () => {
|
|
106
|
-
render(
|
|
107
|
-
<AvatarWithStatus>
|
|
108
|
-
<AvatarFallback>AB</AvatarFallback>
|
|
109
|
-
</AvatarWithStatus>,
|
|
110
|
-
);
|
|
111
|
-
expect(screen.getByText("AB")).toBeInTheDocument();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("renders status indicator when status is provided", () => {
|
|
115
|
-
const statuses = ["online", "offline", "busy", "away"] as const;
|
|
116
|
-
for (const status of statuses) {
|
|
117
|
-
const { unmount } = render(
|
|
118
|
-
<AvatarWithStatus status={status}>
|
|
119
|
-
<AvatarFallback>X</AvatarFallback>
|
|
120
|
-
</AvatarWithStatus>,
|
|
121
|
-
);
|
|
122
|
-
expect(screen.getByText("X")).toBeInTheDocument();
|
|
123
|
-
unmount();
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
/* ------------------------------------------------------------------ */
|
|
129
|
-
/* AvatarStack */
|
|
130
|
-
/* ------------------------------------------------------------------ */
|
|
131
|
-
|
|
132
|
-
describe("AvatarStack", () => {
|
|
133
|
-
const avatars = [{ name: "Alice" }, { name: "Bob" }, { name: "Carol" }];
|
|
134
|
-
|
|
135
|
-
it("renders all avatars by default", () => {
|
|
136
|
-
render(<AvatarStack avatars={avatars} />);
|
|
137
|
-
expect(screen.getByTitle("Alice")).toBeInTheDocument();
|
|
138
|
-
expect(screen.getByTitle("Bob")).toBeInTheDocument();
|
|
139
|
-
expect(screen.getByTitle("Carol")).toBeInTheDocument();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("shows overflow count when max is set", () => {
|
|
143
|
-
render(<AvatarStack avatars={avatars} max={2} />);
|
|
144
|
-
expect(screen.getByText("+1")).toBeInTheDocument();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("renders all sizes without error", () => {
|
|
148
|
-
const sizes = ["sm", "md", "lg"] as const;
|
|
149
|
-
for (const size of sizes) {
|
|
150
|
-
const { unmount } = render(<AvatarStack avatars={[{ name: "A" }]} size={size} />);
|
|
151
|
-
expect(screen.getByTitle("A")).toBeInTheDocument();
|
|
152
|
-
unmount();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
/* ------------------------------------------------------------------ */
|
|
158
|
-
/* StatCard */
|
|
159
|
-
/* ------------------------------------------------------------------ */
|
|
160
|
-
|
|
161
|
-
describe("StatCard", () => {
|
|
162
|
-
it("renders label and value", () => {
|
|
163
|
-
render(<StatCard label="Total Users" value="1,234" />);
|
|
164
|
-
expect(screen.getByText("Total Users")).toBeInTheDocument();
|
|
165
|
-
expect(screen.getByText("1,234")).toBeInTheDocument();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("renders trend when provided", () => {
|
|
169
|
-
render(<StatCard label="Revenue" value="$10k" trend={{ value: "+12%", direction: "up" }} />);
|
|
170
|
-
expect(screen.getByText("+12%")).toBeInTheDocument();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("renders all trend directions without error", () => {
|
|
174
|
-
const directions = ["up", "down", "neutral"] as const;
|
|
175
|
-
for (const direction of directions) {
|
|
176
|
-
const { unmount } = render(
|
|
177
|
-
<StatCard label="L" value="V" trend={{ value: "5%", direction }} />,
|
|
178
|
-
);
|
|
179
|
-
expect(screen.getByText("5%")).toBeInTheDocument();
|
|
180
|
-
unmount();
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
/* ------------------------------------------------------------------ */
|
|
186
|
-
/* Notification */
|
|
187
|
-
/* ------------------------------------------------------------------ */
|
|
188
|
-
|
|
189
|
-
describe("Notification", () => {
|
|
190
|
-
it("renders title", () => {
|
|
191
|
-
render(<Notification title="File uploaded" />);
|
|
192
|
-
expect(screen.getByText("File uploaded")).toBeInTheDocument();
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("renders description when provided", () => {
|
|
196
|
-
render(<Notification title="Done" description="Your export is ready." />);
|
|
197
|
-
expect(screen.getByText("Your export is ready.")).toBeInTheDocument();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("renders close button when onClose is provided", () => {
|
|
201
|
-
render(<Notification title="Alert" onClose={() => {}} />);
|
|
202
|
-
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("renders actions when provided", () => {
|
|
206
|
-
render(
|
|
207
|
-
<Notification
|
|
208
|
-
title="Update available"
|
|
209
|
-
actions={[{ label: "Install" }, { label: "Dismiss", variant: "ghost" }]}
|
|
210
|
-
/>,
|
|
211
|
-
);
|
|
212
|
-
expect(screen.getByText("Install")).toBeInTheDocument();
|
|
213
|
-
expect(screen.getByText("Dismiss")).toBeInTheDocument();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("renders all variants without error", () => {
|
|
217
|
-
const variants = ["default", "success", "error", "warning", "info"] as const;
|
|
218
|
-
for (const variant of variants) {
|
|
219
|
-
const { unmount } = render(<Notification title="T" variant={variant} />);
|
|
220
|
-
expect(screen.getByText("T")).toBeInTheDocument();
|
|
221
|
-
unmount();
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
/* ------------------------------------------------------------------ */
|
|
227
|
-
/* MediaObject */
|
|
228
|
-
/* ------------------------------------------------------------------ */
|
|
229
|
-
|
|
230
|
-
describe("MediaObject", () => {
|
|
231
|
-
it("renders media and content", () => {
|
|
232
|
-
render(
|
|
233
|
-
<MediaObject media={<img alt="thumb" src="" />}>
|
|
234
|
-
<p>Content text</p>
|
|
235
|
-
</MediaObject>,
|
|
236
|
-
);
|
|
237
|
-
expect(screen.getByAltText("thumb")).toBeInTheDocument();
|
|
238
|
-
expect(screen.getByText("Content text")).toBeInTheDocument();
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('renders mediaPosition="right" without error', () => {
|
|
242
|
-
render(
|
|
243
|
-
<MediaObject media={<span>icon</span>} mediaPosition="right">
|
|
244
|
-
Content
|
|
245
|
-
</MediaObject>,
|
|
246
|
-
);
|
|
247
|
-
expect(screen.getByText("Content")).toBeInTheDocument();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("renders all gap options without error", () => {
|
|
251
|
-
const gaps = ["sm", "md", "lg"] as const;
|
|
252
|
-
for (const gap of gaps) {
|
|
253
|
-
const { unmount } = render(
|
|
254
|
-
<MediaObject media={<span />} gap={gap}>
|
|
255
|
-
Body
|
|
256
|
-
</MediaObject>,
|
|
257
|
-
);
|
|
258
|
-
expect(screen.getByText("Body")).toBeInTheDocument();
|
|
259
|
-
unmount();
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import { Alert, AlertTitle, AlertDescription } from "../alert";
|
|
4
|
-
import { Progress } from "../progress";
|
|
5
|
-
import { Steps } from "../steps";
|
|
6
|
-
import { EmptyState } from "../empty-state";
|
|
7
|
-
import type { Step } from "../steps";
|
|
8
|
-
|
|
9
|
-
/* ------------------------------------------------------------------ */
|
|
10
|
-
/* Alert */
|
|
11
|
-
/* ------------------------------------------------------------------ */
|
|
12
|
-
|
|
13
|
-
describe("Alert", () => {
|
|
14
|
-
it('renders with role="alert"', () => {
|
|
15
|
-
render(<Alert>Message</Alert>);
|
|
16
|
-
expect(screen.getByRole("alert")).toBeInTheDocument();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("renders sub-components together", () => {
|
|
20
|
-
render(
|
|
21
|
-
<Alert>
|
|
22
|
-
<AlertTitle>Warning</AlertTitle>
|
|
23
|
-
<AlertDescription>Something went wrong.</AlertDescription>
|
|
24
|
-
</Alert>,
|
|
25
|
-
);
|
|
26
|
-
expect(screen.getByText("Warning")).toBeInTheDocument();
|
|
27
|
-
expect(screen.getByText("Something went wrong.")).toBeInTheDocument();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("renders all variants without error", () => {
|
|
31
|
-
const variants = ["default", "info", "success", "warning", "destructive"] as const;
|
|
32
|
-
for (const variant of variants) {
|
|
33
|
-
const { unmount } = render(<Alert variant={variant}>Alert</Alert>);
|
|
34
|
-
expect(screen.getByRole("alert")).toBeInTheDocument();
|
|
35
|
-
unmount();
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/* ------------------------------------------------------------------ */
|
|
41
|
-
/* Progress */
|
|
42
|
-
/* ------------------------------------------------------------------ */
|
|
43
|
-
|
|
44
|
-
describe("Progress", () => {
|
|
45
|
-
it("renders with a progressbar role", () => {
|
|
46
|
-
render(<Progress value={50} />);
|
|
47
|
-
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("reflects the correct value", () => {
|
|
51
|
-
render(<Progress value={75} />);
|
|
52
|
-
expect(screen.getByRole("progressbar")).toHaveAttribute("aria-valuenow", "75");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("renders at 0%", () => {
|
|
56
|
-
render(<Progress value={0} />);
|
|
57
|
-
expect(screen.getByRole("progressbar")).toHaveAttribute("aria-valuenow", "0");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("renders at 100%", () => {
|
|
61
|
-
render(<Progress value={100} />);
|
|
62
|
-
expect(screen.getByRole("progressbar")).toHaveAttribute("aria-valuenow", "100");
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/* ------------------------------------------------------------------ */
|
|
67
|
-
/* Steps */
|
|
68
|
-
/* ------------------------------------------------------------------ */
|
|
69
|
-
|
|
70
|
-
const STEPS: Step[] = [
|
|
71
|
-
{ name: "Cart", status: "complete" },
|
|
72
|
-
{ name: "Shipping", status: "current" },
|
|
73
|
-
{ name: "Payment", status: "upcoming" },
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
describe("Steps", () => {
|
|
77
|
-
it("renders all step names in circles variant (default)", () => {
|
|
78
|
-
render(<Steps steps={STEPS} />);
|
|
79
|
-
expect(screen.getByText("Cart")).toBeInTheDocument();
|
|
80
|
-
expect(screen.getByText("Shipping")).toBeInTheDocument();
|
|
81
|
-
expect(screen.getByText("Payment")).toBeInTheDocument();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("renders bullets variant without error", () => {
|
|
85
|
-
render(<Steps steps={STEPS} variant="bullets" />);
|
|
86
|
-
expect(screen.getByRole("navigation", { name: /progress/i })).toBeInTheDocument();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("renders progress variant without error", () => {
|
|
90
|
-
render(<Steps steps={STEPS} variant="progress" />);
|
|
91
|
-
expect(screen.getByText("Cart")).toBeInTheDocument();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("bullets variant shows step count", () => {
|
|
95
|
-
render(<Steps steps={STEPS} variant="bullets" />);
|
|
96
|
-
expect(screen.getByText(/Step 2 of 3/i)).toBeInTheDocument();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("renders with descriptions in circles variant", () => {
|
|
100
|
-
const stepsWithDesc: Step[] = [
|
|
101
|
-
{ name: "Step 1", description: "First step", status: "complete" },
|
|
102
|
-
{ name: "Step 2", status: "current" },
|
|
103
|
-
];
|
|
104
|
-
render(<Steps steps={stepsWithDesc} />);
|
|
105
|
-
expect(screen.getByText("First step")).toBeInTheDocument();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
/* ------------------------------------------------------------------ */
|
|
110
|
-
/* EmptyState */
|
|
111
|
-
/* ------------------------------------------------------------------ */
|
|
112
|
-
|
|
113
|
-
describe("EmptyState", () => {
|
|
114
|
-
it("renders the title", () => {
|
|
115
|
-
render(<EmptyState title="No results found" />);
|
|
116
|
-
expect(screen.getByText("No results found")).toBeInTheDocument();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("renders description when provided", () => {
|
|
120
|
-
render(<EmptyState title="Empty" description="Try a different search." />);
|
|
121
|
-
expect(screen.getByText("Try a different search.")).toBeInTheDocument();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("renders action button with label", () => {
|
|
125
|
-
render(<EmptyState title="Empty" action={{ label: "Add item" }} />);
|
|
126
|
-
expect(screen.getByRole("button", { name: "Add item" })).toBeInTheDocument();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("renders action as anchor when href is provided", () => {
|
|
130
|
-
render(<EmptyState title="Empty" action={{ label: "Create", href: "/new" }} />);
|
|
131
|
-
expect(screen.getByRole("link", { name: "Create" })).toHaveAttribute("href", "/new");
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("renders icon when provided", () => {
|
|
135
|
-
render(<EmptyState title="Empty" icon={<span data-testid="icon">📭</span>} />);
|
|
136
|
-
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
|
137
|
-
});
|
|
138
|
-
});
|