diff --git a/apps/desktop/src/stores/savedSqlStore.ts b/apps/desktop/src/stores/savedSqlStore.ts index 0b659676f..a08be5db8 100644 --- a/apps/desktop/src/stores/savedSqlStore.ts +++ b/apps/desktop/src/stores/savedSqlStore.ts @@ -13,6 +13,16 @@ interface SavedSqlState { files: SavedSqlFile[]; } +interface SaveFileInput { + id?: string; + connectionId: string; + folderId?: string; + name: string; + database: string; + schema?: string; + sql: string; +} + function nowIso() { return new Date().toISOString(); } @@ -206,13 +216,16 @@ export const useSavedSqlStore = defineStore("savedSql", () => { await syncToLocalDirectory(); } - async function saveFile(input: { id?: string; connectionId: string; folderId?: string; name: string; database: string; schema?: string; sql: string }) { + async function saveFile(input: SaveFileInput) { const timestamp = nowIso(); const existing = input.id ? getFile(input.id) : undefined; + const hasFolderIdInput = Object.prototype.hasOwnProperty.call(input, "folderId"); const file: SavedSqlFile = existing ? { ...existing, - folderId: input.folderId || undefined, + // Partial metadata updates should not move files out of their folder. + // Callers that intentionally move to root pass `folderId: undefined`. + folderId: hasFolderIdInput ? input.folderId || undefined : existing.folderId, name: input.name, database: input.database, schema: input.schema, diff --git a/packages/app-tests/savedSqlStore.test.ts b/packages/app-tests/savedSqlStore.test.ts index b322e4b49..eb547bd8a 100644 --- a/packages/app-tests/savedSqlStore.test.ts +++ b/packages/app-tests/savedSqlStore.test.ts @@ -8,6 +8,7 @@ const apiMock = vi.hoisted(() => ({ loadSavedSqlLibrary: vi.fn<() => Promise>(), loadSavedSqlFile: vi.fn<(id: string) => Promise>(), saveSavedSqlFolder: vi.fn<(folder: SavedSqlFolder) => Promise>(), + saveSavedSqlFile: vi.fn<(file: SavedSqlFile) => Promise>(), syncSavedSqlDirectory: vi.fn<() => Promise>(), })); @@ -18,6 +19,7 @@ beforeEach(() => { apiMock.loadSavedSqlLibrary.mockResolvedValue({ folders: [], files: [] }); apiMock.loadSavedSqlFile.mockResolvedValue(null); apiMock.saveSavedSqlFolder.mockImplementation(async (folder) => folder); + apiMock.saveSavedSqlFile.mockImplementation(async (file) => file); apiMock.syncSavedSqlDirectory.mockResolvedValue(); vi.clearAllMocks(); }); @@ -72,3 +74,64 @@ test("saved SQL summaries load file content on demand", async () => { assert.equal(store.files[0]?.sql, "SELECT 1;"); assert.equal(apiMock.loadSavedSqlFile.mock.calls.length, 1); }); + +test("saving an existing SQL file without folderId keeps its folder", async () => { + const file: SavedSqlFile = { + id: "sql-1", + connectionId: "conn-1", + folderId: "folder-1", + name: "query.sql", + database: "db", + sql: "SELECT 1;", + sqlLoaded: true, + createdAt: "2026-06-27T00:00:00.000Z", + updatedAt: "2026-06-27T00:00:00.000Z", + }; + apiMock.loadSavedSqlLibrary.mockResolvedValue({ folders: [], files: [file] }); + + const store = useSavedSqlStore(); + await store.initFromStorage(); + + const saved = await store.saveFile({ + id: "sql-1", + connectionId: "conn-2", + name: "query.sql", + database: "other_db", + sql: "SELECT 1;", + }); + + assert.equal(saved.folderId, "folder-1"); + assert.equal(apiMock.saveSavedSqlFile.mock.calls[0]?.[0].folderId, "folder-1"); + assert.equal(store.getFile("sql-1")?.folderId, "folder-1"); +}); + +test("saving an existing SQL file with root folder explicitly moves it to root", async () => { + const file: SavedSqlFile = { + id: "sql-1", + connectionId: "conn-1", + folderId: "folder-1", + name: "query.sql", + database: "db", + sql: "SELECT 1;", + sqlLoaded: true, + createdAt: "2026-06-27T00:00:00.000Z", + updatedAt: "2026-06-27T00:00:00.000Z", + }; + apiMock.loadSavedSqlLibrary.mockResolvedValue({ folders: [], files: [file] }); + + const store = useSavedSqlStore(); + await store.initFromStorage(); + + const saved = await store.saveFile({ + id: "sql-1", + connectionId: "conn-1", + folderId: undefined, + name: "query.sql", + database: "db", + sql: "SELECT 1;", + }); + + assert.equal(saved.folderId, undefined); + assert.equal(apiMock.saveSavedSqlFile.mock.calls[0]?.[0].folderId, undefined); + assert.equal(store.getFile("sql-1")?.folderId, undefined); +});