mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(tabs): isolate tab collision numbering by mode
This commit is contained in:
@@ -469,6 +469,26 @@ describe("tab group presentation", () => {
|
||||
expect(titles.get("tab-preview")).toBe("SQL");
|
||||
});
|
||||
|
||||
it("does not collide query tabs with object browser or table data tabs sharing the same title", () => {
|
||||
const store = useConnectionStore();
|
||||
store.connections = [{ id: "conn-1", name: "PostgreSQL", db_type: "postgres", driver_profile: "postgres", database: "app" } as ConnectionConfig];
|
||||
useSettingsStore().editorSettings.compactTabTitle = true;
|
||||
|
||||
// In compact mode, object tab displays "test" (schema), data tab displays "test" (tableName), query tab displays "test" (custom title)
|
||||
const objectTab = queryTab({ id: "tab-obj", mode: "objects", title: "test", database: "test", objectBrowser: { schema: "test" } });
|
||||
const dataTab = queryTab({ id: "tab-data", mode: "data", title: "test", tableMeta: { schema: "public", tableName: "test", columns: [], primaryKeys: [] } });
|
||||
const queryTab1 = queryTab({ id: "tab-query-1", mode: "query", title: "test", customTitle: true });
|
||||
const queryTab2 = queryTab({ id: "tab-query-2", mode: "query", title: "test", customTitle: true });
|
||||
|
||||
const titles = titlesWithNumbers([objectTab, dataTab, queryTab1, queryTab2]);
|
||||
// Object and table tabs are not in query mode, so they must not be numbered or affected by query tabs
|
||||
expect(titles.get("tab-obj")).toBe("test");
|
||||
expect(titles.get("tab-data")).toBe("test");
|
||||
// Multiple query tabs with the same title still collide with each other and receive numeric suffixes
|
||||
expect(titles.get("tab-query-1")).toBe("test 1");
|
||||
expect(titles.get("tab-query-2")).toBe("test 2");
|
||||
});
|
||||
|
||||
describe("custom title suffix", () => {
|
||||
const dataTab = (id: string, overrides: Partial<QueryTab> = {}) => queryTab({ id, mode: "data", title: "ticket_bug", tableMeta: { schema: "feedback_v2", tableName: "ticket_bug", columns: [], primaryKeys: [] }, ...overrides });
|
||||
|
||||
@@ -581,6 +601,15 @@ describe("tab group presentation", () => {
|
||||
expect(findTabWithSameTitleSuffix(tabs, tabs[1]!, "", translate)).toBeUndefined();
|
||||
expect(findTabWithSameTitleSuffix(tabs, tabs[1]!, "done", translate)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores tabs of a different mode sharing the same base title and suffix", () => {
|
||||
seedConnection();
|
||||
const data = dataTab("tab-data", { titleSuffix: "pending" });
|
||||
const query = queryTab({ id: "tab-query", mode: "query", customTitle: true, title: tabDisplayTitle(data, translate) });
|
||||
syncTabTitleNumbers([data, query], translate);
|
||||
|
||||
expect(findTabWithSameTitleSuffix([data, query], query, "pending", translate)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -188,7 +188,8 @@ export function findTabWithSameTitleSuffix(tabs: QueryTab[], target: QueryTab, s
|
||||
const wanted = suffix.trim();
|
||||
if (!wanted) return undefined;
|
||||
const base = tabDisplayTitle(target, t);
|
||||
return tabs.find((tab) => tab.id !== target.id && tabDisplayTitle(tab, t) === base && tabTitleSuffix(tab, base) === wanted);
|
||||
const targetMode = target.mode || "query";
|
||||
return tabs.find((tab) => tab.id !== target.id && (tab.mode || "query") === targetMode && tabDisplayTitle(tab, t) === base && tabTitleSuffix(tab, base) === wanted);
|
||||
}
|
||||
|
||||
function tabTitleNumber(tab: QueryTab, title: string): number | undefined {
|
||||
@@ -222,6 +223,7 @@ export function syncTabTitleNumbers(tabs: QueryTab[], t: Translate): void {
|
||||
for (const tab of tabs) {
|
||||
if (isPreviewTab(tab)) continue;
|
||||
const title = tabDisplayTitle(tab, t);
|
||||
const groupKey = `${tab.mode || "query"}\u0000${title}`;
|
||||
const custom = customTitleSuffix(tab);
|
||||
if (custom) {
|
||||
// A custom suffix replaces the number, so the tab leaves the numbering
|
||||
@@ -229,17 +231,18 @@ export function syncTabTitleNumbers(tabs: QueryTab[], t: Translate): void {
|
||||
// fresh one instead of resurrecting a stale value.
|
||||
tab.titleNumber = undefined;
|
||||
tab.titleNumberKey = undefined;
|
||||
const used = customSuffixes.get(title);
|
||||
const used = customSuffixes.get(groupKey);
|
||||
if (used) used.add(custom);
|
||||
else customSuffixes.set(title, new Set([custom]));
|
||||
else customSuffixes.set(groupKey, new Set([custom]));
|
||||
continue;
|
||||
}
|
||||
const group = groups.get(title);
|
||||
const group = groups.get(groupKey);
|
||||
if (group) group.push(tab);
|
||||
else groups.set(title, [tab]);
|
||||
else groups.set(groupKey, [tab]);
|
||||
}
|
||||
|
||||
for (const [title, group] of groups) {
|
||||
for (const [groupKey, group] of groups) {
|
||||
const title = groupKey.slice(groupKey.indexOf("\u0000") + 1);
|
||||
const numbered = group.filter((tab) => tabTitleNumber(tab, title) !== undefined);
|
||||
if (group.length < 2 && numbered.length === 0) {
|
||||
// A lone, never-numbered tab shows its plain title; drop a number that a
|
||||
@@ -255,7 +258,7 @@ export function syncTabTitleNumbers(tabs: QueryTab[], t: Translate): void {
|
||||
let next = numbered.reduce((highest, tab) => Math.max(highest, tab.titleNumber ?? 0), 0) + 1;
|
||||
for (const tab of group) {
|
||||
if (tabTitleNumber(tab, title) !== undefined) continue;
|
||||
while (customSuffixes.get(title)?.has(String(next))) next += 1;
|
||||
while (customSuffixes.get(groupKey)?.has(String(next))) next += 1;
|
||||
tab.titleNumber = next;
|
||||
tab.titleNumberKey = title;
|
||||
next += 1;
|
||||
|
||||
@@ -96,4 +96,21 @@ describe("queryStore tab title suffix", () => {
|
||||
store.setTabTitleSuffix(second, "");
|
||||
expect(numberOf(second)).toBe(2);
|
||||
});
|
||||
|
||||
it("does not number or alter a data tab when a query tab is renamed to match its name", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const dataTab = store.createTab("pg-1", "app", "users", "data", "public", undefined, undefined, { forceNew: true });
|
||||
const queryTab = store.createTab("pg-1", "app", "scratch", "query", undefined, undefined, undefined, { forceNew: true });
|
||||
|
||||
const dataTabInstance = store.tabs.find((t) => t.id === dataTab)!;
|
||||
expect(dataTabInstance.titleNumber).toBeUndefined();
|
||||
|
||||
store.renameTab(queryTab, dataTabInstance.title);
|
||||
await nextTick();
|
||||
|
||||
const queryTabInstance = store.tabs.find((t) => t.id === queryTab)!;
|
||||
expect(dataTabInstance.titleNumber).toBeUndefined();
|
||||
expect(queryTabInstance.titleNumber).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1173,7 +1173,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// 里而不是渲染函数里,是为了避免在 computed 求值过程中写标签状态;getter 只读
|
||||
// 参与标题计算的字段(id + 显示标题),分配编号本身不会再次触发它。
|
||||
watch(
|
||||
() => tabs.value.map((tab) => `${tab.id}\u0000${tabDisplayTitle(tab, t)}\u0000${tab.titleSuffix ?? ""}`),
|
||||
() => tabs.value.map((tab) => `${tab.id}\u0000${tab.mode || "query"}\u0000${tabDisplayTitle(tab, t)}\u0000${tab.titleSuffix ?? ""}`),
|
||||
() => syncTabTitleNumbers(tabs.value, t),
|
||||
{ immediate: true, flush: "sync" },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user