From 68c22e878b52dcc903eb79741904e93b5e8e3be4 Mon Sep 17 00:00:00 2001 From: Melody Ruch <396921921@qq.com> Date: Thu, 13 Aug 2026 19:27:37 +0800 Subject: [PATCH] fix(editor): wrap generated SQL templates --- .../src/components/layout/ContentArea.vue | 1 + .../sidebar/SidebarTreeRuntimeHost.vue | 2 +- .../queryStore.templateForceWordWrap.spec.ts | 49 +++++++++++++++++++ apps/desktop/src/stores/queryStore.ts | 3 +- apps/desktop/src/types/database.ts | 2 + 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 apps/desktop/src/stores/__tests__/queryStore.templateForceWordWrap.spec.ts diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 7cdde7e105..8cd90eecac 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -1060,6 +1060,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand :statement-execution-markers="activeStatementExecutionMarkers" :initial-viewport="activeTab.editorViewport" :initial-selection="activeTab.editorSelection" + :force-word-wrap="activeTab.forceWordWrap" @update:model-value="emit('editorUpdate', activeTab.id, $event)" @selection-change="emit('editorSelectionChange', $event)" @send-selection-to-ai="emit('sendSelectionToAi', $event)" diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index 0c4aca1d1c..d11227e560 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -1529,7 +1529,7 @@ async function loadTemplateContext(allowView = false) { } function openSqlTemplateTab(connectionId: string, database: string, schema: string | undefined, catalog: string | undefined, sql: string, title?: string) { - const tabId = queryStore.createTab(connectionId, database, title, "query", schema, undefined, catalog); + const tabId = queryStore.createTab(connectionId, database, title, "query", schema, undefined, catalog, { forceWordWrap: true }); queryStore.updateSql(tabId, sql); } diff --git a/apps/desktop/src/stores/__tests__/queryStore.templateForceWordWrap.spec.ts b/apps/desktop/src/stores/__tests__/queryStore.templateForceWordWrap.spec.ts new file mode 100644 index 0000000000..9967ff78c7 --- /dev/null +++ b/apps/desktop/src/stores/__tests__/queryStore.templateForceWordWrap.spec.ts @@ -0,0 +1,49 @@ +import { readFileSync } from "node:fs"; +import { createPinia, setActivePinia } from "pinia"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +describe("queryStore createTab forceWordWrap", () => { + beforeEach(() => { + vi.resetModules(); + vi.unstubAllGlobals(); + vi.stubGlobal("localStorage", { + getItem: vi.fn(() => null), + setItem: vi.fn(), + removeItem: vi.fn(), + }); + setActivePinia(createPinia()); + }); + + it("defaults new tabs to no forced word wrap", async () => { + const { useQueryStore } = await import("@/stores/queryStore"); + const queryStore = useQueryStore(); + + const tabId = queryStore.createTab("pg-1", "app", "Query"); + const tab = queryStore.tabs.find((t) => t.id === tabId); + + expect(tab?.forceWordWrap).toBeFalsy(); + }); + + it("marks a tab as force-wrapped when requested via options", async () => { + const { useQueryStore } = await import("@/stores/queryStore"); + const queryStore = useQueryStore(); + + const tabId = queryStore.createTab("pg-1", "app", "SELECT users", "query", undefined, undefined, undefined, { forceNew: true, forceWordWrap: true }); + const tab = queryStore.tabs.find((t) => t.id === tabId); + + expect(tab?.forceWordWrap).toBe(true); + }); +}); + +describe("Generate SQL template tabs wire forceWordWrap end to end (issue #6038)", () => { + const sidebarSource = readFileSync(new URL("../../components/sidebar/SidebarTreeRuntimeHost.vue", import.meta.url), "utf8"); + const contentAreaSource = readFileSync(new URL("../../components/layout/ContentArea.vue", import.meta.url), "utf8"); + + it("openSqlTemplateTab requests forceWordWrap when creating the tab", () => { + expect(sidebarSource).toMatch(/function openSqlTemplateTab\([^)]*\)\s*{[\s\S]*?createTab\([^)]*forceWordWrap:\s*true[^)]*\)/); + }); + + it("ContentArea forwards the tab's forceWordWrap flag into QueryEditor", () => { + expect(contentAreaSource).toMatch(/ { return tabs.value.find((tab) => tab.connectionId === connectionId && tab.database === database && tab.title === title && tab.mode === mode && (tab.schema || "") === (schema || "") && (tab.catalog || "") === (catalog || "")); } - function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query", schema?: string, initialSql?: string, catalog?: string, options: { forceNew?: boolean; activate?: boolean } = {}) { + function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query", schema?: string, initialSql?: string, catalog?: string, options: { forceNew?: boolean; activate?: boolean; forceWordWrap?: boolean } = {}) { if (title && !options.forceNew) { const existing = findTabByIdentity(connectionId, database, title, mode, schema, catalog); if (existing) { @@ -1621,6 +1621,7 @@ export const useQueryStore = defineStore("query", () => { id, title: title || `query_${tabs.value.length + 1}`, customTitle: mode === "query" && !!title ? true : undefined, + forceWordWrap: options.forceWordWrap, connectionId, database, schema, diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 1bf0801032..60be540885 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -1078,6 +1078,8 @@ export interface QueryTab { id: string; title: string; customTitle?: boolean; + /** Force the editor to word-wrap regardless of the global setting, e.g. for auto-generated single-line templates. */ + forceWordWrap?: boolean; connectionId: string; database: string; schema?: string;