fix(objects): keep browse search filter per tab

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-09-05 22:28:14 +08:00
1 parent 38df8e3097
commit 5244e57dca
9 files changed
+79 -3

No files matched your search

@@ -176,6 +176,7 @@ const props = defineProps<{
/** 显式"新建事件"请求号:每次菜单点击递增,用于打开/重新进入 CREATE 编辑器 */
initialEventCreateRequestId?: number;
initialObjectFilter?: "tables" | "events";
initialSearchQuery?: string;
viewport?: ObjectBrowserViewport;
}>();
@@ -183,6 +184,7 @@ const emit = defineEmits<{
openTable: [target: { tableName: string; schema?: string; tableType?: string; catalog?: string }];
schemaChange: [schema: string | undefined];
viewportChange: [viewport: ObjectBrowserViewport];
searchChange: [query: string];
}>();
const { t } = useI18n();
@@ -200,7 +202,7 @@ const schemas = ref<string[]>([]);
const selectedSchema = ref<string | undefined>(props.schema);
const rows = ref<ObjectBrowserRow[]>([]);
const rootRef = ref<HTMLElement>();
const search = ref("");
const search = ref(props.initialSearchQuery ?? "");
const objectFilter = ref<ObjectFilter>("all");
const userHasSelectedFilter = ref(false);
const sortKey = ref<ObjectBrowserSortKey>("name");
@@ -534,7 +536,10 @@ watch([sortKey, sortDirection], () => scrollObjectsToTop());
// Also jump to the top when the search query or object-type filter changes —
// filtered results bear no relation to the previous scroll position.
watch(search, () => scrollObjectsToTop());
watch(search, (value) => {
scrollObjectsToTop();
emit("searchChange", value);
});
watch(objectFilter, () => {
if (preserveObjectFilterScrollOnce) {
preserveObjectFilterScrollOnce = false;
@@ -0,0 +1,48 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// Issue #8236: switching away from the "Browse Objects" tab and back reset
// the keyword search filter, because ObjectBrowser has no <KeepAlive> around
// it (ContentArea renders it inside a plain v-else-if chain) and its `search`
// ref was always seeded to "". The fix threads the value through
// activeTab.objectBrowser.searchQuery, mirroring the existing viewport
// persistence path.
const objectBrowserSource = readFileSync(new URL("../ObjectBrowser.vue", import.meta.url), "utf8");
const contentAreaSource = readFileSync(new URL("../../layout/ContentArea.vue", import.meta.url), "utf8");
const querySurfacesSource = readFileSync(new URL("../../layout/querySurfaces.ts", import.meta.url), "utf8");
const contentSurfaceEventsSource = readFileSync(new URL("../../../lib/tabs/contentSurfaceEvents.ts", import.meta.url), "utf8");
const queryStoreSource = readFileSync(new URL("../../../stores/queryStore.ts", import.meta.url), "utf8");
const databaseTypesSource = readFileSync(new URL("../../../types/database.ts", import.meta.url), "utf8");
describe("ObjectBrowser keyword search survives tab switches", () => {
it("seeds the local search ref from a persisted initial value instead of always starting empty", () => {
expect(objectBrowserSource).toContain("initialSearchQuery?: string;");
expect(objectBrowserSource).toContain('const search = ref(props.initialSearchQuery ?? "");');
});
it("emits searchChange whenever the keyword changes, so the parent can persist it", () => {
expect(objectBrowserSource).toContain("searchChange: [query: string];");
expect(objectBrowserSource).toMatch(/watch\(search, \(value\) => \{\s*scrollObjectsToTop\(\);\s*emit\("searchChange", value\);\s*\}\);/);
});
it("ContentArea binds the persisted search query as a prop and forwards the change event", () => {
expect(contentAreaSource).toContain(':initial-search-query="activeTab.objectBrowser?.searchQuery"');
expect(contentAreaSource).toContain("@search-change=\"emit('objectBrowserSearchChange', activeTab.id, $event)\"");
});
it("the surface event contract and forwarding list both know about objectBrowserSearchChange", () => {
expect(querySurfacesSource).toContain("objectBrowserSearchChange: [tabId: string, query: string];");
expect(contentSurfaceEventsSource).toContain('"objectBrowserSearchChange",');
});
it("queryStore persists the search query onto the tab's objectBrowser state", () => {
expect(queryStoreSource).toContain("function updateObjectBrowserSearch(id: string, query: string)");
expect(queryStoreSource).toContain("tab.objectBrowser = { ...tab.objectBrowser, searchQuery: query };");
expect(queryStoreSource).toContain("updateObjectBrowserSearch,");
});
it("the tab type declares searchQuery on objectBrowser state", () => {
expect(databaseTypesSource).toMatch(/objectBrowser\?:\s*\{[^}]*searchQuery\?:\s*string;/s);
});
});