From 03c509e17bfa71a124a69b6cf4897414d3bd1cb5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 2 Nov 2022 06:09:00 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/frontend/ide/components/ide_spec.js | 31 ++- .../components/panes/collapsible_sidebar_spec.js | 26 ++- spec/frontend/ide/components/panes/right_spec.js | 45 +++++ .../switch_editors/switch_editors_view_spec.js | 214 +++++++++++++++++++++ 4 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js (limited to 'spec/frontend/ide/components') diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js index 48c670757a2..a575f428a69 100644 --- a/spec/frontend/ide/components/ide_spec.js +++ b/spec/frontend/ide/components/ide_spec.js @@ -3,9 +3,11 @@ import Vue from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; import { stubPerformanceWebAPI } from 'helpers/performance'; +import { __ } from '~/locale'; import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue'; import ErrorMessage from '~/ide/components/error_message.vue'; import Ide from '~/ide/components/ide.vue'; +import eventHub from '~/ide/eventhub'; import { MSG_CANNOT_PUSH_CODE_GO_TO_FORK, MSG_GO_TO_FORK } from '~/ide/messages'; import { createStore } from '~/ide/stores'; import { file } from '../helpers'; @@ -14,6 +16,7 @@ import { projectData } from '../mock_data'; Vue.use(Vuex); const TEST_FORK_IDE_PATH = '/test/ide/path'; +const MSG_ARE_YOU_SURE = __('Are you sure you want to lose unsaved changes?'); describe('WebIDE', () => { const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; @@ -40,6 +43,8 @@ describe('WebIDE', () => { const findAlert = () => wrapper.findComponent(CannotPushCodeAlert); + const callOnBeforeUnload = (e = {}) => window.onbeforeunload(e); + beforeEach(() => { stubPerformanceWebAPI(); @@ -49,6 +54,7 @@ describe('WebIDE', () => { afterEach(() => { wrapper.destroy(); wrapper = null; + window.onbeforeunload = null; }); describe('ide component, empty repo', () => { @@ -90,7 +96,8 @@ describe('WebIDE', () => { describe('onBeforeUnload', () => { it('returns undefined when no staged files or changed files', () => { createComponent(); - expect(wrapper.vm.onBeforeUnload()).toBe(undefined); + + expect(callOnBeforeUnload()).toBe(undefined); }); it('returns warning text when their are changed files', () => { @@ -100,7 +107,10 @@ describe('WebIDE', () => { }, }); - expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + const e = {}; + + expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE); + expect(e.returnValue).toBe(MSG_ARE_YOU_SURE); }); it('returns warning text when their are staged files', () => { @@ -110,20 +120,27 @@ describe('WebIDE', () => { }, }); - expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + const e = {}; + + expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE); + expect(e.returnValue).toBe(MSG_ARE_YOU_SURE); }); - it('updates event object', () => { - const event = {}; + it('returns undefined once after "skip-beforeunload" was emitted', () => { createComponent({ state: { stagedFiles: [file()], }, }); - wrapper.vm.onBeforeUnload(event); + eventHub.$emit('skip-beforeunload'); + const e = {}; + + expect(callOnBeforeUnload()).toBe(undefined); + expect(e.returnValue).toBe(undefined); - expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?'); + expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE); + expect(e.returnValue).toBe(MSG_ARE_YOU_SURE); }); }); diff --git a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js index 1d38231a767..e92f843ae6e 100644 --- a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js +++ b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue'; import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue'; @@ -127,5 +127,29 @@ describe('ide/components/panes/collapsible_sidebar.vue', () => { }); }); }); + + describe('with initOpenView that does not exist', () => { + beforeEach(async () => { + createComponent({ extensionTabs, initOpenView: 'does-not-exist' }); + + await nextTick(); + }); + + it('nothing is dispatched', () => { + expect(store.dispatch).not.toHaveBeenCalled(); + }); + }); + + describe('with initOpenView that does exist', () => { + beforeEach(async () => { + createComponent({ extensionTabs, initOpenView: fakeView.name }); + + await nextTick(); + }); + + it('dispatches open with view on create', () => { + expect(store.dispatch).toHaveBeenCalledWith('rightPane/open', fakeView); + }); + }); }); }); diff --git a/spec/frontend/ide/components/panes/right_spec.js b/spec/frontend/ide/components/panes/right_spec.js index 4555f519bc2..b7349b8fed1 100644 --- a/spec/frontend/ide/components/panes/right_spec.js +++ b/spec/frontend/ide/components/panes/right_spec.js @@ -3,12 +3,16 @@ import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue'; import RightPane from '~/ide/components/panes/right.vue'; +import SwitchEditorsView from '~/ide/components/switch_editors/switch_editors_view.vue'; import { rightSidebarViews } from '~/ide/constants'; import { createStore } from '~/ide/stores'; import extendStore from '~/ide/stores/extend'; +import { __ } from '~/locale'; Vue.use(Vuex); +const SWITCH_EDITORS_VIEW_NAME = 'switch-editors'; + describe('ide/components/panes/right.vue', () => { let wrapper; let store; @@ -33,6 +37,19 @@ describe('ide/components/panes/right.vue', () => { wrapper = null; }); + describe('default', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders collapsible-sidebar', () => { + expect(wrapper.findComponent(CollapsibleSidebar).props()).toMatchObject({ + side: 'right', + initOpenView: SWITCH_EDITORS_VIEW_NAME, + }); + }); + }); + describe('pipelines tab', () => { it('is always shown', () => { createComponent(); @@ -113,4 +130,32 @@ describe('ide/components/panes/right.vue', () => { ); }); }); + + describe('switch editors tab', () => { + beforeEach(() => { + createComponent(); + }); + + it.each` + desc | canUseNewWebIde | expectedShow + ${'is shown'} | ${true} | ${true} + ${'is not shown'} | ${false} | ${false} + `('with canUseNewWebIde=$canUseNewWebIde, $desc', async ({ canUseNewWebIde, expectedShow }) => { + Object.assign(store.state, { canUseNewWebIde }); + + await nextTick(); + + expect(wrapper.findComponent(CollapsibleSidebar).props('extensionTabs')).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + show: expectedShow, + title: __('Switch editors'), + views: [ + { component: SwitchEditorsView, name: SWITCH_EDITORS_VIEW_NAME, keepAlive: true }, + ], + }), + ]), + ); + }); + }); }); diff --git a/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js b/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js new file mode 100644 index 00000000000..7a958391fea --- /dev/null +++ b/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js @@ -0,0 +1,214 @@ +import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; +import { createAlert } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { logError } from '~/lib/logger'; +import { __ } from '~/locale'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import SwitchEditorsView, { + MSG_ERROR_ALERT, + MSG_CONFIRM, + MSG_TITLE, + MSG_LEARN_MORE, + MSG_DESCRIPTION, +} from '~/ide/components/switch_editors/switch_editors_view.vue'; +import eventHub from '~/ide/eventhub'; +import { createStore } from '~/ide/stores'; + +jest.mock('~/flash'); +jest.mock('~/lib/logger'); +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); + +const TEST_USER_PREFERENCES_PATH = '/test/user-pref/path'; +const TEST_SWITCH_EDITOR_SVG_PATH = '/test/switch/editor/path.svg'; +const TEST_HREF = '/test/new/web/ide/href'; + +describe('~/ide/components/switch_editors/switch_editors_view.vue', () => { + useMockLocationHelper(); + + let store; + let wrapper; + let confirmResolve; + let requestSpy; + let skipBeforeunloadSpy; + let axiosMock; + + // region: finders ------------------ + const findButton = () => wrapper.findComponent(GlButton); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + + // region: actions ------------------ + const triggerSwitchPreference = () => findButton().vm.$emit('click'); + const submitConfirm = async (val) => { + confirmResolve(val); + + // why: We need to wait for promises for the immediate next lines to be executed + await waitForPromises(); + }; + + const createComponent = () => { + wrapper = shallowMount(SwitchEditorsView, { + store, + stubs: { + GlEmptyState, + }, + }); + }; + + // region: test setup ------------------ + beforeEach(() => { + // Setup skip-beforeunload side-effect + skipBeforeunloadSpy = jest.fn(); + eventHub.$on('skip-beforeunload', skipBeforeunloadSpy); + + // Setup request side-effect + requestSpy = jest.fn().mockImplementation(() => new Promise(() => {})); + axiosMock = new MockAdapter(axios); + axiosMock.onPut(TEST_USER_PREFERENCES_PATH).reply(({ data }) => requestSpy(data)); + + // Setup store + store = createStore(); + store.state.userPreferencesPath = TEST_USER_PREFERENCES_PATH; + store.state.switchEditorSvgPath = TEST_SWITCH_EDITOR_SVG_PATH; + store.state.links = { + newWebIDEHelpPagePath: TEST_HREF, + }; + + // Setup user confirm side-effect + confirmAction.mockImplementation( + () => + new Promise((resolve) => { + confirmResolve = resolve; + }), + ); + }); + + afterEach(() => { + eventHub.$off('skip-beforeunload', skipBeforeunloadSpy); + + axiosMock.restore(); + }); + + // region: tests ------------------ + describe('default', () => { + beforeEach(() => { + createComponent(); + }); + + it('render empty state', () => { + expect(findEmptyState().props()).toMatchObject({ + svgPath: TEST_SWITCH_EDITOR_SVG_PATH, + svgHeight: 150, + title: MSG_TITLE, + }); + }); + + it('render link', () => { + expect(wrapper.findComponent(GlLink).attributes('href')).toBe(TEST_HREF); + expect(wrapper.findComponent(GlLink).text()).toBe(MSG_LEARN_MORE); + }); + + it('renders description', () => { + expect(findEmptyState().text()).toContain(MSG_DESCRIPTION); + }); + + it('is not loading', () => { + expect(findButton().props('loading')).toBe(false); + }); + }); + + describe('when user triggers switch preference', () => { + beforeEach(() => { + createComponent(); + + triggerSwitchPreference(); + }); + + it('creates a single confirm', () => { + // Call again to ensure that we only show 1 confirm action + triggerSwitchPreference(); + + expect(confirmAction).toHaveBeenCalledTimes(1); + expect(confirmAction).toHaveBeenCalledWith(MSG_CONFIRM, { + primaryBtnText: __('Switch editors'), + cancelBtnText: __('Cancel'), + }); + }); + + it('starts loading', () => { + expect(findButton().props('loading')).toBe(true); + }); + + describe('when user cancels confirm', () => { + beforeEach(async () => { + await submitConfirm(false); + }); + + it('does not make request', () => { + expect(requestSpy).not.toHaveBeenCalled(); + }); + + it('can be triggered again', () => { + triggerSwitchPreference(); + + expect(confirmAction).toHaveBeenCalledTimes(2); + }); + }); + + describe('when user accepts confirm and response success', () => { + beforeEach(async () => { + requestSpy.mockReturnValue([200, {}]); + await submitConfirm(true); + }); + + it('does not handle error', () => { + expect(logError).not.toHaveBeenCalled(); + expect(createAlert).not.toHaveBeenCalled(); + }); + + it('emits "skip-beforeunload" and reloads', () => { + expect(skipBeforeunloadSpy).toHaveBeenCalledTimes(1); + expect(window.location.reload).toHaveBeenCalledTimes(1); + }); + + it('calls request', () => { + expect(requestSpy).toHaveBeenCalledTimes(1); + expect(requestSpy).toHaveBeenCalledWith( + JSON.stringify({ user: { use_legacy_web_ide: false } }), + ); + }); + + it('is not loading', () => { + expect(findButton().props('loading')).toBe(false); + }); + }); + + describe('when user accepts confirm and response fails', () => { + beforeEach(async () => { + requestSpy.mockReturnValue([400, {}]); + await submitConfirm(true); + }); + + it('handles error', () => { + expect(logError).toHaveBeenCalledTimes(1); + expect(logError).toHaveBeenCalledWith( + 'Error while updating user preferences', + expect.any(Error), + ); + + expect(createAlert).toHaveBeenCalledTimes(1); + expect(createAlert).toHaveBeenCalledWith({ + message: MSG_ERROR_ALERT, + }); + }); + + it('does not reload', () => { + expect(skipBeforeunloadSpy).not.toHaveBeenCalled(); + expect(window.location.reload).not.toHaveBeenCalled(); + }); + }); + }); +}); -- cgit v1.2.3