mirror of
https://github.com/apache/superset.git
synced 2026-05-12 11:25:56 +00:00
feat: Add confirmation modal for unsaved changes (#33809)
This commit is contained in:
committed by
GitHub
parent
050ccdcb3d
commit
057218d87f
@@ -23,6 +23,7 @@ import {
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
within,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import * as chartAction from 'src/components/Chart/chartAction';
|
||||
@@ -30,6 +31,7 @@ import * as saveModalActions from 'src/explore/actions/saveModalActions';
|
||||
import * as downloadAsImage from 'src/utils/downloadAsImage';
|
||||
import * as exploreUtils from 'src/explore/exploreUtils';
|
||||
import { FeatureFlag, VizType } from '@superset-ui/core';
|
||||
import { useUnsavedChangesPrompt } from 'src/hooks/useUnsavedChangesPrompt';
|
||||
import ExploreHeader from '.';
|
||||
|
||||
const chartEndpoint = 'glob:*api/v1/chart/*';
|
||||
@@ -40,6 +42,10 @@ window.featureFlags = {
|
||||
[FeatureFlag.EmbeddableCharts]: true,
|
||||
};
|
||||
|
||||
jest.mock('src/hooks/useUnsavedChangesPrompt', () => ({
|
||||
useUnsavedChangesPrompt: jest.fn(),
|
||||
}));
|
||||
|
||||
const createProps = (additionalProps = {}) => ({
|
||||
chart: {
|
||||
id: 1,
|
||||
@@ -134,6 +140,18 @@ fetchMock.post(
|
||||
describe('ExploreChartHeader', () => {
|
||||
jest.setTimeout(15000); // ✅ Applies to all tests in this suite
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: false,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
test('Cancelling changes to the properties should reset previous properties', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
@@ -201,35 +219,173 @@ describe('ExploreChartHeader', () => {
|
||||
});
|
||||
|
||||
test('Save chart', async () => {
|
||||
const setSaveChartModalVisibility = jest.spyOn(
|
||||
const setSaveChartModalVisibilitySpy = jest.spyOn(
|
||||
saveModalActions,
|
||||
'setSaveChartModalVisibility',
|
||||
);
|
||||
|
||||
const setSaveChartModalVisibilityMock =
|
||||
setSaveChartModalVisibilitySpy as jest.Mock;
|
||||
|
||||
const triggerManualSave = jest.fn(() => {
|
||||
setSaveChartModalVisibilityMock(true);
|
||||
});
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: false,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave,
|
||||
});
|
||||
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
expect(await screen.findByText('Save')).toBeInTheDocument();
|
||||
userEvent.click(screen.getByText('Save'));
|
||||
expect(setSaveChartModalVisibility).toHaveBeenCalledWith(true);
|
||||
setSaveChartModalVisibility.mockClear();
|
||||
|
||||
const saveButton: HTMLElement = await screen.findByRole('button', {
|
||||
name: /save/i,
|
||||
});
|
||||
|
||||
userEvent.click(saveButton);
|
||||
|
||||
expect(triggerManualSave).toHaveBeenCalled();
|
||||
expect(setSaveChartModalVisibilityMock).toHaveBeenCalledWith(true);
|
||||
|
||||
setSaveChartModalVisibilityMock.mockClear();
|
||||
});
|
||||
|
||||
test('Save disabled', async () => {
|
||||
const setSaveChartModalVisibility = jest.spyOn(
|
||||
saveModalActions,
|
||||
'setSaveChartModalVisibility',
|
||||
);
|
||||
const triggerManualSave = jest.fn();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: false,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave,
|
||||
});
|
||||
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} saveDisabled />, { useRedux: true });
|
||||
expect(await screen.findByText('Save')).toBeInTheDocument();
|
||||
userEvent.click(screen.getByText('Save'));
|
||||
expect(setSaveChartModalVisibility).not.toHaveBeenCalled();
|
||||
setSaveChartModalVisibility.mockClear();
|
||||
|
||||
const saveButton: HTMLElement = await screen.findByRole('button', {
|
||||
name: /save/i,
|
||||
});
|
||||
|
||||
expect(saveButton).toBeDisabled();
|
||||
|
||||
userEvent.click(saveButton);
|
||||
|
||||
expect(triggerManualSave).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should render UnsavedChangesModal when showModal is true', async () => {
|
||||
const props = createProps();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: true,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('Save changes to your chart?'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText("If you don't save, changes will be lost."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call handleSaveAndCloseModal when clicking Save in UnsavedChangesModal', async () => {
|
||||
const handleSaveAndCloseModal = jest.fn();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: true,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal,
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
|
||||
const modal: HTMLElement = await screen.findByRole('dialog');
|
||||
const saveButton: HTMLElement = within(modal).getByRole('button', {
|
||||
name: /save/i,
|
||||
});
|
||||
|
||||
userEvent.click(saveButton);
|
||||
|
||||
expect(handleSaveAndCloseModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call handleConfirmNavigation when clicking Discard in UnsavedChangesModal', async () => {
|
||||
const handleConfirmNavigation = jest.fn();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: true,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation,
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
|
||||
const modal: HTMLElement = await screen.findByRole('dialog');
|
||||
const discardButton: HTMLElement = within(modal).getByRole('button', {
|
||||
name: /discard/i,
|
||||
});
|
||||
|
||||
userEvent.click(discardButton);
|
||||
|
||||
expect(handleConfirmNavigation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call setShowModal(false) when clicking close button in UnsavedChangesModal', async () => {
|
||||
const setShowModal = jest.fn();
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: true,
|
||||
setShowModal,
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
|
||||
const closeButton: HTMLElement = await screen.findByRole('button', {
|
||||
name: /close/i,
|
||||
});
|
||||
|
||||
userEvent.click(closeButton);
|
||||
|
||||
expect(setShowModal).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Additional actions tests', () => {
|
||||
jest.setTimeout(15000); // ✅ Applies to all tests in this suite
|
||||
|
||||
beforeEach(() => {
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: false,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
test('Should render a button', async () => {
|
||||
const props = createProps();
|
||||
render(<ExploreHeader {...props} />, { useRedux: true });
|
||||
@@ -356,6 +512,14 @@ describe('Additional actions tests', () => {
|
||||
beforeEach(() => {
|
||||
spyDownloadAsImage = sinon.spy(downloadAsImage, 'default');
|
||||
spyExportChart = sinon.spy(exploreUtils, 'exportChart');
|
||||
|
||||
(useUnsavedChangesPrompt as jest.Mock).mockReturnValue({
|
||||
showModal: false,
|
||||
setShowModal: jest.fn(),
|
||||
handleConfirmNavigation: jest.fn(),
|
||||
handleSaveAndCloseModal: jest.fn(),
|
||||
triggerManualSave: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
Reference in New Issue
Block a user