Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/Stepper/Step/Step.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
text-decoration: underline;
}

.step-enabled {
@include vf-focus-themed;
}

.step-disabled {
color: $colors--theme--text-muted;
pointer-events: none;
Expand Down
55 changes: 54 additions & 1 deletion src/components/Stepper/Step/Step.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { createEvent, fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Step from "./Step";
import type { Props } from "./Step";
Expand All @@ -14,6 +14,10 @@ describe("Step component", () => {
handleClick: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it("renders the step with the required props", () => {
render(<Step {...props} />);
expect(screen.getByText("Title")).toBeInTheDocument();
Expand Down Expand Up @@ -47,6 +51,55 @@ describe("Step component", () => {
expect(props.handleClick).toHaveBeenCalled();
});

it("exposes the title as a button role", () => {
render(<Step {...props} />);
expect(screen.getByRole("button", { name: "Title" })).toBeInTheDocument();
});

it("is keyboard focusable and not aria-disabled when enabled", () => {
render(<Step {...props} />);
const title = screen.getByText("Title");
expect(title).toHaveAttribute("tabindex", "0");
expect(title).toHaveAttribute("aria-disabled", "false");
});

it("calls handleClick when Enter is pressed and enabled", async () => {
render(<Step {...props} />);
screen.getByText("Title").focus();
await userEvent.keyboard("{Enter}");
expect(props.handleClick).toHaveBeenCalled();
});

it("calls handleClick and prevents default when Space is pressed and enabled", () => {
render(<Step {...props} />);
const title = screen.getByText("Title");
const event = createEvent.keyDown(title, { key: " " });
fireEvent(title, event);
expect(props.handleClick).toHaveBeenCalled();
expect(event.defaultPrevented).toBe(true);
});

it("is not focusable and is aria-disabled when disabled", () => {
render(<Step {...props} enabled={false} />);
const title = screen.getByText("Title");
expect(title).toHaveAttribute("tabindex", "-1");
expect(title).toHaveAttribute("aria-disabled", "true");
});

it("does not call handleClick on keyboard activation when disabled", () => {
render(<Step {...props} enabled={false} />);
const title = screen.getByText("Title");
fireEvent.keyDown(title, { key: "Enter" });
fireEvent.keyDown(title, { key: " " });
expect(props.handleClick).not.toHaveBeenCalled();
});

it("does not call handleClick when clicked while disabled", async () => {
render(<Step {...props} enabled={false} />);
await userEvent.click(screen.getByText("Title"));
expect(props.handleClick).not.toHaveBeenCalled();
});

it("can display optional label", () => {
render(<Step {...props} label="Optional label" />);

Expand Down
26 changes: 25 additions & 1 deletion src/components/Stepper/Step/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ const Step = ({
}: Props): React.JSX.Element => {
const stepStatusClass = enabled ? "step-enabled" : "step-disabled";

const handleKeyDown = (event: React.KeyboardEvent<HTMLSpanElement>) => {
if (!enabled) {
return;
}
const isEnter = event.key === "Enter";
const isSpace = event.key === " " || event.key === "Spacebar";
if (isEnter || isSpace) {
if (event.repeat) {
return;
}
if (isSpace) {
event.preventDefault();
}
handleClick();
}
};

return (
<div
className={classNames("step", {
Expand All @@ -86,7 +103,14 @@ const Step = ({
/>
)}
<div className="step-content">
<span className={classNames(stepStatusClass)} onClick={handleClick}>
<span
className={classNames(stepStatusClass)}
onClick={enabled ? handleClick : undefined}
onKeyDown={handleKeyDown}
role="button"
tabIndex={enabled ? 0 : -1}
aria-disabled={!enabled}
>
{title}
</span>
{label && (
Expand Down
14 changes: 13 additions & 1 deletion src/components/Stepper/__snapshots__/Stepper.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Stepper component renders the stepper 1`] = `
<ul
Expand All @@ -17,7 +17,10 @@ exports[`Stepper component renders the stepper 1`] = `
class="step-content"
>
<span
aria-disabled="false"
class="step-enabled"
role="button"
tabindex="0"
>
Step 1
</span>
Expand All @@ -42,7 +45,10 @@ exports[`Stepper component renders the stepper 1`] = `
class="step-content"
>
<span
aria-disabled="false"
class="step-enabled"
role="button"
tabindex="0"
>
Step 2
</span>
Expand All @@ -69,7 +75,10 @@ exports[`Stepper component renders the stepper 1`] = `
class="step-content"
>
<span
aria-disabled="false"
class="step-enabled"
role="button"
tabindex="0"
>
Step 3
</span>
Expand Down Expand Up @@ -102,7 +111,10 @@ exports[`Stepper component renders the stepper 1`] = `
class="step-content"
>
<span
aria-disabled="true"
class="step-disabled"
role="button"
tabindex="-1"
>
Step 4
</span>
Expand Down
Loading