1 parent
53b7be3c6f
commit
906ea8ba26
8 files changed
+104
-16
No files matched your search
@@ -2041,6 +2041,12 @@ function setSidebarOpen(open: boolean) {
|
|||||||
safeLocalStorageSet("dbx-sidebar-open", open ? "true" : "false");
|
safeLocalStorageSet("dbx-sidebar-open", open ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function locateTabInSidebar(tab: QueryTab) {
|
||||||
|
setSidebarOpen(true);
|
||||||
|
await nextTick();
|
||||||
|
await appSidebarRef.value?.locateTabInSidebar(tab);
|
||||||
|
}
|
||||||
|
|
||||||
function ensureQueryTab(): string {
|
function ensureQueryTab(): string {
|
||||||
const tab = activeTab.value;
|
const tab = activeTab.value;
|
||||||
if (tab && tab.mode === "query") return tab.id;
|
if (tab && tab.mode === "query") return tab.id;
|
||||||
@@ -2847,6 +2853,7 @@ onUnmounted(() => {
|
|||||||
:agent-driver-update-count="toolbarAgentDriverUpdateCount"
|
:agent-driver-update-count="toolbarAgentDriverUpdateCount"
|
||||||
@activate-driver-store="openDriverStorePage"
|
@activate-driver-store="openDriverStorePage"
|
||||||
@activate-settings-page="activateSettingsPage"
|
@activate-settings-page="activateSettingsPage"
|
||||||
|
@locate-tab="locateTabInSidebar"
|
||||||
@activate-tab="
|
@activate-tab="
|
||||||
driverStoreActive = false;
|
driverStoreActive = false;
|
||||||
settingsStore.settingsPageActive = false;
|
settingsStore.settingsPageActive = false;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import ConnectionTree from "@/components/sidebar/ConnectionTree.vue";
|
|||||||
import { applyConnectionMultiSelection, emptyConnectionMultiSelection, isExitConnectionMultiSelectionShortcut } from "@/lib/sidebar/sidebarConnectionMultiSelect";
|
import { applyConnectionMultiSelection, emptyConnectionMultiSelection, isExitConnectionMultiSelectionShortcut } from "@/lib/sidebar/sidebarConnectionMultiSelect";
|
||||||
import { useConnectionStore } from "@/stores/connectionStore";
|
import { useConnectionStore } from "@/stores/connectionStore";
|
||||||
import { useToast } from "@/composables/useToast";
|
import { useToast } from "@/composables/useToast";
|
||||||
import type { TreeNode } from "@/types/database";
|
import type { QueryTab, TreeNode } from "@/types/database";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
sidebarWidth: number;
|
sidebarWidth: number;
|
||||||
@@ -88,6 +88,10 @@ function focusSearch(): boolean {
|
|||||||
return connectionTreeRef.value?.focusSearch() ?? false;
|
return connectionTreeRef.value?.focusSearch() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function locateTabInSidebar(tab: QueryTab) {
|
||||||
|
return connectionTreeRef.value?.locateTabInSidebar(tab);
|
||||||
|
}
|
||||||
|
|
||||||
function clearConnectionMultiSelection() {
|
function clearConnectionMultiSelection() {
|
||||||
applyConnectionMultiSelection(connectionStore, emptyConnectionMultiSelection());
|
applyConnectionMultiSelection(connectionStore, emptyConnectionMultiSelection());
|
||||||
}
|
}
|
||||||
@@ -163,7 +167,7 @@ function confirmCreateSelectedGroup() {
|
|||||||
showCreateSelectedGroupDialog.value = false;
|
showCreateSelectedGroupDialog.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ focusSearch });
|
defineExpose({ focusSearch, locateTabInSidebar });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { computed, ref, watch, nextTick, onUnmounted } from "vue";
|
import { computed, ref, watch, nextTick, onUnmounted } from "vue";
|
||||||
import type { CSSProperties } from "vue";
|
import type { CSSProperties } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { X, Pin, ChevronDown, Search, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Lock, Copy, AlertTriangle, Network, Minimize2, Maximize2, Settings, CalendarClock, Activity, Gauge, ShieldCheck, Database, GitBranch } from "@lucide/vue";
|
import { X, Pin, ChevronDown, Search, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Lock, Copy, AlertTriangle, Network, Minimize2, Maximize2, Settings, CalendarClock, Activity, Gauge, ShieldCheck, Database, GitBranch, Crosshair } from "@lucide/vue";
|
||||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
@@ -19,6 +19,7 @@ import { connectionColor, isConnectionReadonly, tabDisplayTitle, tabTooltipLines
|
|||||||
import { hexToRgba } from "@/lib/common/color";
|
import { hexToRgba } from "@/lib/common/color";
|
||||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||||
import { useToast } from "@/composables/useToast";
|
import { useToast } from "@/composables/useToast";
|
||||||
|
import { activeTabSidebarTarget } from "@/lib/sidebar/sidebarActiveTabTarget";
|
||||||
import type { QueryTab } from "@/types/database";
|
import type { QueryTab } from "@/types/database";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -31,6 +32,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"activate-tab": [];
|
"activate-tab": [];
|
||||||
|
"locate-tab": [tab: QueryTab];
|
||||||
"activate-driver-store": [];
|
"activate-driver-store": [];
|
||||||
"close-driver-store": [];
|
"close-driver-store": [];
|
||||||
"activate-settings-page": [];
|
"activate-settings-page": [];
|
||||||
@@ -318,6 +320,12 @@ function getTabMenuItems(tab: QueryTab): ContextMenuItem[] {
|
|||||||
},
|
},
|
||||||
icon: Copy,
|
icon: Copy,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("sidebar.locateActiveTab"),
|
||||||
|
action: () => emit("locate-tab", tab),
|
||||||
|
icon: Crosshair,
|
||||||
|
visible: !!activeTabSidebarTarget(tab),
|
||||||
|
},
|
||||||
{ label: "", separator: true },
|
{ label: "", separator: true },
|
||||||
{
|
{
|
||||||
label: tab.pinned ? t("contextMenu.unfixTab") : t("contextMenu.fixTab"),
|
label: tab.pinned ? t("contextMenu.unfixTab") : t("contextMenu.fixTab"),
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ import { describe, expect, it } from "vitest";
|
|||||||
|
|
||||||
const tabBarSource = readFileSync(new URL("../AppTabBar.vue", import.meta.url), "utf8");
|
const tabBarSource = readFileSync(new URL("../AppTabBar.vue", import.meta.url), "utf8");
|
||||||
|
|
||||||
|
function sourceBetween(start: string, end: string): string {
|
||||||
|
const startIndex = tabBarSource.indexOf(start);
|
||||||
|
const endIndex = tabBarSource.indexOf(end, startIndex + start.length);
|
||||||
|
expect(startIndex).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(endIndex).toBeGreaterThan(startIndex);
|
||||||
|
return tabBarSource.slice(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
describe("AppTabBar close confirmation layout", () => {
|
describe("AppTabBar close confirmation layout", () => {
|
||||||
it("allows long unbroken tab titles to shrink and wrap inside the dialog", () => {
|
it("allows long unbroken tab titles to shrink and wrap inside the dialog", () => {
|
||||||
expect(tabBarSource).toMatch(/<DialogContent class="[^"]*\bmin-w-0\b[^"]*\bsm:max-w-md\b/);
|
expect(tabBarSource).toMatch(/<DialogContent class="[^"]*\bmin-w-0\b[^"]*\bsm:max-w-md\b/);
|
||||||
@@ -38,6 +46,26 @@ describe("AppTabBar object browser presentation", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("AppTabBar locate-in-sidebar action", () => {
|
||||||
|
it("emits the exact right-clicked tab only when it has a sidebar target", () => {
|
||||||
|
const menu = sourceBetween("function getTabMenuItems", "function handleSaveAndClose");
|
||||||
|
|
||||||
|
expect(tabBarSource).toContain('"locate-tab": [tab: QueryTab];');
|
||||||
|
expect(menu).toMatch(/label: t\("sidebar\.locateActiveTab"\),\s*action: \(\) => emit\("locate-tab", tab\),\s*icon: Crosshair,\s*visible: !!activeTabSidebarTarget\(tab\)/);
|
||||||
|
expect(menu).not.toContain("activateTab(tab.id)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves settings, driver, and existing regular-tab menu actions unchanged", () => {
|
||||||
|
const specialMenu = sourceBetween("function getSpecialRegularTabMenuItems", "function getTabMenuItems");
|
||||||
|
const regularMenu = sourceBetween("function getTabMenuItems", "function handleSaveAndClose");
|
||||||
|
|
||||||
|
expect(specialMenu).not.toContain('t("sidebar.locateActiveTab")');
|
||||||
|
expect(regularMenu).toContain('label: t("contextMenu.copyName")');
|
||||||
|
expect(regularMenu).toContain("action: () => queryStore.togglePinnedTab(tab.id)");
|
||||||
|
expect(regularMenu).toContain("action: () => queryStore.closeTab(tab.id)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("AppTabBar right-side close action", () => {
|
describe("AppTabBar right-side close action", () => {
|
||||||
it("places the action after close-other and disables it when the target has no tabs to its right", () => {
|
it("places the action after close-other and disables it when the target has no tabs to its right", () => {
|
||||||
expect(tabBarSource).toContain('label: t("contextMenu.closeRightTabs")');
|
expect(tabBarSource).toContain('label: t("contextMenu.closeRightTabs")');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useQueryStore } from "@/stores/queryStore";
|
|||||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
import { useToast } from "@/composables/useToast";
|
import { useToast } from "@/composables/useToast";
|
||||||
import type { ObjectSourceKind, TableInfo, TableNameFilter, TreeNode, TreeNodeType } from "@/types/database";
|
import type { ObjectSourceKind, QueryTab, TableInfo, TableNameFilter, TreeNode, TreeNodeType } from "@/types/database";
|
||||||
import {
|
import {
|
||||||
createSidebarSearchSubtreePreserver,
|
createSidebarSearchSubtreePreserver,
|
||||||
filterSidebarSearchRootsByConnectionState,
|
filterSidebarSearchRootsByConnectionState,
|
||||||
@@ -1250,6 +1250,7 @@ async function scrollToSidebarNode(nodeId: string, options?: { align?: SidebarNo
|
|||||||
index,
|
index,
|
||||||
currentScrollTop: scroller.scrollTop,
|
currentScrollTop: scroller.scrollTop,
|
||||||
viewportHeight: scroller.clientHeight,
|
viewportHeight: scroller.clientHeight,
|
||||||
|
scrollHeight: scroller.scrollHeight,
|
||||||
topOcclusionHeight: topOcclusionHeightForSidebarNode(nodeId),
|
topOcclusionHeight: topOcclusionHeightForSidebarNode(nodeId),
|
||||||
...(options?.align ? { align: options.align } : {}),
|
...(options?.align ? { align: options.align } : {}),
|
||||||
});
|
});
|
||||||
@@ -1304,7 +1305,10 @@ async function startRenamingConnectionNode(connectionId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function locateActiveTabInSidebar() {
|
async function locateActiveTabInSidebar() {
|
||||||
const tab = activeTab.value;
|
await locateTabInSidebar(activeTab.value, "smart");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function locateTabInSidebar(tab: QueryTab | undefined | null, align: SidebarNodeScrollAlign = "center") {
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
|
|
||||||
const tabTarget = activeTabSidebarTarget(tab);
|
const tabTarget = activeTabSidebarTarget(tab);
|
||||||
@@ -1397,7 +1401,7 @@ async function locateActiveTabInSidebar() {
|
|||||||
store.treeSelectionAnchorId = match.id;
|
store.treeSelectionAnchorId = match.id;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
await scrollToSidebarNode(match.id, { align: "smart" });
|
await scrollToSidebarNode(match.id, { align });
|
||||||
await flashSidebarNode(match.id);
|
await flashSidebarNode(match.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1955,6 +1959,7 @@ async function selectActiveTabSidebarNode(options: { scroll: boolean }) {
|
|||||||
index,
|
index,
|
||||||
currentScrollTop: scroller.scrollTop,
|
currentScrollTop: scroller.scrollTop,
|
||||||
viewportHeight: scroller.clientHeight,
|
viewportHeight: scroller.clientHeight,
|
||||||
|
scrollHeight: scroller.scrollHeight,
|
||||||
topOcclusionHeight: topOcclusionHeightForSidebarNode(match.id),
|
topOcclusionHeight: topOcclusionHeightForSidebarNode(match.id),
|
||||||
});
|
});
|
||||||
if (nextScrollTop !== scroller.scrollTop) {
|
if (nextScrollTop !== scroller.scrollTop) {
|
||||||
@@ -2163,7 +2168,7 @@ onUnmounted(() => {
|
|||||||
if (sidebarTreeContentMeasureFrame) window.cancelAnimationFrame(sidebarTreeContentMeasureFrame);
|
if (sidebarTreeContentMeasureFrame) window.cancelAnimationFrame(sidebarTreeContentMeasureFrame);
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
|
defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSidebar });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -331,30 +331,37 @@ export function shouldScrollActiveSidebarSelection(options: { activeTabId: strin
|
|||||||
return options.activeTabId !== options.previousActiveTabId || (options.autoSelectEnabled && options.previousAutoSelectEnabled === false);
|
return options.activeTabId !== options.previousActiveTabId || (options.autoSelectEnabled && options.previousAutoSelectEnabled === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// nearest is used for passive auto-selection; smart keeps context for explicit locate actions.
|
// nearest is used for passive auto-selection; smart keeps context for the existing toolbar action.
|
||||||
export type SidebarNodeScrollAlign = "nearest" | "top" | "smart";
|
export type SidebarNodeScrollAlign = "nearest" | "top" | "smart" | "center";
|
||||||
|
|
||||||
export function scrollTopForSidebarNode(options: { index: number; currentScrollTop: number; viewportHeight: number; rowHeight?: number; topOcclusionHeight?: number; align?: SidebarNodeScrollAlign }): number {
|
export function scrollTopForSidebarNode(options: { index: number; currentScrollTop: number; viewportHeight: number; scrollHeight?: number; rowHeight?: number; topOcclusionHeight?: number; align?: SidebarNodeScrollAlign }): number {
|
||||||
const rowHeight = options.rowHeight ?? SIDEBAR_TREE_ROW_HEIGHT;
|
const rowHeight = options.rowHeight ?? SIDEBAR_TREE_ROW_HEIGHT;
|
||||||
if (options.index < 0 || options.viewportHeight <= 0) return options.currentScrollTop;
|
if (options.index < 0 || options.viewportHeight <= 0) return options.currentScrollTop;
|
||||||
|
|
||||||
const rowTop = options.index * rowHeight;
|
const rowTop = options.index * rowHeight;
|
||||||
const rowBottom = rowTop + rowHeight;
|
const rowBottom = rowTop + rowHeight;
|
||||||
const topOcclusionHeight = options.topOcclusionHeight ?? 0;
|
const topOcclusionHeight = options.topOcclusionHeight ?? 0;
|
||||||
if (options.align === "top") return Math.max(0, rowTop - topOcclusionHeight);
|
const maxScrollTop = options.scrollHeight == null ? Number.POSITIVE_INFINITY : Math.max(0, options.scrollHeight - options.viewportHeight);
|
||||||
|
const clampScrollTop = (value: number) => Math.max(0, Math.min(maxScrollTop, value));
|
||||||
|
if (options.align === "top") return clampScrollTop(rowTop - topOcclusionHeight);
|
||||||
if (options.align === "smart") {
|
if (options.align === "smart") {
|
||||||
// Similar to IDE Locate: place the target around the upper third of the viewport.
|
// Similar to IDE Locate: place the target around the upper third of the viewport.
|
||||||
const availableViewportHeight = Math.max(rowHeight, options.viewportHeight - topOcclusionHeight);
|
const availableViewportHeight = Math.max(rowHeight, options.viewportHeight - topOcclusionHeight);
|
||||||
const smartOffset = Math.max(0, (availableViewportHeight - rowHeight) / 3);
|
const smartOffset = Math.max(0, (availableViewportHeight - rowHeight) / 3);
|
||||||
return Math.max(0, Math.round(rowTop - topOcclusionHeight - smartOffset));
|
return clampScrollTop(Math.round(rowTop - topOcclusionHeight - smartOffset));
|
||||||
|
}
|
||||||
|
if (options.align === "center") {
|
||||||
|
const availableViewportHeight = Math.max(rowHeight, options.viewportHeight - topOcclusionHeight);
|
||||||
|
const centerOffset = Math.max(0, (availableViewportHeight - rowHeight) / 2);
|
||||||
|
return clampScrollTop(Math.round(rowTop - topOcclusionHeight - centerOffset));
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewportTop = options.currentScrollTop + topOcclusionHeight;
|
const viewportTop = options.currentScrollTop + topOcclusionHeight;
|
||||||
const viewportBottom = options.currentScrollTop + options.viewportHeight;
|
const viewportBottom = options.currentScrollTop + options.viewportHeight;
|
||||||
|
|
||||||
if (rowTop < viewportTop) return Math.max(0, rowTop - topOcclusionHeight);
|
if (rowTop < viewportTop) return clampScrollTop(rowTop - topOcclusionHeight);
|
||||||
if (rowBottom > viewportBottom) return Math.max(0, rowBottom - options.viewportHeight);
|
if (rowBottom > viewportBottom) return clampScrollTop(rowBottom - options.viewportHeight);
|
||||||
return options.currentScrollTop;
|
return clampScrollTop(options.currentScrollTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findNodePathForActiveTab(tab: QueryTab | undefined | null, treeNodes: readonly TreeNode[]): TreeNode[] | null {
|
export function findNodePathForActiveTab(tab: QueryTab | undefined | null, treeNodes: readonly TreeNode[]): TreeNode[] | null {
|
||||||
|
|||||||
@@ -542,6 +542,13 @@ test("sidebar node scrolling supports top and smart locate alignment", () => {
|
|||||||
assert.equal(scrollTopForSidebarNode({ index: 0, currentScrollTop: 300, viewportHeight: 140, topOcclusionHeight: 28, align: "smart" }), 0);
|
assert.equal(scrollTopForSidebarNode({ index: 0, currentScrollTop: 300, viewportHeight: 140, topOcclusionHeight: 28, align: "smart" }), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("sidebar node scrolling centers explicit tab locate with sticky and boundary clamping", () => {
|
||||||
|
assert.equal(scrollTopForSidebarNode({ index: 20, currentScrollTop: 0, viewportHeight: 140, align: "center" }), 504);
|
||||||
|
assert.equal(scrollTopForSidebarNode({ index: 11, currentScrollTop: 0, viewportHeight: 140, topOcclusionHeight: 28, align: "center" }), 238);
|
||||||
|
assert.equal(scrollTopForSidebarNode({ index: 0, currentScrollTop: 300, viewportHeight: 140, topOcclusionHeight: 28, align: "center" }), 0);
|
||||||
|
assert.equal(scrollTopForSidebarNode({ index: 20, currentScrollTop: 0, viewportHeight: 140, scrollHeight: 588, align: "center" }), 448);
|
||||||
|
});
|
||||||
|
|
||||||
test("active sidebar selection only scrolls on tab or setting changes", () => {
|
test("active sidebar selection only scrolls on tab or setting changes", () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
shouldScrollActiveSidebarSelection({
|
shouldScrollActiveSidebarSelection({
|
||||||
|
|||||||
+23
-1
@@ -178,7 +178,7 @@ test("saved SQL tree rows expose copy, paste, export, rename, and confirmed dele
|
|||||||
|
|
||||||
test("explicit locate prioritizes the saved SQL row over SQL cursor table navigation", () => {
|
test("explicit locate prioritizes the saved SQL row over SQL cursor table navigation", () => {
|
||||||
const connectionTree = readFileSync("apps/desktop/src/components/sidebar/ConnectionTree.vue", "utf8");
|
const connectionTree = readFileSync("apps/desktop/src/components/sidebar/ConnectionTree.vue", "utf8");
|
||||||
const locateBody = functionBody(connectionTree, "locateActiveTabInSidebar");
|
const locateBody = functionBody(connectionTree, "locateTabInSidebar");
|
||||||
|
|
||||||
assert.match(locateBody, /const locatesSavedSql = tabTarget\?\.type === "saved-sql-file"/);
|
assert.match(locateBody, /const locatesSavedSql = tabTarget\?\.type === "saved-sql-file"/);
|
||||||
assert.match(locateBody, /const cursorCandidate = locatesSavedSql \? null : queryCursorTableCandidate/);
|
assert.match(locateBody, /const cursorCandidate = locatesSavedSql \? null : queryCursorTableCandidate/);
|
||||||
@@ -186,6 +186,28 @@ test("explicit locate prioritizes the saved SQL row over SQL cursor table naviga
|
|||||||
assert.match(locateBody, /findNodePathForTarget\(target, store\.treeNodes\)/);
|
assert.match(locateBody, /findNodePathForTarget\(target, store\.treeNodes\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("tab context menu forwards the exact tab to centered sidebar locate without activating it", () => {
|
||||||
|
const app = readFileSync("apps/desktop/src/App.vue", "utf8");
|
||||||
|
const appSidebar = readFileSync("apps/desktop/src/components/layout/AppSidebar.vue", "utf8");
|
||||||
|
const connectionTree = readFileSync("apps/desktop/src/components/sidebar/ConnectionTree.vue", "utf8");
|
||||||
|
const appLocateBody = functionBody(app, "locateTabInSidebar");
|
||||||
|
const sidebarLocateBody = functionBody(appSidebar, "locateTabInSidebar");
|
||||||
|
const activeLocateBody = functionBody(connectionTree, "locateActiveTabInSidebar");
|
||||||
|
const locateBody = functionBody(connectionTree, "locateTabInSidebar");
|
||||||
|
|
||||||
|
assert.match(app, /@locate-tab="locateTabInSidebar"/);
|
||||||
|
assert.match(appLocateBody, /setSidebarOpen\(true\)/);
|
||||||
|
assert.match(appLocateBody, /await nextTick\(\)/);
|
||||||
|
assert.match(appLocateBody, /await appSidebarRef\.value\?\.locateTabInSidebar\(tab\)/);
|
||||||
|
assert.doesNotMatch(appLocateBody, /activateQueryTab|activeTabId/);
|
||||||
|
assert.match(sidebarLocateBody, /return connectionTreeRef\.value\?\.locateTabInSidebar\(tab\)/);
|
||||||
|
assert.match(appSidebar, /defineExpose\(\{ focusSearch, locateTabInSidebar \}\)/);
|
||||||
|
assert.match(activeLocateBody, /await locateTabInSidebar\(activeTab\.value, "smart"\)/);
|
||||||
|
assert.match(locateBody, /await scrollToSidebarNode\(match\.id, \{ align \}\)/);
|
||||||
|
assert.match(connectionTree, /defineExpose\(\{ focusSearch, createNewGroup, collapseAllTreeNodes, locateTabInSidebar \}\)/);
|
||||||
|
assert.match(connectionTree, /@request-connection-rename="startRenamingConnectionNode"/);
|
||||||
|
});
|
||||||
|
|
||||||
test("batch table paste refreshes each object list after all tables are processed", () => {
|
test("batch table paste refreshes each object list after all tables are processed", () => {
|
||||||
const runtimeHost = readFileSync("apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue", "utf8");
|
const runtimeHost = readFileSync("apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue", "utf8");
|
||||||
const confirmPasteTableBody = functionBody(runtimeHost, "confirmPasteTable");
|
const confirmPasteTableBody = functionBody(runtimeHost, "confirmPasteTable");
|
||||||
|
|||||||
Reference in new issue
Block a user