All checks were successful
ci/woodpecker/push/web Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
|
|
import React from "react";
|
|
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { WidgetPicker } from "../WidgetPicker";
|
|
import type { WidgetPlacement } from "@mosaic/shared";
|
|
|
|
describe("WidgetPicker", (): void => {
|
|
const defaultProps = {
|
|
open: true,
|
|
onClose: vi.fn(),
|
|
onAddWidget: vi.fn(),
|
|
currentLayout: [] as WidgetPlacement[],
|
|
};
|
|
|
|
beforeEach((): void => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should render nothing when closed", (): void => {
|
|
const { container } = render(<WidgetPicker {...defaultProps} open={false} />);
|
|
expect(container.innerHTML).toBe("");
|
|
});
|
|
|
|
it("should render dialog when open", (): void => {
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
expect(screen.getByRole("dialog", { name: "Add Widget" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display Add Widget heading", (): void => {
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
expect(screen.getByText("Add Widget")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render search input", (): void => {
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
expect(screen.getByPlaceholderText("Search widgets...")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display available widgets", (): void => {
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
// Widget registry has multiple widgets; at least one Add button should appear
|
|
const addButtons = screen.getAllByText("Add");
|
|
expect(addButtons.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should filter widgets by search text", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
|
|
const searchInput = screen.getByPlaceholderText("Search widgets...");
|
|
// Type a search term that won't match any widget
|
|
await user.type(searchInput, "zzz-nonexistent-widget-zzz");
|
|
|
|
expect(screen.getByText("No widgets found")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should call onAddWidget when Add is clicked", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
const onAdd = vi.fn();
|
|
render(<WidgetPicker {...defaultProps} onAddWidget={onAdd} />);
|
|
|
|
const addButtons = screen.getAllByText("Add");
|
|
await user.click(addButtons[0]!);
|
|
|
|
expect(onAdd).toHaveBeenCalledTimes(1);
|
|
const placement = onAdd.mock.calls[0]![0] as WidgetPlacement;
|
|
expect(placement).toHaveProperty("i");
|
|
expect(placement).toHaveProperty("x");
|
|
expect(placement).toHaveProperty("y");
|
|
expect(placement).toHaveProperty("w");
|
|
expect(placement).toHaveProperty("h");
|
|
});
|
|
|
|
it("should call onClose when close button is clicked", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<WidgetPicker {...defaultProps} />);
|
|
|
|
await user.click(screen.getByLabelText("Close"));
|
|
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should place new widgets after existing layout items", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
const onAdd = vi.fn();
|
|
const existingLayout: WidgetPlacement[] = [
|
|
{ i: "test-1", x: 0, y: 0, w: 6, h: 3 },
|
|
{ i: "test-2", x: 0, y: 3, w: 6, h: 2 },
|
|
];
|
|
render(<WidgetPicker {...defaultProps} onAddWidget={onAdd} currentLayout={existingLayout} />);
|
|
|
|
const addButtons = screen.getAllByText("Add");
|
|
await user.click(addButtons[0]!);
|
|
|
|
const placement = onAdd.mock.calls[0]![0] as WidgetPlacement;
|
|
// Should be placed at y=5 (3+2) to avoid overlap
|
|
expect(placement.y).toBe(5);
|
|
});
|
|
});
|