feat(sidebar): persist custom pinned item ordering
This commit is contained in:
1 parent
b635ea1466
commit
406b7acdbb
16 files changed
+1019
-82
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { treeNodePinKey } from "@/lib/app/pinnedItems";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
|
||||
function installLocalStorage() {
|
||||
const data = new Map<string, string>();
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: vi.fn((key: string) => data.get(key) ?? null),
|
||||
setItem: vi.fn((key: string, value: string) => data.set(key, value)),
|
||||
removeItem: vi.fn((key: string) => data.delete(key)),
|
||||
});
|
||||
}
|
||||
|
||||
function tableNode(name = "users"): TreeNode {
|
||||
return {
|
||||
id: `conn:db:public:${name}`,
|
||||
label: name,
|
||||
type: "table",
|
||||
connectionId: "conn",
|
||||
database: "db",
|
||||
schema: "public",
|
||||
tableName: name,
|
||||
};
|
||||
}
|
||||
|
||||
describe("connectionStore pinned tree node removal", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
installLocalStorage();
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("does not pin a new table that reuses a deleted pinned table identity", async () => {
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const deletedTable = tableNode();
|
||||
store.treeNodes = [
|
||||
{
|
||||
id: "conn",
|
||||
label: "Connection",
|
||||
type: "connection",
|
||||
connectionId: "conn",
|
||||
children: [deletedTable],
|
||||
},
|
||||
];
|
||||
|
||||
store.toggleTreeNodePin(deletedTable);
|
||||
expect(store.isTreeNodePinned(deletedTable)).toBe(true);
|
||||
|
||||
store.removeTreeNode(deletedTable.id);
|
||||
const replacement = tableNode();
|
||||
store.treeNodes[0].children = [replacement];
|
||||
|
||||
expect(store.isTreeNodePinned(replacement)).toBe(false);
|
||||
});
|
||||
|
||||
it("serializes desktop pin saves so an older reorder cannot overwrite the latest one", async () => {
|
||||
const savePinnedTreeNodeIds = vi.fn().mockResolvedValue(undefined);
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => true }));
|
||||
vi.doMock("@/lib/backend/api", () => ({ savePinnedTreeNodeIds }));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const users = tableNode("users");
|
||||
const orders = tableNode("orders");
|
||||
store.treeNodes = [{ id: "conn", label: "Connection", type: "connection", connectionId: "conn", children: [users, orders] }];
|
||||
store.toggleTreeNodePin(users);
|
||||
store.toggleTreeNodePin(orders);
|
||||
await vi.waitFor(() => expect(savePinnedTreeNodeIds).toHaveBeenCalledTimes(2));
|
||||
|
||||
savePinnedTreeNodeIds.mockClear();
|
||||
const snapshots: string[][] = [];
|
||||
const resolvers: Array<() => void> = [];
|
||||
savePinnedTreeNodeIds.mockImplementation((ids: string[]) => {
|
||||
snapshots.push([...ids]);
|
||||
return new Promise<void>((resolve) => resolvers.push(resolve));
|
||||
});
|
||||
|
||||
const usersKey = treeNodePinKey(users);
|
||||
const ordersKey = treeNodePinKey(orders);
|
||||
expect(store.reorderPinnedTreeNodes(usersKey, ordersKey, "after")).toBe(true);
|
||||
await vi.waitFor(() => expect(savePinnedTreeNodeIds).toHaveBeenCalledTimes(1));
|
||||
expect(store.reorderPinnedTreeNodes(ordersKey, usersKey, "after")).toBe(true);
|
||||
|
||||
expect(savePinnedTreeNodeIds).toHaveBeenCalledTimes(1);
|
||||
resolvers[0]!();
|
||||
await vi.waitFor(() => expect(savePinnedTreeNodeIds).toHaveBeenCalledTimes(2));
|
||||
resolvers[1]!();
|
||||
|
||||
expect(snapshots).toEqual([
|
||||
[ordersKey, usersKey],
|
||||
[usersKey, ordersKey],
|
||||
]);
|
||||
});
|
||||
|
||||
it("moves a renamed pinned object to its new identity so recreating the old name is unpinned", async () => {
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const users = tableNode("users");
|
||||
const accounts = tableNode("accounts");
|
||||
store.treeNodes = [{ id: "conn", label: "Connection", type: "connection", connectionId: "conn", children: [users] }];
|
||||
store.toggleTreeNodePin(users);
|
||||
|
||||
store.treeNodes[0].children = [accounts];
|
||||
store.replacePinnedTreeNode(users, accounts);
|
||||
|
||||
const recreatedUsers = tableNode("users");
|
||||
store.treeNodes[0].children = [accounts, recreatedUsers];
|
||||
|
||||
expect(store.isTreeNodePinned(accounts)).toBe(true);
|
||||
expect(store.isTreeNodePinned(recreatedUsers)).toBe(false);
|
||||
});
|
||||
|
||||
it("removes the old pin when a renamed replacement is not loaded in the sidebar", async () => {
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const store = useConnectionStore();
|
||||
const users = tableNode("users");
|
||||
const accounts = tableNode("accounts");
|
||||
store.treeNodes = [{ id: "conn", label: "Connection", type: "connection", connectionId: "conn", children: [users] }];
|
||||
store.toggleTreeNodePin(users);
|
||||
|
||||
expect(store.replacePinnedTreeNode(users, accounts)).toBe(true);
|
||||
expect(store.isTreeNodePinned(users)).toBe(false);
|
||||
expect(store.isTreeNodePinned(accounts)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user