fix(editor): Prevent unloading when changes are pending in new canvas (no-changelog) (#10474)
This commit is contained in:
86
packages/editor-ui/src/composables/useBeforeUnload.spec.ts
Normal file
86
packages/editor-ui/src/composables/useBeforeUnload.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useBeforeUnload } from '@/composables/useBeforeUnload';
|
||||
import { STORES, VIEWS } from '@/constants';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import type { useRoute } from 'vue-router';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { describe } from 'vitest';
|
||||
|
||||
describe('useBeforeUnload', () => {
|
||||
const defaultRoute = mock<ReturnType<typeof useRoute>>({ name: 'someRoute' });
|
||||
|
||||
let uiStore: ReturnType<typeof useUIStore>;
|
||||
let canvasStore: ReturnType<typeof useCanvasStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createTestingPinia({
|
||||
initialState: {
|
||||
[STORES.UI]: {
|
||||
stateIsDirty: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
setActivePinia(pinia);
|
||||
|
||||
uiStore = useUIStore();
|
||||
canvasStore = useCanvasStore();
|
||||
});
|
||||
|
||||
describe('onBeforeUnload', () => {
|
||||
it('should do nothing if route is demo', () => {
|
||||
const route = mock<ReturnType<typeof useRoute>>({ name: VIEWS.DEMO });
|
||||
const { onBeforeUnload } = useBeforeUnload({ route });
|
||||
const event = new Event('beforeunload');
|
||||
|
||||
const result = onBeforeUnload(event);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should prompt user if state is dirty', () => {
|
||||
uiStore.stateIsDirty = true;
|
||||
const { onBeforeUnload } = useBeforeUnload({ route: defaultRoute });
|
||||
const event = new Event('beforeunload');
|
||||
|
||||
const result = onBeforeUnload(event);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should start loading if state is not dirty', () => {
|
||||
uiStore.stateIsDirty = false;
|
||||
const startLoadingSpy = vi.spyOn(canvasStore, 'startLoading');
|
||||
const { onBeforeUnload } = useBeforeUnload({ route: defaultRoute });
|
||||
const event = new Event('beforeunload');
|
||||
|
||||
const result = onBeforeUnload(event);
|
||||
|
||||
expect(startLoadingSpy).toHaveBeenCalledWith(expect.any(String));
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addBeforeUnloadEventBindings', () => {
|
||||
it('should add beforeunload event listener', () => {
|
||||
const { addBeforeUnloadEventBindings } = useBeforeUnload({ route: defaultRoute });
|
||||
const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
|
||||
|
||||
addBeforeUnloadEventBindings();
|
||||
|
||||
expect(addEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeBeforeUnloadEventBindings', () => {
|
||||
it('should remove beforeunload event listener', () => {
|
||||
const { removeBeforeUnloadEventBindings } = useBeforeUnload({ route: defaultRoute });
|
||||
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
|
||||
|
||||
removeBeforeUnloadEventBindings();
|
||||
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
||||
});
|
||||
});
|
||||
});
|
||||
48
packages/editor-ui/src/composables/useBeforeUnload.ts
Normal file
48
packages/editor-ui/src/composables/useBeforeUnload.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { computed } from 'vue';
|
||||
import { VIEWS } from '@/constants';
|
||||
import type { useRoute } from 'vue-router';
|
||||
|
||||
/**
|
||||
* Composable to handle the beforeunload event in canvas views.
|
||||
*
|
||||
* This hook will prevent closing the tab and prompt the user if the ui state is dirty
|
||||
* (workflow has changes) and the user tries to leave the page.
|
||||
*/
|
||||
|
||||
export function useBeforeUnload({ route }: { route: ReturnType<typeof useRoute> }) {
|
||||
const uiStore = useUIStore();
|
||||
const canvasStore = useCanvasStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
|
||||
|
||||
function onBeforeUnload(e: BeforeUnloadEvent) {
|
||||
if (isDemoRoute.value || window.preventNodeViewBeforeUnload) {
|
||||
return;
|
||||
} else if (uiStore.stateIsDirty) {
|
||||
e.returnValue = true; //Gecko + IE
|
||||
return true; //Gecko + Webkit, Safari, Chrome etc.
|
||||
} else {
|
||||
canvasStore.startLoading(i18n.baseText('nodeView.redirecting'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function addBeforeUnloadEventBindings() {
|
||||
window.addEventListener('beforeunload', onBeforeUnload);
|
||||
}
|
||||
|
||||
function removeBeforeUnloadEventBindings() {
|
||||
window.removeEventListener('beforeunload', onBeforeUnload);
|
||||
}
|
||||
|
||||
return {
|
||||
onBeforeUnload,
|
||||
addBeforeUnloadEventBindings,
|
||||
removeBeforeUnloadEventBindings,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user