@nucel/ui 0.2.0 → 0.3.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/package.json +5 -1
- package/src/lib/components/ui/CodeBlock.svelte +92 -0
- package/src/lib/components/ui/CopyButton.svelte +43 -0
- package/src/lib/components/ui/FilterBar.svelte +63 -0
- package/src/lib/components/ui/KanbanBoard.svelte +27 -0
- package/src/lib/components/ui/KanbanCard.svelte +43 -0
- package/src/lib/components/ui/KanbanColumn.svelte +52 -0
- package/src/lib/components/ui/MetricCard.svelte +79 -0
- package/src/lib/components/ui/Pagination.svelte +85 -0
- package/src/lib/components/ui/Timeline.svelte +85 -0
- package/src/lib/index.ts +25 -0
- package/src/lib/components/ui/Alert.test.ts +0 -206
- package/src/lib/components/ui/BranchPill.test.ts +0 -121
- package/src/lib/components/ui/CostDisplay.test.ts +0 -1115
- package/src/lib/components/ui/FormField.test.ts +0 -41
- package/src/lib/components/ui/PageHeader.test.ts +0 -72
- package/src/lib/components/ui/ProgressRing.test.ts +0 -239
- package/src/lib/components/ui/Section.test.ts +0 -44
- package/src/lib/components/ui/StatusBadge.test.ts +0 -150
- package/src/lib/components/ui/StatusPill.test.ts +0 -125
- package/src/lib/components/ui/table/Table.test.ts +0 -317
- package/src/lib/utils/cn.test.ts +0 -993
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/svelte";
|
|
3
|
-
import FormField from "./FormField.svelte";
|
|
4
|
-
|
|
5
|
-
describe("FormField", () => {
|
|
6
|
-
it("renders children (wrapper div is present)", () => {
|
|
7
|
-
const { container } = render(FormField, { props: {} });
|
|
8
|
-
expect(container.querySelector("div")).not.toBeNull();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("renders label when label prop is given", () => {
|
|
12
|
-
render(FormField, { props: { label: "Username" } });
|
|
13
|
-
expect(screen.getByText("Username")).toBeInTheDocument();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("does NOT render a label element when label prop is absent", () => {
|
|
17
|
-
const { container } = render(FormField, { props: {} });
|
|
18
|
-
expect(container.querySelector("label")).toBeNull();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("renders error text when error prop is given", () => {
|
|
22
|
-
render(FormField, { props: { error: "This field is required" } });
|
|
23
|
-
expect(screen.getByText("This field is required")).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("renders hint text when hint prop is given and error is absent", () => {
|
|
27
|
-
render(FormField, { props: { hint: "Must be between 3–20 characters" } });
|
|
28
|
-
expect(screen.getByText("Must be between 3–20 characters")).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("does NOT render hint when error is also given", () => {
|
|
32
|
-
render(FormField, {
|
|
33
|
-
props: {
|
|
34
|
-
hint: "Some hint",
|
|
35
|
-
error: "Some error",
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
expect(screen.queryByText("Some hint")).toBeNull();
|
|
39
|
-
expect(screen.getByText("Some error")).toBeInTheDocument();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/svelte";
|
|
3
|
-
import PageHeader from "./PageHeader.svelte";
|
|
4
|
-
|
|
5
|
-
describe("PageHeader", () => {
|
|
6
|
-
it("renders the title", () => {
|
|
7
|
-
render(PageHeader, { props: { title: "Repositories" } });
|
|
8
|
-
expect(screen.getByText("Repositories")).toBeInTheDocument();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("title is rendered as an h1 element", () => {
|
|
12
|
-
render(PageHeader, { props: { title: "Repositories" } });
|
|
13
|
-
const h1 = screen.getByRole("heading", { level: 1 });
|
|
14
|
-
expect(h1).toBeInTheDocument();
|
|
15
|
-
expect(h1.textContent).toBe("Repositories");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("renders the subtitle when provided", () => {
|
|
19
|
-
render(PageHeader, { props: { title: "Repositories", subtitle: "All repositories" } });
|
|
20
|
-
expect(screen.getByText("All repositories")).toBeInTheDocument();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("does not render a subtitle element when subtitle is not given", () => {
|
|
24
|
-
render(PageHeader, { props: { title: "Repositories" } });
|
|
25
|
-
expect(screen.queryByText("All repositories")).not.toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("subtitle is rendered as a paragraph element", () => {
|
|
29
|
-
const { container } = render(PageHeader, {
|
|
30
|
-
props: { title: "Repositories", subtitle: "Some subtitle" },
|
|
31
|
-
});
|
|
32
|
-
const p = container.querySelector("p");
|
|
33
|
-
expect(p).not.toBeNull();
|
|
34
|
-
expect(p!.textContent).toBe("Some subtitle");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("does not render a paragraph element when subtitle is absent", () => {
|
|
38
|
-
const { container } = render(PageHeader, { props: { title: "Only Title" } });
|
|
39
|
-
expect(container.querySelector("p")).toBeNull();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("renders different title text correctly", () => {
|
|
43
|
-
render(PageHeader, { props: { title: "Issues" } });
|
|
44
|
-
expect(screen.getByText("Issues")).toBeInTheDocument();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("renders without crashing when only title is provided", () => {
|
|
48
|
-
expect(() =>
|
|
49
|
-
render(PageHeader, { props: { title: "Test" } }),
|
|
50
|
-
).not.toThrow();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("renders without crashing when title and subtitle are provided", () => {
|
|
54
|
-
expect(() =>
|
|
55
|
-
render(PageHeader, { props: { title: "Test", subtitle: "A subtitle" } }),
|
|
56
|
-
).not.toThrow();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("h1 has tracking-tight class", () => {
|
|
60
|
-
const { container } = render(PageHeader, { props: { title: "Test" } });
|
|
61
|
-
const h1 = container.querySelector("h1");
|
|
62
|
-
expect(h1!.className).toContain("tracking-tight");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("subtitle paragraph has text-muted-foreground class", () => {
|
|
66
|
-
const { container } = render(PageHeader, {
|
|
67
|
-
props: { title: "Test", subtitle: "Sub" },
|
|
68
|
-
});
|
|
69
|
-
const p = container.querySelector("p");
|
|
70
|
-
expect(p!.className).toContain("text-muted-foreground");
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render } from "@testing-library/svelte";
|
|
3
|
-
import ProgressRing from "./ProgressRing.svelte";
|
|
4
|
-
|
|
5
|
-
describe("ProgressRing — rendering", () => {
|
|
6
|
-
it("renders nothing when limit === 0", () => {
|
|
7
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 0 } });
|
|
8
|
-
expect(container.querySelector("svg")).toBeNull();
|
|
9
|
-
expect(container.querySelector("span")).toBeNull();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("renders an SVG when limit > 0", () => {
|
|
13
|
-
const { container } = render(ProgressRing, { props: { spent: 25, limit: 100 } });
|
|
14
|
-
expect(container.querySelector("svg")).not.toBeNull();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("renders a span wrapper when limit > 0", () => {
|
|
18
|
-
const { container } = render(ProgressRing, { props: { spent: 10, limit: 100 } });
|
|
19
|
-
expect(container.querySelector("span")).not.toBeNull();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("renders two circle elements inside the SVG", () => {
|
|
23
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100 } });
|
|
24
|
-
expect(container.querySelectorAll("circle").length).toBe(2);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("renders nothing when both spent and limit are 0", () => {
|
|
28
|
-
const { container } = render(ProgressRing, { props: { spent: 0, limit: 0 } });
|
|
29
|
-
expect(container.querySelector("svg")).toBeNull();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("renders when spent > limit (clamped to 1)", () => {
|
|
33
|
-
const { container } = render(ProgressRing, { props: { spent: 200, limit: 100 } });
|
|
34
|
-
expect(container.querySelector("svg")).not.toBeNull();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("renders without crashing for fractional values", () => {
|
|
38
|
-
expect(() =>
|
|
39
|
-
render(ProgressRing, { props: { spent: 33.33, limit: 100 } }),
|
|
40
|
-
).not.toThrow();
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("ProgressRing — title attribute", () => {
|
|
45
|
-
it("title contains the percentage for 25%", () => {
|
|
46
|
-
const { container } = render(ProgressRing, { props: { spent: 25, limit: 100 } });
|
|
47
|
-
const span = container.querySelector("span");
|
|
48
|
-
expect(span?.getAttribute("title")).toContain("25%");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("title contains the percentage for 50%", () => {
|
|
52
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100 } });
|
|
53
|
-
const span = container.querySelector("span");
|
|
54
|
-
expect(span?.getAttribute("title")).toContain("50%");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("title contains the percentage for 80%", () => {
|
|
58
|
-
const { container } = render(ProgressRing, { props: { spent: 80, limit: 100 } });
|
|
59
|
-
const span = container.querySelector("span");
|
|
60
|
-
expect(span?.getAttribute("title")).toContain("80%");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("title contains the percentage for 100% when spent === limit", () => {
|
|
64
|
-
const { container } = render(ProgressRing, { props: { spent: 100, limit: 100 } });
|
|
65
|
-
const span = container.querySelector("span");
|
|
66
|
-
expect(span?.getAttribute("title")).toContain("100%");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("title contains the spent value formatted with 2 decimal places", () => {
|
|
70
|
-
const { container } = render(ProgressRing, { props: { spent: 25, limit: 100 } });
|
|
71
|
-
const span = container.querySelector("span");
|
|
72
|
-
expect(span?.getAttribute("title")).toContain("25.00");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("title contains the limit value formatted with 2 decimal places", () => {
|
|
76
|
-
const { container } = render(ProgressRing, { props: { spent: 25, limit: 100 } });
|
|
77
|
-
const span = container.querySelector("span");
|
|
78
|
-
expect(span?.getAttribute("title")).toContain("100.00");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("title contains '$' signs for spent and limit", () => {
|
|
82
|
-
const { container } = render(ProgressRing, { props: { spent: 10, limit: 50 } });
|
|
83
|
-
const span = container.querySelector("span");
|
|
84
|
-
const title = span?.getAttribute("title") ?? "";
|
|
85
|
-
expect(title.match(/\$/g)?.length).toBeGreaterThanOrEqual(2);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("ProgressRing — color derivation", () => {
|
|
90
|
-
it("uses stroke-success class when pct < 0.5 (0%)", () => {
|
|
91
|
-
const { container } = render(ProgressRing, { props: { spent: 0, limit: 100 } });
|
|
92
|
-
const circles = container.querySelectorAll("circle");
|
|
93
|
-
const hasSuccess = Array.from(circles).some((c) =>
|
|
94
|
-
c.getAttribute("class")?.includes("stroke-success"),
|
|
95
|
-
);
|
|
96
|
-
expect(hasSuccess).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("uses stroke-success class when pct < 0.5 (25%)", () => {
|
|
100
|
-
const { container } = render(ProgressRing, { props: { spent: 25, limit: 100 } });
|
|
101
|
-
const circles = container.querySelectorAll("circle");
|
|
102
|
-
const hasSuccess = Array.from(circles).some((c) =>
|
|
103
|
-
c.getAttribute("class")?.includes("stroke-success"),
|
|
104
|
-
);
|
|
105
|
-
expect(hasSuccess).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("uses stroke-warning class when pct is 0.5 (exactly 50%)", () => {
|
|
109
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100 } });
|
|
110
|
-
const circles = container.querySelectorAll("circle");
|
|
111
|
-
const hasWarning = Array.from(circles).some((c) =>
|
|
112
|
-
c.getAttribute("class")?.includes("stroke-warning"),
|
|
113
|
-
);
|
|
114
|
-
expect(hasWarning).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("uses stroke-warning class when pct is 0.79 (79%)", () => {
|
|
118
|
-
const { container } = render(ProgressRing, { props: { spent: 79, limit: 100 } });
|
|
119
|
-
const circles = container.querySelectorAll("circle");
|
|
120
|
-
const hasWarning = Array.from(circles).some((c) =>
|
|
121
|
-
c.getAttribute("class")?.includes("stroke-warning"),
|
|
122
|
-
);
|
|
123
|
-
expect(hasWarning).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("uses stroke-destructive class when pct is 0.8 (exactly 80%)", () => {
|
|
127
|
-
const { container } = render(ProgressRing, { props: { spent: 80, limit: 100 } });
|
|
128
|
-
const circles = container.querySelectorAll("circle");
|
|
129
|
-
const hasDestructive = Array.from(circles).some((c) =>
|
|
130
|
-
c.getAttribute("class")?.includes("stroke-destructive"),
|
|
131
|
-
);
|
|
132
|
-
expect(hasDestructive).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("uses stroke-destructive class when pct is 1.0 (100%)", () => {
|
|
136
|
-
const { container } = render(ProgressRing, { props: { spent: 100, limit: 100 } });
|
|
137
|
-
const circles = container.querySelectorAll("circle");
|
|
138
|
-
const hasDestructive = Array.from(circles).some((c) =>
|
|
139
|
-
c.getAttribute("class")?.includes("stroke-destructive"),
|
|
140
|
-
);
|
|
141
|
-
expect(hasDestructive).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it("uses stroke-destructive class when spent > limit (clamped to 100%)", () => {
|
|
145
|
-
const { container } = render(ProgressRing, { props: { spent: 150, limit: 100 } });
|
|
146
|
-
const circles = container.querySelectorAll("circle");
|
|
147
|
-
const hasDestructive = Array.from(circles).some((c) =>
|
|
148
|
-
c.getAttribute("class")?.includes("stroke-destructive"),
|
|
149
|
-
);
|
|
150
|
-
expect(hasDestructive).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("ProgressRing — bgColor derivation", () => {
|
|
155
|
-
it("uses stroke-success/20 bg class when pct < 0.5", () => {
|
|
156
|
-
const { container } = render(ProgressRing, { props: { spent: 10, limit: 100 } });
|
|
157
|
-
const circles = container.querySelectorAll("circle");
|
|
158
|
-
const hasBg = Array.from(circles).some((c) =>
|
|
159
|
-
c.getAttribute("class")?.includes("stroke-success/20"),
|
|
160
|
-
);
|
|
161
|
-
expect(hasBg).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("uses stroke-warning/20 bg class when pct is in warning range", () => {
|
|
165
|
-
const { container } = render(ProgressRing, { props: { spent: 65, limit: 100 } });
|
|
166
|
-
const circles = container.querySelectorAll("circle");
|
|
167
|
-
const hasBg = Array.from(circles).some((c) =>
|
|
168
|
-
c.getAttribute("class")?.includes("stroke-warning/20"),
|
|
169
|
-
);
|
|
170
|
-
expect(hasBg).toBe(true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("uses stroke-destructive/20 bg class when pct >= 0.8", () => {
|
|
174
|
-
const { container } = render(ProgressRing, { props: { spent: 90, limit: 100 } });
|
|
175
|
-
const circles = container.querySelectorAll("circle");
|
|
176
|
-
const hasBg = Array.from(circles).some((c) =>
|
|
177
|
-
c.getAttribute("class")?.includes("stroke-destructive/20"),
|
|
178
|
-
);
|
|
179
|
-
expect(hasBg).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe("ProgressRing — size and SVG attributes", () => {
|
|
184
|
-
it("SVG has correct width and height for default size (16)", () => {
|
|
185
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100 } });
|
|
186
|
-
const svg = container.querySelector("svg");
|
|
187
|
-
expect(svg?.getAttribute("width")).toBe("16");
|
|
188
|
-
expect(svg?.getAttribute("height")).toBe("16");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("SVG has correct width for custom size", () => {
|
|
192
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100, size: 40 } });
|
|
193
|
-
const svg = container.querySelector("svg");
|
|
194
|
-
expect(svg?.getAttribute("width")).toBe("40");
|
|
195
|
-
expect(svg?.getAttribute("height")).toBe("40");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("SVG has -rotate-90 class", () => {
|
|
199
|
-
const { container } = render(ProgressRing, { props: { spent: 50, limit: 100 } });
|
|
200
|
-
const svg = container.querySelector("svg");
|
|
201
|
-
// SVG elements use SVGAnimatedString for className — use getAttribute instead
|
|
202
|
-
expect(svg?.getAttribute("class")).toContain("-rotate-90");
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("applies custom class to span wrapper", () => {
|
|
206
|
-
const { container } = render(ProgressRing, {
|
|
207
|
-
props: { spent: 50, limit: 100, class: "my-ring-class" },
|
|
208
|
-
});
|
|
209
|
-
const span = container.querySelector("span");
|
|
210
|
-
expect(span?.className).toContain("my-ring-class");
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe("ProgressRing — pct calculation", () => {
|
|
215
|
-
it("pct is 0 when limit === 0 (no SVG rendered)", () => {
|
|
216
|
-
// The component renders nothing when limit === 0 because pct would be 0
|
|
217
|
-
const { container } = render(ProgressRing, { props: { spent: 100, limit: 0 } });
|
|
218
|
-
expect(container.querySelector("svg")).toBeNull();
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("pct is clamped to 1 when spent > limit", () => {
|
|
222
|
-
// At 100% the destructive color should be used
|
|
223
|
-
const { container } = render(ProgressRing, { props: { spent: 999, limit: 100 } });
|
|
224
|
-
const circles = container.querySelectorAll("circle");
|
|
225
|
-
const hasDestructive = Array.from(circles).some((c) =>
|
|
226
|
-
c.getAttribute("class")?.includes("stroke-destructive"),
|
|
227
|
-
);
|
|
228
|
-
expect(hasDestructive).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("pct is 0 when spent === 0 (success color)", () => {
|
|
232
|
-
const { container } = render(ProgressRing, { props: { spent: 0, limit: 100 } });
|
|
233
|
-
const circles = container.querySelectorAll("circle");
|
|
234
|
-
const hasSuccess = Array.from(circles).some((c) =>
|
|
235
|
-
c.getAttribute("class")?.includes("stroke-success"),
|
|
236
|
-
);
|
|
237
|
-
expect(hasSuccess).toBe(true);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render } from "@testing-library/svelte";
|
|
3
|
-
import Section from "./Section.svelte";
|
|
4
|
-
|
|
5
|
-
describe("Section", () => {
|
|
6
|
-
it("renders a container div", () => {
|
|
7
|
-
const { container } = render(Section, { props: {} });
|
|
8
|
-
expect(container.querySelector("div")).not.toBeNull();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("renders the title when given", () => {
|
|
12
|
-
const { container } = render(Section, {
|
|
13
|
-
props: { title: "My Section" },
|
|
14
|
-
});
|
|
15
|
-
const h3 = container.querySelector("h3");
|
|
16
|
-
expect(h3).not.toBeNull();
|
|
17
|
-
expect(h3!.textContent).toBe("My Section");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("does NOT render an h3 when no title is given", () => {
|
|
21
|
-
const { container } = render(Section, {
|
|
22
|
-
props: {},
|
|
23
|
-
});
|
|
24
|
-
expect(container.querySelector("h3")).toBeNull();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("the h3 has the uppercase class", () => {
|
|
28
|
-
const { container } = render(Section, {
|
|
29
|
-
props: { title: "Settings" },
|
|
30
|
-
});
|
|
31
|
-
const h3 = container.querySelector("h3");
|
|
32
|
-
expect(h3).not.toBeNull();
|
|
33
|
-
expect(h3!.className).toContain("uppercase");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("extra class prop is merged into the container div", () => {
|
|
37
|
-
const { container } = render(Section, {
|
|
38
|
-
props: { class: "my-extra-class" },
|
|
39
|
-
});
|
|
40
|
-
const div = container.querySelector("div");
|
|
41
|
-
expect(div).not.toBeNull();
|
|
42
|
-
expect(div!.className).toContain("my-extra-class");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/svelte";
|
|
3
|
-
import StatusBadge from "./StatusBadge.svelte";
|
|
4
|
-
|
|
5
|
-
describe("StatusBadge — label rendering", () => {
|
|
6
|
-
it("renders 'open' as the label for status=open", () => {
|
|
7
|
-
render(StatusBadge, { props: { status: "open" } });
|
|
8
|
-
expect(screen.getByText("open")).toBeInTheDocument();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("renders 'closed' as the label for status=closed", () => {
|
|
12
|
-
render(StatusBadge, { props: { status: "closed" } });
|
|
13
|
-
expect(screen.getByText("closed")).toBeInTheDocument();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("renders 'running' as the label for status=running", () => {
|
|
17
|
-
render(StatusBadge, { props: { status: "running" } });
|
|
18
|
-
expect(screen.getByText("running")).toBeInTheDocument();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("renders 'succeeded' as the label for status=succeeded", () => {
|
|
22
|
-
render(StatusBadge, { props: { status: "succeeded" } });
|
|
23
|
-
expect(screen.getByText("succeeded")).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("renders 'failed' as the label for status=failed", () => {
|
|
27
|
-
render(StatusBadge, { props: { status: "failed" } });
|
|
28
|
-
expect(screen.getByText("failed")).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("renders 'merged' as the label for status=merged", () => {
|
|
32
|
-
render(StatusBadge, { props: { status: "merged" } });
|
|
33
|
-
expect(screen.getByText("merged")).toBeInTheDocument();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("renders custom label when label prop is provided", () => {
|
|
37
|
-
render(StatusBadge, { props: { status: "open", label: "In Progress" } });
|
|
38
|
-
expect(screen.getByText("In Progress")).toBeInTheDocument();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("custom label overrides the default status label", () => {
|
|
42
|
-
render(StatusBadge, { props: { status: "open", label: "My Label" } });
|
|
43
|
-
expect(screen.queryByText("open")).not.toBeInTheDocument();
|
|
44
|
-
expect(screen.getByText("My Label")).toBeInTheDocument();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("renders without crashing for any known status", () => {
|
|
48
|
-
const statuses = ["open", "closed", "running", "succeeded", "failed", "merged"];
|
|
49
|
-
for (const status of statuses) {
|
|
50
|
-
expect(() => render(StatusBadge, { props: { status } })).not.toThrow();
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("StatusBadge — color classes", () => {
|
|
56
|
-
it("applies success color classes for status=open", () => {
|
|
57
|
-
const { container } = render(StatusBadge, { props: { status: "open" } });
|
|
58
|
-
const badge = container.firstElementChild;
|
|
59
|
-
expect(badge?.className).toContain("text-success");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("applies purple color classes for status=merged", () => {
|
|
63
|
-
const { container } = render(StatusBadge, { props: { status: "merged" } });
|
|
64
|
-
const badge = container.firstElementChild;
|
|
65
|
-
expect(badge?.className).toContain("text-purple-400");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("applies blue color classes for status=running", () => {
|
|
69
|
-
const { container } = render(StatusBadge, { props: { status: "running" } });
|
|
70
|
-
const badge = container.firstElementChild;
|
|
71
|
-
expect(badge?.className).toContain("text-blue-400");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("applies success color classes for status=succeeded", () => {
|
|
75
|
-
const { container } = render(StatusBadge, { props: { status: "succeeded" } });
|
|
76
|
-
const badge = container.firstElementChild;
|
|
77
|
-
expect(badge?.className).toContain("text-success");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("applies destructive color classes for status=failed", () => {
|
|
81
|
-
const { container } = render(StatusBadge, { props: { status: "failed" } });
|
|
82
|
-
const badge = container.firstElementChild;
|
|
83
|
-
expect(badge?.className).toContain("text-destructive");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("uses secondary variant for status=closed", () => {
|
|
87
|
-
// closed maps to secondary variant (no extraClass)
|
|
88
|
-
const { container } = render(StatusBadge, { props: { status: "closed" } });
|
|
89
|
-
expect(container.firstElementChild).not.toBeNull();
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("StatusBadge — structure", () => {
|
|
94
|
-
it("renders an inline-flex element", () => {
|
|
95
|
-
const { container } = render(StatusBadge, { props: { status: "open" } });
|
|
96
|
-
const badge = container.firstElementChild;
|
|
97
|
-
expect(badge?.className).toContain("inline-flex");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("renders items-center class", () => {
|
|
101
|
-
const { container } = render(StatusBadge, { props: { status: "open" } });
|
|
102
|
-
const badge = container.firstElementChild;
|
|
103
|
-
expect(badge?.className).toContain("items-center");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("contains a span with the label text", () => {
|
|
107
|
-
const { container } = render(StatusBadge, { props: { status: "open" } });
|
|
108
|
-
const span = container.querySelector("span");
|
|
109
|
-
expect(span).not.toBeNull();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("renders without crashing for unknown status", () => {
|
|
113
|
-
expect(() => render(StatusBadge, { props: { status: "unknown" } })).not.toThrow();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("renders custom label for any status", () => {
|
|
117
|
-
render(StatusBadge, { props: { status: "failed", label: "Broken" } });
|
|
118
|
-
expect(screen.getByText("Broken")).toBeInTheDocument();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("background color class is present for status=open", () => {
|
|
122
|
-
const { container } = render(StatusBadge, { props: { status: "open" } });
|
|
123
|
-
const badge = container.firstElementChild;
|
|
124
|
-
expect(badge?.className).toContain("bg-success/10");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("background color class is present for status=merged", () => {
|
|
128
|
-
const { container } = render(StatusBadge, { props: { status: "merged" } });
|
|
129
|
-
const badge = container.firstElementChild;
|
|
130
|
-
expect(badge?.className).toContain("bg-purple-500/10");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("background color class is present for status=running", () => {
|
|
134
|
-
const { container } = render(StatusBadge, { props: { status: "running" } });
|
|
135
|
-
const badge = container.firstElementChild;
|
|
136
|
-
expect(badge?.className).toContain("bg-blue-500/10");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("background color class is present for status=failed", () => {
|
|
140
|
-
const { container } = render(StatusBadge, { props: { status: "failed" } });
|
|
141
|
-
const badge = container.firstElementChild;
|
|
142
|
-
expect(badge?.className).toContain("bg-destructive/10");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("background color class is present for status=succeeded", () => {
|
|
146
|
-
const { container } = render(StatusBadge, { props: { status: "succeeded" } });
|
|
147
|
-
const badge = container.firstElementChild;
|
|
148
|
-
expect(badge?.className).toContain("bg-success/10");
|
|
149
|
-
});
|
|
150
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/svelte";
|
|
3
|
-
import StatusPill from "./StatusPill.svelte";
|
|
4
|
-
|
|
5
|
-
describe("StatusPill", () => {
|
|
6
|
-
it("renders default label 'passed' for status=success", () => {
|
|
7
|
-
render(StatusPill, { props: { status: "success" } });
|
|
8
|
-
expect(screen.getByText("passed")).toBeInTheDocument();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("renders default label 'failed' for status=failure", () => {
|
|
12
|
-
render(StatusPill, { props: { status: "failure" } });
|
|
13
|
-
expect(screen.getByText("failed")).toBeInTheDocument();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("renders default label 'running' for status=running", () => {
|
|
17
|
-
render(StatusPill, { props: { status: "running" } });
|
|
18
|
-
expect(screen.getByText("running")).toBeInTheDocument();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("renders default label 'pending' for status=pending", () => {
|
|
22
|
-
render(StatusPill, { props: { status: "pending" } });
|
|
23
|
-
expect(screen.getByText("pending")).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("renders default label 'cancelled' for status=cancelled", () => {
|
|
27
|
-
render(StatusPill, { props: { status: "cancelled" } });
|
|
28
|
-
expect(screen.getByText("cancelled")).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("renders default label 'warning' for status=warning", () => {
|
|
32
|
-
render(StatusPill, { props: { status: "warning" } });
|
|
33
|
-
expect(screen.getByText("warning")).toBeInTheDocument();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("renders custom label when label prop is provided", () => {
|
|
37
|
-
render(StatusPill, { props: { status: "success", label: "Deployed" } });
|
|
38
|
-
expect(screen.getByText("Deployed")).toBeInTheDocument();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("custom label overrides the default label", () => {
|
|
42
|
-
render(StatusPill, { props: { status: "success", label: "Custom" } });
|
|
43
|
-
expect(screen.queryByText("passed")).not.toBeInTheDocument();
|
|
44
|
-
expect(screen.getByText("Custom")).toBeInTheDocument();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("size='xs' applies text-[10px] class", () => {
|
|
48
|
-
const { container } = render(StatusPill, { props: { status: "success", size: "xs" } });
|
|
49
|
-
const span = container.querySelector("span");
|
|
50
|
-
expect(span).not.toBeNull();
|
|
51
|
-
expect(span!.className).toContain("text-[10px]");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("size='xs' applies px-1.5 class", () => {
|
|
55
|
-
const { container } = render(StatusPill, { props: { status: "success", size: "xs" } });
|
|
56
|
-
const span = container.querySelector("span");
|
|
57
|
-
expect(span!.className).toContain("px-1.5");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("size='sm' (default) applies text-[11px] class", () => {
|
|
61
|
-
const { container } = render(StatusPill, { props: { status: "success" } });
|
|
62
|
-
const span = container.querySelector("span");
|
|
63
|
-
expect(span!.className).toContain("text-[11px]");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("size='sm' applies px-2 class", () => {
|
|
67
|
-
const { container } = render(StatusPill, { props: { status: "success", size: "sm" } });
|
|
68
|
-
const span = container.querySelector("span");
|
|
69
|
-
expect(span!.className).toContain("px-2");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("size='xs' does not apply text-[11px] class", () => {
|
|
73
|
-
const { container } = render(StatusPill, { props: { status: "running", size: "xs" } });
|
|
74
|
-
const span = container.querySelector("span");
|
|
75
|
-
expect(span!.className).not.toContain("text-[11px]");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("root element is a span", () => {
|
|
79
|
-
const { container } = render(StatusPill, { props: { status: "success" } });
|
|
80
|
-
expect(container.firstElementChild?.tagName.toLowerCase()).toBe("span");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("applies green color classes for success status", () => {
|
|
84
|
-
const { container } = render(StatusPill, { props: { status: "success" } });
|
|
85
|
-
const span = container.querySelector("span");
|
|
86
|
-
expect(span!.className).toContain("text-green-500");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("applies destructive color classes for failure status", () => {
|
|
90
|
-
const { container } = render(StatusPill, { props: { status: "failure" } });
|
|
91
|
-
const span = container.querySelector("span");
|
|
92
|
-
expect(span!.className).toContain("text-destructive");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("applies blue color classes for running status", () => {
|
|
96
|
-
const { container } = render(StatusPill, { props: { status: "running" } });
|
|
97
|
-
const span = container.querySelector("span");
|
|
98
|
-
expect(span!.className).toContain("text-blue-400");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("applies yellow color classes for pending status", () => {
|
|
102
|
-
const { container } = render(StatusPill, { props: { status: "pending" } });
|
|
103
|
-
const span = container.querySelector("span");
|
|
104
|
-
expect(span!.className).toContain("text-yellow-500");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("applies amber color classes for warning status", () => {
|
|
108
|
-
const { container } = render(StatusPill, { props: { status: "warning" } });
|
|
109
|
-
const span = container.querySelector("span");
|
|
110
|
-
expect(span!.className).toContain("text-amber-500");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("applies muted color classes for cancelled status", () => {
|
|
114
|
-
const { container } = render(StatusPill, { props: { status: "cancelled" } });
|
|
115
|
-
const span = container.querySelector("span");
|
|
116
|
-
expect(span!.className).toContain("text-muted-foreground");
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("renders without crashing for all statuses", () => {
|
|
120
|
-
const statuses = ["success", "failure", "running", "pending", "cancelled", "warning"] as const;
|
|
121
|
-
for (const status of statuses) {
|
|
122
|
-
expect(() => render(StatusPill, { props: { status } })).not.toThrow();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|