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>
112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
/**
|
|
* WidgetGrid Component Tests
|
|
* Following TDD - write tests first!
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeAll } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { WidgetGrid } from "../WidgetGrid";
|
|
import type { WidgetPlacement } from "@mosaic/shared";
|
|
|
|
// ResizeObserver is not available in jsdom
|
|
beforeAll((): void => {
|
|
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
observe: vi.fn(),
|
|
unobserve: vi.fn(),
|
|
disconnect: vi.fn(),
|
|
}));
|
|
});
|
|
|
|
// Mock react-grid-layout
|
|
vi.mock("react-grid-layout", () => ({
|
|
default: ({ children }: { children: React.ReactNode }): React.JSX.Element => (
|
|
<div data-testid="grid-layout">{children}</div>
|
|
),
|
|
Responsive: ({ children }: { children: React.ReactNode }): React.JSX.Element => (
|
|
<div data-testid="responsive-grid-layout">{children}</div>
|
|
),
|
|
}));
|
|
|
|
describe("WidgetGrid", (): void => {
|
|
const mockLayout: WidgetPlacement[] = [
|
|
{ i: "tasks-1", x: 0, y: 0, w: 2, h: 2 },
|
|
{ i: "calendar-1", x: 2, y: 0, w: 2, h: 2 },
|
|
];
|
|
|
|
const mockOnLayoutChange = vi.fn();
|
|
|
|
it("should render grid layout", (): void => {
|
|
render(<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} />);
|
|
|
|
expect(screen.getByTestId("grid-layout")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render widgets from layout", (): void => {
|
|
render(<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} />);
|
|
|
|
// Should render correct number of widgets
|
|
const widgets = screen.getAllByTestId(/widget-/);
|
|
expect(widgets).toHaveLength(mockLayout.length);
|
|
});
|
|
|
|
it("should call onLayoutChange when layout changes", (): void => {
|
|
const { rerender } = render(
|
|
<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} />
|
|
);
|
|
|
|
const newLayout: WidgetPlacement[] = [
|
|
{ i: "tasks-1", x: 1, y: 0, w: 2, h: 2 },
|
|
{ i: "calendar-1", x: 2, y: 0, w: 2, h: 2 },
|
|
];
|
|
|
|
rerender(<WidgetGrid layout={newLayout} onLayoutChange={mockOnLayoutChange} />);
|
|
|
|
// Layout change handler should be set up (actual calls handled by react-grid-layout)
|
|
expect(mockOnLayoutChange).toBeDefined();
|
|
});
|
|
|
|
it("should support edit mode", (): void => {
|
|
render(<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} isEditing={true} />);
|
|
|
|
// In edit mode, widgets should have edit controls
|
|
expect(screen.getByTestId("grid-layout")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should support read-only mode", (): void => {
|
|
render(
|
|
<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} isEditing={false} />
|
|
);
|
|
|
|
expect(screen.getByTestId("grid-layout")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render empty state when no widgets", (): void => {
|
|
render(<WidgetGrid layout={[]} onLayoutChange={mockOnLayoutChange} />);
|
|
|
|
expect(screen.getByText(/no widgets/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle widget removal", (): void => {
|
|
const mockOnRemoveWidget = vi.fn();
|
|
render(
|
|
<WidgetGrid
|
|
layout={mockLayout}
|
|
onLayoutChange={mockOnLayoutChange}
|
|
onRemoveWidget={mockOnRemoveWidget}
|
|
isEditing={true}
|
|
/>
|
|
);
|
|
|
|
// Widget removal should be supported
|
|
expect(mockOnRemoveWidget).toBeDefined();
|
|
});
|
|
|
|
it("should apply custom className", (): void => {
|
|
const { container } = render(
|
|
<WidgetGrid layout={mockLayout} onLayoutChange={mockOnLayoutChange} className="custom-grid" />
|
|
);
|
|
|
|
expect(container.querySelector(".custom-grid")).toBeInTheDocument();
|
|
});
|
|
});
|