From 85dc423f7090da0a52c73eb66faf22ddb20efff9 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Sat, 19 Sep 2020 01:45:44 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-4-stable-ee --- spec/frontend/ide/commit_icon_spec.js | 1 - spec/frontend/ide/components/branches/item_spec.js | 6 +- .../ide/components/commit_sidebar/form_spec.js | 53 +++++-- spec/frontend/ide/components/error_message_spec.js | 2 +- .../frontend/ide/components/file_row_extra_spec.js | 4 +- .../ide/components/ide_status_list_spec.js | 3 +- .../jobs/__snapshots__/stage_spec.js.snap | 2 +- .../ide/components/jobs/detail/description_spec.js | 12 +- .../components/jobs/detail/scroll_button_spec.js | 4 +- spec/frontend/ide/components/jobs/detail_spec.js | 8 +- spec/frontend/ide/components/jobs/item_spec.js | 2 +- spec/frontend/ide/components/jobs/list_spec.js | 6 +- .../ide/components/merge_requests/item_spec.js | 2 +- .../ide/components/merge_requests/list_spec.js | 36 ++--- .../ide/components/nav_dropdown_button_spec.js | 2 +- spec/frontend/ide/components/nav_dropdown_spec.js | 2 +- .../ide/components/new_dropdown/button_spec.js | 2 +- .../ide/components/new_dropdown/upload_spec.js | 2 - .../frontend/ide/components/pipelines/list_spec.js | 63 ++++---- .../ide/components/preview/clientside_spec.js | 22 +-- spec/frontend/ide/components/repo_editor_spec.js | 13 +- spec/frontend/ide/components/repo_tab_spec.js | 132 +++++++---------- spec/frontend/ide/components/repo_tabs_spec.js | 43 ++++-- .../ide/components/terminal/session_spec.js | 2 +- .../components/terminal/terminal_controls_spec.js | 8 +- .../terminal_sync/terminal_sync_status_spec.js | 9 +- spec/frontend/ide/helpers.js | 1 - spec/frontend/ide/lib/editor_spec.js | 22 --- spec/frontend/ide/lib/errors_spec.js | 70 +++++++++ spec/frontend/ide/lib/files_spec.js | 19 +-- spec/frontend/ide/mock_data.js | 3 +- spec/frontend/ide/services/index_spec.js | 6 +- spec/frontend/ide/stores/actions/file_spec.js | 26 ++-- .../ide/stores/actions/merge_request_spec.js | 2 - spec/frontend/ide/stores/actions_spec.js | 15 +- spec/frontend/ide/stores/getters_spec.js | 45 ++++++ spec/frontend/ide/stores/integration_spec.js | 2 - .../ide/stores/modules/commit/actions_spec.js | 22 ++- .../ide/stores/modules/commit/mutations_spec.js | 21 +++ .../ide/stores/modules/pipelines/actions_spec.js | 40 ++--- .../ide/stores/modules/pipelines/mutations_spec.js | 12 +- spec/frontend/ide/stores/mutations/file_spec.js | 2 - spec/frontend/ide/stores/mutations_spec.js | 18 +-- spec/frontend/ide/sync_router_and_store_spec.js | 8 +- spec/frontend/ide/utils_spec.js | 162 ++++++++++----------- 45 files changed, 523 insertions(+), 414 deletions(-) create mode 100644 spec/frontend/ide/lib/errors_spec.js (limited to 'spec/frontend/ide') diff --git a/spec/frontend/ide/commit_icon_spec.js b/spec/frontend/ide/commit_icon_spec.js index e4a7394b089..0dfcae00298 100644 --- a/spec/frontend/ide/commit_icon_spec.js +++ b/spec/frontend/ide/commit_icon_spec.js @@ -7,7 +7,6 @@ const createFile = (name = 'name', id = name, type = '', parent = null) => id, type, icon: 'icon', - url: 'url', name, path: parent ? `${parent.path}/${name}` : name, parentPath: parent ? parent.path : '', diff --git a/spec/frontend/ide/components/branches/item_spec.js b/spec/frontend/ide/components/branches/item_spec.js index d8175025755..f1aa9187a8d 100644 --- a/spec/frontend/ide/components/branches/item_spec.js +++ b/spec/frontend/ide/components/branches/item_spec.js @@ -1,8 +1,8 @@ import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import { createStore } from '~/ide/stores'; import { createRouter } from '~/ide/ide_router'; import Item from '~/ide/components/branches/item.vue'; -import Icon from '~/vue_shared/components/icon.vue'; import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; import { projectData } from '../../mock_data'; @@ -45,7 +45,7 @@ describe('IDE branch item', () => { it('renders branch name and timeago', () => { expect(wrapper.text()).toContain(TEST_BRANCH.name); expect(wrapper.find(Timeago).props('time')).toBe(TEST_BRANCH.committedDate); - expect(wrapper.find(Icon).exists()).toBe(false); + expect(wrapper.find(GlIcon).exists()).toBe(false); }); it('renders link to branch', () => { @@ -60,6 +60,6 @@ describe('IDE branch item', () => { it('renders icon if is not active', () => { createComponent({ isActive: true }); - expect(wrapper.find(Icon).exists()).toBe(true); + expect(wrapper.find(GlIcon).exists()).toBe(true); }); }); diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js index 9245cefc183..56667d6b03d 100644 --- a/spec/frontend/ide/components/commit_sidebar/form_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js @@ -1,10 +1,13 @@ import Vue from 'vue'; +import { getByText } from '@testing-library/dom'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { projectData } from 'jest/ide/mock_data'; import waitForPromises from 'helpers/wait_for_promises'; import { createStore } from '~/ide/stores'; +import consts from '~/ide/stores/modules/commit/constants'; import CommitForm from '~/ide/components/commit_sidebar/form.vue'; import { leftSidebarViews } from '~/ide/constants'; +import { createCodeownersCommitError, createUnexpectedCommitError } from '~/ide/lib/errors'; describe('IDE commit form', () => { const Component = Vue.extend(CommitForm); @@ -259,21 +262,47 @@ describe('IDE commit form', () => { }); }); - it('opens new branch modal if commitChanges throws an error', () => { - vm.commitChanges.mockRejectedValue({ success: false }); + it.each` + createError | props + ${() => createCodeownersCommitError('test message')} | ${{ actionPrimary: { text: 'Create new branch' } }} + ${createUnexpectedCommitError} | ${{ actionPrimary: null }} + `('opens error modal if commitError with $error', async ({ createError, props }) => { + jest.spyOn(vm.$refs.commitErrorModal, 'show'); - jest.spyOn(vm.$refs.createBranchModal, 'show').mockImplementation(); + const error = createError(); + store.state.commit.commitError = error; - return vm - .$nextTick() - .then(() => { - vm.$el.querySelector('.btn-success').click(); + await vm.$nextTick(); - return vm.$nextTick(); - }) - .then(() => { - expect(vm.$refs.createBranchModal.show).toHaveBeenCalled(); - }); + expect(vm.$refs.commitErrorModal.show).toHaveBeenCalled(); + expect(vm.$refs.commitErrorModal).toMatchObject({ + actionCancel: { text: 'Cancel' }, + ...props, + }); + // Because of the legacy 'mountComponent' approach here, the only way to + // test the text of the modal is by viewing the content of the modal added to the document. + expect(document.body).toHaveText(error.messageHTML); + }); + }); + + describe('with error modal with primary', () => { + beforeEach(() => { + jest.spyOn(vm.$store, 'dispatch').mockReturnValue(Promise.resolve()); + }); + + it('updates commit action and commits', async () => { + store.state.commit.commitError = createCodeownersCommitError('test message'); + + await vm.$nextTick(); + + getByText(document.body, 'Create new branch').click(); + + await waitForPromises(); + + expect(vm.$store.dispatch.mock.calls).toEqual([ + ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH], + ['commit/commitChanges', undefined], + ]); }); }); }); diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js index 3a4dcc5873d..8b7e7da3b51 100644 --- a/spec/frontend/ide/components/error_message_spec.js +++ b/spec/frontend/ide/components/error_message_spec.js @@ -51,7 +51,7 @@ describe('IDE error message component', () => { createComponent(); findDismissButton().trigger('click'); - expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null, undefined); + expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null); }); describe('with action', () => { diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js index 4bd27d23f76..2a106ad37c0 100644 --- a/spec/frontend/ide/components/file_row_extra_spec.js +++ b/spec/frontend/ide/components/file_row_extra_spec.js @@ -153,14 +153,14 @@ describe('IDE extra file row component', () => { describe('merge request icon', () => { it('hides when not a merge request change', () => { - expect(vm.$el.querySelector('.ic-git-merge')).toBe(null); + expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).toBe(null); }); it('shows when a merge request change', done => { vm.file.mrChange = true; vm.$nextTick(() => { - expect(vm.$el.querySelector('.ic-git-merge')).not.toBe(null); + expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).not.toBe(null); done(); }); diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index fed61233e55..bb8165d1a52 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -75,7 +75,8 @@ describe('ide/components/ide_status_list', () => { describe('with binary file', () => { beforeEach(() => { - activeFile.binary = true; + activeFile.name = 'abc.dat'; + activeFile.content = '🐱'; // non-ascii binary content createComponent(); }); diff --git a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap index dbfacb98813..a65d9e6f78b 100644 --- a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap +++ b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap @@ -34,7 +34,7 @@ exports[`IDE pipeline stage renders stage details & icon 1`] = ` - { }); it('renders CI icon', () => { - expect(vm.$el.querySelector('.ci-status-icon .ic-status_success_borderless')).not.toBe(null); + expect( + vm.$el.querySelector('.ci-status-icon [data-testid="status_success_borderless-icon"]'), + ).not.toBe(null); + }); + + it('renders bridge job details without the job link', () => { + vm = mountComponent(Component, { + job: { ...jobs[0], path: undefined }, + }); + + expect(vm.$el.querySelector('[data-testid="description-detail-link"]')).toBe(null); }); }); diff --git a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js index b8dbca97ade..42526590ebb 100644 --- a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js +++ b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import Icon from '~/vue_shared/components/icon.vue'; +import { GlIcon } from '@gitlab/ui'; import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue'; describe('IDE job log scroll button', () => { @@ -27,7 +27,7 @@ describe('IDE job log scroll button', () => { beforeEach(() => createComponent({ direction })); it('returns proper icon name', () => { - expect(wrapper.find(Icon).props('name')).toBe(icon); + expect(wrapper.find(GlIcon).props('name')).toBe(icon); }); it('returns proper title', () => { diff --git a/spec/frontend/ide/components/jobs/detail_spec.js b/spec/frontend/ide/components/jobs/detail_spec.js index acd30dee718..496d8284fdd 100644 --- a/spec/frontend/ide/components/jobs/detail_spec.js +++ b/spec/frontend/ide/components/jobs/detail_spec.js @@ -24,7 +24,7 @@ describe('IDE jobs detail view', () => { beforeEach(() => { vm = createComponent(); - jest.spyOn(vm, 'fetchJobTrace').mockResolvedValue(); + jest.spyOn(vm, 'fetchJobLogs').mockResolvedValue(); }); afterEach(() => { @@ -36,8 +36,8 @@ describe('IDE jobs detail view', () => { vm = vm.$mount(); }); - it('calls fetchJobTrace', () => { - expect(vm.fetchJobTrace).toHaveBeenCalled(); + it('calls fetchJobLogs', () => { + expect(vm.fetchJobLogs).toHaveBeenCalled(); }); it('scrolls to bottom', () => { @@ -96,7 +96,7 @@ describe('IDE jobs detail view', () => { describe('scroll buttons', () => { beforeEach(() => { vm = createComponent(); - jest.spyOn(vm, 'fetchJobTrace').mockResolvedValue(); + jest.spyOn(vm, 'fetchJobLogs').mockResolvedValue(); }); afterEach(() => { diff --git a/spec/frontend/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js index 2f97d39e98e..93c01640b54 100644 --- a/spec/frontend/ide/components/jobs/item_spec.js +++ b/spec/frontend/ide/components/jobs/item_spec.js @@ -24,7 +24,7 @@ describe('IDE jobs item', () => { }); it('renders CI icon', () => { - expect(vm.$el.querySelector('.ic-status_success_borderless')).not.toBe(null); + expect(vm.$el.querySelector('[data-testid="status_success_borderless-icon"]')).not.toBe(null); }); it('does not render view logs button if not started', done => { diff --git a/spec/frontend/ide/components/jobs/list_spec.js b/spec/frontend/ide/components/jobs/list_spec.js index d8880fa7cb7..e821a585e18 100644 --- a/spec/frontend/ide/components/jobs/list_spec.js +++ b/spec/frontend/ide/components/jobs/list_spec.js @@ -99,11 +99,7 @@ describe('IDE stages list', () => { it('calls toggleStageCollapsed when clicking stage header', () => { findCardHeader().trigger('click'); - expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith( - expect.any(Object), - 0, - undefined, - ); + expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith(expect.any(Object), 0); }); it('calls fetchJobs when stage is mounted', () => { diff --git a/spec/frontend/ide/components/merge_requests/item_spec.js b/spec/frontend/ide/components/merge_requests/item_spec.js index b1da89d7a9b..20adaa7abbc 100644 --- a/spec/frontend/ide/components/merge_requests/item_spec.js +++ b/spec/frontend/ide/components/merge_requests/item_spec.js @@ -33,7 +33,7 @@ describe('IDE merge request item', () => { store, }); }; - const findIcon = () => wrapper.find('.ic-mobile-issue-close'); + const findIcon = () => wrapper.find('[data-testid="mobile-issue-close-icon"]'); beforeEach(() => { store = createStore(); diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js index e2c6ac49e07..80dcd861451 100644 --- a/spec/frontend/ide/components/merge_requests/list_spec.js +++ b/spec/frontend/ide/components/merge_requests/list_spec.js @@ -56,14 +56,10 @@ describe('IDE merge requests list', () => { it('calls fetch on mounted', () => { createComponent(); - expect(fetchMergeRequestsMock).toHaveBeenCalledWith( - expect.any(Object), - { - search: '', - type: '', - }, - undefined, - ); + expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), { + search: '', + type: '', + }); }); it('renders loading icon when merge request is loading', () => { @@ -95,14 +91,10 @@ describe('IDE merge requests list', () => { const searchType = wrapper.vm.$options.searchTypes[0]; expect(findTokenedInput().props('tokens')).toEqual([searchType]); - expect(fetchMergeRequestsMock).toHaveBeenCalledWith( - expect.any(Object), - { - type: searchType.type, - search: '', - }, - undefined, - ); + expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), { + type: searchType.type, + search: '', + }); }); }); @@ -136,14 +128,10 @@ describe('IDE merge requests list', () => { input.vm.$emit('input', 'something'); return wrapper.vm.$nextTick().then(() => { - expect(fetchMergeRequestsMock).toHaveBeenCalledWith( - expect.any(Object), - { - search: 'something', - type: '', - }, - undefined, - ); + expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), { + search: 'something', + type: '', + }); }); }); }); diff --git a/spec/frontend/ide/components/nav_dropdown_button_spec.js b/spec/frontend/ide/components/nav_dropdown_button_spec.js index 2aa3992a6d8..c98aa313f40 100644 --- a/spec/frontend/ide/components/nav_dropdown_button_spec.js +++ b/spec/frontend/ide/components/nav_dropdown_button_spec.js @@ -23,7 +23,7 @@ describe('NavDropdown', () => { vm.$mount(); }; - const findIcon = name => vm.$el.querySelector(`.ic-${name}`); + const findIcon = name => vm.$el.querySelector(`[data-testid="${name}-icon"]`); const findMRIcon = () => findIcon('merge-request'); const findBranchIcon = () => findIcon('branch'); diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js index ce123d925c8..2f91ab7af0a 100644 --- a/spec/frontend/ide/components/nav_dropdown_spec.js +++ b/spec/frontend/ide/components/nav_dropdown_spec.js @@ -39,7 +39,7 @@ describe('IDE NavDropdown', () => { }); }; - const findIcon = name => wrapper.find(`.ic-${name}`); + const findIcon = name => wrapper.find(`[data-testid="${name}-icon"]`); const findMRIcon = () => findIcon('merge-request'); const findNavForm = () => wrapper.find('.ide-nav-form'); const showDropdown = () => { diff --git a/spec/frontend/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js index 3c611b7de8f..66317296ee9 100644 --- a/spec/frontend/ide/components/new_dropdown/button_spec.js +++ b/spec/frontend/ide/components/new_dropdown/button_spec.js @@ -28,7 +28,7 @@ describe('IDE new entry dropdown button component', () => { }); it('renders icon', () => { - expect(vm.$el.querySelector('.ic-doc-new')).not.toBe(null); + expect(vm.$el.querySelector('[data-testid="doc-new-icon"]')).not.toBe(null); }); it('emits click event', () => { diff --git a/spec/frontend/ide/components/new_dropdown/upload_spec.js b/spec/frontend/ide/components/new_dropdown/upload_spec.js index ad27954cd10..ae497106f73 100644 --- a/spec/frontend/ide/components/new_dropdown/upload_spec.js +++ b/spec/frontend/ide/components/new_dropdown/upload_spec.js @@ -85,7 +85,6 @@ describe('new dropdown upload', () => { name: textFile.name, type: 'blob', content: 'plain text', - binary: false, rawPath: '', }); }) @@ -102,7 +101,6 @@ describe('new dropdown upload', () => { name: binaryFile.name, type: 'blob', content: binaryTarget.result.split('base64,')[1], - binary: true, rawPath: binaryTarget.result, }); }); diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js index 86cdbafaff9..7f083fa7c25 100644 --- a/spec/frontend/ide/components/pipelines/list_spec.js +++ b/spec/frontend/ide/components/pipelines/list_spec.js @@ -22,11 +22,11 @@ describe('IDE pipelines list', () => { const defaultState = { links: { ciHelpPagePath: TEST_HOST }, pipelinesEmptyStateSvgPath: TEST_HOST, - pipelines: { - stages: [], - failedStages: [], - isLoadingJobs: false, - }, + }; + const defaultPipelinesState = { + stages: [], + failedStages: [], + isLoadingJobs: false, }; const fetchLatestPipelineMock = jest.fn(); @@ -34,23 +34,20 @@ describe('IDE pipelines list', () => { const failedStagesGetterMock = jest.fn().mockReturnValue([]); const fakeProjectPath = 'alpha/beta'; - const createComponent = (state = {}) => { - const { pipelines: pipelinesState, ...restOfState } = state; - const { defaultPipelines, ...defaultRestOfState } = defaultState; - - const fakeStore = new Vuex.Store({ + const createStore = (rootState, pipelinesState) => { + return new Vuex.Store({ getters: { currentProject: () => ({ web_url: 'some/url ', path_with_namespace: fakeProjectPath }), }, state: { - ...defaultRestOfState, - ...restOfState, + ...defaultState, + ...rootState, }, modules: { pipelines: { namespaced: true, state: { - ...defaultPipelines, + ...defaultPipelinesState, ...pipelinesState, }, actions: { @@ -69,10 +66,12 @@ describe('IDE pipelines list', () => { }, }, }); + }; + const createComponent = (state = {}, pipelinesState = {}) => { wrapper = shallowMount(List, { localVue, - store: fakeStore, + store: createStore(state, pipelinesState), }); }; @@ -94,31 +93,33 @@ describe('IDE pipelines list', () => { describe('when loading', () => { let defaultPipelinesLoadingState; + beforeAll(() => { defaultPipelinesLoadingState = { - ...defaultState.pipelines, isLoadingPipeline: true, }; }); it('does not render when pipeline has loaded before', () => { - createComponent({ - pipelines: { + createComponent( + {}, + { ...defaultPipelinesLoadingState, hasLoadedPipeline: true, }, - }); + ); expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); }); it('renders loading state', () => { - createComponent({ - pipelines: { + createComponent( + {}, + { ...defaultPipelinesLoadingState, hasLoadedPipeline: false, }, - }); + ); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); @@ -126,21 +127,22 @@ describe('IDE pipelines list', () => { describe('when loaded', () => { let defaultPipelinesLoadedState; + beforeAll(() => { defaultPipelinesLoadedState = { - ...defaultState.pipelines, isLoadingPipeline: false, hasLoadedPipeline: true, }; }); it('renders empty state when no latestPipeline', () => { - createComponent({ pipelines: { ...defaultPipelinesLoadedState, latestPipeline: null } }); + createComponent({}, { ...defaultPipelinesLoadedState, latestPipeline: null }); expect(wrapper.element).toMatchSnapshot(); }); describe('with latest pipeline loaded', () => { let withLatestPipelineState; + beforeAll(() => { withLatestPipelineState = { ...defaultPipelinesLoadedState, @@ -149,12 +151,12 @@ describe('IDE pipelines list', () => { }); it('renders ci icon', () => { - createComponent({ pipelines: withLatestPipelineState }); + createComponent({}, withLatestPipelineState); expect(wrapper.find(CiIcon).exists()).toBe(true); }); it('renders pipeline data', () => { - createComponent({ pipelines: withLatestPipelineState }); + createComponent({}, withLatestPipelineState); expect(wrapper.text()).toContain('#1'); }); @@ -162,7 +164,7 @@ describe('IDE pipelines list', () => { it('renders list of jobs', () => { const stages = []; const isLoadingJobs = true; - createComponent({ pipelines: { ...withLatestPipelineState, stages, isLoadingJobs } }); + createComponent({}, { ...withLatestPipelineState, stages, isLoadingJobs }); const jobProps = wrapper .findAll(Tab) @@ -177,7 +179,7 @@ describe('IDE pipelines list', () => { const failedStages = []; failedStagesGetterMock.mockReset().mockReturnValue(failedStages); const isLoadingJobs = true; - createComponent({ pipelines: { ...withLatestPipelineState, isLoadingJobs } }); + createComponent({}, { ...withLatestPipelineState, isLoadingJobs }); const jobProps = wrapper .findAll(Tab) @@ -191,12 +193,13 @@ describe('IDE pipelines list', () => { describe('with YAML error', () => { it('renders YAML error', () => { const yamlError = 'test yaml error'; - createComponent({ - pipelines: { + createComponent( + {}, + { ...defaultPipelinesLoadedState, latestPipeline: { ...pipelines[0], yamlError }, }, - }); + ); expect(wrapper.text()).toContain('Found errors in your .gitlab-ci.yml:'); expect(wrapper.text()).toContain(yamlError); diff --git a/spec/frontend/ide/components/preview/clientside_spec.js b/spec/frontend/ide/components/preview/clientside_spec.js index 7b2025f5e9f..7b22f75cee4 100644 --- a/spec/frontend/ide/components/preview/clientside_spec.js +++ b/spec/frontend/ide/components/preview/clientside_spec.js @@ -279,24 +279,16 @@ describe('IDE clientside preview', () => { }); it('calls getFileData', () => { - expect(storeActions.getFileData).toHaveBeenCalledWith( - expect.any(Object), - { - path: 'package.json', - makeFileActive: false, - }, - undefined, // vuex callback - ); + expect(storeActions.getFileData).toHaveBeenCalledWith(expect.any(Object), { + path: 'package.json', + makeFileActive: false, + }); }); it('calls getRawFileData', () => { - expect(storeActions.getRawFileData).toHaveBeenCalledWith( - expect.any(Object), - { - path: 'package.json', - }, - undefined, // vuex callback - ); + expect(storeActions.getRawFileData).toHaveBeenCalledWith(expect.any(Object), { + path: 'package.json', + }); }); }); diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index f0ae2ba732b..9f4c9c1622a 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -45,7 +45,7 @@ describe('RepoEditor', () => { const createOpenFile = path => { const origFile = store.state.openFiles[0]; - const newFile = { ...origFile, path, key: path }; + const newFile = { ...origFile, path, key: path, name: 'myfile.txt', content: 'hello world' }; store.state.entries[path] = newFile; @@ -54,8 +54,9 @@ describe('RepoEditor', () => { beforeEach(() => { const f = { - ...file(), + ...file('file.txt'), viewMode: FILE_VIEW_MODE_EDITOR, + content: 'hello world', }; const storeOptions = createStoreOptions(); @@ -142,6 +143,7 @@ describe('RepoEditor', () => { ...vm.file, projectId: 'namespace/project', path: 'sample.md', + name: 'sample.md', content: 'testing 123', }); @@ -200,7 +202,8 @@ describe('RepoEditor', () => { describe('when open file is binary and not raw', () => { beforeEach(done => { - vm.file.binary = true; + vm.file.name = 'file.dat'; + vm.file.content = '🐱'; // non-ascii binary content vm.$nextTick(done); }); @@ -407,6 +410,9 @@ describe('RepoEditor', () => { beforeEach(done => { jest.spyOn(vm.editor, 'updateDimensions').mockImplementation(); vm.file.viewMode = FILE_VIEW_MODE_PREVIEW; + vm.file.name = 'myfile.md'; + vm.file.content = 'hello world'; + vm.$nextTick(done); }); @@ -650,7 +656,6 @@ describe('RepoEditor', () => { path: 'foo/foo.png', type: 'blob', content: 'Zm9v', - binary: true, rawPath: 'data:image/png;base64,Zm9v', }); }); diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js index 5a591d3dcd0..f35726de27c 100644 --- a/spec/frontend/ide/components/repo_tab_spec.js +++ b/spec/frontend/ide/components/repo_tab_spec.js @@ -1,21 +1,24 @@ -import Vue from 'vue'; +import { mount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; import { createStore } from '~/ide/stores'; -import repoTab from '~/ide/components/repo_tab.vue'; +import RepoTab from '~/ide/components/repo_tab.vue'; import { createRouter } from '~/ide/ide_router'; import { file } from '../helpers'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('RepoTab', () => { - let vm; + let wrapper; let store; let router; function createComponent(propsData) { - const RepoTab = Vue.extend(repoTab); - - return new RepoTab({ + wrapper = mount(RepoTab, { + localVue, store, propsData, - }).$mount(); + }); } beforeEach(() => { @@ -25,23 +28,24 @@ describe('RepoTab', () => { }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); it('renders a close link and a name link', () => { - vm = createComponent({ + createComponent({ tab: file(), }); - vm.$store.state.openFiles.push(vm.tab); - const close = vm.$el.querySelector('.multi-file-tab-close'); - const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`); + wrapper.vm.$store.state.openFiles.push(wrapper.vm.tab); + const close = wrapper.find('.multi-file-tab-close'); + const name = wrapper.find(`[title]`); - expect(close.innerHTML).toContain('#close'); - expect(name.textContent.trim()).toEqual(vm.tab.name); + expect(close.html()).toContain('#close'); + expect(name.text().trim()).toEqual(wrapper.vm.tab.name); }); - it('does not call openPendingTab when tab is active', done => { - vm = createComponent({ + it('does not call openPendingTab when tab is active', async () => { + createComponent({ tab: { ...file(), pending: true, @@ -49,63 +53,51 @@ describe('RepoTab', () => { }, }); - jest.spyOn(vm, 'openPendingTab').mockImplementation(() => {}); + jest.spyOn(wrapper.vm, 'openPendingTab').mockImplementation(() => {}); - vm.$el.click(); + await wrapper.trigger('click'); - vm.$nextTick(() => { - expect(vm.openPendingTab).not.toHaveBeenCalled(); - - done(); - }); + expect(wrapper.vm.openPendingTab).not.toHaveBeenCalled(); }); it('fires clickFile when the link is clicked', () => { - vm = createComponent({ + createComponent({ tab: file(), }); - jest.spyOn(vm, 'clickFile').mockImplementation(() => {}); + jest.spyOn(wrapper.vm, 'clickFile').mockImplementation(() => {}); - vm.$el.click(); + wrapper.trigger('click'); - expect(vm.clickFile).toHaveBeenCalledWith(vm.tab); + expect(wrapper.vm.clickFile).toHaveBeenCalledWith(wrapper.vm.tab); }); it('calls closeFile when clicking close button', () => { - vm = createComponent({ + createComponent({ tab: file(), }); - jest.spyOn(vm, 'closeFile').mockImplementation(() => {}); + jest.spyOn(wrapper.vm, 'closeFile').mockImplementation(() => {}); - vm.$el.querySelector('.multi-file-tab-close').click(); + wrapper.find('.multi-file-tab-close').trigger('click'); - expect(vm.closeFile).toHaveBeenCalledWith(vm.tab); + expect(wrapper.vm.closeFile).toHaveBeenCalledWith(wrapper.vm.tab); }); - it('changes icon on hover', done => { + it('changes icon on hover', async () => { const tab = file(); tab.changed = true; - vm = createComponent({ + createComponent({ tab, }); - vm.$el.dispatchEvent(new Event('mouseover')); + await wrapper.trigger('mouseover'); - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.file-modified')).toBeNull(); + expect(wrapper.find('.file-modified').exists()).toBe(false); - vm.$el.dispatchEvent(new Event('mouseout')); - }) - .then(Vue.nextTick) - .then(() => { - expect(vm.$el.querySelector('.file-modified')).not.toBeNull(); + await wrapper.trigger('mouseout'); - done(); - }) - .catch(done.fail); + expect(wrapper.find('.file-modified').exists()).toBe(true); }); describe('locked file', () => { @@ -120,21 +112,17 @@ describe('RepoTab', () => { }, }; - vm = createComponent({ + createComponent({ tab: f, }); }); - afterEach(() => { - vm.$destroy(); - }); - it('renders lock icon', () => { - expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull(); + expect(wrapper.find('.file-status-icon')).not.toBeNull(); }); it('renders a tooltip', () => { - expect(vm.$el.querySelector('span:nth-child(2)').dataset.originalTitle).toContain( + expect(wrapper.find('span:nth-child(2)').attributes('data-original-title')).toContain( 'Locked by testuser', ); }); @@ -142,45 +130,37 @@ describe('RepoTab', () => { describe('methods', () => { describe('closeTab', () => { - it('closes tab if file has changed', done => { + it('closes tab if file has changed', async () => { const tab = file(); tab.changed = true; tab.opened = true; - vm = createComponent({ + createComponent({ tab, }); - vm.$store.state.openFiles.push(tab); - vm.$store.state.changedFiles.push(tab); - vm.$store.state.entries[tab.path] = tab; - vm.$store.dispatch('setFileActive', tab.path); - - vm.$el.querySelector('.multi-file-tab-close').click(); + wrapper.vm.$store.state.openFiles.push(tab); + wrapper.vm.$store.state.changedFiles.push(tab); + wrapper.vm.$store.state.entries[tab.path] = tab; + wrapper.vm.$store.dispatch('setFileActive', tab.path); - vm.$nextTick(() => { - expect(tab.opened).toBeFalsy(); - expect(vm.$store.state.changedFiles.length).toBe(1); + await wrapper.find('.multi-file-tab-close').trigger('click'); - done(); - }); + expect(tab.opened).toBeFalsy(); + expect(wrapper.vm.$store.state.changedFiles).toHaveLength(1); }); - it('closes tab when clicking close btn', done => { + it('closes tab when clicking close btn', async () => { const tab = file('lose'); tab.opened = true; - vm = createComponent({ + createComponent({ tab, }); - vm.$store.state.openFiles.push(tab); - vm.$store.state.entries[tab.path] = tab; - vm.$store.dispatch('setFileActive', tab.path); + wrapper.vm.$store.state.openFiles.push(tab); + wrapper.vm.$store.state.entries[tab.path] = tab; + wrapper.vm.$store.dispatch('setFileActive', tab.path); - vm.$el.querySelector('.multi-file-tab-close').click(); + await wrapper.find('.multi-file-tab-close').trigger('click'); - vm.$nextTick(() => { - expect(tab.opened).toBeFalsy(); - - done(); - }); + expect(tab.opened).toBeFalsy(); }); }); }); diff --git a/spec/frontend/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js index df5b01770f5..b251f207853 100644 --- a/spec/frontend/ide/components/repo_tabs_spec.js +++ b/spec/frontend/ide/components/repo_tabs_spec.js @@ -1,27 +1,40 @@ -import Vue from 'vue'; -import repoTabs from '~/ide/components/repo_tabs.vue'; -import createComponent from '../../helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { mount, createLocalVue } from '@vue/test-utils'; +import { createStore } from '~/ide/stores'; +import RepoTabs from '~/ide/components/repo_tabs.vue'; import { file } from '../helpers'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('RepoTabs', () => { - const openedFiles = [file('open1'), file('open2')]; - const RepoTabs = Vue.extend(repoTabs); - let vm; + let wrapper; + let store; + + beforeEach(() => { + store = createStore(); + store.state.openFiles = [file('open1'), file('open2')]; + + wrapper = mount(RepoTabs, { + propsData: { + files: store.state.openFiles, + viewer: 'editor', + activeFile: file('activeFile'), + }, + store, + localVue, + }); + }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders a list of tabs', done => { - vm = createComponent(RepoTabs, { - files: openedFiles, - viewer: 'editor', - activeFile: file('activeFile'), - }); - openedFiles[0].active = true; + store.state.openFiles[0].active = true; - vm.$nextTick(() => { - const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')]; + wrapper.vm.$nextTick(() => { + const tabs = [...wrapper.vm.$el.querySelectorAll('.multi-file-tab')]; expect(tabs.length).toEqual(2); expect(tabs[0].parentNode.classList.contains('active')).toEqual(true); diff --git a/spec/frontend/ide/components/terminal/session_spec.js b/spec/frontend/ide/components/terminal/session_spec.js index 2399446ed15..ce61a31691a 100644 --- a/spec/frontend/ide/components/terminal/session_spec.js +++ b/spec/frontend/ide/components/terminal/session_spec.js @@ -52,7 +52,7 @@ describe('IDE TerminalSession', () => { state.session = null; factory(); - expect(wrapper.isEmpty()).toBe(true); + expect(wrapper.html()).toBe(''); }); it('shows terminal', () => { diff --git a/spec/frontend/ide/components/terminal/terminal_controls_spec.js b/spec/frontend/ide/components/terminal/terminal_controls_spec.js index 6c2871abb46..c22063e1d72 100644 --- a/spec/frontend/ide/components/terminal/terminal_controls_spec.js +++ b/spec/frontend/ide/components/terminal/terminal_controls_spec.js @@ -42,24 +42,24 @@ describe('IDE TerminalControls', () => { it('emits "scroll-up" when click up button', () => { factory({ propsData: { canScrollUp: true } }); - expect(wrapper.emittedByOrder()).toEqual([]); + expect(wrapper.emitted()).toEqual({}); buttons.at(0).vm.$emit('click'); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emittedByOrder()).toEqual([{ name: 'scroll-up', args: [] }]); + expect(wrapper.emitted('scroll-up')).toEqual([[]]); }); }); it('emits "scroll-down" when click down button', () => { factory({ propsData: { canScrollDown: true } }); - expect(wrapper.emittedByOrder()).toEqual([]); + expect(wrapper.emitted()).toEqual({}); buttons.at(1).vm.$emit('click'); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emittedByOrder()).toEqual([{ name: 'scroll-down', args: [] }]); + expect(wrapper.emitted('scroll-down')).toEqual([[]]); }); }); }); diff --git a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js index 16a76fae1dd..9adf5848f9d 100644 --- a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js +++ b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js @@ -1,13 +1,12 @@ import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; import TerminalSyncStatus from '~/ide/components/terminal_sync/terminal_sync_status.vue'; import { MSG_TERMINAL_SYNC_CONNECTING, MSG_TERMINAL_SYNC_UPLOADING, MSG_TERMINAL_SYNC_RUNNING, } from '~/ide/stores/modules/terminal_sync/messages'; -import Icon from '~/vue_shared/components/icon.vue'; const TEST_MESSAGE = 'lorem ipsum dolar sit'; const START_LOADING = 'START_LOADING'; @@ -58,7 +57,7 @@ describe('ide/components/terminal_sync/terminal_sync_status', () => { it('shows nothing', () => { createComponent(); - expect(wrapper.isEmpty()).toBe(true); + expect(wrapper.html()).toBe(''); }); }); @@ -80,7 +79,7 @@ describe('ide/components/terminal_sync/terminal_sync_status', () => { if (!icon) { it('does not render icon', () => { - expect(wrapper.find(Icon).exists()).toBe(false); + expect(wrapper.find(GlIcon).exists()).toBe(false); }); it('renders loading icon', () => { @@ -88,7 +87,7 @@ describe('ide/components/terminal_sync/terminal_sync_status', () => { }); } else { it('renders icon', () => { - expect(wrapper.find(Icon).props('name')).toEqual(icon); + expect(wrapper.find(GlIcon).props('name')).toEqual(icon); }); it('does not render loading icon', () => { diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js index 8caa9c2b437..0e85b523cbd 100644 --- a/spec/frontend/ide/helpers.js +++ b/spec/frontend/ide/helpers.js @@ -6,7 +6,6 @@ export const file = (name = 'name', id = name, type = '', parent = null) => id, type, icon: 'icon', - url: 'url', name, path: parent ? `${parent.path}/${name}` : name, parentPath: parent ? parent.path : '', diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js index 529f80e6f6f..01c2eab33a5 100644 --- a/spec/frontend/ide/lib/editor_spec.js +++ b/spec/frontend/ide/lib/editor_spec.js @@ -202,28 +202,6 @@ describe('Multi-file editor library', () => { }); }); - describe('schemas', () => { - let originalGon; - - beforeEach(() => { - originalGon = window.gon; - window.gon = { features: { schemaLinting: true } }; - - delete Editor.editorInstance; - instance = Editor.create(); - }); - - afterEach(() => { - window.gon = originalGon; - }); - - it('registers custom schemas defined with Monaco', () => { - expect(monacoLanguages.yaml.yamlDefaults.diagnosticsOptions).toMatchObject({ - schemas: [{ fileMatch: ['*.gitlab-ci.yml'] }], - }); - }); - }); - describe('replaceSelectedText', () => { let model; let editor; diff --git a/spec/frontend/ide/lib/errors_spec.js b/spec/frontend/ide/lib/errors_spec.js new file mode 100644 index 00000000000..8c3fb378302 --- /dev/null +++ b/spec/frontend/ide/lib/errors_spec.js @@ -0,0 +1,70 @@ +import { + createUnexpectedCommitError, + createCodeownersCommitError, + createBranchChangedCommitError, + parseCommitError, +} from '~/ide/lib/errors'; + +const TEST_SPECIAL = '&special<'; +const TEST_SPECIAL_ESCAPED = '&special<'; +const TEST_MESSAGE = 'Test message.'; +const CODEOWNERS_MESSAGE = + 'Push to protected branches that contain changes to files matching CODEOWNERS is not allowed'; +const CHANGED_MESSAGE = 'Things changed since you started editing'; + +describe('~/ide/lib/errors', () => { + const createResponseError = message => ({ + response: { + data: { + message, + }, + }, + }); + + describe('createCodeownersCommitError', () => { + it('uses given message', () => { + expect(createCodeownersCommitError(TEST_MESSAGE)).toEqual({ + title: 'CODEOWNERS rule violation', + messageHTML: TEST_MESSAGE, + canCreateBranch: true, + }); + }); + + it('escapes special chars', () => { + expect(createCodeownersCommitError(TEST_SPECIAL)).toEqual({ + title: 'CODEOWNERS rule violation', + messageHTML: TEST_SPECIAL_ESCAPED, + canCreateBranch: true, + }); + }); + }); + + describe('createBranchChangedCommitError', () => { + it.each` + message | expectedMessage + ${TEST_MESSAGE} | ${`${TEST_MESSAGE}

Would you like to create a new branch?`} + ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}

Would you like to create a new branch?`} + `('uses given message="$message"', ({ message, expectedMessage }) => { + expect(createBranchChangedCommitError(message)).toEqual({ + title: 'Branch changed', + messageHTML: expectedMessage, + canCreateBranch: true, + }); + }); + }); + + describe('parseCommitError', () => { + it.each` + message | expectation + ${null} | ${createUnexpectedCommitError()} + ${{}} | ${createUnexpectedCommitError()} + ${{ response: {} }} | ${createUnexpectedCommitError()} + ${{ response: { data: {} } }} | ${createUnexpectedCommitError()} + ${createResponseError('test')} | ${createUnexpectedCommitError()} + ${createResponseError(CODEOWNERS_MESSAGE)} | ${createCodeownersCommitError(CODEOWNERS_MESSAGE)} + ${createResponseError(CHANGED_MESSAGE)} | ${createBranchChangedCommitError(CHANGED_MESSAGE)} + `('parses message into error object with "$message"', ({ message, expectation }) => { + expect(parseCommitError(message)).toEqual(expectation); + }); + }); +}); diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js index 6974cdc4074..8ca6f01d9a6 100644 --- a/spec/frontend/ide/lib/files_spec.js +++ b/spec/frontend/ide/lib/files_spec.js @@ -1,29 +1,16 @@ -import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils'; import { decorateFiles, splitParent } from '~/ide/lib/files'; import { decorateData } from '~/ide/stores/utils'; -const TEST_BRANCH_ID = 'lorem-ipsum'; -const TEST_PROJECT_ID = 10; - const createEntries = paths => { const createEntry = (acc, { path, type, children }) => { - // Sometimes we need to end the url with a '/' - const createUrl = base => (type === 'tree' ? `${base}/` : base); - const { name, parent } = splitParent(path); - const previewMode = viewerInformationForPath(name); acc[path] = { ...decorateData({ - projectId: TEST_PROJECT_ID, - branchId: TEST_BRANCH_ID, id: path, name, path, - url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`), type, - previewMode, - binary: (previewMode && previewMode.binary) || false, parentPath: parent, }), tree: children.map(childName => expect.objectContaining({ name: childName })), @@ -56,11 +43,7 @@ describe('IDE lib decorate files', () => { { path: 'README.md', type: 'blob', children: [] }, ]); - const { entries, treeList } = decorateFiles({ - data, - branchId: TEST_BRANCH_ID, - projectId: TEST_PROJECT_ID, - }); + const { entries, treeList } = decorateFiles({ data }); // Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)` // was taking a very long time for some reason. Probably due to large objects and nested `expect.objectContaining`. diff --git a/spec/frontend/ide/mock_data.js b/spec/frontend/ide/mock_data.js index 472516b6a2c..c8925e6745d 100644 --- a/spec/frontend/ide/mock_data.js +++ b/spec/frontend/ide/mock_data.js @@ -112,7 +112,8 @@ export const jobs = [ { id: 4, name: 'test 4', - path: 'testing4', + // bridge jobs don't have details page and so there is no path attribute + // see https://gitlab.com/gitlab-org/gitlab/-/issues/216480 status: { icon: 'status_failed', text: 'failed', diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js index bc3f86702cf..d2c32a81811 100644 --- a/spec/frontend/ide/services/index_spec.js +++ b/spec/frontend/ide/services/index_spec.js @@ -146,7 +146,7 @@ describe('IDE services', () => { it('gives back file.baseRaw for files with that property present', () => { file.baseRaw = TEST_FILE_CONTENTS; - return services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => { + return services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => { expect(content).toEqual(TEST_FILE_CONTENTS); }); }); @@ -155,7 +155,7 @@ describe('IDE services', () => { file.tempFile = true; file.baseRaw = TEST_FILE_CONTENTS; - return services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => { + return services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => { expect(content).toEqual(TEST_FILE_CONTENTS); }); }); @@ -192,7 +192,7 @@ describe('IDE services', () => { }); it('fetches file content', () => - services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => { + services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => { expect(content).toEqual(TEST_FILE_CONTENTS); })); }, diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index 88e7a9fff36..974c0715c06 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -27,6 +27,10 @@ describe('IDE store file actions', () => { }; store = createStore(); + + store.state.currentProjectId = 'test/test'; + store.state.currentBranchId = 'master'; + router = createRouter(store); jest.spyOn(store, 'commit'); @@ -72,10 +76,7 @@ describe('IDE store file actions', () => { }); it('closes file & opens next available file', () => { - const f = { - ...file('newOpenFile'), - url: '/newOpenFile', - }; + const f = file('newOpenFile'); store.state.openFiles.push(f); store.state.entries[f.path] = f; @@ -84,7 +85,7 @@ describe('IDE store file actions', () => { .dispatch('closeFile', localFile) .then(Vue.nextTick) .then(() => { - expect(router.push).toHaveBeenCalledWith(`/project${f.url}`); + expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/newOpenFile/'); }); }); @@ -240,7 +241,6 @@ describe('IDE store file actions', () => { 200, { raw_path: 'raw_path', - binary: false, }, { 'page-title': 'testing getFileData', @@ -296,7 +296,6 @@ describe('IDE store file actions', () => { describe('Re-named success', () => { beforeEach(() => { localFile = file(`newCreate-${Math.random()}`); - localFile.url = `project/getFileDataURL`; localFile.prevPath = 'old-dull-file'; localFile.path = 'new-shiny-file'; store.state.entries[localFile.path] = localFile; @@ -305,7 +304,6 @@ describe('IDE store file actions', () => { 200, { raw_path: 'raw_path', - binary: false, }, { 'page-title': 'testing old-dull-file', @@ -393,7 +391,11 @@ describe('IDE store file actions', () => { tmpFile.mrChange = { new_file: false }; return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => { - expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA'); + expect(service.getBaseRawFileData).toHaveBeenCalledWith( + tmpFile, + 'gitlab-org/gitlab-ce', + 'SHA', + ); expect(tmpFile.baseRaw).toBe('baseraw'); }); }); @@ -660,7 +662,7 @@ describe('IDE store file actions', () => { }); it('pushes route for active file', () => { - expect(router.push).toHaveBeenCalledWith(`/project${tmpFile.url}`); + expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/tempFile/'); }); }); }); @@ -735,10 +737,8 @@ describe('IDE store file actions', () => { }); it('pushes router URL when added', () => { - store.state.currentBranchId = 'master'; - return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => { - expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/'); + expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/'); }); }); }); diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js index 62971b9cad6..b1cceda9d85 100644 --- a/spec/frontend/ide/stores/actions/merge_request_spec.js +++ b/spec/frontend/ide/stores/actions/merge_request_spec.js @@ -453,11 +453,9 @@ describe('IDE store merge request actions', () => { it('updates activity bar view and gets file data, if changes are found', done => { store.state.entries.foo = { - url: 'test', type: 'blob', }; store.state.entries.bar = { - url: 'test', type: 'blob', }; diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js index f77dbd80025..ebf39df2f6f 100644 --- a/spec/frontend/ide/stores/actions_spec.js +++ b/spec/frontend/ide/stores/actions_spec.js @@ -123,7 +123,6 @@ describe('Multi-file store actions', () => { it('creates temp tree', done => { store .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, name: 'test', type: 'tree', }) @@ -150,7 +149,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, name: 'testing/test', type: 'tree', }) @@ -176,7 +174,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, name: 'testing', type: 'tree', }) @@ -197,7 +194,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { name, - branchId: 'mybranch', type: 'blob', }) .then(() => { @@ -217,7 +213,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { name, - branchId: 'mybranch', type: 'blob', }) .then(() => { @@ -237,7 +232,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { name, - branchId: 'mybranch', type: 'blob', }) .then(() => { @@ -249,7 +243,7 @@ describe('Multi-file store actions', () => { }); it('sets tmp file as active', () => { - createTempEntry(store, { name: 'test', branchId: 'mybranch', type: 'blob' }); + createTempEntry(store, { name: 'test', type: 'blob' }); expect(store.dispatch).toHaveBeenCalledWith('setFileActive', 'test'); }); @@ -262,7 +256,6 @@ describe('Multi-file store actions', () => { store .dispatch('createTempEntry', { name: 'test', - branchId: 'mybranch', type: 'blob', }) .then(() => { @@ -780,9 +773,11 @@ describe('Multi-file store actions', () => { }); it('routes to the renamed file if the original file has been opened', done => { + store.state.currentProjectId = 'test/test'; + store.state.currentBranchId = 'master'; + Object.assign(store.state.entries.orig, { opened: true, - url: '/foo-bar.md', }); store @@ -792,7 +787,7 @@ describe('Multi-file store actions', () => { }) .then(() => { expect(router.push.mock.calls).toHaveLength(1); - expect(router.push).toHaveBeenCalledWith(`/project/foo-bar.md`); + expect(router.push).toHaveBeenCalledWith(`/project/test/test/tree/master/-/renamed/`); }) .then(done) .catch(done.fail); diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js index dcf05329ce0..e24f08fa802 100644 --- a/spec/frontend/ide/stores/getters_spec.js +++ b/spec/frontend/ide/stores/getters_spec.js @@ -1,3 +1,4 @@ +import { TEST_HOST } from 'helpers/test_constants'; import * as getters from '~/ide/stores/getters'; import { createStore } from '~/ide/stores'; import { file } from '../helpers'; @@ -482,4 +483,48 @@ describe('IDE store getters', () => { expect(localStore.getters.getAvailableFileName('foo-bar1.jpg')).toBe('foo-bar1.jpg'); }); }); + + describe('getUrlForPath', () => { + it('returns a route url for the given path', () => { + localState.currentProjectId = 'test/test'; + localState.currentBranchId = 'master'; + + expect(localStore.getters.getUrlForPath('path/to/foo/bar-1.jpg')).toBe( + `/project/test/test/tree/master/-/path/to/foo/bar-1.jpg/`, + ); + }); + }); + + describe('getJsonSchemaForPath', () => { + beforeEach(() => { + localState.currentProjectId = 'path/to/some/project'; + localState.currentBranchId = 'master'; + }); + + it('returns a json schema uri and match config for a json/yaml file that can be loaded by monaco', () => { + expect(localStore.getters.getJsonSchemaForPath('.gitlab-ci.yml')).toEqual({ + fileMatch: ['*.gitlab-ci.yml'], + uri: `${TEST_HOST}/path/to/some/project/-/schema/master/.gitlab-ci.yml`, + }); + }); + + it('returns a path containing sha if branch details are present in state', () => { + localState.projects['path/to/some/project'] = { + name: 'project', + branches: { + master: { + name: 'master', + commit: { + id: 'abcdef123456', + }, + }, + }, + }; + + expect(localStore.getters.getJsonSchemaForPath('.gitlab-ci.yml')).toEqual({ + fileMatch: ['*.gitlab-ci.yml'], + uri: `${TEST_HOST}/path/to/some/project/-/schema/abcdef123456/.gitlab-ci.yml`, + }); + }); + }); }); diff --git a/spec/frontend/ide/stores/integration_spec.js b/spec/frontend/ide/stores/integration_spec.js index f95f036f572..b6a7c7fd02d 100644 --- a/spec/frontend/ide/stores/integration_spec.js +++ b/spec/frontend/ide/stores/integration_spec.js @@ -36,8 +36,6 @@ describe('IDE store integration', () => { beforeEach(() => { const { entries, treeList } = decorateFiles({ data: [`${TEST_PATH_DIR}/`, TEST_PATH, 'README.md'], - projectId: TEST_PROJECT_ID, - branchId: TEST_BRANCH, }); Object.assign(entries[TEST_PATH], { diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js index a14879112fd..babc50e54f1 100644 --- a/spec/frontend/ide/stores/modules/commit/actions_spec.js +++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js @@ -9,6 +9,7 @@ import eventHub from '~/ide/eventhub'; import consts from '~/ide/stores/modules/commit/constants'; import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types'; import * as actions from '~/ide/stores/modules/commit/actions'; +import { createUnexpectedCommitError } from '~/ide/lib/errors'; import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants'; import testAction from '../../../../helpers/vuex_action_helper'; @@ -510,7 +511,7 @@ describe('IDE commit module actions', () => { }); }); - describe('failed', () => { + describe('success response with failed message', () => { beforeEach(() => { jest.spyOn(service, 'commit').mockResolvedValue({ data: { @@ -533,6 +534,25 @@ describe('IDE commit module actions', () => { }); }); + describe('failed response', () => { + beforeEach(() => { + jest.spyOn(service, 'commit').mockRejectedValue({}); + }); + + it('commits error updates', async () => { + jest.spyOn(store, 'commit'); + + await store.dispatch('commit/commitChanges').catch(() => {}); + + expect(store.commit.mock.calls).toEqual([ + ['commit/CLEAR_ERROR', undefined, undefined], + ['commit/UPDATE_LOADING', true, undefined], + ['commit/UPDATE_LOADING', false, undefined], + ['commit/SET_ERROR', createUnexpectedCommitError(), undefined], + ]); + }); + }); + describe('first commit of a branch', () => { const COMMIT_RESPONSE = { id: '123456', diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js index 45ac1a86ab3..6393a70eac6 100644 --- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js @@ -1,5 +1,6 @@ import commitState from '~/ide/stores/modules/commit/state'; import mutations from '~/ide/stores/modules/commit/mutations'; +import * as types from '~/ide/stores/modules/commit/mutation_types'; describe('IDE commit module mutations', () => { let state; @@ -62,4 +63,24 @@ describe('IDE commit module mutations', () => { expect(state.shouldCreateMR).toBe(false); }); }); + + describe(types.CLEAR_ERROR, () => { + it('should clear commitError', () => { + state.commitError = {}; + + mutations[types.CLEAR_ERROR](state); + + expect(state.commitError).toBeNull(); + }); + }); + + describe(types.SET_ERROR, () => { + it('should set commitError', () => { + const error = { title: 'foo' }; + + mutations[types.SET_ERROR](state, error); + + expect(state.commitError).toBe(error); + }); + }); }); diff --git a/spec/frontend/ide/stores/modules/pipelines/actions_spec.js b/spec/frontend/ide/stores/modules/pipelines/actions_spec.js index 71918e7e2c2..8511843cc92 100644 --- a/spec/frontend/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/frontend/ide/stores/modules/pipelines/actions_spec.js @@ -15,10 +15,10 @@ import { fetchJobs, toggleStageCollapsed, setDetailJob, - requestJobTrace, - receiveJobTraceError, - receiveJobTraceSuccess, - fetchJobTrace, + requestJobLogs, + receiveJobLogsError, + receiveJobLogsSuccess, + fetchJobLogs, resetLatestPipeline, } from '~/ide/stores/modules/pipelines/actions'; import state from '~/ide/stores/modules/pipelines/state'; @@ -324,24 +324,24 @@ describe('IDE pipelines actions', () => { }); }); - describe('requestJobTrace', () => { + describe('requestJobLogs', () => { it('commits request', done => { - testAction(requestJobTrace, null, mockedState, [{ type: types.REQUEST_JOB_TRACE }], [], done); + testAction(requestJobLogs, null, mockedState, [{ type: types.REQUEST_JOB_LOGS }], [], done); }); }); - describe('receiveJobTraceError', () => { + describe('receiveJobLogsError', () => { it('commits error', done => { testAction( - receiveJobTraceError, + receiveJobLogsError, null, mockedState, - [{ type: types.RECEIVE_JOB_TRACE_ERROR }], + [{ type: types.RECEIVE_JOB_LOGS_ERROR }], [ { type: 'setErrorMessage', payload: { - text: 'An error occurred while fetching the job trace.', + text: 'An error occurred while fetching the job logs.', action: expect.any(Function), actionText: 'Please try again', actionPayload: null, @@ -353,20 +353,20 @@ describe('IDE pipelines actions', () => { }); }); - describe('receiveJobTraceSuccess', () => { + describe('receiveJobLogsSuccess', () => { it('commits data', done => { testAction( - receiveJobTraceSuccess, + receiveJobLogsSuccess, 'data', mockedState, - [{ type: types.RECEIVE_JOB_TRACE_SUCCESS, payload: 'data' }], + [{ type: types.RECEIVE_JOB_LOGS_SUCCESS, payload: 'data' }], [], done, ); }); }); - describe('fetchJobTrace', () => { + describe('fetchJobLogs', () => { beforeEach(() => { mockedState.detailJob = { path: `${TEST_HOST}/project/builds` }; }); @@ -379,20 +379,20 @@ describe('IDE pipelines actions', () => { it('dispatches request', done => { testAction( - fetchJobTrace, + fetchJobLogs, null, mockedState, [], [ - { type: 'requestJobTrace' }, - { type: 'receiveJobTraceSuccess', payload: { html: 'html' } }, + { type: 'requestJobLogs' }, + { type: 'receiveJobLogsSuccess', payload: { html: 'html' } }, ], done, ); }); it('sends get request to correct URL', () => { - fetchJobTrace({ + fetchJobLogs({ state: mockedState, dispatch() {}, @@ -410,11 +410,11 @@ describe('IDE pipelines actions', () => { it('dispatches error', done => { testAction( - fetchJobTrace, + fetchJobLogs, null, mockedState, [], - [{ type: 'requestJobTrace' }, { type: 'receiveJobTraceError' }], + [{ type: 'requestJobLogs' }, { type: 'receiveJobLogsError' }], done, ); }); diff --git a/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js b/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js index 3b7f92cfa74..7d2f5d5d710 100644 --- a/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/pipelines/mutations_spec.js @@ -175,37 +175,37 @@ describe('IDE pipelines mutations', () => { }); }); - describe('REQUEST_JOB_TRACE', () => { + describe('REQUEST_JOB_LOGS', () => { beforeEach(() => { mockedState.detailJob = { ...jobs[0] }; }); it('sets loading on detail job', () => { - mutations[types.REQUEST_JOB_TRACE](mockedState); + mutations[types.REQUEST_JOB_LOGS](mockedState); expect(mockedState.detailJob.isLoading).toBe(true); }); }); - describe('RECEIVE_JOB_TRACE_ERROR', () => { + describe('RECEIVE_JOB_LOGS_ERROR', () => { beforeEach(() => { mockedState.detailJob = { ...jobs[0], isLoading: true }; }); it('sets loading to false on detail job', () => { - mutations[types.RECEIVE_JOB_TRACE_ERROR](mockedState); + mutations[types.RECEIVE_JOB_LOGS_ERROR](mockedState); expect(mockedState.detailJob.isLoading).toBe(false); }); }); - describe('RECEIVE_JOB_TRACE_SUCCESS', () => { + describe('RECEIVE_JOB_LOGS_SUCCESS', () => { beforeEach(() => { mockedState.detailJob = { ...jobs[0], isLoading: true }; }); it('sets output on detail job', () => { - mutations[types.RECEIVE_JOB_TRACE_SUCCESS](mockedState, { html: 'html' }); + mutations[types.RECEIVE_JOB_LOGS_SUCCESS](mockedState, { html: 'html' }); expect(mockedState.detailJob.output).toBe('html'); expect(mockedState.detailJob.isLoading).toBe(false); }); diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js index ff904bbc9cd..b53e40be980 100644 --- a/spec/frontend/ide/stores/mutations/file_spec.js +++ b/spec/frontend/ide/stores/mutations/file_spec.js @@ -61,13 +61,11 @@ describe('IDE store file mutations', () => { mutations.SET_FILE_DATA(localState, { data: { raw_path: 'raw', - binary: true, }, file: localFile, }); expect(localFile.rawPath).toBe('raw'); - expect(localFile.binary).toBeTruthy(); expect(localFile.raw).toBeNull(); expect(localFile.baseRaw).toBeNull(); }); diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js index 1b29648fb8b..09e9481e5d4 100644 --- a/spec/frontend/ide/stores/mutations_spec.js +++ b/spec/frontend/ide/stores/mutations_spec.js @@ -113,8 +113,6 @@ describe('Multi-file store mutations', () => { }, treeList: [tmpFile], }, - projectId: 'gitlab-ce', - branchId: 'master', }); expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1); @@ -272,7 +270,6 @@ describe('Multi-file store mutations', () => { prevId: undefined, prevPath: undefined, prevName: undefined, - prevUrl: undefined, prevKey: undefined, }), ); @@ -337,7 +334,6 @@ describe('Multi-file store mutations', () => { }; Object.assign(localState.entries['root-folder/oldPath'], { parentPath: 'root-folder', - url: 'root-folder/oldPath-blob-root-folder/oldPath', }); mutations.RENAME_ENTRY(localState, { @@ -366,9 +362,6 @@ describe('Multi-file store mutations', () => { }); it('renames entry, preserving old parameters', () => { - Object.assign(localState.entries.oldPath, { - url: `project/-/oldPath`, - }); const oldPathData = localState.entries.oldPath; mutations.RENAME_ENTRY(localState, { @@ -382,12 +375,10 @@ describe('Multi-file store mutations', () => { id: 'newPath', path: 'newPath', name: 'newPath', - url: `project/-/newPath`, key: expect.stringMatching('newPath'), prevId: 'oldPath', prevName: 'oldPath', prevPath: 'oldPath', - prevUrl: `project/-/oldPath`, prevKey: oldPathData.key, prevParentPath: oldPathData.parentPath, }); @@ -409,7 +400,6 @@ describe('Multi-file store mutations', () => { prevId: expect.anything(), prevName: expect.anything(), prevPath: expect.anything(), - prevUrl: expect.anything(), prevKey: expect.anything(), prevParentPath: expect.anything(), }), @@ -419,7 +409,7 @@ describe('Multi-file store mutations', () => { it('properly handles files with spaces in name', () => { const path = 'my fancy path'; const newPath = 'new path'; - const oldEntry = { ...file(path, path, 'blob'), url: `project/-/${path}` }; + const oldEntry = file(path, path, 'blob'); localState.entries[path] = oldEntry; @@ -435,12 +425,10 @@ describe('Multi-file store mutations', () => { id: newPath, path: newPath, name: newPath, - url: `project/-/new path`, key: expect.stringMatching(newPath), prevId: path, prevName: path, prevPath: path, - prevUrl: `project/-/my fancy path`, prevKey: oldEntry.key, prevParentPath: oldEntry.parentPath, }); @@ -549,7 +537,7 @@ describe('Multi-file store mutations', () => { it('correctly saves original values if an entry is renamed multiple times', () => { const original = { ...localState.entries.oldPath }; - const paramsToCheck = ['prevId', 'prevPath', 'prevName', 'prevUrl']; + const paramsToCheck = ['prevId', 'prevPath', 'prevName']; const expectedObj = paramsToCheck.reduce( (o, param) => ({ ...o, [param]: original[param.replace('prev', '').toLowerCase()] }), {}, @@ -577,7 +565,6 @@ describe('Multi-file store mutations', () => { prevId: 'lorem/orig', prevPath: 'lorem/orig', prevName: 'orig', - prevUrl: 'project/-/loren/orig', prevKey: 'lorem/orig', prevParentPath: 'lorem', }; @@ -602,7 +589,6 @@ describe('Multi-file store mutations', () => { prevId: undefined, prevPath: undefined, prevName: undefined, - prevUrl: undefined, prevKey: undefined, prevParentPath: undefined, }), diff --git a/spec/frontend/ide/sync_router_and_store_spec.js b/spec/frontend/ide/sync_router_and_store_spec.js index ccf6e200806..20fd77c4dfb 100644 --- a/spec/frontend/ide/sync_router_and_store_spec.js +++ b/spec/frontend/ide/sync_router_and_store_spec.js @@ -17,9 +17,13 @@ describe('~/ide/sync_router_and_store', () => { const getRouterCurrentPath = () => router.currentRoute.fullPath; const getStoreCurrentPath = () => store.state.router.fullPath; - const updateRouter = path => { + const updateRouter = async path => { + if (getRouterCurrentPath() === path) { + return; + } + router.push(path); - return waitForPromises(); + await waitForPromises(); }; const updateStore = path => { store.dispatch('router/push', path); diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js index e7ef0de45a0..97dc8217ecc 100644 --- a/spec/frontend/ide/utils_spec.js +++ b/spec/frontend/ide/utils_spec.js @@ -2,7 +2,7 @@ import { languages } from 'monaco-editor'; import { isTextFile, registerLanguages, - registerSchemas, + registerSchema, trimPathComponents, insertFinalNewline, trimTrailingWhitespace, @@ -13,60 +13,78 @@ import { describe('WebIDE utils', () => { describe('isTextFile', () => { - it('returns false for known binary types', () => { - expect(isTextFile('file content', 'image/png', 'my.png')).toBeFalsy(); - // mime types are case insensitive - expect(isTextFile('file content', 'IMAGE/PNG', 'my.png')).toBeFalsy(); + it.each` + mimeType | name | type | result + ${'image/png'} | ${'my.png'} | ${'binary'} | ${false} + ${'IMAGE/PNG'} | ${'my.png'} | ${'binary'} | ${false} + ${'text/plain'} | ${'my.txt'} | ${'text'} | ${true} + ${'TEXT/PLAIN'} | ${'my.txt'} | ${'text'} | ${true} + `('returns $result for known $type types', ({ mimeType, name, result }) => { + expect(isTextFile({ content: 'file content', mimeType, name })).toBe(result); }); - it('returns true for known text types', () => { - expect(isTextFile('file content', 'text/plain', 'my.txt')).toBeTruthy(); - // mime types are case insensitive - expect(isTextFile('file content', 'TEXT/PLAIN', 'my.txt')).toBeTruthy(); - }); + it.each` + content | mimeType | name + ${'{"éêė":"value"}'} | ${'application/json'} | ${'my.json'} + ${'{"éêė":"value"}'} | ${'application/json'} | ${'.tsconfig'} + ${'SELECT "éêė" from tablename'} | ${'application/sql'} | ${'my.sql'} + ${'{"éêė":"value"}'} | ${'application/json'} | ${'MY.JSON'} + ${'SELECT "éêė" from tablename'} | ${'application/sql'} | ${'MY.SQL'} + ${'var code = "something"'} | ${'application/javascript'} | ${'Gruntfile'} + ${'MAINTAINER Александр "a21283@me.com"'} | ${'application/octet-stream'} | ${'dockerfile'} + `( + 'returns true for file extensions that Monaco supports syntax highlighting for', + ({ content, mimeType, name }) => { + expect(isTextFile({ content, mimeType, name })).toBe(true); + }, + ); - it('returns true for file extensions that Monaco supports syntax highlighting for', () => { - // test based on both MIME and extension - expect(isTextFile('{"éêė":"value"}', 'application/json', 'my.json')).toBeTruthy(); - expect(isTextFile('{"éêė":"value"}', 'application/json', '.tsconfig')).toBeTruthy(); - expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'my.sql')).toBeTruthy(); + it('returns false if filename is same as the expected extension', () => { + expect( + isTextFile({ + name: 'sql', + content: 'SELECT "éêė" from tablename', + mimeType: 'application/sql', + }), + ).toBeFalsy(); }); - it('returns true even irrespective of whether the mimes, extensions or file names are lowercase or upper case', () => { - expect(isTextFile('{"éêė":"value"}', 'application/json', 'MY.JSON')).toBeTruthy(); - expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'MY.SQL')).toBeTruthy(); - expect( - isTextFile('var code = "something"', 'application/javascript', 'Gruntfile'), - ).toBeTruthy(); + it('returns true for ASCII only content for unknown types', () => { expect( - isTextFile( - 'MAINTAINER Александр "alexander11354322283@me.com"', - 'application/octet-stream', - 'dockerfile', - ), + isTextFile({ + name: 'hello.mytype', + content: 'plain text', + mimeType: 'application/x-new-type', + }), ).toBeTruthy(); }); - it('returns false if filename is same as the expected extension', () => { - expect(isTextFile('SELECT "éêė" from tablename', 'application/sql', 'sql')).toBeFalsy(); - }); - - it('returns true for ASCII only content for unknown types', () => { - expect(isTextFile('plain text', 'application/x-new-type', 'hello.mytype')).toBeTruthy(); + it('returns false for non-ASCII content for unknown types', () => { + expect( + isTextFile({ + name: 'my.random', + content: '{"éêė":"value"}', + mimeType: 'application/octet-stream', + }), + ).toBeFalsy(); }); - it('returns true for relevant filenames', () => { - expect( - isTextFile( - 'MAINTAINER Александр "alexander11354322283@me.com"', - 'application/octet-stream', - 'Dockerfile', - ), - ).toBeTruthy(); + it.each` + name | result + ${'myfile.txt'} | ${true} + ${'Dockerfile'} | ${true} + ${'img.png'} | ${false} + ${'abc.js'} | ${true} + ${'abc.random'} | ${false} + ${'image.jpeg'} | ${false} + `('returns $result for $filename when no content or mimeType is passed', ({ name, result }) => { + expect(isTextFile({ name })).toBe(result); }); - it('returns false for non-ASCII content for unknown types', () => { - expect(isTextFile('{"éêė":"value"}', 'application/octet-stream', 'my.random')).toBeFalsy(); + it('returns true if content is empty string but false if content is not passed', () => { + expect(isTextFile({ name: 'abc.dat' })).toBe(false); + expect(isTextFile({ name: 'abc.dat', content: '' })).toBe(true); + expect(isTextFile({ name: 'abc.dat', content: ' ' })).toBe(true); }); }); @@ -159,55 +177,37 @@ describe('WebIDE utils', () => { }); }); - describe('registerSchemas', () => { - let options; + describe('registerSchema', () => { + let schema; beforeEach(() => { - options = { - validate: true, - enableSchemaRequest: true, - hover: true, - completion: true, - schemas: [ - { - uri: 'http://myserver/foo-schema.json', - fileMatch: ['*'], - schema: { - id: 'http://myserver/foo-schema.json', - type: 'object', - properties: { - p1: { enum: ['v1', 'v2'] }, - p2: { $ref: 'http://myserver/bar-schema.json' }, - }, - }, - }, - { - uri: 'http://myserver/bar-schema.json', - schema: { - id: 'http://myserver/bar-schema.json', - type: 'object', - properties: { q1: { enum: ['x1', 'x2'] } }, - }, + schema = { + uri: 'http://myserver/foo-schema.json', + fileMatch: ['*'], + schema: { + id: 'http://myserver/foo-schema.json', + type: 'object', + properties: { + p1: { enum: ['v1', 'v2'] }, + p2: { $ref: 'http://myserver/bar-schema.json' }, }, - ], + }, }; jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions'); jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions'); }); - it.each` - language | defaultsObj - ${'json'} | ${languages.json.jsonDefaults} - ${'yaml'} | ${languages.yaml.yamlDefaults} - `( - 'registers the given schemas with monaco for lang: $language', - ({ language, defaultsObj }) => { - registerSchemas({ language, options }); + it('registers the given schemas with monaco for both json and yaml languages', () => { + registerSchema(schema); - expect(defaultsObj.setDiagnosticsOptions).toHaveBeenCalledWith(options); - }, - ); + expect(languages.json.jsonDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect.objectContaining({ schemas: [schema] }), + ); + expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect.objectContaining({ schemas: [schema] }), + ); + }); }); describe('trimTrailingWhitespace', () => { -- cgit v1.2.1