Files
SnapOtter/tests/unit/landing/bento-grid.test.tsx
T
SnapOtter 2c45a3a9e8 fix: update tool count to 52 and add ai-canvas-expand to landing page
ai-canvas-expand was added to constants.ts and route files but never
added to the landing page bento grid, causing all hardcoded counts
to remain at 51. This updates all references across source, docs,
i18n, and tests to reflect the correct count of 52 tools.
2026-05-16 10:02:19 +08:00

120 lines
4.0 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import type React from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
vi.mock("framer-motion", () => ({
motion: {
div: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => (
<div {...props}>{children}</div>
),
},
AnimatePresence: ({ children }: React.PropsWithChildren) => <>{children}</>,
}));
import { BentoGrid } from "@landing/components/bento-grid";
afterEach(cleanup);
function getCountText(): string {
const el = screen.getByText(/^Showing/);
return el.textContent ?? "";
}
describe("BentoGrid", () => {
it("renders the section heading", () => {
render(<BentoGrid />);
expect(screen.getByText("52 tools. Zero cloud dependency.")).toBeDefined();
});
it("renders the search input", () => {
render(<BentoGrid />);
expect(screen.getByPlaceholderText("Search tools...")).toBeDefined();
});
it("shows all tools by default", () => {
render(<BentoGrid />);
const text = getCountText();
expect(text).toMatch(/Showing 52 of 52 tools/);
});
it("renders category filter pills including All", () => {
render(<BentoGrid />);
expect(screen.getByText((_, el) => el?.textContent === "All (52)")).toBeDefined();
expect(screen.getByText(/Essentials/)).toBeDefined();
expect(screen.getByText(/AI Tools/)).toBeDefined();
expect(screen.getByText(/Optimization/)).toBeDefined();
});
it("filters tools by search query", () => {
render(<BentoGrid />);
const input = screen.getByPlaceholderText("Search tools...");
fireEvent.change(input, { target: { value: "resize" } });
expect(screen.getByText("Resize")).toBeDefined();
const text = getCountText();
expect(text).toMatch(/Showing \d+ of 52 tools/);
expect(screen.queryByText("OCR / Text Extraction")).toBeNull();
});
it("filters tools by category", () => {
render(<BentoGrid />);
const aiButton = screen.getByText(/AI Tools/);
fireEvent.click(aiButton);
const text = getCountText();
expect(text).toMatch(/Showing 16 of 52 tools/);
expect(screen.getByText("Remove Background")).toBeDefined();
expect(screen.queryByText("Resize")).toBeNull();
});
it("shows empty state when search matches nothing", () => {
render(<BentoGrid />);
const input = screen.getByPlaceholderText("Search tools...");
fireEvent.change(input, { target: { value: "xyznonexistent" } });
expect(screen.getByText("No tools found. Try a different search.")).toBeDefined();
const text = getCountText();
expect(text).toMatch(/Showing 0 of 52 tools/);
});
it("combines search and category filter", () => {
render(<BentoGrid />);
const aiButton = screen.getByText(/AI Tools/);
fireEvent.click(aiButton);
const input = screen.getByPlaceholderText("Search tools...");
fireEvent.change(input, { target: { value: "background" } });
expect(screen.getByText("Remove Background")).toBeDefined();
expect(screen.queryByText("Image Upscaling")).toBeNull();
});
it("clicking All resets category filter", () => {
render(<BentoGrid />);
fireEvent.click(screen.getByText(/AI Tools/));
expect(getCountText()).toMatch(/Showing 16 of 52 tools/);
fireEvent.click(screen.getByText((_, el) => el?.textContent === "All (52)"));
expect(getCountText()).toMatch(/Showing 52 of 52 tools/);
});
it("renders each tool with name and description", () => {
render(<BentoGrid />);
expect(screen.getByText("Crop")).toBeDefined();
expect(screen.getByText("Freeform crop, aspect ratio presets, shape crop")).toBeDefined();
});
it("renders all 8 category pills", () => {
render(<BentoGrid />);
const categoryNames = [
"Essentials",
"Optimization",
"Adjustments",
"AI Tools",
"Watermark & Overlay",
"Utilities",
"Layout & Composition",
"Format & Conversion",
];
for (const name of categoryNames) {
expect(screen.getByText(new RegExp(name))).toBeDefined();
}
});
});