c4a631a5d8
CI Pipeline / test (20) (pull_request) Successful in 2m55s
CI Pipeline / test (18) (pull_request) Successful in 3m32s
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
CI Pipeline / visual-regression (pull_request) Has been cancelled
CI Pipeline / performance (pull_request) Has been cancelled
CI Pipeline / storybook (pull_request) Has been cancelled
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (chromium) (pull_request) Has been cancelled
CI Pipeline / e2e (firefox) (pull_request) Has been cancelled
322 lines
9.8 KiB
React
322 lines
9.8 KiB
React
import React from "react";
|
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { expect, test, describe, it, vi, beforeEach } from "vitest";
|
|
import { axe, toHaveNoViolations } from "jest-axe";
|
|
import ContextMenu from "../../app/components/ContextMenu";
|
|
import ContextMenuItem from "../../app/components/ContextMenuItem";
|
|
import ContextMenuSection from "../../app/components/ContextMenuSection";
|
|
import ContextMenuDivider from "../../app/components/ContextMenuDivider";
|
|
|
|
expect.extend(toHaveNoViolations);
|
|
|
|
describe("ContextMenu Component", () => {
|
|
const defaultProps = {
|
|
children: "Context Menu Content",
|
|
};
|
|
|
|
describe("Rendering", () => {
|
|
it("renders with default props", () => {
|
|
render(<ContextMenu {...defaultProps} />);
|
|
|
|
expect(screen.getByText("Context Menu Content")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders with custom className", () => {
|
|
render(<ContextMenu {...defaultProps} className="custom-class" />);
|
|
|
|
const menu = screen.getByText("Context Menu Content").closest("div");
|
|
expect(menu).toHaveClass("custom-class");
|
|
});
|
|
|
|
it("applies correct base styles", () => {
|
|
render(<ContextMenu {...defaultProps} />);
|
|
|
|
const menu = screen.getByText("Context Menu Content").closest("div");
|
|
expect(menu).toHaveClass(
|
|
"bg-black",
|
|
"border",
|
|
"rounded-[var(--measures-radius-medium)]",
|
|
"shadow-lg",
|
|
"p-[4px]",
|
|
);
|
|
});
|
|
|
|
it("has solid black background", () => {
|
|
render(<ContextMenu {...defaultProps} />);
|
|
|
|
const menu = screen.getByText("Context Menu Content").closest("div");
|
|
expect(menu).toHaveStyle({ backgroundColor: "#000000" });
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
it("has no accessibility violations", async () => {
|
|
const { container } = render(
|
|
<ContextMenu {...defaultProps}>
|
|
<ContextMenuItem onClick={vi.fn()}>Menu Item</ContextMenuItem>
|
|
</ContextMenu>,
|
|
);
|
|
const results = await axe(container);
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
|
|
it("has proper role", () => {
|
|
render(<ContextMenu {...defaultProps} />);
|
|
|
|
const menu = screen.getByText("Context Menu Content").closest("div");
|
|
expect(menu).toHaveAttribute("role", "menu");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("ContextMenuItem Component", () => {
|
|
const defaultProps = {
|
|
children: "Menu Item",
|
|
onClick: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("Rendering", () => {
|
|
it("renders with default props", () => {
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
expect(screen.getByText("Menu Item")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders as selected when selected prop is true", () => {
|
|
render(<ContextMenuItem {...defaultProps} selected={true} />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass(
|
|
"bg-[var(--color-surface-default-secondary)]",
|
|
"rounded-[var(--measures-radius-small)]",
|
|
);
|
|
});
|
|
|
|
it("renders with submenu arrow when hasSubmenu prop is true", () => {
|
|
render(<ContextMenuItem {...defaultProps} hasSubmenu={true} />);
|
|
|
|
// Check for the right-pointing chevron SVG
|
|
const item = screen.getByRole("menuitem");
|
|
const svg = item.querySelector("svg:last-child");
|
|
expect(svg).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders with checkmark when selected prop is true", () => {
|
|
render(<ContextMenuItem {...defaultProps} selected={true} />);
|
|
|
|
// Check for the checkmark SVG
|
|
const item = screen.getByRole("menuitem");
|
|
const svg = item.querySelector("svg:first-child");
|
|
expect(svg).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies correct size styles", () => {
|
|
render(<ContextMenuItem {...defaultProps} size="small" />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass("text-[10px]", "leading-[14px]");
|
|
});
|
|
|
|
it("applies medium size styles", () => {
|
|
render(<ContextMenuItem {...defaultProps} size="medium" />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass("text-[14px]", "leading-[20px]");
|
|
});
|
|
|
|
it("applies large size styles", () => {
|
|
render(<ContextMenuItem {...defaultProps} size="large" />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass("text-[16px]", "leading-[24px]");
|
|
});
|
|
});
|
|
|
|
describe("Interaction", () => {
|
|
it("calls onClick when clicked", async () => {
|
|
const user = userEvent.setup();
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
const item = screen.getByText("Menu Item");
|
|
await user.click(item);
|
|
|
|
expect(defaultProps.onClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not call onClick when disabled", async () => {
|
|
const user = userEvent.setup();
|
|
render(<ContextMenuItem {...defaultProps} disabled={true} />);
|
|
|
|
const item = screen.getByText("Menu Item");
|
|
await user.click(item);
|
|
|
|
expect(defaultProps.onClick).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("has hover effects", () => {
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass(
|
|
"hover:!bg-[var(--color-surface-default-secondary)]",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
it("has no accessibility violations", async () => {
|
|
const { container } = render(
|
|
<ContextMenu>
|
|
<ContextMenuItem {...defaultProps} />
|
|
</ContextMenu>,
|
|
);
|
|
const results = await axe(container);
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
|
|
it("has proper role", () => {
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Styling", () => {
|
|
it("applies correct text color", () => {
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass(
|
|
"text-[var(--color-content-default-brand-primary)]",
|
|
);
|
|
});
|
|
|
|
it("applies correct padding", () => {
|
|
render(<ContextMenuItem {...defaultProps} />);
|
|
|
|
const item = screen.getByRole("menuitem");
|
|
expect(item).toHaveClass("px-[8px]", "py-[4px]");
|
|
});
|
|
|
|
it("applies correct gap between checkmark and text", () => {
|
|
render(<ContextMenuItem {...defaultProps} selected={true} />);
|
|
|
|
const item = screen.getByText("Menu Item").closest("div");
|
|
expect(item).toHaveClass("gap-[8px]");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("ContextMenuSection Component", () => {
|
|
const defaultProps = {
|
|
title: "Section Title",
|
|
children: "Section Content",
|
|
};
|
|
|
|
describe("Rendering", () => {
|
|
it("renders with title and children", () => {
|
|
render(<ContextMenuSection {...defaultProps} />);
|
|
|
|
expect(screen.getByText("Section Title")).toBeInTheDocument();
|
|
expect(screen.getByText("Section Content")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders without title when not provided", () => {
|
|
render(<ContextMenuSection>Section Content</ContextMenuSection>);
|
|
|
|
expect(screen.getByText("Section Content")).toBeInTheDocument();
|
|
expect(screen.queryByText("Section Title")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("applies correct title styling", () => {
|
|
render(<ContextMenuSection {...defaultProps} />);
|
|
|
|
const title = screen.getByText("Section Title");
|
|
expect(title).toHaveClass(
|
|
"text-[var(--color-content-default-primary)]",
|
|
"font-medium",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
it("has no accessibility violations", async () => {
|
|
const { container } = render(<ContextMenuSection {...defaultProps} />);
|
|
const results = await axe(container);
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("ContextMenuDivider Component", () => {
|
|
describe("Rendering", () => {
|
|
it("renders divider", () => {
|
|
render(<ContextMenuDivider />);
|
|
|
|
const divider = screen.getByRole("separator");
|
|
expect(divider).toBeInTheDocument();
|
|
});
|
|
|
|
it("applies correct styling", () => {
|
|
render(<ContextMenuDivider />);
|
|
|
|
const divider = screen.getByRole("separator");
|
|
expect(divider).toHaveClass(
|
|
"border-t",
|
|
"border-[var(--color-border-default-tertiary)]",
|
|
"my-1",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
it("has no accessibility violations", async () => {
|
|
const { container } = render(<ContextMenuDivider />);
|
|
const results = await axe(container);
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("ContextMenu Components Integration", () => {
|
|
const TestMenu = () => (
|
|
<ContextMenu>
|
|
<ContextMenuSection title="First Section">
|
|
<ContextMenuItem onClick={vi.fn()}>Item 1</ContextMenuItem>
|
|
<ContextMenuItem onClick={vi.fn()} selected={true}>
|
|
Item 2
|
|
</ContextMenuItem>
|
|
</ContextMenuSection>
|
|
<ContextMenuDivider />
|
|
<ContextMenuSection title="Second Section">
|
|
<ContextMenuItem onClick={vi.fn()} hasSubmenu={true}>
|
|
Item 3
|
|
</ContextMenuItem>
|
|
</ContextMenuSection>
|
|
</ContextMenu>
|
|
);
|
|
|
|
it("renders all components together", () => {
|
|
render(<TestMenu />);
|
|
|
|
expect(screen.getByText("First Section")).toBeInTheDocument();
|
|
expect(screen.getByText("Item 1")).toBeInTheDocument();
|
|
expect(screen.getByText("Item 2")).toBeInTheDocument();
|
|
expect(screen.getByText("Second Section")).toBeInTheDocument();
|
|
expect(screen.getByText("Item 3")).toBeInTheDocument();
|
|
expect(screen.getByRole("separator")).toBeInTheDocument();
|
|
});
|
|
|
|
it("has no accessibility violations when integrated", async () => {
|
|
const { container } = render(<TestMenu />);
|
|
const results = await axe(container);
|
|
expect(results).toHaveNoViolations();
|
|
});
|
|
});
|