diff --git a/frontend/src/components/OnboardingProgressTracker.test.tsx b/frontend/src/components/OnboardingProgressTracker.test.tsx
new file mode 100644
index 0000000..2e3c4f1
--- /dev/null
+++ b/frontend/src/components/OnboardingProgressTracker.test.tsx
@@ -0,0 +1,191 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { OnboardingProgressTracker } from "./OnboardingProgressTracker";
+import React from "react";
+
+// Mock the dependencies
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string) => key,
+}));
+
+// Mock framer-motion to avoid animation issues in tests
+vi.mock("framer-motion", () => ({
+ motion: {
+ div: React.forwardRef(({ children, ...props }: any, ref) =>
{children}
),
+ button: React.forwardRef(({ children, ...props }: any, ref) => ),
+ span: React.forwardRef(({ children, ...props }: any, ref) => {children}),
+ svg: React.forwardRef(({ children, ...props }: any, ref) => ),
+ },
+ AnimatePresence: ({ children }: any) => <>{children}>,
+}));
+
+/**
+ * Unit tests for OnboardingProgressTracker component
+ */
+describe("OnboardingProgressTracker", () => {
+ const mockSteps = [
+ {
+ id: "1",
+ title: "Step 1",
+ description: "Description 1",
+ completed: true,
+ required: true,
+ order: 1,
+ },
+ {
+ id: "2",
+ title: "Step 2",
+ description: "Description 2",
+ completed: false,
+ required: true,
+ order: 2,
+ },
+ {
+ id: "3",
+ title: "Step 3",
+ description: "Description 3",
+ completed: false,
+ required: false,
+ order: 3,
+ },
+ ];
+
+ const defaultProps = {
+ steps: mockSteps,
+ onStepChange: vi.fn(),
+ onComplete: vi.fn(),
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe("Rendering", () => {
+ it("should render the component with correct title", () => {
+ render();
+ expect(screen.getByText("onboarding.title")).toBeInTheDocument();
+ });
+
+ it("should render all steps with correct titles and descriptions", () => {
+ render();
+
+ mockSteps.forEach(step => {
+ expect(screen.getByText(step.title)).toBeInTheDocument();
+ expect(screen.getByText(step.description)).toBeInTheDocument();
+ });
+ });
+
+ it("should show correct progress percentage", () => {
+ render();
+ // 1 out of 3 steps completed = 33%
+ expect(screen.getByText("33%")).toBeInTheDocument();
+ });
+
+ it("should show progress bar with correct width", () => {
+ render();
+ const progressBar = screen.getByLabelText("onboarding.progressBar");
+ expect(progressBar).toHaveStyle("width: 33%");
+ });
+ });
+
+ describe("Interactions", () => {
+ it("should call onStepChange when a step is clicked", () => {
+ render();
+
+ // The aria-label is constructed as: `Step ${index + 1}: ${step.title}${step.completed ? ". Completed" : ""}${step.required ? ". Required" : ""}`
+ const secondStepButton = screen.getByLabelText(/Step 2: Step 2. Required/i);
+ fireEvent.click(secondStepButton);
+
+ expect(defaultProps.onStepChange).toHaveBeenCalledWith("2");
+ });
+
+ it("should update announcement text when a step is clicked", async () => {
+ render();
+
+ const secondStepButton = screen.getByLabelText(/Step 2: Step 2. Required/i);
+ fireEvent.click(secondStepButton);
+
+ const announcementArea = screen.getByRole("status");
+ // The announcement text is: `${t("onboarding.stepProgress") || "Step"} ${step.order}: ${step.title}. ${step.description}`
+ expect(announcementArea.textContent).toContain("Step 2: Step 2. Description 2");
+ });
+ });
+
+ describe("Completion Logic", () => {
+ it("should call onComplete when all required steps are completed", async () => {
+ const completedRequiredSteps = [
+ { ...mockSteps[0], completed: true },
+ { ...mockSteps[1], completed: true },
+ { ...mockSteps[2], completed: false }, // Not required
+ ];
+
+ render();
+
+ await waitFor(() => {
+ expect(defaultProps.onComplete).toHaveBeenCalled();
+ });
+ expect(screen.getByText("onboarding.allCompleted")).toBeInTheDocument();
+ expect(screen.getByText("onboarding.successTitle")).toBeInTheDocument();
+ });
+
+ it("should not call onComplete if a required step is missing", () => {
+ const incompleteRequiredSteps = [
+ { ...mockSteps[0], completed: true },
+ { ...mockSteps[1], completed: false }, // Required
+ { ...mockSteps[2], completed: true }, // Not required
+ ];
+
+ render();
+
+ expect(defaultProps.onComplete).not.toHaveBeenCalled();
+ expect(screen.queryByText("onboarding.successTitle")).not.toBeInTheDocument();
+ });
+ });
+
+ describe("Accessibility", () => {
+ it("should have proper ARIA attributes for the container", () => {
+ render();
+ const region = screen.getByRole("region");
+ expect(region).toHaveAttribute("aria-label", "onboarding.progressTracker");
+ expect(region).toHaveAttribute("aria-live", "polite");
+ });
+
+ it("should have proper ARIA attributes for the steps list", () => {
+ render();
+ const list = screen.getByRole("list");
+ expect(list).toHaveAttribute("aria-label", "onboarding.stepsList");
+ });
+
+ it("should mark the current step with aria-current='step'", () => {
+ // By default currentStep is steps[0].id if not provided
+ render();
+ const firstStepButton = screen.getByLabelText(/Step 1: Step 1. Completed. Required/i);
+ expect(firstStepButton).toHaveAttribute("aria-current", "step");
+ });
+ });
+
+ describe("Props and Variants", () => {
+ it("should not show step numbers when showStepNumbers is false", () => {
+ render();
+ expect(screen.queryByText("1")).not.toBeInTheDocument();
+ expect(screen.queryByText("2")).not.toBeInTheDocument();
+ });
+
+ it("should apply compact padding when compact prop is true", () => {
+ const { container } = render();
+ const innerContainer = container.querySelector(".p-4");
+ expect(innerContainer).toBeInTheDocument();
+ });
+
+ it("should apply horizontal layout classes when orientation is horizontal", () => {
+ render();
+ const list = screen.getByRole("list");
+ expect(list).toHaveClass("flex");
+ expect(list).toHaveClass("gap-4");
+ });
+ });
+});