fix(tabs): allow saving a saved sql tab after clearing content
Closes #9247
This commit is contained in:
4 files changed
+66
-5
No files matched your search
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canSaveSqlTab, type SqlTabSaveTarget } from "@/lib/tabs/sqlTabSaveTarget";
|
||||
|
||||
const PROCEDURE_SOURCE = { schema: "dbo", name: "p", objectType: "PROCEDURE" } as const;
|
||||
|
||||
function saveTarget(overrides: Partial<SqlTabSaveTarget>): SqlTabSaveTarget {
|
||||
return { externalSqlPath: undefined, savedSqlId: undefined, sql: "", objectSource: undefined, ...overrides };
|
||||
}
|
||||
|
||||
describe("canSaveSqlTab", () => {
|
||||
it("keeps a saved SQL library tab saveable after its content is deleted", () => {
|
||||
// Issue #9247: emptying a query file is a legitimate edit that must reach the file.
|
||||
expect(canSaveSqlTab(saveTarget({ savedSqlId: "file-1", sql: "" }))).toBe(true);
|
||||
});
|
||||
|
||||
it("treats whitespace-only content in a saved tab as the emptied state, not as content", () => {
|
||||
expect(canSaveSqlTab(saveTarget({ savedSqlId: "file-1", sql: "\n\t " }))).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps an external .sql file tab saveable after its content is deleted", () => {
|
||||
expect(canSaveSqlTab(saveTarget({ externalSqlPath: "/tmp/query.sql", sql: "" }))).toBe(true);
|
||||
});
|
||||
|
||||
it("still allows a brand-new tab with content", () => {
|
||||
expect(canSaveSqlTab(saveTarget({ sql: "SELECT 1;" }))).toBe(true);
|
||||
});
|
||||
|
||||
it("still blocks a brand-new tab with nothing to persist", () => {
|
||||
expect(canSaveSqlTab(saveTarget({ sql: " \n" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("still blocks an emptied object-source tab", () => {
|
||||
// Saving an empty routine body would send a body-less CREATE to the server.
|
||||
expect(canSaveSqlTab(saveTarget({ objectSource: PROCEDURE_SOURCE, sql: "" }))).toBe(false);
|
||||
expect(canSaveSqlTab(saveTarget({ objectSource: PROCEDURE_SOURCE, savedSqlId: "file-1", sql: "" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("allows an object-source tab with content", () => {
|
||||
expect(canSaveSqlTab(saveTarget({ objectSource: PROCEDURE_SOURCE, sql: "CREATE PROCEDURE dbo.p AS SELECT 1;" }))).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { QueryTab } from "@/types/database";
|
||||
|
||||
/** Tab fields that decide whether its SQL can be persisted. */
|
||||
export type SqlTabSaveTarget = Pick<QueryTab, "externalSqlPath" | "savedSqlId" | "sql" | "objectSource">;
|
||||
|
||||
/**
|
||||
* Whether the tab's SQL can be saved at all.
|
||||
*
|
||||
* A tab that already has a persistence target — an external `.sql` file or a
|
||||
* saved-SQL library entry — stays saveable after its content is emptied.
|
||||
* Deleting everything is a legitimate edit that must reach the target, and the
|
||||
* emptied state is also the only state in which the user can observe the guard.
|
||||
*
|
||||
* A brand-new tab has nothing to persist, and an emptied object-source tab would
|
||||
* push a body-less `CREATE` for a routine or view to the server, so both keep the
|
||||
* content requirement.
|
||||
*/
|
||||
export function canSaveSqlTab(tab: SqlTabSaveTarget): boolean {
|
||||
if (tab.externalSqlPath || tab.sql.trim()) return true;
|
||||
if (tab.objectSource) return false;
|
||||
return !!tab.savedSqlId;
|
||||
}
|
||||
Reference in new issue
Block a user