diff --git a/src/components/atoms/Label/Label.test.tsx b/src/components/atoms/Label/Label.test.tsx new file mode 100644 index 0000000..254998f --- /dev/null +++ b/src/components/atoms/Label/Label.test.tsx @@ -0,0 +1,34 @@ +import {render, screen} from "@testing-library/react"; +import {describe, expect, it} from "vitest"; +import {Label} from "./Label"; + +describe("Label Atom", () => { + it("renders the label text correctly", () => { + render(); + + expect(screen.getByText("Email Address")).toBeInTheDocument(); + }); + + it("applies the htmlFor attribute correctly", () => { + render(); + + const label = screen.getByText("Email"); + expect(label).toHaveAttribute("for", "email"); + }); + + it("shows required indicator when required prop is true", () => { + render( + + ); + + expect(screen.getByText("*")).toBeInTheDocument(); + }); + + it("does not show required indicator when required is false", () => { + render(); + + expect(screen.queryByText("*")).not.toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/components/atoms/Label/Label.tsx b/src/components/atoms/Label/Label.tsx new file mode 100644 index 0000000..5a80bf8 --- /dev/null +++ b/src/components/atoms/Label/Label.tsx @@ -0,0 +1,58 @@ +// src/components/atoms/Label/Label.tsx +import type {LabelProps} from "./LabelProps"; + +const sizeClasses: Record, string> = { + sm: "text-xs", + md: "text-sm", + lg: "text-base", +}; + +/** + * A form label component that supports various sizes, required indicators, and disabled states. + * @see LabelProps + * @example + * + */ +export default function Label({ + htmlFor, + children, + required, + size = "md", + disabled, + className, + }: Readonly) { + return ( + + ); +} diff --git a/src/components/atoms/Label/LabelProps.ts b/src/components/atoms/Label/LabelProps.ts new file mode 100644 index 0000000..87c241c --- /dev/null +++ b/src/components/atoms/Label/LabelProps.ts @@ -0,0 +1,24 @@ +// src/components/atoms/Label/LabelProps.ts +import type {ReactNode} from "react"; + +export type LabelSize = "sm" | "md" | "lg"; + +export interface LabelProps { + /** id of the input this label is associated with */ + htmlFor?: string; + + /** label text */ + children: ReactNode; + + /** show required asterisk */ + required?: boolean; + + /** visual size */ + size?: LabelSize; + + /** disabled appearance */ + disabled?: boolean; + + /** extra tailwind classes */ + className?: string; +} diff --git a/src/components/layout/Header/Header.test.tsx b/src/components/layout/Header/Header.test.tsx new file mode 100644 index 0000000..2465a1a --- /dev/null +++ b/src/components/layout/Header/Header.test.tsx @@ -0,0 +1,56 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import Header from "./Header"; + +describe("Header component", () => { + const mockLogout = vi.fn(); + + const defaultProps = { + user: { name: "Karuna" }, + onLogout: mockLogout, + }; + + it("renders the navbar container", () => { + render(
); + expect(screen.getByRole("banner")).toBeInTheDocument(); + }); + + it("renders the app title", () => { + render(
); + expect(screen.getByText("daisyUI")).toBeInTheDocument(); + }); + + it("renders the hamburger menu link", () => { + render(
); + const menuLink = screen.getAllByRole("link")[0]; + expect(menuLink).toHaveAttribute("href", "/"); + }); + + it("renders profile button", () => { + render(
); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("shows the first letter of the user's name in profile image", () => { + render(
); + const profileImg = screen.getByAltText("Karuna profile"); + expect(profileImg).toHaveAttribute( + "src", + expect.stringContaining("K") + ); + }); + + it("uses fallback text when user is undefined", () => { + render(
); + expect(screen.getByAltText("User profile")).toBeInTheDocument(); + }); + + it("calls onLogout when profile button is clicked", () => { + render(
); + const profileButton = screen.getByRole("button"); + + fireEvent.click(profileButton); + + expect(mockLogout).toHaveBeenCalledOnce(); + }); +});