Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 302 additions & 6 deletions apps/desktop/src/features/vault/FileTree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1110,12 +1110,15 @@ describe("FileTree", () => {
const rootMoveTarget = await screen.findByRole("button", {
name: "/ Root",
});
const moveMenu = getFixedMenuElement(rootMoveTarget);
expect(moveMenu).not.toBeNull();
expect(moveMenu).toHaveStyle({
overflowY: "auto",
});
expect(moveMenu?.getAttribute("style")).toContain("max-height:");
expect(screen.getByText("Move to Folder")).toBeInTheDocument();
expect(
screen.getByPlaceholderText("Search folders..."),
).toBeInTheDocument();
expect(getFixedMenuElement(rootMoveTarget)).not.toBeNull();
const picker = screen.getByRole("dialog", { name: "Move to Folder" });
expect(
within(picker).getByRole("button", { name: "notes" }),
).toBeDisabled();

const archiveTargets = await screen.findAllByRole("button", {
name: "archive",
Expand All @@ -1129,6 +1132,299 @@ describe("FileTree", () => {
expect(renameNote).toHaveBeenCalledWith("notes/beta", "archive/beta");
});

it("moves a PDF from the context menu with the folder picker", async () => {
const user = userEvent.setup();

setVaultNotes([]);
setVaultEntries([
buildFolderEntry("docs"),
buildFolderEntry("archive"),
{
id: "docs/reference.pdf",
path: "/vault/docs/reference.pdf",
relative_path: "docs/reference.pdf",
title: "Reference",
file_name: "reference.pdf",
extension: "pdf",
kind: "pdf",
modified_at: 1,
created_at: 1,
size: 256,
mime_type: "application/pdf",
},
]);
vi.mocked(invoke).mockImplementation(async (command) => {
if (command === "move_vault_entry") {
return {
id: "archive/reference.pdf",
path: "/vault/archive/reference.pdf",
relative_path: "archive/reference.pdf",
title: "Reference",
file_name: "reference.pdf",
extension: "pdf",
kind: "pdf",
modified_at: 2,
created_at: 1,
size: 256,
mime_type: "application/pdf",
};
}
if (command === "list_vault_entries") {
return [
buildFolderEntry("docs"),
buildFolderEntry("archive"),
{
id: "archive/reference.pdf",
path: "/vault/archive/reference.pdf",
relative_path: "archive/reference.pdf",
title: "Reference",
file_name: "reference.pdf",
extension: "pdf",
kind: "pdf",
modified_at: 2,
created_at: 1,
size: 256,
mime_type: "application/pdf",
},
];
}
return undefined;
});

renderComponent(<FileTree />);
await expandFolder(user, "docs");

fireEvent.contextMenu(getFileRow("Reference"));
await user.click(
await screen.findByRole("button", { name: "Move File to…" }),
);
await user.click(
within(screen.getByRole("dialog", { name: "Move to Folder" }))
.getByRole("button", { name: "archive" }),
);

await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("move_vault_entry", {
relativePath: "docs/reference.pdf",
newRelativePath: "archive/reference.pdf",
vaultPath: "/vault",
});
});
});

it("moves a generic file from the context menu with the folder picker", async () => {
const user = userEvent.setup();

setVaultNotes([]);
setVaultEntries([
buildFolderEntry("docs"),
buildFolderEntry("archive"),
buildFileEntry("docs/config.toml", "application/toml"),
]);
useSettingsStore
.getState()
.setSetting("fileTreeContentMode", "all_files");
vi.mocked(invoke).mockImplementation(async (command) => {
if (command === "move_vault_entry") {
return {
...buildFileEntry("archive/config.toml", "application/toml"),
modified_at: 2,
};
}
if (command === "list_vault_entries") {
return [
buildFolderEntry("docs"),
buildFolderEntry("archive"),
buildFileEntry("archive/config.toml", "application/toml"),
];
}
return undefined;
});

renderComponent(<FileTree />);
await expandFolder(user, "docs");

fireEvent.contextMenu(getFileRow("config"));
await user.click(
await screen.findByRole("button", { name: "Move File to…" }),
);
await user.click(
within(screen.getByRole("dialog", { name: "Move to Folder" }))
.getByRole("button", { name: "archive" }),
);

await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("move_vault_entry", {
relativePath: "docs/config.toml",
newRelativePath: "archive/config.toml",
vaultPath: "/vault",
});
});
});

it("moves mixed selected notes and files from the context menu", async () => {
const user = userEvent.setup();
const renameNote = vi
.fn()
.mockImplementation(async (_noteId: string, newPath: string) => ({
id: newPath,
path: `/vault/${newPath}.md`,
title: newPath.split("/").pop() ?? newPath,
}));

useVaultStore.setState({ renameNote });
setVaultNotes([
{
id: "notes/alpha",
path: "/vault/notes/alpha.md",
title: "Alpha",
modified_at: 1,
created_at: 1,
},
]);
setVaultEntries([
buildFolderEntry("notes"),
buildFolderEntry("docs"),
buildFolderEntry("archive"),
buildFileEntry("docs/config.toml", "application/toml"),
]);
useSettingsStore
.getState()
.setSetting("fileTreeContentMode", "all_files");
vi.mocked(invoke).mockImplementation(async (command) => {
if (command === "move_vault_entry") {
return {
...buildFileEntry("archive/config.toml", "application/toml"),
modified_at: 2,
};
}
if (command === "list_vault_entries") {
return [
buildFolderEntry("notes"),
buildFolderEntry("docs"),
buildFolderEntry("archive"),
buildFileEntry("archive/config.toml", "application/toml"),
];
}
return undefined;
});

renderComponent(<FileTree />);
await expandFolder(user, "notes");
await expandFolder(user, "docs");

fireEvent.click(getNoteRow("Alpha"), { metaKey: true });
fireEvent.click(getFileRow("config"), { metaKey: true });
fireEvent.contextMenu(getFileRow("config"));

await user.click(
await screen.findByRole("button", {
name: "Move Selected Items to…",
}),
);
await user.click(
within(screen.getByRole("dialog", { name: "Move to Folder" }))
.getByRole("button", { name: "archive" }),
);

await waitFor(() => {
expect(renameNote).toHaveBeenCalledWith("notes/alpha", "archive/alpha");
expect(invoke).toHaveBeenCalledWith("move_vault_entry", {
relativePath: "docs/config.toml",
newRelativePath: "archive/config.toml",
vaultPath: "/vault",
});
});
});

it("shows move for mixed selections opened from a selected folder", async () => {
const user = userEvent.setup();

setVaultNotes([]);
setVaultEntries([
buildFolderEntry("docs"),
buildFolderEntry("archive"),
buildFileEntry("docs/config.toml", "application/toml"),
]);
useSettingsStore
.getState()
.setSetting("fileTreeContentMode", "all_files");

renderComponent(<FileTree />);
await expandFolder(user, "docs");

fireEvent.click(getFolderRow("docs"), { metaKey: true });
fireEvent.click(getFileRow("config"), { metaKey: true });
fireEvent.contextMenu(getFolderRow("docs"));

await user.click(
await screen.findByRole("button", {
name: "Move Selected Items to…",
}),
);

const picker = screen.getByRole("dialog", { name: "Move to Folder" });
expect(
within(picker).getByRole("button", { name: "docs" }),
).toBeDisabled();
expect(
within(picker).getByRole("button", { name: "archive" }),
).toBeEnabled();
});

it("filters folders in the move destination picker", async () => {
const user = userEvent.setup();
const renameNote = vi
.fn()
.mockImplementation(async (_noteId: string, newPath: string) => ({
id: newPath,
path: `/vault/${newPath}.md`,
title: newPath.split("/").pop() ?? newPath,
}));

useVaultStore.setState({ renameNote });
setVaultNotes([
{
id: "notes/alpha",
path: "/vault/notes/alpha.md",
title: "Alpha",
modified_at: 1,
created_at: 1,
},
{
id: "archive/keep",
path: "/vault/archive/keep.md",
title: "Keep",
modified_at: 1,
created_at: 1,
},
{
id: "receipts/may",
path: "/vault/receipts/may.md",
title: "May",
modified_at: 1,
created_at: 1,
},
]);

renderComponent(<FileTree />);
await expandFolder(user, "notes");

fireEvent.contextMenu(getNoteRow("Alpha"));
await user.click(
await screen.findByRole("button", { name: "Move Note to…" }),
);
const picker = screen.getByRole("dialog", { name: "Move to Folder" });
await user.type(within(picker).getByPlaceholderText("Search folders..."), "rece");

expect(
within(picker).getByRole("button", { name: "receipts" }),
).toBeInTheDocument();
expect(
within(picker).queryByRole("button", { name: "archive" }),
).not.toBeInTheDocument();
});

it("opens a note when clicked in the tree", async () => {
const user = userEvent.setup();

Expand Down
Loading
Loading