fix(tests): remove low-value source snapshots and define test standards
This commit is contained in:
80 files changed
+14
-2225
No files matched your search
@@ -1,18 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("BigQuery connection dialog", () => {
|
||||
it("exposes and persists external JDBC driver configuration", () => {
|
||||
const nativeAgentDriverSupport = dialogSource.match(/function supportsNativeAgentJdbcDriverConfigType\(dbType: DatabaseType\): boolean \{([\s\S]*?)\}/)?.[1] ?? "";
|
||||
const jdbcBackedDatabaseTypes = dialogSource.match(/const jdbcBackedDatabaseTypes = new Set<DatabaseType>\(\[([\s\S]*?)\]\);/)?.[1] ?? "";
|
||||
|
||||
expect(nativeAgentDriverSupport).toContain('dbType === "bigquery"');
|
||||
expect(jdbcBackedDatabaseTypes).toContain('"bigquery"');
|
||||
expect(dialogSource).toContain("const supportsNativeAgentJdbcDriverConfig = computed(() => supportsNativeAgentJdbcDriverConfigType(form.value.db_type)");
|
||||
expect(dialogSource).toContain('<template v-if="supportsNativeAgentJdbcDriverConfig">');
|
||||
expect(dialogSource).toContain('if (profile.type === "bigquery") {');
|
||||
expect(dialogSource).toContain("jdbcManualClasspathOpen.value = true;");
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const tlsCapableDatabaseTypesSource = dialogSource.match(/const tlsCapableDatabaseTypes = new Set<DatabaseType>\(\[([\s\S]*?)\]\);/)?.[1] ?? "";
|
||||
|
||||
describe("Dameng SSL connection form", () => {
|
||||
it("shows Dameng in the TLS tab with certificate directory and password fields", () => {
|
||||
expect(tlsCapableDatabaseTypesSource).toContain('"dameng"');
|
||||
expect(dialogSource).toContain("<template v-if=\"form.db_type === 'dameng'\">");
|
||||
expect(dialogSource).toContain('v-model="damengSslFilesPath"');
|
||||
expect(dialogSource).toContain('v-model="damengSslKeystorePassword"');
|
||||
expect(dialogSource).toContain('v-model="damengSslProtocol"');
|
||||
expect(dialogSource).toContain('t("connection.damengSslVerificationHint")');
|
||||
});
|
||||
});
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("connection dialog accessibility", () => {
|
||||
it("moves initial focus to an input inside the dialog", () => {
|
||||
expect(dialogSource).toContain('<Input v-model="dbSearchQuery" v-connection-dialog-auto-focus');
|
||||
expect(dialogSource).toContain('<Input v-model="form.name" v-connection-dialog-auto-focus');
|
||||
expect(dialogSource).toContain("const vConnectionDialogAutoFocus: ObjectDirective<HTMLInputElement>");
|
||||
expect(dialogSource).toContain("input.focus({ preventScroll: true })");
|
||||
expect(dialogSource).not.toMatch(/<DialogContent[^>]*\stabindex="-1"/);
|
||||
});
|
||||
|
||||
it("reserves focus ring space around the scrollable category navigation", () => {
|
||||
const nav = dialogSource.match(/<nav data-connection-category-nav class="([^"]+)"/)?.[1] ?? "";
|
||||
|
||||
expect(nav).toContain("px-0.5");
|
||||
expect(nav).toContain("pt-0.5");
|
||||
expect(nav).toContain("sm:py-0.5");
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("connection dialog corner style", () => {
|
||||
it("uses the shared corner radius token for configuration controls", () => {
|
||||
expect(dialogSource).toContain("border-radius: var(--dbx-radius-fixed-4, 4px);");
|
||||
expect(dialogSource).not.toMatch(/\.connection-config-step[\s\S]*?border-radius:\s*4px;/);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("connection dialog scrolling", () => {
|
||||
it("keeps every configuration tab inside a shrinkable form viewport", () => {
|
||||
expect(dialogSource).toContain('connection-dialog-content--config${scrollableAdvancedNacos ? " connection-dialog-content--scrollable" : ""}');
|
||||
expect(dialogSource.match(/<TabsContent[^>]*class="m-0 flex min-h-0 flex-1 flex-col overflow-hidden">/g)).toHaveLength(4);
|
||||
expect(dialogSource.match(/class="connection-form-body grid min-h-0 flex-1 scroll-pb-6 gap-4 overflow-y-auto/g)).toHaveLength(4);
|
||||
expect(dialogSource).toMatch(/\.connection-dialog-content--scrollable\s*\{[\s\S]*?height:\s*min\(720px, calc\(var\(--dbx-viewport-height\) - 2rem\)\);/);
|
||||
expect(dialogSource).toMatch(/@media \(max-height: 720px\)[\s\S]*?height:\s*calc\(var\(--dbx-viewport-height\) - 2rem\);/);
|
||||
expect(dialogSource).toMatch(/\.connection-dialog-content--config \.connection-form-body\s*\{[\s\S]*?align-content:\s*start;/);
|
||||
});
|
||||
|
||||
it("hides proxy and HTTP tunnel controls for SQLite", () => {
|
||||
expect(dialogSource).toContain('v-if="!sqliteSshOnlyTransport"');
|
||||
expect(dialogSource).toContain('v-if="!selectedLayerProfileId && !sqliteSshOnlyTransport"');
|
||||
});
|
||||
|
||||
it("keeps the transport form inside a shrinkable scroll viewport", () => {
|
||||
expect(dialogSource).toContain('<TabsContent v-if="canUseTransportLayers" value="transport" class="m-0 flex min-h-0 flex-1 flex-col overflow-hidden">');
|
||||
expect(dialogSource).toContain('class="connection-form-body grid min-h-0 flex-1 scroll-pb-6 gap-4 overflow-y-auto overflow-x-hidden pt-4 pr-2 pb-6"');
|
||||
});
|
||||
|
||||
it("keeps Nacos cards at their content height on both form tabs", () => {
|
||||
expect(dialogSource.match(/:class="\{ 'connection-form-body--nacos': form\.db_type === 'nacos' \}"/g)).toHaveLength(2);
|
||||
expect(dialogSource).toMatch(/\.connection-form-body--nacos\s*\{[\s\S]*?grid-auto-rows:\s*max-content;/);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
function section(startMarker: string, endMarker: string): string {
|
||||
const start = dialogSource.indexOf(startMarker);
|
||||
const end = dialogSource.indexOf(endMarker, start);
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
return dialogSource.slice(start, end);
|
||||
}
|
||||
|
||||
describe("Consul connection dialog", () => {
|
||||
it("keeps Operator feature switches in the Consul form instead of the JDBC form", () => {
|
||||
const jdbcForm = section("<!-- JDBC: optional external plugin -->", "<!-- Local database files: file path only -->");
|
||||
const consulForm = section("<!-- Consul KV: HTTP endpoint, ACL token and scope -->", "<!-- etcd: endpoints, user, password, TLS -->");
|
||||
const operatorModels = ["consulOperatorVisible", "consulOperatorSnapshotRestoreEnabled", "consulOperatorAutopilotWriteEnabled", "consulOperatorRaftWriteEnabled", "consulOperatorKeyringWriteEnabled", "consulOperatorLicenseWriteEnabled"];
|
||||
|
||||
for (const model of operatorModels) {
|
||||
expect(jdbcForm).not.toContain(`v-model="${model}"`);
|
||||
expect(consulForm).toContain(`v-model="${model}"`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("GaussDB connection dialog persistence", () => {
|
||||
it("preserves count-query DOP in submitted connection configs", () => {
|
||||
const source = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const submitConfig = source.slice(source.indexOf("function connectionConfigForSubmit"), source.indexOf("function connectionNameForSubmit"));
|
||||
|
||||
expect(submitConfig).toContain("const countQueryDop = gaussdbCountQueryDop(config);");
|
||||
expect(submitConfig).toContain("setGaussdbCountQueryDop(config, countQueryDop);");
|
||||
});
|
||||
});
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("MongoDB OIDC connection form", () => {
|
||||
it("keeps the browser authentication hint inside a complete form row", () => {
|
||||
expect(dialogSource).toContain(`<div v-if="mongoAuthMechanism === 'MONGODB-OIDC'" class="grid grid-cols-4 items-start gap-4">`);
|
||||
expect(dialogSource).not.toContain(`<p v-if="mongoAuthMechanism === 'MONGODB-OIDC'" class="col-start-2 col-span-3`);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const mqttSectionStart = dialogSource.indexOf("<template v-else-if=\"form.db_type === 'mqtt'\">");
|
||||
const mqttSectionEnd = dialogSource.indexOf("<template v-else-if=\"form.db_type === 'victoriametrics'\">", mqttSectionStart);
|
||||
const mqttSection = dialogSource.slice(mqttSectionStart, mqttSectionEnd);
|
||||
|
||||
describe("MQTT connection dialog", () => {
|
||||
it("binds TLS switches through the shared Switch model API", () => {
|
||||
expect(mqttSection).toContain('<Switch v-model="mqttTls" />');
|
||||
expect(mqttSection).toContain('<Switch v-model="mqttTlsSkipVerify" class="ml-4" />');
|
||||
expect(mqttSection).not.toContain(":checked=");
|
||||
expect(mqttSection).not.toContain("@update:checked=");
|
||||
});
|
||||
|
||||
it("serializes both TLS choices into the MQTT external config", () => {
|
||||
expect(dialogSource).toContain("tls: mqttTls.value");
|
||||
expect(dialogSource).toContain("tlsSkipVerify: mqttTlsSkipVerify.value");
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("SSH agent connection form", () => {
|
||||
it("offers SSH agent for a new tunnel and exposes its custom socket path", () => {
|
||||
expect(dialogSource).toContain('<SelectItem value="agent">{{ t("connection.sshUseAgent") }}</SelectItem>');
|
||||
expect(dialogSource).toContain('<div v-if="selectedSshLayer.auth_method === \'agent\'" class="grid grid-cols-4 items-center gap-4">');
|
||||
expect(dialogSource).toContain('v-model="selectedSshLayer.ssh_agent_sock_path"');
|
||||
expect(dialogSource).not.toContain('v-if="isLegacySshAgentMethod(selectedSshLayer)" value="agent" disabled');
|
||||
});
|
||||
});
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const managerSource = readFileSync(new URL("../../../components/connection/TunnelProfileManager.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("SSH agent tunnel profile form", () => {
|
||||
it("offers the same SSH agent login method as the connection editor", () => {
|
||||
expect(managerSource).toContain('<SelectItem value="agent">{{ t("connection.sshUseAgent") }}</SelectItem>');
|
||||
});
|
||||
|
||||
it("exposes the custom agent socket path while keeping the shared auth mapping", () => {
|
||||
expect(managerSource).toContain(`<div v-if="selectedSsh.auth_method === 'agent'" class="grid grid-cols-4 items-center gap-4">`);
|
||||
expect(managerSource).toContain('v-model="selectedSsh.ssh_agent_sock_path"');
|
||||
expect(managerSource).toContain('import { applySshAuthMethod } from "@/lib/connection/sshAuthMethod";');
|
||||
expect(managerSource).toContain("applySshAuthMethod(profile, value);");
|
||||
});
|
||||
|
||||
it("does not keep a second auth-method mapping that could drift from applySshAuthMethod", () => {
|
||||
expect(managerSource).not.toContain('value === "key+password" ? "key+password"');
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("ConnectionDialog SSH tunnel test", () => {
|
||||
it("exposes a dedicated SSH tunnel test without running the database test", () => {
|
||||
expect(dialogSource).toContain("async function testSshTunnel()");
|
||||
expect(dialogSource).toContain("await api.testSshTunnel(config)");
|
||||
expect(dialogSource).toContain('@click="testSshTunnel"');
|
||||
expect(dialogSource).toContain('t("connection.sshTunnelTest")');
|
||||
});
|
||||
|
||||
it("builds the tunnel probe config without database-specific validation", () => {
|
||||
const start = dialogSource.indexOf("function connectionConfigForSshTunnelTest");
|
||||
const end = dialogSource.indexOf("function connectionConfigForSubmit", start);
|
||||
const helperSource = dialogSource.slice(start, end);
|
||||
|
||||
expect(start).toBeGreaterThan(-1);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
expect(helperSource).toContain("normalizeTransportLayersForSubmit(config)");
|
||||
expect(helperSource).not.toContain("kingbaseDatabaseRequired");
|
||||
expect(helperSource).not.toContain("dynamodbCredentialsRequired");
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const dialogSource = readFileSync(new URL("../../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const enSource = readFileSync(new URL("../../../i18n/locales/en.ts", import.meta.url), "utf8");
|
||||
const zhCnSource = readFileSync(new URL("../../../i18n/locales/zh-CN.ts", import.meta.url), "utf8");
|
||||
const fallbackLocaleSources = ["az", "es", "it", "ja", "ko", "pt-BR", "tr", "zh-TW"].map((locale) => readFileSync(new URL(`../../../i18n/locales/${locale}.ts`, import.meta.url), "utf8"));
|
||||
|
||||
describe("ZooKeeper connection dialog", () => {
|
||||
it("binds the existing Select component to the ZooKeeper auth_scheme helper", () => {
|
||||
expect(dialogSource).toContain("setZooKeeperAuthScheme");
|
||||
expect(dialogSource).toContain("resolveZooKeeperAuthScheme");
|
||||
expect(dialogSource).toContain("const zookeeperAuthScheme = computed<ZooKeeperAuthScheme>");
|
||||
expect(dialogSource).toContain('<Select v-model="zookeeperAuthScheme">');
|
||||
expect(dialogSource).toContain('<SelectItem value="digest">');
|
||||
expect(dialogSource).toContain('<SelectItem value="sasl_digest">');
|
||||
});
|
||||
|
||||
it("uses localized auth labels and tells users both cluster input locations", () => {
|
||||
for (const source of [enSource, zhCnSource]) {
|
||||
expect(source).toContain("zookeeperAuthMethod:");
|
||||
expect(source).toContain("zookeeperAuthDigest:");
|
||||
expect(source).toContain("zookeeperAuthSaslDigest:");
|
||||
expect(source).toContain("zookeeperClusterInputHint:");
|
||||
}
|
||||
for (const source of fallbackLocaleSources) {
|
||||
expect(source).toContain("withEnglishFallback");
|
||||
}
|
||||
expect(dialogSource).toContain('t("connection.zookeeperAuthMethod")');
|
||||
expect(dialogSource).toContain('t("connection.zookeeperClusterInputHint")');
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const dataGridToolbarSource = readFileSync(new URL("../../../components/grid/DataGridToolbar.vue", import.meta.url), "utf8");
|
||||
const dataGridSource = readFileSync(new URL("../../../components/grid/DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("data grid toolbar delete row", () => {
|
||||
it("declares and renders the delete-row action button", () => {
|
||||
expect(dataGridToolbarSource).toMatch(/deleteRow\?: DataGridToolbarActionCapability/);
|
||||
expect(dataGridToolbarSource).toMatch(/isDataGridToolbarCapabilityVisible\(deleteRow\)/);
|
||||
expect(dataGridToolbarSource).toMatch(/triggerDataGridToolbarAction\(deleteRow\)/);
|
||||
});
|
||||
|
||||
it("wires the delete-row capability into the toolbar", () => {
|
||||
expect(dataGridSource).toMatch(/:delete-row="deleteRowToolbarCapability"/);
|
||||
});
|
||||
|
||||
it("keeps the delete-row capability tied to row deletion state", () => {
|
||||
expect(dataGridSource).toMatch(/deleteRowToolbarTargetCount = computed\(\(\) => deletableRowIds\(selectedOrCurrentRowIds\(\)\)\.length\)/);
|
||||
expect(dataGridSource).toMatch(/visible: deleteRowToolbarState\.value\.visible/);
|
||||
expect(dataGridSource).toMatch(/disabled: deleteRowToolbarState\.value\.disabled/);
|
||||
expect(dataGridSource).toMatch(/onTrigger: \(\) => \{\s*deleteCurrentRow\(\);/);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const toolbarSource = readFileSync(new URL("../../../components/grid/DataGridToolbar.vue", import.meta.url), "utf8");
|
||||
const dataGridSource = readFileSync(new URL("../../../components/grid/DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("DataGrid geometry layer preview toolbar entry", () => {
|
||||
it("opens the registered geometry preview action from a visible map button", () => {
|
||||
expect(toolbarSource).toMatch(/layerPreview\?: DataGridToolbarActionCapability/);
|
||||
expect(toolbarSource).toMatch(/isDataGridToolbarCapabilityVisible\(layerPreview\)/);
|
||||
expect(toolbarSource).toMatch(/triggerDataGridToolbarAction\(layerPreview\)/);
|
||||
expect(toolbarSource).toMatch(/<Map class="data-grid-topbar-action-icon/);
|
||||
|
||||
expect(dataGridSource).toMatch(/candidate\.id === "geometry-map-preview"/);
|
||||
expect(dataGridSource).toMatch(/:layer-preview="layerPreviewToolbarCapability"/);
|
||||
expect(dataGridSource).toMatch(/executePreviewAction\(action\)/);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor column prefix cache", () => {
|
||||
it("hydrates qualified completions from the persistent prefix cache", () => {
|
||||
const prefixLookups = queryEditorSource.match(/lookupLocalCompletionColumnsByPrefix/g) ?? [];
|
||||
|
||||
expect(prefixLookups.length).toBeGreaterThanOrEqual(2);
|
||||
expect(queryEditorSource).toContain("completionColumnRequestContext(reference)");
|
||||
});
|
||||
});
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
it("maps inline projected columns into local completion results", () => {
|
||||
expect(queryEditorSource).toMatch(/if \(refTable\.columns\?\.length\) \{[\s\S]*?columnsByTable\.set\([\s\S]*?refTable\.columns\.map/);
|
||||
});
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
// Regression test for issue #8096: clicking a long SQL statement in the
|
||||
// execution-summary panel (previewStatementRange) used to only recolor the
|
||||
// gutter line number, which is easy to miss and reads as "click does
|
||||
// nothing" for statements that are already scrolled into view. The fix adds
|
||||
// a visible text-highlight decoration alongside the existing gutter marker.
|
||||
describe("QueryEditor result-source preview highlight", () => {
|
||||
it("builds a highlight decoration field in addition to the gutter line-number field", () => {
|
||||
const extensionStart = queryEditorSource.indexOf("buildResultSourceRangeExtension = () => {");
|
||||
expect(extensionStart).toBeGreaterThan(-1);
|
||||
const extensionEnd = queryEditorSource.indexOf("\n };", extensionStart);
|
||||
const extensionBody = queryEditorSource.slice(extensionStart, extensionEnd);
|
||||
|
||||
expect(extensionBody).toContain("lineNumberMarkers.from(field)");
|
||||
expect(extensionBody).toContain("const highlightField = StateField.define({");
|
||||
expect(extensionBody).toContain('Decoration.mark({ class: "cm-db-result-source-highlight" })');
|
||||
expect(extensionBody).toContain("EditorView.decorations.from(field)");
|
||||
expect(extensionBody).toContain("return [field, highlightField];");
|
||||
});
|
||||
|
||||
it("drives both the gutter marker and the highlight decoration from the same effect", () => {
|
||||
const extensionStart = queryEditorSource.indexOf("buildResultSourceRangeExtension = () => {");
|
||||
const extensionEnd = queryEditorSource.indexOf("\n };", extensionStart);
|
||||
const extensionBody = queryEditorSource.slice(extensionStart, extensionEnd);
|
||||
const effectUses = extensionBody.match(/effect\.is\(effectType\)/g) ?? [];
|
||||
|
||||
expect(effectUses.length).toBe(2);
|
||||
});
|
||||
|
||||
it("defines a visible background style for the highlight class", () => {
|
||||
expect(queryEditorSource).toMatch(/:deep\(\.cm-db-result-source-highlight\)\s*{\s*background:/);
|
||||
});
|
||||
|
||||
it("keeps previewStatementRange wired to setResultSourceRangeEffect via setResultSourceRange", () => {
|
||||
const fnStart = queryEditorSource.indexOf("function previewStatementRange(");
|
||||
const fnEnd = queryEditorSource.indexOf("\n}", fnStart);
|
||||
const fnBody = queryEditorSource.slice(fnStart, fnEnd);
|
||||
|
||||
expect(fnBody).toContain("setResultSourceRangeEffect.of({ from, to })");
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor bottom scroll space", () => {
|
||||
it("allows editable documents to scroll past the last line without changing read-only previews", () => {
|
||||
expect(queryEditorSource).toMatch(/scrollPastEnd,\s*ViewPlugin/);
|
||||
expect(queryEditorSource).toMatch(/layer,\s*RectangleMarker/);
|
||||
expect(queryEditorSource).toContain("props.readOnly ? [] : scrollPastEnd()");
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const globalStylesSource = readFileSync(new URL("../../../styles/globals.css", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor tooltip container", () => {
|
||||
it("portals CodeMirror tooltips to a zero-sized app host", () => {
|
||||
expect(queryEditorSource).toContain("const editorElement = editorRef.value;");
|
||||
expect(queryEditorSource).toContain("if (!editorElement) return;");
|
||||
expect(queryEditorSource).toContain('querySelector<HTMLElement>("#dbx-query-editor-tooltip-root") ?? editorElement');
|
||||
expect(queryEditorSource).toContain("tooltips({ parent: tooltipParent })");
|
||||
expect(queryEditorSource).toContain("new EditorView({ state, parent: editorElement })");
|
||||
expect(queryEditorSource).not.toContain("tooltips({ parent: document.body })");
|
||||
expect(appSource).toContain('id="dbx-query-editor-tooltip-root"');
|
||||
expect(appSource).toContain('class="fixed left-0 top-0 z-[70] h-0 w-0 overflow-visible"');
|
||||
});
|
||||
|
||||
it("keeps the app root viewport-sized without a transformed containing block", () => {
|
||||
const rootRule = globalStylesSource.match(/html,\s*body,\s*#root\s*\{(?<declarations>[^}]*)\}/)?.groups?.declarations;
|
||||
|
||||
expect(rootRule).toBeDefined();
|
||||
expect(rootRule).toMatch(/width:\s*100%/);
|
||||
expect(rootRule).toMatch(/height:\s*100%/);
|
||||
expect(rootRule).toMatch(/overflow:\s*hidden/);
|
||||
expect(rootRule).not.toMatch(/(?:^|\s)(?:transform|contain)\s*:/);
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const useSqlExecutionSource = readFileSync(new URL("../../../composables/useSqlExecution.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("toolbar explain routing", () => {
|
||||
it("passes the tab id as the execution context, not as SQL", () => {
|
||||
// Regression: the injected action once called tryExplain(tabId), whose
|
||||
// first parameter is sqlOverride — the id silently typed as SQL and the
|
||||
// EXPLAIN button sent the tab UUID to the backend.
|
||||
expect(appSource).toContain("explain: (tabId: string) => tryExplain(undefined, { tabId }),");
|
||||
expect(appSource).not.toContain("explain: (tabId: string) => tryExplain(tabId),");
|
||||
});
|
||||
|
||||
it("keeps tryExplain's first parameter an SQL override", () => {
|
||||
expect(useSqlExecutionSource).toContain("async function tryExplain(sqlOverride?: SqlExecutionOverride, options: SqlExecutionOptions = {})");
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const searchableSelectSource = readFileSync(new URL("../../../components/ui/searchable-select/SearchableSelect.vue", import.meta.url), "utf8");
|
||||
const editorSettingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
const dataTransferDialogSource = readFileSync(new URL("../../../components/transfer/DataTransferDialog.vue", import.meta.url), "utf8");
|
||||
const editorToolbarSource = readFileSync(new URL("../../../components/layout/EditorToolbar.vue", import.meta.url), "utf8");
|
||||
const schemaDiffConfigStepSource = readFileSync(new URL("../../../components/diff/SchemaDiffConfigStep.vue", import.meta.url), "utf8");
|
||||
const dataCompareDialogSource = readFileSync(new URL("../../../components/diff/DataCompareDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("SearchableSelect layout", () => {
|
||||
it("keeps slotted option labels inside a shrinkable overflow boundary", () => {
|
||||
const labelBoundaries = searchableSelectSource.match(/dbx-searchable-select-option-label min-w-0 flex-1 overflow-hidden/g) ?? [];
|
||||
|
||||
expect(labelBoundaries).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("can preserve custom option whitespace for exact database identifiers", () => {
|
||||
expect(searchableSelectSource).toContain("trimCustom?: boolean");
|
||||
expect(searchableSelectSource).toContain("trimCustom: true");
|
||||
expect(searchableSelectSource).toContain("props.trimCustom ? searchText.value.trim() : searchText.value");
|
||||
});
|
||||
|
||||
it("forwards an optional style to its portaled content for local preview isolation", () => {
|
||||
expect(searchableSelectSource).toContain('contentStyle?: HTMLAttributes["style"]');
|
||||
expect(searchableSelectSource).toContain(':style="contentStyle"');
|
||||
expect(editorSettingsDialogSource).toContain(':content-style="{ fontFamily: editUiFontFamily || DEFAULT_UI_FONT_FAMILY }"');
|
||||
expect(editorSettingsDialogSource).toContain('@option-hover="previewUiFontOption"');
|
||||
expect(editorSettingsDialogSource).toContain('@option-highlight="previewUiFontOption"');
|
||||
expect(editorSettingsDialogSource).toContain('@option-leave="restoreUiFontFamilyPreview"');
|
||||
});
|
||||
|
||||
it("renders data transfer connection pickers as sidebar-like trees", () => {
|
||||
const treeSelects = dataTransferDialogSource.match(/<ConnectionTreeSelect/g) ?? [];
|
||||
|
||||
expect(treeSelects).toHaveLength(2);
|
||||
expect(dataTransferDialogSource).toContain(':layout="store.sidebarLayout"');
|
||||
expect(dataTransferDialogSource).not.toContain("ConnectionGroupBadge");
|
||||
});
|
||||
|
||||
it("renders the toolbar connection picker as a sidebar-like tree", () => {
|
||||
expect(editorToolbarSource).toContain("<ConnectionTreeSelect");
|
||||
expect(editorToolbarSource).toContain(':connections="connectionStore.connections"');
|
||||
expect(editorToolbarSource).toContain(':layout="connectionStore.sidebarLayout"');
|
||||
});
|
||||
|
||||
it.each([
|
||||
["schema compare", schemaDiffConfigStepSource],
|
||||
["data compare", dataCompareDialogSource],
|
||||
])("renders %s connection pickers as sidebar-like trees", (_name, source) => {
|
||||
expect(source.match(/<ConnectionTreeSelect/g) ?? []).toHaveLength(2);
|
||||
expect(source).toContain(':connections="sqlConnections"');
|
||||
expect(source).toContain(':layout="store.sidebarLayout"');
|
||||
expect(source).not.toContain("ConnectionGroupBadge");
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const settingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("openDataTabsNextToActive settings control", () => {
|
||||
it("binds the navigation switch through apply and both reset paths", () => {
|
||||
expect(settingsDialogSource).toContain("const editOpenDataTabsNextToActive = ref(settingsStore.editorSettings.openDataTabsNextToActive)");
|
||||
expect(settingsDialogSource).toContain("openDataTabsNextToActive: editOpenDataTabsNextToActive.value");
|
||||
expect(settingsDialogSource.match(/editOpenDataTabsNextToActive\.value = DEFAULT_EDITOR_SETTINGS\.openDataTabsNextToActive/g)).toHaveLength(2);
|
||||
expect(settingsDialogSource).toContain('id="open-data-tabs-next-to-active" v-model="editOpenDataTabsNextToActive"');
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const settingsDialogSource = readFileSync(new URL("../../../components/editor/EditorSettingsDialog.vue", import.meta.url), "utf8");
|
||||
const changelogPanelSource = readFileSync(new URL("../../../components/settings/ChangelogPanel.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("about page update check", () => {
|
||||
it("reuses the toolbar update check and pending state", () => {
|
||||
expect(changelogPanelSource).toContain('"check-updates": []');
|
||||
expect(changelogPanelSource).toContain("@click=\"emit('check-updates')\"");
|
||||
expect(changelogPanelSource).toContain(':disabled="checkingUpdates"');
|
||||
expect(settingsDialogSource).toContain(':checking-updates="props.checkingUpdates"');
|
||||
expect(settingsDialogSource).toContain("@check-updates=\"emit('check-updates')\"");
|
||||
expect(appSource).toContain(':checking-updates="checkingUpdates"');
|
||||
expect(appSource).toContain('@check-updates="checkUpdates()"');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user