perf(tabs): retain bounded hot tab surfaces

This commit is contained in:
t8y2
2026-09-11 10:46:18 +08:00
parent c89a2f4979
commit bad696ed11
2 changed files with 63 additions and 3 deletions
@@ -33,6 +33,8 @@ const props = defineProps<
}
>();
const HOT_TAB_SURFACE_CACHE_SIZE = 3;
const emit = defineEmits<
ContentAreaSurfaceEmits & {
"focus-group": [groupId: string];
@@ -209,8 +211,10 @@ const groupExecutableSql = computed(() => {
@clear-default-database="activeTab && toolbar.clearDefaultDatabase(activeTab.id)"
/>
<div class="relative flex-1 min-h-0">
<QueryEditorSurface v-if="activeTab?.mode === 'query'" ref="activeSurfaceRef" v-bind="surfaceBindings" :auto-focus="groupId === queryStore.focusedGroupId" class="h-full" />
<ContentArea v-else-if="activeTab" ref="activeSurfaceRef" v-bind="surfaceBindings" class="h-full" />
<KeepAlive v-if="activeTab" :max="HOT_TAB_SURFACE_CACHE_SIZE">
<QueryEditorSurface v-if="activeTab?.mode === 'query'" :key="`query:${activeTab.id}`" ref="activeSurfaceRef" v-bind="surfaceBindings" :auto-focus="groupId === queryStore.focusedGroupId" class="h-full" />
<ContentArea v-else :key="`content:${activeTab.id}`" ref="activeSurfaceRef" v-bind="surfaceBindings" class="h-full" />
</KeepAlive>
<slot v-else name="empty">
<div class="flex h-full items-center justify-center text-sm text-muted-foreground">
{{ t("tabs.emptyGroup") }}
@@ -1,9 +1,14 @@
// @vitest-environment happy-dom
import { createApp, nextTick } from "vue";
import { createApp, defineComponent, h, nextTick, ref } from "vue";
import { createPinia, setActivePinia } from "pinia";
import { createI18n } from "vue-i18n";
import { beforeEach, describe, expect, it, vi } from "vitest";
const lifecycle = vi.hoisted(() => ({
queryCreated: 0,
queryUnmounted: 0,
}));
vi.mock("@/components/layout/EditorGroupTabBar.vue", () => ({
default: {
name: "EditorGroupTabBarStub",
@@ -23,6 +28,12 @@ vi.mock("@/components/layout/QueryEditorSurface.vue", () => ({
default: {
name: "QueryEditorSurfaceStub",
props: ["activeTab", "autoFocus"],
created() {
lifecycle.queryCreated += 1;
},
unmounted() {
lifecycle.queryUnmounted += 1;
},
template: `<div data-test="query-editor">{{ activeTab.id }}</div>`,
},
}));
@@ -66,6 +77,8 @@ describe("EditorGroup mount contract", () => {
beforeEach(() => {
document.body.innerHTML = "";
lifecycle.queryCreated = 0;
lifecycle.queryUnmounted = 0;
pinia = createPinia();
setActivePinia(pinia);
i18n = createI18n({
@@ -75,6 +88,49 @@ describe("EditorGroup mount contract", () => {
});
});
it("keeps a bounded hot surface cache while switching query tabs", async () => {
const store = useQueryStore();
store.tabs = [tab("tab-a"), tab("tab-b")];
const activeTabId = ref("tab-a");
const host = createHost();
const root = defineComponent({
setup() {
return () =>
h(EditorGroup, {
groupId: "group-1",
tabIds: ["tab-a", "tab-b"],
activeTabId: activeTabId.value,
activeTab: tab(activeTabId.value),
activeConnection: undefined,
executableSql: "SELECT 1",
activeOutputView: "result",
formatSqlRequest: null,
compressSqlRequest: null,
selectedSql: "",
cursorPos: 0,
blockDangerousRedisCommands: false,
});
},
});
const app = createApp(root);
app.use(pinia);
app.use(i18n);
app.mount(host);
await nextTick();
activeTabId.value = "tab-b";
await nextTick();
activeTabId.value = "tab-a";
await nextTick();
expect(lifecycle.queryCreated).toBe(2);
expect(lifecycle.queryUnmounted).toBe(0);
app.unmount();
expect(lifecycle.queryUnmounted).toBe(2);
host.remove();
});
it("passes the group active tab to the query editor surface instead of the global active tab", async () => {
const store = useQueryStore();
store.tabs = [tab("tab-a"), tab("tab-b")];