From ff017440a79666e2b83f14d33f27b65a7b0c1871 Mon Sep 17 00:00:00 2001 From: Ghoost-404 Date: Fri, 24 Apr 2026 16:08:30 +0100 Subject: [PATCH] feat/transaction-signer --- .../OnboardingProgressTracker.test.tsx | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 frontend/src/components/OnboardingProgressTracker.test.tsx 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) => {children}), + }, + 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"); + }); + }); +});