Fixes on create component tests
This commit is contained in:
@@ -9,7 +9,6 @@ import Progress from "../components/Progress";
|
|||||||
import Create from "../components/Create";
|
import Create from "../components/Create";
|
||||||
import Input from "../components/Input";
|
import Input from "../components/Input";
|
||||||
import InputWithCounter from "../components/InputWithCounter";
|
import InputWithCounter from "../components/InputWithCounter";
|
||||||
import { getAssetPath } from "../../lib/assetUtils";
|
|
||||||
|
|
||||||
export default function ComponentsPreview() {
|
export default function ComponentsPreview() {
|
||||||
const [alertVisible, setAlertVisible] = useState({
|
const [alertVisible, setAlertVisible] = useState({
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ const ContentLockupContainer = memo<ContentLockupProps>(
|
|||||||
titleContainer: "flex items-center justify-start w-full",
|
titleContainer: "flex items-center justify-start w-full",
|
||||||
title:
|
title:
|
||||||
"font-bricolage-grotesque font-bold text-[28px] leading-[36px] tracking-[0] text-[var(--color-content-default-primary)] text-left",
|
"font-bricolage-grotesque font-bold text-[28px] leading-[36px] tracking-[0] text-[var(--color-content-default-primary)] text-left",
|
||||||
|
subtitle:
|
||||||
|
"font-inter font-normal text-[16px] leading-[24px] tracking-[0] text-[var(--color-content-default-tertiary)] text-left",
|
||||||
description:
|
description:
|
||||||
"font-inter font-normal text-[16px] leading-[24px] tracking-[0] text-[var(--color-content-default-tertiary)] text-left",
|
"font-inter font-normal text-[16px] leading-[24px] tracking-[0] text-[var(--color-content-default-tertiary)] text-left",
|
||||||
shape: "w-[16px] h-[16px]",
|
shape: "w-[16px] h-[16px]",
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ const CreateContainer = memo<CreateProps>(
|
|||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
||||||
// Store previous active element
|
// Store previous active element
|
||||||
previousActiveElementRef.current =
|
previousActiveElementRef.current = document.activeElement as HTMLElement;
|
||||||
document.activeElement as HTMLElement;
|
|
||||||
|
|
||||||
// Lock body scroll
|
// Lock body scroll
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
@@ -108,13 +107,13 @@ const CreateContainer = memo<CreateProps>(
|
|||||||
};
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CreateView
|
<CreateView
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
|
// eslint-disable-next-line react/no-children-prop
|
||||||
children={children}
|
children={children}
|
||||||
footerContent={footerContent}
|
footerContent={footerContent}
|
||||||
showBackButton={showBackButton}
|
showBackButton={showBackButton}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */
|
||||||
export interface InputWithCounterProps {
|
export interface InputWithCounterProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -8,3 +9,4 @@ export interface InputWithCounterProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getAssetPath } from "../../../lib/assetUtils";
|
|
||||||
import type { InputWithCounterProps } from "./InputWithCounter.types";
|
import type { InputWithCounterProps } from "./InputWithCounter.types";
|
||||||
|
|
||||||
export function InputWithCounterView({
|
export function InputWithCounterView({
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ export function ModalFooterView({
|
|||||||
{/* Back Button - Absolutely positioned bottom left */}
|
{/* Back Button - Absolutely positioned bottom left */}
|
||||||
{showBackButton && (
|
{showBackButton && (
|
||||||
<div className="absolute left-[16px] top-[12px]">
|
<div className="absolute left-[16px] top-[12px]">
|
||||||
<Button
|
<Button variant="outlined" size="medium" onClick={onBack}>
|
||||||
variant="outlined"
|
|
||||||
size="medium"
|
|
||||||
onClick={onBack}
|
|
||||||
>
|
|
||||||
{defaultBackText}
|
{defaultBackText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type StepperActive = 1 | 2 | 3 | 4 | 5;
|
export type StepperActive = number;
|
||||||
|
|
||||||
export interface StepperProps {
|
export interface StepperProps {
|
||||||
active?: StepperActive;
|
active?: StepperActive;
|
||||||
|
|||||||
@@ -176,6 +176,14 @@ const eslintConfig = [
|
|||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Type definition files - interface properties are used in implementation files
|
||||||
|
{
|
||||||
|
files: ["**/*.types.ts"],
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
@@ -65,15 +65,10 @@ export const Default = Template.bind({});
|
|||||||
Default.args = {
|
Default.args = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
title: "What do you call your group's new policy?",
|
title: "What do you call your group's new policy?",
|
||||||
description:
|
description: "You can also combine or add new approaches to the list",
|
||||||
"You can also combine or add new approaches to the list",
|
|
||||||
children: (
|
children: (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input label="Label" placeholder="Policy name" value="" />
|
||||||
label="Label"
|
|
||||||
placeholder="Policy name"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
||||||
0/48
|
0/48
|
||||||
</p>
|
</p>
|
||||||
@@ -90,15 +85,10 @@ export const WithStepper = Template.bind({});
|
|||||||
WithStepper.args = {
|
WithStepper.args = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
title: "What do you call your group's new policy?",
|
title: "What do you call your group's new policy?",
|
||||||
description:
|
description: "You can also combine or add new approaches to the list",
|
||||||
"You can also combine or add new approaches to the list",
|
|
||||||
children: (
|
children: (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input label="Label" placeholder="Policy name" value="" />
|
||||||
label="Label"
|
|
||||||
placeholder="Policy name"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
||||||
0/48
|
0/48
|
||||||
</p>
|
</p>
|
||||||
@@ -117,15 +107,10 @@ export const Step2 = Template.bind({});
|
|||||||
Step2.args = {
|
Step2.args = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
title: "How should conflicts be resolved?",
|
title: "How should conflicts be resolved?",
|
||||||
description:
|
description: "You can also combine or add new approaches to the list",
|
||||||
"You can also combine or add new approaches to the list",
|
|
||||||
children: (
|
children: (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input label="Label" placeholder="Enter text" value="" />
|
||||||
label="Label"
|
|
||||||
placeholder="Enter text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
@@ -178,15 +163,10 @@ export const NextButtonDisabled = Template.bind({});
|
|||||||
NextButtonDisabled.args = {
|
NextButtonDisabled.args = {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
title: "What do you call your group's new policy?",
|
title: "What do you call your group's new policy?",
|
||||||
description:
|
description: "You can also combine or add new approaches to the list",
|
||||||
"You can also combine or add new approaches to the list",
|
|
||||||
children: (
|
children: (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input label="Label" placeholder="Policy name" value="" />
|
||||||
label="Label"
|
|
||||||
placeholder="Policy name"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
<p className="text-[12px] text-[var(--color-content-default-tertiary)]">
|
||||||
0/48
|
0/48
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
import "@testing-library/jest-dom/vitest";
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import { renderWithProviders } from "../utils/test-utils";
|
||||||
import Create from "../../app/components/Create";
|
import Create from "../../app/components/Create";
|
||||||
import Input from "../../app/components/Input";
|
import Input from "../../app/components/Input";
|
||||||
|
|
||||||
@@ -20,20 +21,22 @@ describe("Create", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders when isOpen is true", () => {
|
it("renders when isOpen is true", () => {
|
||||||
render(<Create {...defaultProps}>Create dialog content</Create>);
|
renderWithProviders(
|
||||||
|
<Create {...defaultProps}>Create dialog content</Create>,
|
||||||
|
);
|
||||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Test Create Dialog")).toBeInTheDocument();
|
expect(screen.getByText("Test Create Dialog")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Create dialog content")).toBeInTheDocument();
|
expect(screen.getByText("Create dialog content")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render when isOpen is false", () => {
|
it("does not render when isOpen is false", () => {
|
||||||
render(<Create {...defaultProps} isOpen={false} />);
|
renderWithProviders(<Create {...defaultProps} isOpen={false} />);
|
||||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls onClose when close button is clicked", () => {
|
it("calls onClose when close button is clicked", () => {
|
||||||
const onClose = vi.fn();
|
const onClose = vi.fn();
|
||||||
render(<Create {...defaultProps} onClose={onClose} />);
|
renderWithProviders(<Create {...defaultProps} onClose={onClose} />);
|
||||||
const closeButton = screen.getByLabelText("Close dialog");
|
const closeButton = screen.getByLabelText("Close dialog");
|
||||||
fireEvent.click(closeButton);
|
fireEvent.click(closeButton);
|
||||||
expect(onClose).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
@@ -41,14 +44,14 @@ describe("Create", () => {
|
|||||||
|
|
||||||
it("calls onClose when ESC key is pressed", () => {
|
it("calls onClose when ESC key is pressed", () => {
|
||||||
const onClose = vi.fn();
|
const onClose = vi.fn();
|
||||||
render(<Create {...defaultProps} onClose={onClose} />);
|
renderWithProviders(<Create {...defaultProps} onClose={onClose} />);
|
||||||
fireEvent.keyDown(document, { key: "Escape" });
|
fireEvent.keyDown(document, { key: "Escape" });
|
||||||
expect(onClose).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls onClose when overlay is clicked", () => {
|
it("calls onClose when overlay is clicked", () => {
|
||||||
const onClose = vi.fn();
|
const onClose = vi.fn();
|
||||||
render(<Create {...defaultProps} onClose={onClose} />);
|
renderWithProviders(<Create {...defaultProps} onClose={onClose} />);
|
||||||
const overlay = document.querySelector(".fixed.inset-0");
|
const overlay = document.querySelector(".fixed.inset-0");
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
fireEvent.click(overlay);
|
fireEvent.click(overlay);
|
||||||
@@ -59,7 +62,7 @@ describe("Create", () => {
|
|||||||
it("renders footer buttons when provided", () => {
|
it("renders footer buttons when provided", () => {
|
||||||
const onBack = vi.fn();
|
const onBack = vi.fn();
|
||||||
const onNext = vi.fn();
|
const onNext = vi.fn();
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
showBackButton={true}
|
showBackButton={true}
|
||||||
@@ -76,7 +79,7 @@ describe("Create", () => {
|
|||||||
|
|
||||||
it("calls onBack when back button is clicked", () => {
|
it("calls onBack when back button is clicked", () => {
|
||||||
const onBack = vi.fn();
|
const onBack = vi.fn();
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
showBackButton={true}
|
showBackButton={true}
|
||||||
@@ -91,7 +94,7 @@ describe("Create", () => {
|
|||||||
|
|
||||||
it("calls onNext when next button is clicked", () => {
|
it("calls onNext when next button is clicked", () => {
|
||||||
const onNext = vi.fn();
|
const onNext = vi.fn();
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
showNextButton={true}
|
showNextButton={true}
|
||||||
@@ -105,7 +108,7 @@ describe("Create", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("disables next button when nextButtonDisabled is true", () => {
|
it("disables next button when nextButtonDisabled is true", () => {
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
showNextButton={true}
|
showNextButton={true}
|
||||||
@@ -118,28 +121,20 @@ describe("Create", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders stepper when currentStep and totalSteps are provided", () => {
|
it("renders stepper when currentStep and totalSteps are provided", () => {
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create {...defaultProps} currentStep={2} totalSteps={5} />,
|
||||||
{...defaultProps}
|
|
||||||
currentStep={2}
|
|
||||||
totalSteps={5}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
const steppers = screen.getAllByRole("progressbar");
|
// Find the stepper by its aria-label
|
||||||
// Find the stepper in the footer (not the progress bar if any)
|
const stepper = screen.getByRole("progressbar", {
|
||||||
const footerStepper = steppers.find((stepper) => {
|
name: "Step 2 of 5",
|
||||||
const parent = stepper.closest(".absolute.bottom-0");
|
|
||||||
return parent !== null;
|
|
||||||
});
|
});
|
||||||
expect(footerStepper).toBeInTheDocument();
|
expect(stepper).toBeInTheDocument();
|
||||||
if (footerStepper) {
|
expect(stepper).toHaveAttribute("aria-valuenow", "2");
|
||||||
expect(footerStepper).toHaveAttribute("aria-valuenow", "2");
|
expect(stepper).toHaveAttribute("aria-valuemax", "5");
|
||||||
expect(footerStepper).toHaveAttribute("aria-valuemax", "5");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders custom footer content", () => {
|
it("renders custom footer content", () => {
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create
|
<Create
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
footerContent={<button>Custom Footer</button>}
|
footerContent={<button>Custom Footer</button>}
|
||||||
@@ -149,33 +144,35 @@ describe("Create", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("has proper ARIA attributes", () => {
|
it("has proper ARIA attributes", () => {
|
||||||
render(<Create {...defaultProps} ariaLabel="Test create dialog" />);
|
renderWithProviders(
|
||||||
|
<Create {...defaultProps} ariaLabel="Test create dialog" />,
|
||||||
|
);
|
||||||
const dialog = screen.getByRole("dialog");
|
const dialog = screen.getByRole("dialog");
|
||||||
expect(dialog).toHaveAttribute("aria-modal", "true");
|
expect(dialog).toHaveAttribute("aria-modal", "true");
|
||||||
expect(dialog).toHaveAttribute("aria-label", "Test create dialog");
|
expect(dialog).toHaveAttribute("aria-label", "Test create dialog");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("locks body scroll when open", () => {
|
it("locks body scroll when open", () => {
|
||||||
render(<Create {...defaultProps} />);
|
renderWithProviders(<Create {...defaultProps} />);
|
||||||
expect(document.body.style.overflow).toBe("hidden");
|
expect(document.body.style.overflow).toBe("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("restores body scroll when closed", () => {
|
it("restores body scroll when closed", () => {
|
||||||
const { rerender } = render(<Create {...defaultProps} />);
|
const { rerender } = renderWithProviders(<Create {...defaultProps} />);
|
||||||
expect(document.body.style.overflow).toBe("hidden");
|
expect(document.body.style.overflow).toBe("hidden");
|
||||||
rerender(<Create {...defaultProps} isOpen={false} />);
|
rerender(<Create {...defaultProps} isOpen={false} />);
|
||||||
expect(document.body.style.overflow).toBe("");
|
expect(document.body.style.overflow).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("traps focus within create dialog", async () => {
|
it("traps focus within create dialog", async () => {
|
||||||
render(
|
renderWithProviders(
|
||||||
<Create {...defaultProps}>
|
<Create {...defaultProps}>
|
||||||
<Input label="Test Input" />
|
<Input label="Test Input" />
|
||||||
</Create>,
|
</Create>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeButton = screen.getByLabelText("Close dialog");
|
const closeButton = screen.getByLabelText("Close dialog");
|
||||||
const input = screen.getByLabelText("Test Input");
|
screen.getByLabelText("Test Input"); // Verify input is rendered
|
||||||
|
|
||||||
// Focus should start on first focusable element (close button)
|
// Focus should start on first focusable element (close button)
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user