diff options
Diffstat (limited to 'spec/frontend')
12 files changed, 177 insertions, 48 deletions
diff --git a/spec/frontend/boards/board_card_spec.js b/spec/frontend/boards/board_card_spec.js index 959c71d05ca..6bfbc7cb12f 100644 --- a/spec/frontend/boards/board_card_spec.js +++ b/spec/frontend/boards/board_card_spec.js @@ -196,7 +196,7 @@ describe('Board card', () => { wrapper.trigger('mousedown'); wrapper.trigger('mouseup'); - expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', wrapper.vm.issue, undefined); + expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', [wrapper.vm.issue, undefined]); expect(boardsStore.detail.list).toEqual(wrapper.vm.list); }); diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js index 480bb756731..1fe80d3b1ce 100644 --- a/spec/frontend/cycle_analytics/stage_nav_item_spec.js +++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js @@ -10,7 +10,6 @@ describe('StageNavItem', () => { const func = shallow ? shallowMount : mount; return func(StageNavItem, { propsData: { - canEdit: false, isActive: false, isUserAllowed: false, isDefaultStage: true, @@ -125,7 +124,7 @@ describe('StageNavItem', () => { describe('User can edit stages', () => { beforeEach(() => { - wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false); + wrapper = createComponent({ isUserAllowed: true }, false); }); afterEach(() => { diff --git a/spec/frontend/deploy_keys/components/action_btn_spec.js b/spec/frontend/deploy_keys/components/action_btn_spec.js index b8211b02464..78312751429 100644 --- a/spec/frontend/deploy_keys/components/action_btn_spec.js +++ b/spec/frontend/deploy_keys/components/action_btn_spec.js @@ -32,7 +32,7 @@ describe('Deploy keys action btn', () => { wrapper.trigger('click'); return wrapper.vm.$nextTick().then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything()); + expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', [deployKey, expect.any(Function)]); }); }); diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js index 291502c9ed7..cf54b7e60aa 100644 --- a/spec/frontend/deploy_keys/components/app_spec.js +++ b/spec/frontend/deploy_keys/components/app_spec.js @@ -88,7 +88,7 @@ describe('Deploy keys app component', () => { jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve()); - eventHub.$emit('enable.key', key); + eventHub.$emit('enable.key', [key]); return wrapper.vm.$nextTick(); }) @@ -106,7 +106,7 @@ describe('Deploy keys app component', () => { jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve()); - eventHub.$emit('disable.key', key); + eventHub.$emit('disable.key', [key]); return wrapper.vm.$nextTick(); }) @@ -124,7 +124,7 @@ describe('Deploy keys app component', () => { jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve()); - eventHub.$emit('remove.key', key); + eventHub.$emit('remove.key', [key]); return wrapper.vm.$nextTick(); }) diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js index 35eda21e047..3c04cac7402 100644 --- a/spec/frontend/groups/components/app_spec.js +++ b/spec/frontend/groups/components/app_spec.js @@ -184,7 +184,7 @@ describe('AppComponent', () => { jest.spyOn(window.history, 'replaceState').mockImplementation(() => {}); jest.spyOn($, 'scrollTo').mockImplementation(() => {}); - const fetchPagePromise = vm.fetchPage(2, null, null, true); + const fetchPagePromise = vm.fetchPage([2, null, null, true]); expect(vm.isLoading).toBe(true); expect(vm.fetchGroups).toHaveBeenCalledWith({ @@ -275,7 +275,7 @@ describe('AppComponent', () => { expect(vm.targetGroup).toBe(null); expect(vm.targetParentGroup).toBe(null); - vm.showLeaveGroupModal(group, mockParentGroupItem); + vm.showLeaveGroupModal([group, mockParentGroupItem]); expect(vm.targetGroup).not.toBe(null); expect(vm.targetParentGroup).not.toBe(null); @@ -286,7 +286,7 @@ describe('AppComponent', () => { expect(vm.showModal).toBe(false); expect(vm.groupLeaveConfirmationMessage).toBe(''); - vm.showLeaveGroupModal(group, mockParentGroupItem); + vm.showLeaveGroupModal([group, mockParentGroupItem]); expect(vm.showModal).toBe(true); expect(vm.groupLeaveConfirmationMessage).toBe( @@ -298,7 +298,7 @@ describe('AppComponent', () => { describe('hideLeaveGroupModal', () => { it('hides modal confirmation which is shown before leaving the group', () => { const group = { ...mockParentGroupItem }; - vm.showLeaveGroupModal(group, mockParentGroupItem); + vm.showLeaveGroupModal([group, mockParentGroupItem]); expect(vm.showModal).toBe(true); vm.hideLeaveGroupModal(); diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js index 6205400eb03..42c3c813941 100644 --- a/spec/frontend/groups/components/groups_spec.js +++ b/spec/frontend/groups/components/groups_spec.js @@ -41,13 +41,12 @@ describe('GroupsComponent', () => { vm.change(2); - expect(eventHub.$emit).toHaveBeenCalledWith( - 'fetchPage', + expect(eventHub.$emit).toHaveBeenCalledWith('fetchPage', [ 2, expect.any(Object), expect.any(Object), expect.any(Object), - ); + ]); }); }); }); diff --git a/spec/frontend/groups/components/item_actions_spec.js b/spec/frontend/groups/components/item_actions_spec.js index c0dc1a816e6..1ca42f28c7f 100644 --- a/spec/frontend/groups/components/item_actions_spec.js +++ b/spec/frontend/groups/components/item_actions_spec.js @@ -31,11 +31,10 @@ describe('ItemActionsComponent', () => { jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); vm.onLeaveGroup(); - expect(eventHub.$emit).toHaveBeenCalledWith( - 'showLeaveGroupModal', + expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', [ vm.group, vm.parentGroup, - ); + ]); }); }); }); diff --git a/spec/frontend/helpers/wait_using_real_timer.js b/spec/frontend/helpers/wait_using_real_timer.js new file mode 100644 index 00000000000..ddf23cd97b4 --- /dev/null +++ b/spec/frontend/helpers/wait_using_real_timer.js @@ -0,0 +1,7 @@ +/* useful for timing promises when jest fakeTimers are not reliable enough */ +export default timeout => + new Promise(resolve => { + jest.useRealTimers(); + setTimeout(resolve, timeout); + jest.useFakeTimers(); + }); diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index 6187ed8cd8b..0ba58561f08 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -4,19 +4,25 @@ import MockAdapter from 'axios-mock-adapter'; import '~/behaviors/markdown/render_gfm'; import { Range } from 'monaco-editor'; import axios from '~/lib/utils/axios_utils'; +import service from '~/ide/services'; import { createStoreOptions } from '~/ide/stores'; import RepoEditor from '~/ide/components/repo_editor.vue'; import Editor from '~/ide/lib/editor'; -import { leftSidebarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants'; +import { + leftSidebarViews, + FILE_VIEW_MODE_EDITOR, + FILE_VIEW_MODE_PREVIEW, + viewerTypes, +} from '~/ide/constants'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { file } from '../helpers'; import { exampleConfigs, exampleFiles } from '../lib/editorconfig/mock_data'; +import waitUsingRealTimer from 'helpers/wait_using_real_timer'; describe('RepoEditor', () => { let vm; let store; - let mockActions; const waitForEditorSetup = () => new Promise(resolve => { @@ -30,6 +36,10 @@ describe('RepoEditor', () => { vm = createComponentWithStore(Vue.extend(RepoEditor), store, { file: store.state.openFiles[0], }); + + jest.spyOn(vm, 'getFileData').mockResolvedValue(); + jest.spyOn(vm, 'getRawFileData').mockResolvedValue(); + vm.$mount(); }; @@ -43,21 +53,12 @@ describe('RepoEditor', () => { }; beforeEach(() => { - mockActions = { - getFileData: jest.fn().mockResolvedValue(), - getRawFileData: jest.fn().mockResolvedValue(), - }; - const f = { ...file(), viewMode: FILE_VIEW_MODE_EDITOR, }; const storeOptions = createStoreOptions(); - storeOptions.actions = { - ...storeOptions.actions, - ...mockActions, - }; store = new Vuex.Store(storeOptions); f.active = true; @@ -438,7 +439,7 @@ describe('RepoEditor', () => { vm.initEditor(); vm.$nextTick() .then(() => { - expect(mockActions.getFileData).not.toHaveBeenCalled(); + expect(vm.getFileData).not.toHaveBeenCalled(); }) .then(done) .catch(done.fail); @@ -449,10 +450,11 @@ describe('RepoEditor', () => { vm.file.raw = ''; vm.initEditor(); + vm.$nextTick() .then(() => { - expect(mockActions.getFileData).toHaveBeenCalled(); - expect(mockActions.getRawFileData).toHaveBeenCalled(); + expect(vm.getFileData).toHaveBeenCalled(); + expect(vm.getRawFileData).toHaveBeenCalled(); }) .then(done) .catch(done.fail); @@ -464,8 +466,8 @@ describe('RepoEditor', () => { vm.initEditor(); vm.$nextTick() .then(() => { - expect(mockActions.getFileData).not.toHaveBeenCalled(); - expect(mockActions.getRawFileData).not.toHaveBeenCalled(); + expect(vm.getFileData).not.toHaveBeenCalled(); + expect(vm.getRawFileData).not.toHaveBeenCalled(); expect(vm.editor.createInstance).not.toHaveBeenCalled(); }) .then(done) @@ -526,6 +528,65 @@ describe('RepoEditor', () => { }); }); + describe('populates editor with the fetched content', () => { + beforeEach(() => { + vm.getRawFileData.mockRestore(); + }); + + const createRemoteFile = name => ({ + ...file(name), + tmpFile: false, + }); + + it('after switching viewer from edit to diff', async () => { + jest.spyOn(service, 'getRawFileData').mockImplementation(async () => { + expect(vm.file.loading).toBe(true); + + // switching from edit to diff mode usually triggers editor initialization + store.state.viewer = viewerTypes.diff; + + // we delay returning the file to make sure editor doesn't initialize before we fetch file content + await waitUsingRealTimer(10); + return 'rawFileData123\n'; + }); + + const f = createRemoteFile('newFile'); + Vue.set(store.state.entries, f.path, f); + + vm.file = f; + + // use the real timer to accurately simulate the race condition + await waitUsingRealTimer(20); + expect(vm.model.getModel().getValue()).toBe('rawFileData123\n'); + }); + + it('after opening multiple files at the same time', async () => { + const fileA = createRemoteFile('fileA'); + const fileB = createRemoteFile('fileB'); + Vue.set(store.state.entries, fileA.path, fileA); + Vue.set(store.state.entries, fileB.path, fileB); + + jest + .spyOn(service, 'getRawFileData') + .mockImplementationOnce(async () => { + // opening fileB while the content of fileA is still being fetched + vm.file = fileB; + return 'fileA-rawContent\n'; + }) + .mockImplementationOnce(async () => { + // we delay returning fileB content to make sure the editor doesn't initialize prematurely + await waitUsingRealTimer(10); + return 'fileB-rawContent\n'; + }); + + vm.file = fileA; + + // use the real timer to accurately simulate the race condition + await waitUsingRealTimer(20); + expect(vm.model.getModel().getValue()).toBe('fileB-rawContent\n'); + }); + }); + describe('onPaste', () => { const setFileName = name => { Vue.set(vm, 'file', { @@ -557,6 +618,11 @@ describe('RepoEditor', () => { }); }); + // Pasting an image does a lot of things like using the FileReader API, + // so, waitForPromises isn't very reliable (and causes a flaky spec) + // Read more about state.watch: https://vuex.vuejs.org/api/#watch + const waitForFileContentChange = () => watchState(s => s.entries['foo/bar.md'].content); + beforeEach(() => { setFileName('bar.md'); @@ -576,13 +642,10 @@ describe('RepoEditor', () => { }); }); - // The following test is flaky - // see https://gitlab.com/gitlab-org/gitlab/-/issues/221039 - // eslint-disable-next-line jest/no-disabled-tests - it.skip('adds an image entry to the same folder for a pasted image in a markdown file', () => { + it('adds an image entry to the same folder for a pasted image in a markdown file', () => { pasteImage(); - return waitForPromises().then(() => { + return waitForFileContentChange().then(() => { expect(vm.$store.state.entries['foo/foo.png']).toMatchObject({ path: 'foo/foo.png', type: 'blob', @@ -596,10 +659,7 @@ describe('RepoEditor', () => { it("adds a markdown image tag to the file's contents", () => { pasteImage(); - // Pasting an image does a lot of things like using the FileReader API, - // so, waitForPromises isn't very reliable (and causes a flaky spec) - // Read more about state.watch: https://vuex.vuejs.org/api/#watch - return watchState(s => s.entries['foo/bar.md'].content).then(() => { + return waitForFileContentChange().then(() => { expect(vm.file.content).toBe('hello world\n![foo.png](./foo.png)'); }); }); @@ -632,8 +692,8 @@ describe('RepoEditor', () => { return waitForEditorSetup().then(() => { expect(vm.rules).toEqual(monacoRules); expect(vm.model.options).toMatchObject(monacoRules); - expect(mockActions.getFileData).not.toHaveBeenCalled(); - expect(mockActions.getRawFileData).not.toHaveBeenCalled(); + expect(vm.getFileData).not.toHaveBeenCalled(); + expect(vm.getRawFileData).not.toHaveBeenCalled(); }); }, ); @@ -649,13 +709,13 @@ describe('RepoEditor', () => { createComponent(); return waitForEditorSetup().then(() => { - expect(mockActions.getFileData.mock.calls.map(([, args]) => args)).toEqual([ + expect(vm.getFileData.mock.calls.map(([args]) => args)).toEqual([ { makeFileActive: false, path: 'foo/bar/baz/.editorconfig' }, { makeFileActive: false, path: 'foo/bar/.editorconfig' }, { makeFileActive: false, path: 'foo/.editorconfig' }, { makeFileActive: false, path: '.editorconfig' }, ]); - expect(mockActions.getRawFileData.mock.calls.map(([, args]) => args)).toEqual([ + expect(vm.getRawFileData.mock.calls.map(([args]) => args)).toEqual([ { path: 'foo/bar/baz/.editorconfig' }, { path: 'foo/bar/.editorconfig' }, { path: 'foo/.editorconfig' }, diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index e2dc7626c67..827759c4901 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -446,6 +446,54 @@ describe('IDE store file actions', () => { }) .catch(done.fail); }); + + describe('sets file loading to true', () => { + let loadingWhenGettingRawData; + let loadingWhenGettingBaseRawData; + + beforeEach(() => { + loadingWhenGettingRawData = undefined; + loadingWhenGettingBaseRawData = undefined; + + jest.spyOn(service, 'getRawFileData').mockImplementation(f => { + loadingWhenGettingRawData = f.loading; + return Promise.resolve('raw'); + }); + jest.spyOn(service, 'getBaseRawFileData').mockImplementation(f => { + loadingWhenGettingBaseRawData = f.loading; + return Promise.resolve('rawBase'); + }); + }); + + it('when getting raw file data', async () => { + expect(tmpFile.loading).toBe(false); + + await store.dispatch('getRawFileData', { path: tmpFile.path }); + + expect(loadingWhenGettingRawData).toBe(true); + expect(tmpFile.loading).toBe(false); + }); + + it('when getting base raw file data', async () => { + tmpFile.mrChange = { new_file: false }; + + expect(tmpFile.loading).toBe(false); + + await store.dispatch('getRawFileData', { path: tmpFile.path }); + + expect(loadingWhenGettingBaseRawData).toBe(true); + expect(tmpFile.loading).toBe(false); + }); + + it('when file was already loading', async () => { + tmpFile.loading = true; + + await store.dispatch('getRawFileData', { path: tmpFile.path }); + + expect(loadingWhenGettingRawData).toBe(true); + expect(tmpFile.loading).toBe(false); + }); + }); }); describe('return JSON', () => { @@ -489,6 +537,12 @@ describe('IDE store file actions', () => { }); }); }); + + it('toggles loading off after error', async () => { + await expect(store.dispatch('getRawFileData', { path: tmpFile.path })).rejects.toThrow(); + + expect(tmpFile.loading).toBe(false); + }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js index 8b0253dc01a..b77305277ea 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_suggest_pipeline_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import { GlLink } from '@gitlab/ui'; +import { GlLink, GlSprintf } from '@gitlab/ui'; import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue'; import stubChildren from 'helpers/stub_children'; import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue'; @@ -18,6 +18,7 @@ describe('MRWidgetHeader', () => { propsData: { pipelinePath, pipelineSvgPath, humanAccess }, stubs: { ...stubChildren(PipelineTourState), + GlSprintf, }, }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js index e8f95e099cc..acb7316ae27 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_pipeline_tour_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlPopover } from '@gitlab/ui'; +import { GlPopover, GlLink, GlSprintf } from '@gitlab/ui'; import Cookies from 'js-cookie'; import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper'; import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue'; @@ -51,6 +51,9 @@ describe('MRWidgetPipelineTour', () => { Cookies.remove(cookieKey); wrapper = shallowMount(pipelineTourState, { propsData: popoverProps, + stubs: { + GlSprintf, + }, }); }); @@ -60,6 +63,13 @@ describe('MRWidgetPipelineTour', () => { expect(popover.exists()).toBe(true); }); + it('renders the help link', () => { + const link = wrapper.find(GlLink); + + expect(link.exists()).toBe(true); + expect(link.attributes('href')).toBe(wrapper.vm.$options.helpURL); + }); + it('renders the show me how button', () => { const button = findOkBtn(); |