Mats Bosson 92435b2667 Wizard component
17 tests passing. Implements WizardRoot, WizardStep, WizardStepList, WizardStepTrigger, WizardStepContent, WizardPrev, WizardNext with createControllableSignal step state, completedSteps Set, registerStep/unregisterStep registration, linear guard on triggers, onComplete callback, and Object.assign compound export.
2026-03-29 21:15:55 +07:00

151 lines
5.2 KiB
TypeScript

import { fireEvent, render, screen } from "@solidjs/testing-library";
import type { JSX } from "solid-js";
import { describe, expect, it, vi } from "vitest";
import { Wizard, WizardMeta, WizardRootPropsSchema } from "../../../src/components/wizard/index";
import type { WizardRootProps } from "../../../src/components/wizard/index";
interface ThreeStepWizardProps extends Omit<WizardRootProps, "children"> {}
function ThreeStepWizard(props: ThreeStepWizardProps): JSX.Element {
return (
<Wizard {...props}>
<Wizard.StepList>
<Wizard.Step index={0}>
<Wizard.StepTrigger data-testid="trigger-0">Step 1</Wizard.StepTrigger>
<Wizard.StepContent data-testid="content-0">Content 0</Wizard.StepContent>
</Wizard.Step>
<Wizard.Step index={1}>
<Wizard.StepTrigger data-testid="trigger-1">Step 2</Wizard.StepTrigger>
<Wizard.StepContent data-testid="content-1">Content 1</Wizard.StepContent>
</Wizard.Step>
<Wizard.Step index={2}>
<Wizard.StepTrigger data-testid="trigger-2">Step 3</Wizard.StepTrigger>
<Wizard.StepContent data-testid="content-2">Content 2</Wizard.StepContent>
</Wizard.Step>
</Wizard.StepList>
<Wizard.Prev data-testid="prev">Prev</Wizard.Prev>
<Wizard.Next data-testid="next">Next</Wizard.Next>
</Wizard>
);
}
function renderWizard(props: ThreeStepWizardProps = {}) {
return render(() => <ThreeStepWizard {...props} />);
}
describe("Wizard — rendering", () => {
it("renders first step content by default", () => {
renderWizard();
expect(screen.getByTestId("content-0")).toBeTruthy();
expect(screen.queryByTestId("content-1")).toBeNull();
expect(screen.queryByTestId("content-2")).toBeNull();
});
it("first trigger has aria-selected=true", () => {
renderWizard();
expect(screen.getByTestId("trigger-0").getAttribute("aria-selected")).toBe("true");
expect(screen.getByTestId("trigger-1").getAttribute("aria-selected")).toBe("false");
});
it("step list has role=tablist", () => {
renderWizard();
expect(screen.getByRole("tablist")).toBeTruthy();
});
it("active content has role=tabpanel", () => {
renderWizard();
expect(screen.getByRole("tabpanel")).toBeTruthy();
});
});
describe("Wizard — navigation", () => {
it("next button advances to step 1", () => {
renderWizard();
fireEvent.click(screen.getByTestId("next"));
expect(screen.getByTestId("content-1")).toBeTruthy();
expect(screen.queryByTestId("content-0")).toBeNull();
});
it("prev button goes back", () => {
renderWizard();
fireEvent.click(screen.getByTestId("next"));
fireEvent.click(screen.getByTestId("prev"));
expect(screen.getByTestId("content-0")).toBeTruthy();
});
it("prev button not disabled after advancing", () => {
renderWizard();
fireEvent.click(screen.getByTestId("next"));
expect(screen.getByTestId("prev")).not.toBeDisabled();
});
});
describe("Wizard — linear mode", () => {
it("unreached step triggers are disabled", () => {
renderWizard();
expect(screen.getByTestId("trigger-1")).toBeDisabled();
expect(screen.getByTestId("trigger-2")).toBeDisabled();
});
it("clicking disabled trigger does not navigate", () => {
renderWizard();
fireEvent.click(screen.getByTestId("trigger-2"));
expect(screen.getByTestId("content-0")).toBeTruthy();
});
it("trigger becomes enabled after completing prior step", () => {
renderWizard();
fireEvent.click(screen.getByTestId("next"));
expect(screen.getByTestId("trigger-1")).not.toBeDisabled();
});
it("non-linear: all triggers enabled from start", () => {
renderWizard({ linear: false });
expect(screen.getByTestId("trigger-2")).not.toBeDisabled();
});
});
describe("Wizard — callbacks", () => {
it("onStepChange called on advance", () => {
const onStepChange = vi.fn();
renderWizard({ onStepChange });
fireEvent.click(screen.getByTestId("next"));
expect(onStepChange).toHaveBeenCalledWith(1);
});
it("onStepChange called on prev", () => {
const onStepChange = vi.fn();
renderWizard({ onStepChange });
fireEvent.click(screen.getByTestId("next"));
fireEvent.click(screen.getByTestId("prev"));
expect(onStepChange).toHaveBeenCalledWith(0);
});
it("onComplete called after last step", () => {
const onComplete = vi.fn();
renderWizard({ onComplete });
fireEvent.click(screen.getByTestId("next"));
fireEvent.click(screen.getByTestId("next"));
fireEvent.click(screen.getByTestId("next"));
expect(onComplete).toHaveBeenCalledTimes(1);
});
});
describe("Wizard — schema and meta", () => {
it("schema validates correct input", () => {
const result = WizardRootPropsSchema.safeParse({ step: 1, linear: true, orientation: "horizontal" });
expect(result.success).toBe(true);
});
it("schema rejects invalid orientation", () => {
const result = WizardRootPropsSchema.safeParse({ orientation: "diagonal" });
expect(result.success).toBe(false);
});
it("meta has name and required parts", () => {
expect(WizardMeta.name).toBe("Wizard");
expect(WizardMeta.parts).toContain("Root");
expect(WizardMeta.parts).toContain("StepContent");
});
});