From f7ac0e3f224317ef8664be718bc6ff75ce37e821 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Thu, 20 Mar 2025 17:05:24 -0700 Subject: [PATCH] test(progress-indicator): add unit tests --- tests/ProgressIndicator.test.svelte | 83 ------ .../ProgressIndicator.test.svelte | 35 +++ .../ProgressIndicator.test.ts | 247 ++++++++++++++++++ 3 files changed, 282 insertions(+), 83 deletions(-) delete mode 100644 tests/ProgressIndicator.test.svelte create mode 100644 tests/ProgressIndicator/ProgressIndicator.test.svelte create mode 100644 tests/ProgressIndicator/ProgressIndicator.test.ts diff --git a/tests/ProgressIndicator.test.svelte b/tests/ProgressIndicator.test.svelte deleted file mode 100644 index 0f1e5cc7..00000000 --- a/tests/ProgressIndicator.test.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/ProgressIndicator/ProgressIndicator.test.svelte b/tests/ProgressIndicator/ProgressIndicator.test.svelte new file mode 100644 index 00000000..4f8a4d06 --- /dev/null +++ b/tests/ProgressIndicator/ProgressIndicator.test.svelte @@ -0,0 +1,35 @@ + + + { + console.log("change", e.detail); + }} +> + {#each steps as step} + + {/each} + diff --git a/tests/ProgressIndicator/ProgressIndicator.test.ts b/tests/ProgressIndicator/ProgressIndicator.test.ts new file mode 100644 index 00000000..649d37cb --- /dev/null +++ b/tests/ProgressIndicator/ProgressIndicator.test.ts @@ -0,0 +1,247 @@ +import { render, screen } from "@testing-library/svelte"; +import ProgressIndicator from "./ProgressIndicator.test.svelte"; +import { user } from "../setup-tests"; + +describe("ProgressIndicator", () => { + describe("Default (horizontal)", () => { + it("should render steps with correct states", () => { + render(ProgressIndicator, { + currentIndex: 2, + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { label: "Step 2", description: "Second step", complete: true }, + { label: "Step 3", description: "Third step", complete: true }, + { label: "Step 4", description: "Fourth step", complete: false }, + ], + }); + + const listItems = screen.getAllByRole("listitem"); + + // Check if all steps are rendered + expect(listItems).toHaveLength(4); + + // Check completed steps + const completedSteps = listItems.filter((step) => + step.classList.contains("bx--progress-step--complete"), + ); + expect(completedSteps).toHaveLength(3); + + // Check current step + expect(listItems[2]).toHaveTextContent("Step 3"); + + // Check incomplete step + const incompleteStep = screen.getByText("Step 4"); + expect(incompleteStep).toBeInTheDocument(); + expect(incompleteStep.closest("li")).not.toHaveClass( + "bx--progress-step--complete", + ); + }); + + it("should update currentIndex when clicking on completed steps", async () => { + const consoleLog = vi.spyOn(console, "log"); + render(ProgressIndicator, { + currentIndex: 2, + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { label: "Step 2", description: "Second step", complete: true }, + { label: "Step 3", description: "Third step", complete: true }, + { label: "Step 4", description: "Fourth step", complete: false }, + ], + }); + + expect(consoleLog).not.toHaveBeenCalled(); + + // Click on a completed step + await user.click(screen.getByText("Step 1")); + expect(consoleLog).toHaveBeenCalledWith("change", 0); + }); + + it("should not update currentIndex when preventChangeOnClick is true", async () => { + const { component } = render(ProgressIndicator, { + currentIndex: 2, + preventChangeOnClick: true, + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { label: "Step 2", description: "Second step", complete: true }, + { label: "Step 3", description: "Third step", complete: true }, + { label: "Step 4", description: "Fourth step", complete: false }, + ], + }); + + const changeHandler = vi.fn(); + component.$on("change", changeHandler); + + // Click on a completed step + await user.click(screen.getByText("Step 1")); + expect(changeHandler).not.toHaveBeenCalled(); + }); + }); + + describe("Invalid and disabled states", () => { + it("should render invalid step", () => { + render(ProgressIndicator, { + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { + label: "Step 2", + description: "Second step", + complete: false, + invalid: true, + disabled: false, + }, + { label: "Step 3", description: "Third step", complete: false }, + ], + }); + + const invalidStep = screen.getByText("Step 2").closest("li"); + expect(invalidStep).toHaveClass("bx--progress-step--incomplete"); + }); + + it("should render disabled steps", () => { + render(ProgressIndicator, { + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { + label: "Step 2", + description: "Second step", + complete: false, + invalid: false, + disabled: true, + }, + { + label: "Step 3", + description: "Third step", + complete: false, + invalid: false, + disabled: true, + }, + ], + }); + + const disabledSteps = screen.getAllByRole("listitem").slice(1); + disabledSteps.forEach((step) => { + expect(step).toHaveClass("bx--progress-step--disabled"); + }); + }); + }); + + describe("Variants", () => { + it("should render vertical variant", () => { + render(ProgressIndicator, { + vertical: true, + steps: [ + { label: "Step 1", description: "First step", complete: false }, + { label: "Step 2", description: "Second step", complete: false }, + { label: "Step 3", description: "Third step", complete: false }, + ], + }); + + const progressIndicator = screen.getByRole("list"); + expect(progressIndicator).toHaveClass("bx--progress--vertical"); + }); + + it("should render with equal spacing", () => { + render(ProgressIndicator, { + spaceEqually: true, + steps: [ + { label: "Step 1", description: "First step", complete: false }, + { label: "Step 2", description: "Second step", complete: false }, + { label: "Step 3", description: "Third step", complete: false }, + ], + }); + + const progressIndicator = screen.getByRole("list"); + expect(progressIndicator).toHaveClass("bx--progress--space-equal"); + }); + + it("should not apply equal spacing in vertical variant", () => { + render(ProgressIndicator, { + vertical: true, + spaceEqually: true, + steps: [ + { label: "Step 1", description: "First step", complete: false }, + { label: "Step 2", description: "Second step", complete: false }, + { label: "Step 3", description: "Third step", complete: false }, + ], + }); + + const progressIndicator = screen.getByRole("list"); + expect(progressIndicator).not.toHaveClass("bx--progress--space-equal"); + }); + }); + + describe("Accessibility", () => { + it("should have correct button attributes for different states", () => { + render(ProgressIndicator, { + currentIndex: 1, + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { label: "Step 2", description: "Second step", complete: false }, + { label: "Step 3", description: "Third step", complete: false }, + ], + }); + + const buttons = screen.getAllByRole("button"); + + // Complete step button should be clickable + expect(buttons[0]).toHaveAttribute("tabindex", "0"); + expect(buttons[0]).toHaveAttribute("aria-disabled", "false"); + expect(buttons[0]).not.toHaveClass( + "bx--progress-step-button--unclickable", + ); + + // Current step button should be unclickable + expect(buttons[1]).toHaveAttribute("tabindex", "-1"); + expect(buttons[1]).toHaveAttribute("aria-disabled", "false"); + expect(buttons[1]).toHaveClass("bx--progress-step-button--unclickable"); + + // Incomplete step button should be unclickable + expect(buttons[2]).toHaveAttribute("tabindex", "0"); + expect(buttons[2]).toHaveAttribute("aria-disabled", "false"); + expect(buttons[2]).not.toHaveClass( + "bx--progress-step-button--unclickable", + ); + }); + + it("should have correct button attributes for disabled state", () => { + render(ProgressIndicator, { + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { + label: "Step 2", + description: "Second step", + complete: false, + disabled: true, + }, + ], + }); + + const disabledButton = screen.getAllByRole("button")[1]; + expect(disabledButton).toHaveAttribute("disabled"); + expect(disabledButton).toHaveAttribute("aria-disabled", "true"); + expect(disabledButton).toHaveAttribute("tabindex", "-1"); + }); + + it("should support keyboard navigation for complete steps", async () => { + const consoleLog = vi.spyOn(console, "log"); + render(ProgressIndicator, { + currentIndex: 1, + steps: [ + { label: "Step 1", description: "First step", complete: true }, + { label: "Step 2", description: "Second step", complete: false }, + ], + }); + + expect(consoleLog).not.toHaveBeenCalled(); + const completeStepButton = screen.getAllByRole("button")[0]; + await user.tab(); + expect(completeStepButton).toHaveFocus(); + + await user.keyboard("{Enter}"); + expect(consoleLog).toHaveBeenCalledWith("change", 0); + + await user.keyboard(" "); + expect(consoleLog).toHaveBeenCalledWith("change", 0); + }); + }); +});