test: simplify file-tree selection & login mocking
This commit is contained in:
parent
4fec2777ce
commit
c93b076bf6
2 changed files with 60 additions and 127 deletions
|
|
@ -1,8 +1,44 @@
|
||||||
/** biome-ignore-all lint/style/noNonNullAssertion: Testing file - non-null assertions are acceptable here */
|
/** biome-ignore-all lint/style/noNonNullAssertion: Testing file - non-null assertions are acceptable here */
|
||||||
import { expect, test, describe } from "bun:test";
|
import { expect, test, describe } from "bun:test";
|
||||||
import { render, screen, fireEvent } from "@testing-library/react";
|
import { render, screen, fireEvent, within } from "@testing-library/react";
|
||||||
|
import { useState } from "react";
|
||||||
import { FileTree, type FileEntry } from "../file-tree";
|
import { FileTree, type FileEntry } from "../file-tree";
|
||||||
|
|
||||||
|
const getCheckboxFor = (name: string) => {
|
||||||
|
const row = screen.getByRole("button", { name });
|
||||||
|
return within(row).getByRole("checkbox");
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileTreeSelection = ({
|
||||||
|
files,
|
||||||
|
initialSelectedPaths = [],
|
||||||
|
expandedFolders,
|
||||||
|
}: {
|
||||||
|
files: FileEntry[];
|
||||||
|
initialSelectedPaths?: string[];
|
||||||
|
expandedFolders?: Set<string>;
|
||||||
|
}) => {
|
||||||
|
const [selectedPaths, setSelectedPaths] = useState(() => new Set(initialSelectedPaths));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FileTree
|
||||||
|
files={files}
|
||||||
|
withCheckboxes={true}
|
||||||
|
selectedPaths={selectedPaths}
|
||||||
|
onSelectionChange={setSelectedPaths}
|
||||||
|
expandedFolders={expandedFolders}
|
||||||
|
/>
|
||||||
|
<output aria-label="Selected paths">{JSON.stringify(Array.from(selectedPaths).sort())}</output>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedPaths = () => {
|
||||||
|
const selectedPaths = screen.getByLabelText("Selected paths").textContent;
|
||||||
|
return JSON.parse(selectedPaths ?? "[]") as string[];
|
||||||
|
};
|
||||||
|
|
||||||
describe("FileTree Pagination", () => {
|
describe("FileTree Pagination", () => {
|
||||||
const testFiles: FileEntry[] = [
|
const testFiles: FileEntry[] = [
|
||||||
{ name: "root", path: "/root", type: "folder" },
|
{ name: "root", path: "/root", type: "folder" },
|
||||||
|
|
@ -157,154 +193,67 @@ describe("FileTree Selection Logic", () => {
|
||||||
{ name: "upload", path: "/root/photos/upload", type: "folder" },
|
{ name: "upload", path: "/root/photos/upload", type: "folder" },
|
||||||
];
|
];
|
||||||
|
|
||||||
test("selecting a folder simplifies to parent if it's the only child", async () => {
|
test("selecting a folder simplifies to parent if it's the only child", () => {
|
||||||
let currentSelection = new Set<string>();
|
render(<FileTreeSelection files={testFiles} expandedFolders={new Set(testFiles.map((f) => f.path))} />);
|
||||||
const onSelectionChange = (selection: Set<string>) => {
|
|
||||||
currentSelection = selection;
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
fireEvent.click(getCheckboxFor("photos"));
|
||||||
<FileTree
|
|
||||||
files={testFiles}
|
|
||||||
withCheckboxes={true}
|
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
expandedFolders={new Set(testFiles.map((f) => f.path))}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const photosCheckbox = screen.getByText("photos").parentElement?.querySelector('button[role="checkbox"]');
|
expect(getSelectedPaths()).toEqual(["/root"]);
|
||||||
expect(photosCheckbox).toBeTruthy();
|
|
||||||
|
|
||||||
fireEvent.click(photosCheckbox!);
|
|
||||||
|
|
||||||
expect(currentSelection.has("/root")).toBe(true);
|
|
||||||
expect(currentSelection.size).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("unselecting a child removes the parent from selection", async () => {
|
test("unselecting a child removes the parent from selection", () => {
|
||||||
let currentSelection = new Set<string>(["/root"]);
|
|
||||||
const onSelectionChange = (selection: Set<string>) => {
|
|
||||||
currentSelection = selection;
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<FileTree
|
<FileTreeSelection
|
||||||
files={testFiles}
|
files={testFiles}
|
||||||
withCheckboxes={true}
|
initialSelectedPaths={["/root"]}
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
expandedFolders={new Set(testFiles.map((f) => f.path))}
|
expandedFolders={new Set(testFiles.map((f) => f.path))}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const libraryCheckbox = screen.getByText("library").parentElement?.querySelector('button[role="checkbox"]');
|
fireEvent.click(getCheckboxFor("library"));
|
||||||
fireEvent.click(libraryCheckbox!);
|
|
||||||
|
|
||||||
expect(currentSelection.has("/root")).toBe(false);
|
expect(getSelectedPaths()).toEqual(["/root/photos/backups", "/root/photos/profile", "/root/photos/upload"]);
|
||||||
expect(currentSelection.has("/root/photos")).toBe(false);
|
|
||||||
|
|
||||||
expect(currentSelection.has("/root/photos/backups")).toBe(true);
|
|
||||||
expect(currentSelection.has("/root/photos/profile")).toBe(true);
|
|
||||||
expect(currentSelection.has("/root/photos/upload")).toBe(true);
|
|
||||||
expect(currentSelection.size).toBe(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("recursive simplification when all children are selected", async () => {
|
test("recursive simplification when all children are selected", () => {
|
||||||
let currentSelection = new Set<string>();
|
render(<FileTreeSelection files={testFiles} expandedFolders={new Set(testFiles.map((f) => f.path))} />);
|
||||||
const onSelectionChange = (selection: Set<string>) => {
|
|
||||||
currentSelection = selection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { rerender } = render(
|
|
||||||
<FileTree
|
|
||||||
files={testFiles}
|
|
||||||
withCheckboxes={true}
|
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
expandedFolders={new Set(testFiles.map((f) => f.path))}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const children = ["backups", "library", "profile", "upload"];
|
const children = ["backups", "library", "profile", "upload"];
|
||||||
|
|
||||||
for (const name of children) {
|
for (const name of children) {
|
||||||
const checkbox = screen.getByText(name).parentElement?.querySelector('button[role="checkbox"]');
|
fireEvent.click(getCheckboxFor(name));
|
||||||
fireEvent.click(checkbox!);
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<FileTree
|
|
||||||
files={testFiles}
|
|
||||||
withCheckboxes={true}
|
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
expandedFolders={new Set(testFiles.map((f) => f.path))}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(currentSelection.has("/root")).toBe(true);
|
expect(getSelectedPaths()).toEqual(["/root"]);
|
||||||
expect(currentSelection.size).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not simplify to parent if not all children are selected", async () => {
|
test("does not simplify to parent if not all children are selected", () => {
|
||||||
const multipleFiles: FileEntry[] = [
|
const multipleFiles: FileEntry[] = [
|
||||||
{ name: "root", path: "/root", type: "folder" },
|
{ name: "root", path: "/root", type: "folder" },
|
||||||
{ name: "child1", path: "/root/child1", type: "folder" },
|
{ name: "child1", path: "/root/child1", type: "folder" },
|
||||||
{ name: "child2", path: "/root/child2", type: "folder" },
|
{ name: "child2", path: "/root/child2", type: "folder" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let currentSelection = new Set<string>();
|
render(<FileTreeSelection files={multipleFiles} expandedFolders={new Set(multipleFiles.map((f) => f.path))} />);
|
||||||
const onSelectionChange = (selection: Set<string>) => {
|
|
||||||
currentSelection = selection;
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
fireEvent.click(getCheckboxFor("child1"));
|
||||||
<FileTree
|
|
||||||
files={multipleFiles}
|
|
||||||
withCheckboxes={true}
|
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
expandedFolders={new Set(multipleFiles.map((f) => f.path))}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const child1Checkbox = screen.getByText("child1").parentElement?.querySelector('button[role="checkbox"]');
|
expect(getSelectedPaths()).toEqual(["/root/child1"]);
|
||||||
fireEvent.click(child1Checkbox!);
|
|
||||||
|
|
||||||
expect(currentSelection.has("/root/child1")).toBe(true);
|
|
||||||
expect(currentSelection.has("/root")).toBe(false);
|
|
||||||
expect(currentSelection.size).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("simplifies existing deep paths when parent is selected", async () => {
|
test("simplifies existing deep paths when parent is selected", () => {
|
||||||
const files: FileEntry[] = [
|
const files: FileEntry[] = [
|
||||||
{ name: "hello", path: "/hello", type: "folder" },
|
{ name: "hello", path: "/hello", type: "folder" },
|
||||||
{ name: "hello_prev", path: "/hello_prev", type: "folder" },
|
{ name: "hello_prev", path: "/hello_prev", type: "folder" },
|
||||||
{ name: "service", path: "/service", type: "folder" },
|
{ name: "service", path: "/service", type: "folder" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let currentSelection = new Set<string>(["/hello", "/hello_prev", "/service/app/data/upload"]);
|
|
||||||
const onSelectionChange = (selection: Set<string>) => {
|
|
||||||
currentSelection = selection;
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<FileTree
|
<FileTreeSelection files={files} initialSelectedPaths={["/hello", "/hello_prev", "/service/app/data/upload"]} />,
|
||||||
files={files}
|
|
||||||
withCheckboxes={true}
|
|
||||||
selectedPaths={currentSelection}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const serviceCheckbox = screen.getByText("service").parentElement?.querySelector('button[role="checkbox"]');
|
fireEvent.click(getCheckboxFor("service"));
|
||||||
expect(serviceCheckbox).toBeTruthy();
|
|
||||||
|
|
||||||
fireEvent.click(serviceCheckbox!);
|
expect(getSelectedPaths()).toEqual(["/hello", "/hello_prev", "/service"]);
|
||||||
|
|
||||||
expect(currentSelection.has("/service")).toBe(true);
|
|
||||||
expect(currentSelection.has("/service/app/data/upload")).toBe(false);
|
|
||||||
expect(currentSelection.size).toBe(3); // /hello, /hello_prev, /service
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,8 @@ await mock.module("@tanstack/react-router", () => ({
|
||||||
useNavigate: () => mock(() => {}),
|
useNavigate: () => mock(() => {}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await mock.module("~/client/api-client/@tanstack/react-query.gen", () => ({
|
await mock.module("~/client/modules/sso/components/sso-login-section", () => ({
|
||||||
getPublicSsoProvidersOptions: () => ({
|
SsoLoginSection: () => null,
|
||||||
queryKey: ["public-sso-providers"],
|
|
||||||
queryFn: async () => ({ providers: [] }),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
await mock.module("~/client/lib/auth-client", () => ({
|
|
||||||
authClient: {
|
|
||||||
getSession: mock(async () => ({ data: null })),
|
|
||||||
signIn: {
|
|
||||||
username: mock(async () => ({ data: null, error: null })),
|
|
||||||
sso: mock(async () => ({ data: null, error: null })),
|
|
||||||
},
|
|
||||||
twoFactor: {
|
|
||||||
verifyTotp: mock(async () => ({ data: null, error: null })),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { LoginPage } from "../login";
|
import { LoginPage } from "../login";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue