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.
151 lines
5.2 KiB
TypeScript
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");
|
|
});
|
|
});
|