diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/SaveQuery.test.tsx b/superset-frontend/src/SqlLab/components/SaveQuery/SaveQuery.test.tsx index 893f8afd719..cf4cdcca436 100644 --- a/superset-frontend/src/SqlLab/components/SaveQuery/SaveQuery.test.tsx +++ b/superset-frontend/src/SqlLab/components/SaveQuery/SaveQuery.test.tsx @@ -245,4 +245,104 @@ describe('SavedQuery', () => { expect(overwriteCombobox).toBeInTheDocument(); expect(overwritePlaceholderText).toBeInTheDocument(); }); + + it('modal stays open while save is in progress and closes after completion', async () => { + let resolveSave: () => void; + const savePromise = new Promise(resolve => { + resolveSave = resolve; + }); + + const mockOnSave = jest.fn().mockImplementation(() => savePromise); + + render(, { + useRedux: true, + store: mockStore(mockState), + }); + + // Open the modal + const saveBtn = screen.getByRole('button', { name: /save/i }); + userEvent.click(saveBtn); + + // Verify modal is open + expect( + screen.getByRole('heading', { name: /save query/i }), + ).toBeInTheDocument(); + + // Click save button in the modal + const modalSaveBtn = screen.getAllByRole('button', { name: /save/i })[1]; + userEvent.click(modalSaveBtn); + + // Modal should still be open while save is in progress + expect( + screen.getByRole('heading', { name: /save query/i }), + ).toBeInTheDocument(); + + // Resolve the save promise + resolveSave!(); + + // Wait for modal to close after save completes + await waitFor(() => { + expect( + screen.queryByRole('heading', { name: /save query/i }), + ).not.toBeInTheDocument(); + }); + + expect(mockOnSave).toHaveBeenCalledTimes(1); + }); + + it('handles save with a new tab that has no changes', async () => { + const mockOnSave = jest.fn().mockResolvedValue(undefined); + + // Mock state for a new tab with default SQL + const newTabState = { + ...mockState, + sqlLab: { + ...mockState.sqlLab, + queryEditors: [ + { + id: mockedProps.queryEditorId, + dbId: 1, + catalog: null, + schema: 'main', + sql: 'SELECT ...', // Default SQL for new tabs + name: undefined, + description: undefined, + }, + ], + }, + }; + + render(, { + useRedux: true, + store: mockStore(newTabState), + }); + + // Open the modal + const saveBtn = screen.getByRole('button', { name: /save/i }); + userEvent.click(saveBtn); + + // Modal should open + expect( + screen.getByRole('heading', { name: /save query/i }), + ).toBeInTheDocument(); + + // The name field should have "Undefined" as default + const nameInput = screen.getAllByRole('textbox')[0] as HTMLInputElement; + expect(nameInput).toHaveValue('Undefined'); + + // Click save button + const modalSaveBtn = screen.getAllByRole('button', { name: /save/i })[1]; + userEvent.click(modalSaveBtn); + + // Wait for save to complete and modal to close + await waitFor(() => { + expect(mockOnSave).toHaveBeenCalled(); + }); + + await waitFor(() => { + expect( + screen.queryByRole('heading', { name: /save query/i }), + ).not.toBeInTheDocument(); + }); + }); }); diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx index 82782e59f5e..7d45bd351e8 100644 --- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx @@ -147,14 +147,14 @@ const SaveQuery = ({ const close = () => setShowSave(false); - const onSaveWrapper = () => { + const onSaveWrapper = async () => { logAction(LOG_ACTIONS_SQLLAB_SAVE_QUERY, {}); - onSave(queryPayload(), query.id); + await onSave(queryPayload(), query.id); close(); }; - const onUpdateWrapper = () => { - onUpdate(queryPayload(), query.id); + const onUpdateWrapper = async () => { + await onUpdate(queryPayload(), query.id); close(); }; @@ -220,9 +220,7 @@ const SaveQuery = ({ />