- Create StarRating component with interactive/readonly modes, amber stars, size sm/md - Add PersonalRating type and taskCount/avgCostUsd/lastUsedAt to SkillListItem - Add getRatings and addRating to skillRegistryApi - Add Rating System section to DesignGuide with all variants - Fix SkillCard fixture and DesignGuide examples to include new SkillListItem fields
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { SkillCard } from "./SkillCard";
|
|
import type { SkillListItem } from "../api/skillRegistry";
|
|
|
|
// Stub @/lib/router Link as an <a> tag for SSR
|
|
vi.mock("@/lib/router", () => ({
|
|
Link: ({ to, children, ...props }: { to: string; children: React.ReactNode; [key: string]: unknown }) =>
|
|
<a href={to as string} {...props}>{children}</a>,
|
|
}));
|
|
|
|
const mockSkill: SkillListItem = {
|
|
id: "test-source/test-skill",
|
|
name: "Test Skill",
|
|
description: "A test skill for unit testing",
|
|
sourceId: "test-source",
|
|
category: "testing",
|
|
activeVersionId: null,
|
|
removedAt: null,
|
|
averageRating: 4.2,
|
|
ratingCount: 10,
|
|
taskCount: null,
|
|
avgCostUsd: null,
|
|
lastUsedAt: null,
|
|
};
|
|
|
|
describe("SkillCard", () => {
|
|
it("renders skill name as a link", () => {
|
|
const html = renderToStaticMarkup(<SkillCard skill={mockSkill} />);
|
|
expect(html).toContain("Test Skill");
|
|
expect(html).toContain("skills/detail/");
|
|
});
|
|
|
|
it("renders source badge", () => {
|
|
const html = renderToStaticMarkup(<SkillCard skill={mockSkill} />);
|
|
expect(html).toContain("test-source");
|
|
});
|
|
|
|
it("renders star rating when averageRating is non-null", () => {
|
|
const html = renderToStaticMarkup(<SkillCard skill={mockSkill} />);
|
|
expect(html).toContain("4.2");
|
|
});
|
|
|
|
it("shows Install skill button when not installed", () => {
|
|
const html = renderToStaticMarkup(
|
|
<SkillCard skill={mockSkill} onInstall={() => {}} />,
|
|
);
|
|
expect(html).toContain("Install skill");
|
|
});
|
|
|
|
it("shows Update skill button when installed with update", () => {
|
|
const html = renderToStaticMarkup(
|
|
<SkillCard skill={mockSkill} isInstalled hasUpdate onUpdate={() => {}} />,
|
|
);
|
|
expect(html).toContain("Update skill");
|
|
});
|
|
|
|
it("shows update badge when hasUpdate is true", () => {
|
|
const html = renderToStaticMarkup(
|
|
<SkillCard skill={mockSkill} isInstalled hasUpdate onUpdate={() => {}} />,
|
|
);
|
|
expect(html).toContain("Update");
|
|
expect(html).toContain("amber");
|
|
});
|
|
|
|
it("shows check icon when installed without update", () => {
|
|
const html = renderToStaticMarkup(
|
|
<SkillCard skill={mockSkill} isInstalled />,
|
|
);
|
|
expect(html).toContain("Installed");
|
|
});
|
|
|
|
it("shows loading state on install button", () => {
|
|
const html = renderToStaticMarkup(
|
|
<SkillCard skill={mockSkill} onInstall={() => {}} isLoading />,
|
|
);
|
|
expect(html).toContain("Installing");
|
|
});
|
|
});
|