diff --git a/frontend/src/utils/sqlEditorApplicationQuit.test.ts b/frontend/src/utils/sqlEditorApplicationQuit.test.ts index 7f7eca0e..f5092756 100644 --- a/frontend/src/utils/sqlEditorApplicationQuit.test.ts +++ b/frontend/src/utils/sqlEditorApplicationQuit.test.ts @@ -57,7 +57,7 @@ describe('sqlEditorApplicationQuit', () => { })]); }); - it('collects dirty saved-query tabs and ignores unnamed temporary query tabs', async () => { + it('collects dirty saved-query tabs and unnamed temporary query tabs', async () => { const savedQuery = createSavedQuery(); const savedTab = createQueryTab({ id: 'tab-2', @@ -75,7 +75,7 @@ describe('sqlEditorApplicationQuit', () => { const targets = await collectApplicationQuitUnsavedSQLTargets([savedTab, unnamedTab], [savedQuery], vi.fn()); - expect(targets).toHaveLength(1); + expect(targets).toHaveLength(2); expect(targets[0]).toMatchObject({ kind: 'saved-query', tabId: 'tab-2', @@ -84,9 +84,30 @@ describe('sqlEditorApplicationQuit', () => { connectionId: 'conn-1', dbName: 'main', }); + expect(targets[1]).toMatchObject({ + kind: 'unsaved-query', + tabId: 'tab-3', + title: 'New query', + draft: 'select * from draft_only;', + connectionId: 'conn-1', + dbName: 'main', + }); }); - it('saves external SQL files and existing saved queries before application quit', async () => { + it('ignores blank unnamed temporary query tabs', async () => { + const unnamedTab = createQueryTab({ + id: 'tab-3', + title: 'New query', + query: '', + }); + setQueryTabDraft('tab-3', ' '); + + const targets = await collectApplicationQuitUnsavedSQLTargets([unnamedTab], [], vi.fn()); + + expect(targets).toEqual([]); + }); + + it('saves external SQL files, existing saved queries, and unnamed query drafts before application quit', async () => { const saveQuery = vi.fn(async (query: SavedQuery) => query); const writeSQLFile = vi.fn(async () => ({ success: true })); @@ -107,6 +128,14 @@ describe('sqlEditorApplicationQuit', () => { connectionId: 'conn-2', dbName: 'reporting', }, + { + kind: 'unsaved-query', + tabId: 'tab-3', + title: 'New query', + draft: 'select 3;', + connectionId: 'conn-3', + dbName: 'scratch', + }, ], saveQuery, writeSQLFile); expect(writeSQLFile).toHaveBeenCalledWith('/tmp/file.sql', 'select 1;'); @@ -116,5 +145,11 @@ describe('sqlEditorApplicationQuit', () => { connectionId: 'conn-2', dbName: 'reporting', })); + expect(saveQuery).toHaveBeenCalledWith(expect.objectContaining({ + name: 'New query', + sql: 'select 3;', + connectionId: 'conn-3', + dbName: 'scratch', + })); }); }); diff --git a/frontend/src/utils/sqlEditorApplicationQuit.ts b/frontend/src/utils/sqlEditorApplicationQuit.ts index 12540c80..cd1ed398 100644 --- a/frontend/src/utils/sqlEditorApplicationQuit.ts +++ b/frontend/src/utils/sqlEditorApplicationQuit.ts @@ -31,6 +31,14 @@ export type ApplicationQuitUnsavedSQLTarget = draft: string; connectionId: string; dbName: string; + } + | { + kind: 'unsaved-query'; + tabId: string; + title: string; + draft: string; + connectionId: string; + dbName: string; }; export type ReadSQLFileForQuit = (filePath: string) => Promise; @@ -64,6 +72,13 @@ const hasSavedQueryUnsavedChanges = ( || dbName !== toTrimmedString(savedQuery.dbName); }; +let applicationQuitSavedQueryIdSeed = 0; + +const createApplicationQuitSavedQueryId = (): string => { + applicationQuitSavedQueryIdSeed += 1; + return `saved-${Date.now()}-${applicationQuitSavedQueryIdSeed}`; +}; + export const buildApplicationQuitUnsavedSQLLabel = ( targets: ApplicationQuitUnsavedSQLTarget[], ): string => { @@ -105,9 +120,20 @@ export const collectApplicationQuitUnsavedSQLTargets = async ( continue; } - const savedQuery = resolveSavedQueryForTab(tab, savedQueries); - if (!savedQuery) continue; const draft = getQueryTabDraft(tab.id, String(tab.query ?? '')); + const savedQuery = resolveSavedQueryForTab(tab, savedQueries); + if (!savedQuery) { + if (!draft.trim()) continue; + targets.push({ + kind: 'unsaved-query', + tabId: tab.id, + title: resolveTabTitle(tab, 'SQL Query'), + draft, + connectionId: toTrimmedString(tab.connectionId), + dbName: toTrimmedString(tab.dbName), + }); + continue; + } if (!hasSavedQueryUnsavedChanges(tab, savedQuery, draft)) continue; targets.push({ kind: 'saved-query', @@ -137,11 +163,23 @@ export const saveApplicationQuitUnsavedSQLTargets = async ( continue; } + if (target.kind === 'saved-query') { + await saveQuery({ + ...target.savedQuery, + sql: target.draft, + connectionId: target.connectionId, + dbName: target.dbName, + }); + continue; + } + await saveQuery({ - ...target.savedQuery, + id: createApplicationQuitSavedQueryId(), + name: target.title, sql: target.draft, connectionId: target.connectionId, dbName: target.dbName, + createdAt: Date.now(), }); } };