From 9051ad35b844afe94e223a49db5e4bb0408b81cb Mon Sep 17 00:00:00 2001 From: zipg Date: Tue, 18 Aug 2026 15:24:01 +0800 Subject: [PATCH] fix(connection): clear parsed state when switching profiles --- .../connection/ConnectionDialog.vue | 16 ++- .../connectionDialogProfileSwitch.spec.ts | 119 ++++++++++++++++++ packages/app-tests/connectionUrl.test.ts | 11 ++ 3 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 apps/desktop/src/lib/__tests__/connection/connectionDialogProfileSwitch.spec.ts diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index 7f2b954cb5..eba7066173 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -2770,6 +2770,10 @@ function defaultDatabaseForProfile() { } function onDbTypeChange(val: string) { + if (!editingId.value && val === selectedType.value) return; + if (!editingId.value) { + resetForm({ preservePickerState: true }); + } const category = dbCategoryForOption(val); if (category) selectedDbCategory.value = category; customDriverName.value = ""; @@ -4848,7 +4852,7 @@ function openJdbcDriverManagerFromError() { openJdbcDriverManager(); } -function resetForm() { +function resetForm(options: { preservePickerState?: boolean } = {}) { editingId.value = null; form.value = defaultForm(); resetConnectionNoteVisibilityDraft(connectionNoteVisibilityDraft, settingsStore.editorSettings.sidebarShowConnectionNotes); @@ -4870,10 +4874,12 @@ function resetForm() { appliedConnectionUrlInput.value = ""; resetMeilisearchHostInput(); oracleTnsAdminPath.value = ""; - dialogStep.value = "select"; - dbSearchQuery.value = ""; - selectedDbCategory.value = "sql"; - configTab.value = "connection"; + if (!options.preservePickerState) { + dialogStep.value = "select"; + dbSearchQuery.value = ""; + selectedDbCategory.value = "sql"; + configTab.value = "connection"; + } resetVisibleDatabaseDraftState(); resetVisibleNacosNamespaceDraftState(); resetProductionDatabaseDraftState(); diff --git a/apps/desktop/src/lib/__tests__/connection/connectionDialogProfileSwitch.spec.ts b/apps/desktop/src/lib/__tests__/connection/connectionDialogProfileSwitch.spec.ts new file mode 100644 index 0000000000..8f872b1e0f --- /dev/null +++ b/apps/desktop/src/lib/__tests__/connection/connectionDialogProfileSwitch.spec.ts @@ -0,0 +1,119 @@ +import { readFileSync } from "node:fs"; +import { parse } from "vue/compiler-sfc"; +import ts from "typescript"; +import { describe, expect, it } from "vitest"; + +const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8"); +const parsedDialog = parse(dialogSource, { filename: "ConnectionDialog.vue" }); + +function functionSource(name: string): string { + const script = parsedDialog.descriptor.scriptSetup; + expect(parsedDialog.errors).toEqual([]); + expect(script).toBeDefined(); + + const source = ts.createSourceFile("ConnectionDialog.vue.ts", script!.content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const declaration = source.statements.find((statement): statement is ts.FunctionDeclaration => ts.isFunctionDeclaration(statement) && statement.name?.text === name); + expect(declaration).toBeDefined(); + return declaration!.getText(); +} + +interface ProfileDraft { + port: number; + username: string; + url_params: string; + agent_java_options: string[]; +} + +function profileSwitchHarness(selectedProfile: string, editing = false) { + const form: ProfileDraft = { + port: 15432, + username: "draft-user", + url_params: "sslmode=require&application_name=dbx", + agent_java_options: ["-Xms256m", "-Xmx1g"], + }; + const editingId: { value: string | null } = { value: editing ? "connection-1" : null }; + const selectedType = { value: selectedProfile }; + const selectedDbCategory = { value: "sql" }; + const customDriverName = { value: selectedProfile.startsWith("custom_") ? `Draft ${selectedProfile}` : "" }; + const events: string[] = []; + const defaults: Record = { + mysql: { port: 3306, username: "root", url_params: "", agent_java_options: [] }, + postgres: { port: 5432, username: "postgres", url_params: "", agent_java_options: [] }, + }; + + function resetForm(options: { preservePickerState?: boolean }) { + events.push(`reset:${options.preservePickerState === true}`); + Object.assign(form, defaults.mysql); + selectedType.value = "mysql"; + customDriverName.value = ""; + } + + function applyProfile(profile: string, preserveConnectionFields: boolean) { + events.push(`apply:${profile}:${preserveConnectionFields}`); + selectedType.value = profile; + if (!preserveConnectionFields) { + const defaultProfile = profile.includes("postgres") ? defaults.postgres : defaults.mysql; + Object.assign(form, defaultProfile); + } + } + + const javascript = ts.transpileModule(functionSource("onDbTypeChange"), { + compilerOptions: { module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2022 }, + }).outputText; + const selectProfile = new Function("editingId", "selectedType", "resetForm", "dbCategoryForOption", "selectedDbCategory", "customDriverName", "applyProfile", "resetTestState", "resetVisibleSchemasState", `${javascript}\nreturn onDbTypeChange;`)( + editingId, + selectedType, + resetForm, + () => "sql", + selectedDbCategory, + customDriverName, + applyProfile, + () => events.push("reset-test"), + () => events.push("reset-schemas"), + ) as (profile: string) => void; + + return { customDriverName, events, form, selectProfile, selectedType }; +} + +describe("ConnectionDialog database profile switching", () => { + it.each([ + ["mysql", ""], + ["custom_mysql", "Draft custom_mysql"], + ["custom_postgres", "Draft custom_postgres"], + ])("preserves a new %s draft when the current profile is selected again", (profile, driverName) => { + const harness = profileSwitchHarness(profile); + const formBeforeReselect = structuredClone(harness.form); + + harness.selectProfile(profile); + + expect(harness.form.port).toBe(formBeforeReselect.port); + expect(harness.form.username).toBe(formBeforeReselect.username); + expect(harness.form.url_params).toBe(formBeforeReselect.url_params); + expect(harness.form.agent_java_options).toEqual(formBeforeReselect.agent_java_options); + expect(harness.customDriverName.value).toBe(driverName); + expect(harness.selectedType.value).toBe(profile); + expect(harness.events).toEqual([]); + }); + + it("resets the new connection draft before selecting a different profile", () => { + const harness = profileSwitchHarness("mysql"); + + harness.selectProfile("postgres"); + + expect(harness.form).toEqual({ port: 5432, username: "postgres", url_params: "", agent_java_options: [] }); + expect(harness.customDriverName.value).toBe(""); + expect(harness.selectedType.value).toBe("postgres"); + expect(harness.events).toEqual(["reset:true", "apply:postgres:false", "reset-test", "reset-schemas"]); + }); + + it("keeps the existing edit-path behavior when reselecting its profile", () => { + const harness = profileSwitchHarness("custom_mysql", true); + const formBeforeReselect = structuredClone(harness.form); + + harness.selectProfile("custom_mysql"); + + expect(harness.form).toEqual(formBeforeReselect); + expect(harness.customDriverName.value).toBe(""); + expect(harness.events).toEqual(["apply:custom_mysql:true", "reset-test", "reset-schemas"]); + }); +}); diff --git a/packages/app-tests/connectionUrl.test.ts b/packages/app-tests/connectionUrl.test.ts index ddca428681..eaa511a1ac 100644 --- a/packages/app-tests/connectionUrl.test.ts +++ b/packages/app-tests/connectionUrl.test.ts @@ -252,6 +252,17 @@ test("parses Hive JDBC URLs using HTTP transport", () => { assert.equal(parsed.connectionString, source); }); +test("preserves Hive HTTP URL parameters after the noSasl authentication setting", () => { + const source = "jdbc:hive2://hive.example.com:20001/;transportMode=http;httpPath=cliservice;auth=noSasl?ds=2"; + const parsed = parseConnectionUrl(source); + + assert.equal(parsed.dbType, "hive"); + assert.equal(parsed.host, "hive.example.com"); + assert.equal(parsed.port, 20001); + assert.equal(parsed.urlParams, "transportMode=http;httpPath=cliservice;auth=noSasl?ds=2"); + assert.equal(parsed.connectionString, source); +}); + test("parses Hive JDBC URLs with a database and SSL parameter", () => { const source = "jdbc:hive2://hive.example.com/default;transportMode=http;httpPath=cliservice;ssl=true"; const parsed = parseConnectionUrl(source);