import { describe, expect, it } from "vitest"; import { canApplyDataTabMetadata, dataTabMetadataNeedsRefresh, dataTabOpenModeFromTreeClick, findExistingDataTabCandidate } from "@/lib/sidebar/dataTabOpenPolicy"; import type { QueryTab } from "@/types/database"; function click(modifiers: Partial> = {}) { return { metaKey: modifiers.metaKey ?? false, ctrlKey: modifiers.ctrlKey ?? false, altKey: modifiers.altKey ?? false, shiftKey: modifiers.shiftKey ?? false, }; } function dataTab(id: string, title: string, schema = "public"): QueryTab { return { id, title, connectionId: "conn", database: "app", schema, sql: "", isExecuting: false, mode: "data", }; } const usersTarget = { connectionId: "conn", database: "app", schema: "public", tableName: "users" }; describe("dataTabOpenPolicy", () => { it("maps the default Alt/Option modifier to explicit new-tab mode for data nodes only", () => { expect(dataTabOpenModeFromTreeClick("table", click({ altKey: true }), "Alt")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("view", click({ altKey: true }), "Alt")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("materialized_view", click({ altKey: true }), "Alt")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("database", click({ altKey: true }), "Alt")).toBe("default"); }); it("honors customized modifier-only shortcuts and a cleared shortcut", () => { expect(dataTabOpenModeFromTreeClick("table", click({ ctrlKey: true }), "Mod")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("table", click({ metaKey: true }), "Mod")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("table", click({ shiftKey: true }), "Shift")).toBe("new-tab"); expect(dataTabOpenModeFromTreeClick("table", click({ altKey: true }), "")).toBe("default"); }); it("does not consume tree selection modifier combinations under the default Alt shortcut", () => { expect(dataTabOpenModeFromTreeClick("table", click({ metaKey: true }), "Alt")).toBe("default"); expect(dataTabOpenModeFromTreeClick("table", click({ ctrlKey: true }), "Alt")).toBe("default"); expect(dataTabOpenModeFromTreeClick("table", click({ shiftKey: true }), "Alt")).toBe("default"); expect(dataTabOpenModeFromTreeClick("table", click({ altKey: true, shiftKey: true }), "Alt")).toBe("default"); }); it("never returns an existing tab in explicit new-tab mode", () => { const existing = dataTab("users", "users"); existing.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] }; expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseScope: "database" })).toBeUndefined(); expect(findExistingDataTabCandidate([existing], usersTarget, { openMode: "new-tab", reuseScope: "none" })).toBeUndefined(); }); it("applies none, same-table, and database reuse scopes independently", () => { const sameTable = dataTab("users", "users"); sameTable.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] }; const otherTable = dataTab("orders", "orders"); expect(findExistingDataTabCandidate([sameTable], usersTarget, { openMode: "default", reuseScope: "none" })).toBeUndefined(); expect(findExistingDataTabCandidate([sameTable], usersTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab: sameTable, match: "same-table" }); expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseScope: "same-table" })).toBeUndefined(); expect(findExistingDataTabCandidate([otherTable], usersTarget, { openMode: "default", reuseScope: "database" })).toEqual({ tab: otherTable, match: "database" }); }); it("allows metadata to update a tab that still points to the requested table", () => { const tab = dataTab("users", "users"); tab.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] }; expect(canApplyDataTabMetadata(tab, usersTarget, new AbortController().signal)).toBe(true); }); it("uses restored table metadata as the schema identity fallback", () => { const tab = dataTab("users", "users"); tab.schema = undefined; tab.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] }; expect(canApplyDataTabMetadata(tab, usersTarget, new AbortController().signal)).toBe(true); expect(findExistingDataTabCandidate([tab], usersTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab, match: "same-table" }); }); it("ignores metadata query schemas for database-scoped tables", () => { const tab = dataTab("users", "users"); tab.schema = undefined; tab.tableMeta = { schema: "app", tableName: "users", columns: [], primaryKeys: [] }; const mysqlTarget = { connectionId: "conn", database: "app", tableName: "users" }; expect(canApplyDataTabMetadata(tab, mysqlTarget, new AbortController().signal)).toBe(true); expect(findExistingDataTabCandidate([tab], mysqlTarget, { openMode: "default", reuseScope: "same-table" })).toEqual({ tab, match: "same-table" }); }); it("rejects metadata after its request is cancelled", () => { const tab = dataTab("users", "users"); tab.tableMeta = { schema: "public", tableName: "users", columns: [], primaryKeys: [] }; const controller = new AbortController(); controller.abort(); expect(canApplyDataTabMetadata(tab, usersTarget, controller.signal)).toBe(false); }); it("rejects stale metadata after a reusable tab switches to another table", () => { const tab = dataTab("reused", "orders"); tab.tableMeta = { schema: "public", tableName: "orders", columns: [], primaryKeys: [] }; expect(canApplyDataTabMetadata(tab, usersTarget, new AbortController().signal)).toBe(false); }); it("refreshes missing, restored, and expired table metadata", () => { const now = 100_000; const ttl = 30_000; const tab = dataTab("users", "users"); expect(dataTabMetadataNeedsRefresh(tab, ttl, now)).toBe(true); tab.tableMeta = { schema: "public", tableName: "users", columns: [{ name: "id", data_type: "integer", is_nullable: false, column_default: null, is_primary_key: true, extra: null }], primaryKeys: ["id"], }; expect(dataTabMetadataNeedsRefresh(tab, ttl, now)).toBe(true); tab.tableMetaUpdatedAt = now - ttl + 1; expect(dataTabMetadataNeedsRefresh(tab, ttl, now)).toBe(false); tab.tableMetaUpdatedAt = now - ttl; expect(dataTabMetadataNeedsRefresh(tab, ttl, now)).toBe(true); }); });