Skip to content

Commit

Permalink
Add tests for new variable table functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
desertaxle committed Nov 15, 2024
1 parent 671a931 commit 64bb454
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 5 deletions.
2 changes: 1 addition & 1 deletion ui-v2/src/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function DataTablePageSize<TData>({ table }: DataTablePageSizeProps<TData>) {
table.setPageSize(Number(value));
}}
>
<SelectTrigger>
<SelectTrigger aria-label="Items per page">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
Expand Down
8 changes: 5 additions & 3 deletions ui-v2/src/components/variables/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type VariablesDataTableProps = {
onPaginationChange: OnChangeFn<PaginationState>;
columnFilters: ColumnFiltersState;
onColumnFiltersChange: OnChangeFn<ColumnFiltersState>;
searchDebounceMs?: number;
sorting: components["schemas"]["VariableSort"];
onSortingChange: (sortKey: components["schemas"]["VariableSort"]) => void;
};
Expand All @@ -98,14 +99,15 @@ export const VariablesDataTable = ({
onPaginationChange,
columnFilters,
onColumnFiltersChange,
searchDebounceMs = 500,
sorting,
onSortingChange,
}: VariablesDataTableProps) => {
const initialSearchValue = columnFilters.find(
(filter) => filter.id === "name",
)?.value as string;
const [searchValue, setSearchValue] = useState(initialSearchValue);
const debouncedSearchValue = useDebounce(searchValue, 500);
const [searchValue, setSearchValue] = useState(initialSearchValue ?? "");
const debouncedSearchValue = useDebounce(searchValue, searchDebounceMs);

const handleNameSearchChange = useCallback(
(value: string) => {
Expand Down Expand Up @@ -176,7 +178,7 @@ export const VariablesDataTable = ({
</div>
<div className="xs:col-span-1 md:col-span-2 lg:col-span-2">
<Select value={sorting} onValueChange={onSortingChange}>
<SelectTrigger>
<SelectTrigger aria-label="Variable sort order">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
Expand Down
4 changes: 4 additions & 0 deletions ui-v2/tests/variables/mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ vi.mock("@uiw/react-codemirror", () => ({
theme: () => ({}),
},
}));

vi.mock("@/hooks/use-debounce", () => ({
default: (v: unknown) => v,
}));
228 changes: 227 additions & 1 deletion ui-v2/tests/variables/variables.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@ import "./mocks";
import { render, screen } from "@testing-library/react";
import { VariablesPage } from "@/components/variables/page";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
import {
describe,
it,
expect,
vi,
afterEach,
beforeEach,
beforeAll,
} from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { server } from "../mocks/node";
import { HttpResponse } from "msw";
import { http } from "msw";
import { queryClient } from "@/router";
import type {
ColumnFiltersState,
PaginationState,
} from "@tanstack/react-table";

describe("Variables page", () => {
it("should render with empty state", () => {
Expand All @@ -20,6 +32,11 @@ describe("Variables page", () => {
totalVariableCount={0}
pagination={{ pageIndex: 0, pageSize: 10 }}
onPaginationChange={vi.fn()}
currentVariableCount={0}
columnFilters={[]}
onColumnFiltersChange={vi.fn()}
sorting="CREATED_DESC"
onSortingChange={vi.fn()}
/>
</QueryClientProvider>,
);
Expand Down Expand Up @@ -231,6 +248,26 @@ describe("Variables page", () => {
});

describe("Variables table", () => {
beforeAll(() => {
// Need to mock PointerEvent for the selects to work
class MockPointerEvent extends Event {
button: number;
ctrlKey: boolean;
pointerType: string;

constructor(type: string, props: PointerEventInit) {
super(type, props);
this.button = props.button || 0;
this.ctrlKey = props.ctrlKey || false;
this.pointerType = props.pointerType || "mouse";
}
}
window.PointerEvent =
MockPointerEvent as unknown as typeof window.PointerEvent;
window.HTMLElement.prototype.scrollIntoView = vi.fn();
window.HTMLElement.prototype.releasePointerCapture = vi.fn();
window.HTMLElement.prototype.hasPointerCapture = vi.fn();
});
const originalToLocaleString = Date.prototype.toLocaleString; // eslint-disable-line @typescript-eslint/unbound-method
beforeEach(() => {
// Mock toLocaleString to simulate specific timezone
Expand Down Expand Up @@ -503,5 +540,194 @@ describe("Variables page", () => {
await user.click(screen.getByText("Delete"));
expect(screen.getByText("Variable deleted")).toBeVisible();
});

it("should handle filtering by name", async () => {
const user = userEvent.setup();
const variables = [
{
id: "1",
name: "my-variable",
value: 123,
created: "2021-01-01T00:00:00Z",
updated: "2021-01-01T00:00:00Z",
tags: ["tag1"],
},
];
const onColumnFiltersChange = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<VariablesPage
variables={variables}
totalVariableCount={1}
currentVariableCount={1}
pagination={{ pageIndex: 0, pageSize: 10 }}
onPaginationChange={vi.fn()}
columnFilters={[{ id: "name", value: "start value" }]}
onColumnFiltersChange={onColumnFiltersChange}
sorting="CREATED_DESC"
onSortingChange={vi.fn()}
/>
</QueryClientProvider>,
);

// Clear any initial calls from mounting
onColumnFiltersChange.mockClear();

const nameSearchInput = screen.getByPlaceholderText("Search variables");
expect(nameSearchInput).toHaveValue("start value");

await user.clear(nameSearchInput);
await user.type(nameSearchInput, "my-variable");

const lastCallArgs = onColumnFiltersChange.mock.lastCall?.[0] as (
prev: ColumnFiltersState,
) => ColumnFiltersState;

// Need to resolve the updater function to get the expected value
expect(lastCallArgs([])).toEqual([{ id: "name", value: "my-variable" }]);
});

it("should handle filtering by tags", async () => {
const user = userEvent.setup();
const variables = [
{
id: "1",
name: "my-variable",
value: 123,
created: "2021-01-01T00:00:00Z",
updated: "2021-01-01T00:00:00Z",
tags: ["tag1"],
},
];

const onColumnFiltersChange = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<VariablesPage
variables={variables}
totalVariableCount={1}
currentVariableCount={1}
pagination={{ pageIndex: 0, pageSize: 10 }}
onPaginationChange={vi.fn()}
columnFilters={[{ id: "tags", value: ["tag2"] }]}
onColumnFiltersChange={onColumnFiltersChange}
sorting="CREATED_DESC"
onSortingChange={vi.fn()}
/>
</QueryClientProvider>,
);

// Clear any initial calls from mounting
onColumnFiltersChange.mockClear();

const tagsSearchInput = screen.getByPlaceholderText("Filter by tags");
expect(await screen.findByText("tag2")).toBeVisible();

await user.type(tagsSearchInput, "tag1");
await user.keyboard("{enter}");

const lastCallArgs = onColumnFiltersChange.mock.lastCall?.[0] as (
prev: ColumnFiltersState,
) => ColumnFiltersState;

expect(lastCallArgs([])).toEqual([
{ id: "tags", value: ["tag2", "tag1"] },
]);
});

it("should handle sorting", async () => {
const user = userEvent.setup();
const variables = [
{
id: "1",
name: "my-variable",
value: 123,
created: "2021-01-01T00:00:00Z",
updated: "2021-01-01T00:00:00Z",
tags: ["tag1"],
},
];

const onSortingChange = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<VariablesPage
variables={variables}
totalVariableCount={1}
currentVariableCount={1}
pagination={{ pageIndex: 0, pageSize: 10 }}
onPaginationChange={vi.fn()}
columnFilters={[]}
onColumnFiltersChange={vi.fn()}
sorting="CREATED_DESC"
onSortingChange={onSortingChange}
/>
</QueryClientProvider>,
);

const select = screen.getByRole("combobox", {
name: "Variable sort order",
});
expect(screen.getByText("Created")).toBeVisible();

await user.click(select);
await user.click(screen.getByText("A to Z"));
expect(onSortingChange).toHaveBeenCalledWith("NAME_ASC");

await user.click(select);
await user.click(screen.getByText("Z to A"));
expect(onSortingChange).toHaveBeenCalledWith("NAME_DESC");
});

it("should emit when updating items per page", async () => {
const user = userEvent.setup();
const variables = [
{
id: "1",
name: "my-variable",
value: 123,
created: "2021-01-01T00:00:00Z",
updated: "2021-01-01T00:00:00Z",
tags: ["tag1"],
},
];
const onPaginationChange = vi.fn();

render(
<QueryClientProvider client={queryClient}>
<VariablesPage
variables={variables}
totalVariableCount={1}
currentVariableCount={1}
pagination={{ pageIndex: 0, pageSize: 10 }}
onPaginationChange={onPaginationChange}
columnFilters={[]}
onColumnFiltersChange={vi.fn()}
sorting="CREATED_DESC"
onSortingChange={vi.fn()}
/>
</QueryClientProvider>,
);

const select = screen.getByRole("combobox", {
name: "Items per page",
});
expect(screen.getByText("10")).toBeVisible();

await user.click(select);
await user.click(screen.getByText("25"));

const lastCallArgs = onPaginationChange.mock.lastCall?.[0] as (
prev: PaginationState,
) => PaginationState;
// Need to resolve the updater function to get the expected value
expect(lastCallArgs({ pageIndex: 0, pageSize: 10 })).toEqual({
pageIndex: 0,
pageSize: 25,
});
});
});
});

0 comments on commit 64bb454

Please sign in to comment.