import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; const dialogSource = readFileSync(new URL("../SchemaDiffDialog.vue", import.meta.url), "utf8"); const configStepSource = readFileSync(new URL("../SchemaDiffConfigStep.vue", import.meta.url), "utf8"); describe("SchemaDiffDialog fullscreen layout", () => { it("fits the dialog to its portal instead of the viewport width", () => { expect(dialogSource).toContain('width: "100%"'); expect(dialogSource).toContain('height: "100%"'); expect(dialogSource).not.toContain('width: "100vw"'); expect(dialogSource).not.toContain('height: "100vh"'); }); it("removes the normal dialog gutter and minimum width while maximized", () => { expect(dialogSource).toContain(":portal-class=\"isMaximized ? 'p-0' : undefined\""); expect(dialogSource).toContain("isMaximized ? 'min-w-0' : 'min-w-[800px] resize'"); }); it("closes the options panel before allowing Escape to dismiss the dialog", () => { expect(dialogSource).toContain('@escape-key-down="handleDialogEscape"'); expect(dialogSource).toContain("if (!showOptionsPanel.value) return;"); expect(dialogSource).toContain("event.preventDefault();"); expect(dialogSource).toContain("showOptionsPanel.value = false;"); }); it("lets the deploy confirm dialog body shrink so a long destructive statement can't push the footer buttons off-screen", () => { expect(dialogSource).toContain('
'); }); it("lets the config step scroll vertically so the Compare button stays reachable when 'compare specific tables' is enabled", () => { // Regression for #6627: the "compare specific tables" feature (#6533/#6540) grows the // config step (table multi-select + same-name match). Under a fixed-height dialog that // step used to be clipped (`overflow-hidden`), pushing the Compare button below the // visible area with no way to scroll to it. The container must scroll on all steps // except the result step, which relies on splitpanes to manage its own height. expect(dialogSource).toContain("step === 'result' ? 'overflow-hidden' : 'overflow-y-auto'"); // The config step must not be flex-shrunk (which would swallow its height and suppress // the scrollbar); only then does the taller-than-dialog content actually overflow and // become reachable via scrolling. expect(dialogSource).toMatch(/ { expect(dialogSource).toContain("function resetComparisonResultState() {"); expect(dialogSource).toMatch(/if \(isOpen\) \{\s+resetComparisonResultState\(\);/); expect(dialogSource).toContain("diffObjects.value = [];"); expect(dialogSource).toContain("lastDiffResult.value = null;"); expect(dialogSource).toContain("schemaDiffProgress.value = null;"); }); it("shows real metadata progress and indeterminate comparison phases without fabricated percentages", () => { expect(dialogSource).toContain('phase: "loading-table-lists"'); expect(dialogSource).toContain('phase: "loading-source-details"'); expect(dialogSource).toContain('phase: "loading-target-details"'); expect(dialogSource).toContain('phase: "loading-extra-objects"'); expect(dialogSource).toContain('phase: "comparing"'); expect(dialogSource).toContain('phase: "generating"'); expect(dialogSource).toContain("onProgress: (progress) =>"); expect(dialogSource).toContain("schemaDiffProgressCount"); expect(dialogSource).toContain('role="progressbar"'); expect(dialogSource).toContain("schema-diff-progress-indeterminate"); expect(dialogSource).not.toMatch(/\b(?:20|40|60|80)%/); }); it("renders a localized next-step label alongside the current progress state", () => { expect(dialogSource).toContain("getSchemaDiffNextProgressStep"); expect(dialogSource).toContain("schemaDiffNextProgressLabel"); expect(dialogSource).toContain('t("diff.progress.next"'); expect(dialogSource).toContain('v-if="schemaDiffNextProgressLabel"'); expect(dialogSource).toContain("shouldLoadSchemaDiffExtraObjects"); }); it("clears progress before preserving the existing comparison error flow", () => { expect(dialogSource).toMatch(/\} catch \(e: any\) \{\s+if \(!isCurrentRequest\(\)\) return;\s+schemaDiffProgress\.value = null;/); expect(dialogSource).toContain("toast(e?.message || String(e), 5000);"); expect(dialogSource).toContain('step.value = "config";'); }); it("invalidates an active comparison when the dialog closes", () => { expect(dialogSource).toContain("const requestId = ++comparisonRequestId;"); expect(dialogSource).toContain("if (!isCurrentRequest()) return;"); expect(dialogSource).toMatch(/\} else \{\s+comparisonRequestId\+\+;\s+loading\.value = false;\s+schemaDiffProgress\.value = null;/); }); it("does not restore persisted table matches before source and target identities are ready", () => { expect(configStepSource).toContain('if (!isTableIdentityReady("source")) {'); expect(configStepSource).toContain("clearUnavailableTableSelection();"); expect(configStepSource).toContain('v-if="restrictTables && canConfigureTableSelection"'); expect(configStepSource).toContain("restrictTables && localSelectedTables.length && canConfigureTableSelection && isTableIdentityReady('target')"); }); it("keeps explicit table selection in a shared scope below both sides", () => { const comparisonScope = configStepSource.indexOf(""); const targetInfo = configStepSource.indexOf(""); const options = configStepSource.indexOf(""); const tableSelectors = configStepSource.match(/", comparisonScope); expect(comparisonScope).toBeGreaterThan(targetInfo); expect(comparisonScope).toBeLessThan(options); expect(tableSelectors).toHaveLength(1); expect(tableSelector).toBeGreaterThan(comparisonScope); expect(tableSelector).toBeLessThan(options); expect(targetMatch).toBeGreaterThan(comparisonScope); expect(targetMatch).toBeLessThan(options); }); it("keeps focused and selected deployment SQL projections separate", () => { expect(dialogSource).toContain('const focusedDeploySql = ref("");'); expect(dialogSource).toContain('const selectedDeploySql = ref("");'); expect(dialogSource).toContain("const input = selectSchemaDiffInputForObject(result, diffObjects.value, objectId);"); expect(dialogSource).toContain(':deploy-sql="focusedDeploySql"'); expect(dialogSource).toContain(':deploy-sql-all="selectedDeploySql"'); expect(dialogSource).not.toContain("deploySqlAll.value = result.syncSql"); }); it("executes the selected SQL rather than the focused preview", () => { expect(dialogSource).toContain("sql: selectedDeploySql.value"); expect(dialogSource).toContain("[selectedDeploySql.value]"); expect(dialogSource).toContain("selectedObjectId.value !== objectId"); expect(dialogSource).toContain('selectedDeploySql.value = "";'); expect(dialogSource).toContain("const sql = selectedDeploySql.value.trim();"); }); });