import { describe, it, expect, beforeEach, vi } from "vitest"; import { useDbViewerStore } from "./dbViewerStore"; import type { QueryResult, TableInfo, RoleInfo } from "../lib/types"; import * as commands from "../lib/commands"; vi.mock("../lib/commands", () => ({ getDatabases: vi.fn(), getSchemas: vi.fn(), getTables: vi.fn(), })); beforeEach(() => { useDbViewerStore.getState().reset(); }); describe("dbViewerStore", () => { it("starts empty", () => { const state = useDbViewerStore.getState(); expect(state.tabs).toEqual([]); expect(state.activeTabId).toBeNull(); expect(state.changesQueue).toEqual([]); expect(state.databases).toEqual([]); expect(state.schemas).toEqual([]); expect(state.tables).toEqual([]); expect(state.currentDatabase).toBeNull(); expect(state.currentSchema).toBeNull(); expect(state.objectSearchOpen).toBe(false); expect(state.selectedObjectType).toBeNull(); expect(state.schemaTreeLoading).toBe(false); }); it("object search open + selected object type setters", () => { const { setObjectSearchOpen, setSelectedObjectType } = useDbViewerStore.getState(); setObjectSearchOpen(true); expect(useDbViewerStore.getState().objectSearchOpen).toBe(true); setSelectedObjectType("functions"); expect(useDbViewerStore.getState().selectedObjectType).toBe("functions"); setSelectedObjectType(null); expect(useDbViewerStore.getState().selectedObjectType).toBeNull(); }); it("requestedView setter stores then clears", () => { const { setRequestedView } = useDbViewerStore.getState(); setRequestedView("objects"); expect(useDbViewerStore.getState().requestedView).toBe("objects"); setRequestedView(null); expect(useDbViewerStore.getState().requestedView).toBeNull(); }); it("openTab adds a new tab", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const state = useDbViewerStore.getState(); expect(state.tabs).toHaveLength(1); const tab = state.tabs[0]; expect(tab.schema).toBe("public"); expect(tab.table).toBe("users"); expect(tab.page).toBe(1); expect(state.activeTabId).toBe(tab.id); }); it("openTab does not duplicate", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); store.openTab("public", "users"); const state = useDbViewerStore.getState(); expect(state.tabs).toHaveLength(1); }); it("openTab forceNew creates duplicate", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); store.openTab("public", "users"); const tabsAfterTwo = useDbViewerStore.getState().tabs; expect(tabsAfterTwo).toHaveLength(1); store.openTab("public", "users", true); const state = useDbViewerStore.getState(); expect(state.tabs).toHaveLength(2); }); it("closeTab removes tab and switches activeTabId", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tab1Id = useDbViewerStore.getState().tabs[0].id; store.openTab("public", "posts", true); store.closeTab(tab1Id); const state = useDbViewerStore.getState(); expect(state.tabs).toHaveLength(1); expect(state.activeTabId).toBe(state.tabs[0].id); }); it("closeTabsForTable closes matching table tabs and keeps others", () => { useDbViewerStore.getState().openTab("public", "users"); useDbViewerStore.getState().openTab("public", "posts", true); useDbViewerStore.getState().closeTabsForTable("public", "users"); const tabs = useDbViewerStore.getState().tabs; expect(tabs).toHaveLength(1); expect(tabs[0].table).toBe("posts"); }); it("closeTabsForTable fixes the active tab when it is closed", () => { useDbViewerStore.getState().openTab("public", "users"); const usersId = useDbViewerStore.getState().tabs[0].id; useDbViewerStore.getState().openTab("public", "posts", true); useDbViewerStore.getState().setActiveTab(usersId); useDbViewerStore.getState().closeTabsForTable("public", "users"); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); expect(st.activeTabId).toBe(st.tabs[0].id); }); it("reorderTab moves a tab and keeps the active tab by id", () => { useDbViewerStore.getState().openTab("public", "users"); useDbViewerStore.getState().openTab("public", "posts", true); useDbViewerStore.getState().openTab("public", "comments", true); const before = useDbViewerStore.getState().tabs; const activeId = useDbViewerStore.getState().activeTabId; const activeTable = before.find((t) => t.id === activeId)!.table; useDbViewerStore.getState().reorderTab(0, 2); const after = useDbViewerStore.getState().tabs; expect(after.map((t) => t.table)).toEqual(["posts", "comments", "users"]); // Active tab must follow the moved item (tracked by id, not index). expect(useDbViewerStore.getState().activeTabId).toBe(activeId); expect(after.find((t) => t.id === activeId)!.table).toBe(activeTable); }); it("reorderTab ignores out-of-range and no-op moves", () => { useDbViewerStore.getState().openTab("public", "users"); useDbViewerStore.getState().openTab("public", "posts", true); const reorderTab = useDbViewerStore.getState().reorderTab; reorderTab(0, 0); expect(useDbViewerStore.getState().tabs.map((t) => t.table)).toEqual([ "users", "posts", ]); reorderTab(-1, 1); reorderTab(0, 5); expect(useDbViewerStore.getState().tabs.map((t) => t.table)).toEqual([ "users", "posts", ]); }); it("setPage updates pagination", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().tabs[0].id; store.setPage(tabId, 3); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.page).toBe(3); }); it("setTabData updates tab data", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().tabs[0].id; // First set loading to true to verify it gets cleared store.setTabLoading(tabId, true); const mockData: QueryResult = { columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}], rows: [[1, "Alice"]], total_rows: 1, page: 1, page_size: 50, }; store.setTabData(tabId, mockData); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.data).toEqual(mockData); expect(tab.loading).toBe(false); expect(tab.error).toBeNull(); }); it("setTabError sets error", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().tabs[0].id; store.setTabError(tabId, "Something went wrong"); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.error).toBe("Something went wrong"); expect(tab.loading).toBe(false); }); it("addChange appends to queue", () => { const store = useDbViewerStore.getState(); store.addChange({ type: "insert", sql: "INSERT INTO users (id) VALUES (1)" }); const queue = useDbViewerStore.getState().changesQueue; expect(queue).toHaveLength(1); expect(queue[0].status).toBe("pending"); expect(queue[0].id).toBeTruthy(); expect(queue[0].createdAt).toBeGreaterThan(0); }); it("addChange stages a bulk_insert with columns+rows", () => { useDbViewerStore.getState().addChange({ type: "bulk_insert", schema: "public", table: "t", columns: ["a", "b"], rows: [[1, 2]], description: "Import", } as any); const q = useDbViewerStore.getState().changesQueue; expect(q[q.length - 1].type).toBe("bulk_insert"); expect((q[q.length - 1] as any).columns).toEqual(["a", "b"]); expect((q[q.length - 1] as any).rows).toEqual([[1, 2]]); }); it("cancelChange marks change as cancelled", () => { const store = useDbViewerStore.getState(); store.addChange({ type: "insert", sql: "INSERT INTO users (id) VALUES (1)" }); const changeId = useDbViewerStore.getState().changesQueue[0].id; store.cancelChange(changeId); const change = useDbViewerStore.getState().changesQueue[0]; expect(change.status).toBe("cancelled"); }); it("markChangeCommitted updates status", () => { const store = useDbViewerStore.getState(); store.addChange({ type: "insert", sql: "INSERT INTO users (id) VALUES (1)" }); const changeId = useDbViewerStore.getState().changesQueue[0].id; store.markChangeCommitted(changeId); const change = useDbViewerStore.getState().changesQueue[0]; expect(change.status).toBe("committed"); }); it("markChangeFailed updates status and records error", () => { const store = useDbViewerStore.getState(); store.addChange({ type: "insert", sql: "INSERT INTO users (id) VALUES (1)" }); const changeId = useDbViewerStore.getState().changesQueue[0].id; store.markChangeFailed(changeId, "Constraint violation"); const change = useDbViewerStore.getState().changesQueue[0]; expect(change.status).toBe("failed"); expect(change.error).toBe("Constraint violation"); }); it("removeChange drops the change from the queue", () => { useDbViewerStore.getState().addChange({ type: "insert", schema: "public", table: "t", newData: { a: 1 }, description: "x" } as any); const id = useDbViewerStore.getState().changesQueue[0].id; useDbViewerStore.getState().removeChange(id); expect(useDbViewerStore.getState().changesQueue).toHaveLength(0); }); it("updateChange patches a single change in place", () => { useDbViewerStore.getState().addChange({ type: "insert", schema: "public", table: "t", newData: { a: null }, description: "x" } as any); const id = useDbViewerStore.getState().changesQueue[0].id; useDbViewerStore.getState().updateChange(id, { newData: { a: "hello" } }); const item = useDbViewerStore.getState().changesQueue[0]; expect(item.newData).toEqual({ a: "hello" }); expect(item.type).toBe("insert"); expect(item.description).toBe("x"); }); it("clearChanges empties the queue", () => { useDbViewerStore.getState().addChange({ type: "insert", schema: "public", table: "t", newData: { a: 1 }, description: "x" } as any); useDbViewerStore.getState().addChange({ type: "delete", schema: "public", table: "t", primaryKey: { id: 1 }, description: "y" } as any); useDbViewerStore.getState().clearChanges(); expect(useDbViewerStore.getState().changesQueue).toHaveLength(0); }); it("setSchemaTreeLoading toggles the loading flag", () => { const store = useDbViewerStore.getState(); expect(useDbViewerStore.getState().schemaTreeLoading).toBe(false); store.setSchemaTreeLoading(true); expect(useDbViewerStore.getState().schemaTreeLoading).toBe(true); store.setSchemaTreeLoading(false); expect(useDbViewerStore.getState().schemaTreeLoading).toBe(false); }); it("reset clears all state", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); store.addChange({ type: "insert", sql: "INSERT INTO users (id) VALUES (1)" }); store.populate(["mydb"], ["public"], [{ name: "users", schema: "public", table_type: "TABLE" }]); store.reset(); const state = useDbViewerStore.getState(); expect(state.tabs).toEqual([]); expect(state.activeTabId).toBeNull(); expect(state.changesQueue).toEqual([]); expect(state.databases).toEqual([]); expect(state.schemas).toEqual([]); expect(state.tables).toEqual([]); expect(state.currentDatabase).toBeNull(); expect(state.currentSchema).toBeNull(); expect(state.schemaTreeLoading).toBe(false); }); it("populate sets databases, schemas, tables", () => { const tables: TableInfo[] = [ { name: "users", schema: "public", table_type: "TABLE" }, { name: "posts", schema: "public", table_type: "TABLE" }, ]; const store = useDbViewerStore.getState(); store.populate(["mydb", "testdb"], ["public", "private"], tables); const state = useDbViewerStore.getState(); expect(state.databases).toEqual(["mydb", "testdb"]); expect(state.schemas).toEqual(["public", "private"]); expect(state.tables).toEqual(tables); }); it("stageCellEdit appends an update QueueItem with primaryKey + old/new data", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().activeTabId!; store.stageCellEdit({ tabId, schema: "public", table: "users", primaryKey: { id: 1 }, oldData: { name: "Alice" }, newData: { name: "Alicia" }, description: "Edit users.name", }); const q = useDbViewerStore.getState().changesQueue; expect(q).toHaveLength(1); expect(q[0].type).toBe("update"); expect(q[0].primaryKey).toEqual({ id: 1 }); expect(q[0].oldData).toEqual({ name: "Alice" }); expect(q[0].newData).toEqual({ name: "Alicia" }); }); it("re-staging the same cell replaces the pending entry (keeps original oldData)", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().activeTabId!; store.stageCellEdit({ tabId, schema: "public", table: "users", primaryKey: { id: 1 }, oldData: { name: "Alice" }, newData: { name: "Alicia" }, description: "Edit users.name", }); store.stageCellEdit({ tabId, schema: "public", table: "users", primaryKey: { id: 1 }, oldData: { name: "Alice" }, newData: { name: "Alicia 2" }, description: "Edit users.name", }); const q = useDbViewerStore.getState().changesQueue; expect(q).toHaveLength(1); expect(q[0].newData).toEqual({ name: "Alicia 2" }); expect(q[0].oldData).toEqual({ name: "Alice" }); // original DB value preserved }); it("staging a different cell appends a second entry", () => { const store = useDbViewerStore.getState(); store.openTab("public", "users"); const tabId = useDbViewerStore.getState().activeTabId!; store.stageCellEdit({ tabId, schema: "public", table: "users", primaryKey: { id: 1 }, oldData: { name: "Alice" }, newData: { name: "Alicia" }, }); store.stageCellEdit({ tabId, schema: "public", table: "users", primaryKey: { id: 1 }, oldData: { age: 30 }, newData: { age: 31 }, }); expect(useDbViewerStore.getState().changesQueue).toHaveLength(2); }); it("setIndexes / setConstraints update store slices", () => { const store = useDbViewerStore.getState(); store.setIndexes([{ name: "idx", schema: "public", table: "t", definition: "", is_unique: true, method: "btree", columns: ["id"], size_bytes: 1, tablespace: null }]); store.setConstraints([{ name: "ck", schema: "public", table: "t", contype: "CHECK", definition: "", deferrable: false, validated: true, columns: ["x"] }]); expect(useDbViewerStore.getState().indexes).toHaveLength(1); expect(useDbViewerStore.getState().constraints).toHaveLength(1); }); it("reset clears indexes and constraints", () => { useDbViewerStore.getState().setIndexes([{ name: "x", schema: "s", table: "t", definition: "", is_unique: false, method: "btree", columns: [], size_bytes: null, tablespace: null }]); useDbViewerStore.getState().reset(); expect(useDbViewerStore.getState().indexes).toBeNull(); }); }); describe("refreshTree", () => { it("fetches databases/schemas/tables and populates", async () => { vi.mocked(commands.getDatabases).mockResolvedValue(["mydb"]); vi.mocked(commands.getSchemas).mockResolvedValue(["public"]); vi.mocked(commands.getTables).mockResolvedValue([ { name: "users", schema: "public", table_type: "TABLE" }, ] as any); useDbViewerStore.setState({ currentSchema: "public" }); await useDbViewerStore.getState().refreshTree("c1"); expect(useDbViewerStore.getState().databases).toEqual(["mydb"]); expect(useDbViewerStore.getState().schemas).toEqual(["public"]); expect(useDbViewerStore.getState().tables).toHaveLength(1); expect(commands.getTables).toHaveBeenCalledWith("c1", "public"); }); it("falls back to no schema when currentSchema is null", async () => { vi.mocked(commands.getDatabases).mockResolvedValue([] as any); vi.mocked(commands.getSchemas).mockResolvedValue([] as any); vi.mocked(commands.getTables).mockResolvedValue([] as any); useDbViewerStore.setState({ currentSchema: null }); await useDbViewerStore.getState().refreshTree("c1"); expect(commands.getTables).toHaveBeenCalledWith("c1", undefined); }); it("swallows fetch errors", async () => { vi.mocked(commands.getDatabases).mockResolvedValue([] as any); vi.mocked(commands.getSchemas).mockRejectedValue(new Error("boom")); await expect( useDbViewerStore.getState().refreshTree("c1"), ).resolves.toBeUndefined(); }); }); describe("tabType discriminator", () => { beforeEach(() => { useDbViewerStore.getState().reset(); useDbViewerStore.setState({ schemas: ["public"], currentSchema: "public", currentDatabase: "mydb", }); }); it("openQueryTab creates a query-type tab with empty query", () => { useDbViewerStore.getState().openQueryTab(); const state = useDbViewerStore.getState(); expect(state.tabs).toHaveLength(1); const tab = state.tabs[0]; expect(tab.tabType).toBe("query"); expect(tab.query).toBe(""); expect(tab.schema).toBe("public"); expect(tab.table).toBe("Query"); expect(state.activeTabId).toBe(tab.id); }); it("creates sequential query tabs with unique IDs", () => { const store = useDbViewerStore.getState(); store.openQueryTab(); store.openQueryTab(); store.openQueryTab(); const tabs = useDbViewerStore.getState().tabs; expect(tabs).toHaveLength(3); const ids = tabs.map((t) => t.id); const uniqueIds = new Set(ids); expect(uniqueIds.size).toBe(3); tabs.forEach((t) => expect(t.tabType).toBe("query")); }); it("openTab creates table-type tabs by default (backward compat)", () => { useDbViewerStore.getState().openTab("public", "users"); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.tabType).toBe("table"); }); }); describe("openObjectTab", () => { beforeEach(() => useDbViewerStore.getState().reset()); it("opens an object tab carrying the per-type identity + item", () => { useDbViewerStore.getState().openObjectTab("functions", "public", "add", { name: "add", schema: "public" }); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); expect(st.tabs[0]).toMatchObject({ tabType: "object", objectType: "functions", schema: "public", table: "add" }); expect(st.tabs[0].objectItem).toEqual({ name: "add", schema: "public" }); expect(st.activeTabId).toBe(st.tabs[0].id); }); it("dedups: re-clicking the same object focuses the existing tab", () => { useDbViewerStore.getState().openObjectTab("enums", "public", "role", { name: "role" }); const first = useDbViewerStore.getState().tabs[0]; useDbViewerStore.getState().openObjectTab("enums", "public", "role", { name: "role" }); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); expect(st.activeTabId).toBe(first.id); }); it("does not collide with a table tab of the same name", () => { useDbViewerStore.getState().openTab("public", "users"); useDbViewerStore.getState().openObjectTab("functions", "public", "users", { name: "users" }); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(2); expect(st.tabs.find((t) => t.tabType === "object")).toBeDefined(); }); }); describe("openFormTab", () => { beforeEach(() => useDbViewerStore.getState().reset()); const createOpts = (name: string, title = "Create sequence") => ({ kind: "sequence" as const, schema: "public", name, title, description: `Create ${name}`, mode: "create" as const, params: { schema: "public", name, action: { op: "create" } }, }); it("opens a create form tab carrying kind/params/title/description/mode", () => { useDbViewerStore.getState().openFormTab(createOpts("my_seq")); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); const tab = st.tabs[0]; expect(tab.tabType).toBe("objectForm"); // table holds the display title, used as the TabBar label expect(tab.table).toBe("Create sequence"); expect(tab.schema).toBe("public"); expect(tab.objectType).toBeNull(); expect(tab.form).toBeDefined(); expect(tab.form!.kind).toBe("sequence"); expect(tab.form!.mode).toBe("create"); expect(tab.form!.title).toBe("Create sequence"); expect(tab.form!.description).toBe("Create my_seq"); expect(tab.form!.params).toEqual({ schema: "public", name: "my_seq", action: { op: "create" }, }); expect(st.activeTabId).toBe(tab.id); }); it("dedups create tabs per kind regardless of schema/name and does not clobber params", () => { useDbViewerStore.getState().openFormTab(createOpts("first_seq")); const first = useDbViewerStore.getState().tabs[0]; useDbViewerStore.getState().openFormTab(createOpts("second_seq")); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); expect(st.activeTabId).toBe(first.id); // the existing tab keeps its original params (name not clobbered) expect(st.tabs[0].form!.params.name).toBe("first_seq"); }); it("dedups edit tabs by kind+schema+name but a create tab of the same kind coexists", () => { const editOpts = { kind: "enum" as const, schema: "public", name: "role", title: "Edit role", description: "Edit public.role", mode: "edit" as const, params: { schema: "public", name: "role", action: { op: "update" } }, }; useDbViewerStore.getState().openFormTab(editOpts); const first = useDbViewerStore.getState().tabs[0]; useDbViewerStore.getState().openFormTab(editOpts); let st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(1); expect(st.activeTabId).toBe(first.id); // A create tab of the same kind is a distinct tab. useDbViewerStore.getState().openFormTab(createOpts("new_enum", "Create enum")); st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(2); expect(st.tabs.every((t) => t.tabType === "objectForm")).toBe(true); expect(st.tabs.some((t) => t.form!.mode === "edit")).toBe(true); expect(st.tabs.some((t) => t.form!.mode === "create")).toBe(true); }); it("does not collide with a table tab (distinct types)", () => { useDbViewerStore.getState().openTab("public", "users"); useDbViewerStore.getState().openFormTab(createOpts("users_id_seq")); const st = useDbViewerStore.getState(); expect(st.tabs).toHaveLength(2); expect(st.tabs[0].tabType).toBe("table"); expect(st.tabs[1].tabType).toBe("objectForm"); }); it("resolves an empty schema to the current schema", () => { useDbViewerStore.setState({ currentSchema: "utils" }); useDbViewerStore .getState() .openFormTab({ ...createOpts("my_seq"), schema: "" }); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.schema).toBe("utils"); // params stay untouched — the form owns them expect(tab.form!.params.schema).toBe("public"); }); it("falls back to public when no schema and no currentSchema", () => { useDbViewerStore .getState() .openFormTab({ ...createOpts("my_seq"), schema: "" }); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.schema).toBe("public"); }); it("keeps an explicit schema even when currentSchema is set", () => { useDbViewerStore.setState({ currentSchema: "utils" }); useDbViewerStore .getState() .openFormTab({ ...createOpts("my_seq"), schema: "public" }); const tab = useDbViewerStore.getState().tabs[0]; expect(tab.schema).toBe("public"); }); it("updateFormTabParams updates params without touching title/description/mode/kind", () => { useDbViewerStore.getState().openFormTab(createOpts("my_seq")); const tabId = useDbViewerStore.getState().tabs[0].id; const before = useDbViewerStore.getState().tabs[0].form; expect(before).toBeDefined(); useDbViewerStore .getState() .updateFormTabParams(tabId, { schema: "other", name: "renamed", action: { op: "create" } }); const after = useDbViewerStore.getState().tabs[0].form; expect(after?.params).toEqual({ schema: "other", name: "renamed", action: { op: "create" } }); expect(after?.title).toBe(before?.title); expect(after?.description).toBe(before?.description); expect(after?.mode).toBe(before?.mode); expect(after?.kind).toBe(before?.kind); }); }); describe("roles slice", () => { it("setRoles stores roles", () => { useDbViewerStore.getState().reset(); const r: RoleInfo = { name: "app", superuser: false, inherit: true, create_db: false, create_role: false, can_login: true, replication: false, bypass_rls: false, connection_limit: -1, valid_until: null, memberships: [], }; useDbViewerStore.getState().setRoles([r]); expect(useDbViewerStore.getState().roles).toEqual([r]); }); it("openFormTab accepts kind table and role", () => { useDbViewerStore.getState().reset(); useDbViewerStore.getState().openFormTab({ kind: "table", schema: "public", name: "", title: "Create Table", description: "Create Table", mode: "create", params: { schema: "public", name: "", action: { op: "create", columns: [] } }, }); const t = useDbViewerStore.getState().tabs[0]; expect(t.tabType).toBe("objectForm"); expect(t.form?.kind).toBe("table"); // dedup: second create of same kind focuses existing useDbViewerStore.getState().openFormTab({ kind: "table", schema: "public", name: "", title: "Create Table", description: "Create Table", mode: "create", params: { schema: "public", name: "", action: { op: "create", columns: [] } }, }); expect(useDbViewerStore.getState().tabs.filter((x) => x.form?.kind === "table").length).toBe(1); }); }); describe("openQueryTab with an initial script", () => { it("seeds the tab text and label from the payload", () => { useDbViewerStore.setState({ tabs: [], activeTabId: null }); useDbViewerStore.getState().openQueryTab({ sql: "SELECT 1;", label: "student_db.sql", filePath: "/Users/me/student_db.sql", }); const tab = useDbViewerStore.getState().tabs.slice(-1)[0]!; expect(tab.tabType).toBe("query"); expect(tab.query).toBe("SELECT 1;"); expect(tab.table).toBe("student_db.sql"); expect(tab.filePath).toBe("/Users/me/student_db.sql"); }); it("focuses an existing tab for the same file instead of duplicating it", () => { useDbViewerStore.setState({ tabs: [], activeTabId: null }); const payload = { sql: "SELECT 1;", label: "student_db.sql", filePath: "/Users/me/student_db.sql", }; useDbViewerStore.getState().openQueryTab(payload); useDbViewerStore.getState().openQueryTab({ ...payload, sql: "SELECT 2;" }); const queryTabs = useDbViewerStore.getState().tabs.filter((t) => t.tabType === "query"); expect(queryTabs).toHaveLength(1); // The already-open tab keeps its text: re-opening must not clobber edits. expect(queryTabs[0].query).toBe("SELECT 1;"); }); it("still opens a plain empty tab when called with no payload", () => { useDbViewerStore.setState({ tabs: [], activeTabId: null }); useDbViewerStore.getState().openQueryTab(); const tab = useDbViewerStore.getState().tabs.slice(-1)[0]!; expect(tab.tabType).toBe("query"); expect(tab.query).toBe(""); expect(tab.filePath).toBeUndefined(); }); });