From 48aff82709769b098321c738f3444b9bdaa694c6 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 21 Oct 2020 07:08:36 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-5-stable-ee --- .../ide/components/commit_sidebar/actions_spec.js | 4 +- .../ide/components/commit_sidebar/form_spec.js | 37 ++- spec/frontend/ide/components/ide_review_spec.js | 78 ++++-- spec/frontend/ide/components/ide_side_bar_spec.js | 81 ++++-- spec/frontend/ide/components/ide_tree_list_spec.js | 8 - spec/frontend/ide/components/ide_tree_spec.js | 41 ++- .../jobs/__snapshots__/stage_spec.js.snap | 2 - .../components/jobs/detail/scroll_button_spec.js | 2 +- .../ide/components/new_dropdown/upload_spec.js | 17 +- .../ide/components/repo_commit_section_spec.js | 21 +- spec/frontend/ide/lib/errors_spec.js | 46 ++-- spec/frontend/ide/lib/languages/hcl_spec.js | 290 +++++++++++++++++++++ spec/frontend/ide/stores/actions/file_spec.js | 14 + spec/frontend/ide/stores/getters_spec.js | 10 +- .../ide/stores/modules/commit/actions_spec.js | 82 +++--- spec/frontend/ide/stores/mutations/file_spec.js | 26 +- spec/frontend/ide/stores/utils_spec.js | 10 +- spec/frontend/ide/utils_spec.js | 40 +++ 18 files changed, 632 insertions(+), 177 deletions(-) create mode 100644 spec/frontend/ide/lib/languages/hcl_spec.js (limited to 'spec/frontend/ide') diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js index a303e2b9bee..0003e13c92f 100644 --- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js @@ -83,12 +83,12 @@ describe('IDE commit sidebar actions', () => { }); }); - describe('commitToCurrentBranchText', () => { + describe('currentBranchText', () => { it('escapes current branch', () => { const injectedSrc = ''; createComponent({ currentBranchId: injectedSrc }); - expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc); + expect(vm.currentBranchText).not.toContain(injectedSrc); }); }); diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js index 56667d6b03d..abd7e3bb8fc 100644 --- a/spec/frontend/ide/components/commit_sidebar/form_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js @@ -7,7 +7,12 @@ 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'; +import { + createCodeownersCommitError, + createUnexpectedCommitError, + createBranchChangedCommitError, + branchAlreadyExistsCommitError, +} from '~/ide/lib/errors'; describe('IDE commit form', () => { const Component = Vue.extend(CommitForm); @@ -290,20 +295,30 @@ describe('IDE commit form', () => { jest.spyOn(vm.$store, 'dispatch').mockReturnValue(Promise.resolve()); }); - it('updates commit action and commits', async () => { - store.state.commit.commitError = createCodeownersCommitError('test message'); + const commitActions = [ + ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH], + ['commit/commitChanges'], + ]; - await vm.$nextTick(); + it.each` + commitError | expectedActions + ${createCodeownersCommitError} | ${commitActions} + ${createBranchChangedCommitError} | ${commitActions} + ${branchAlreadyExistsCommitError} | ${[['commit/addSuffixToBranchName'], ...commitActions]} + `( + 'updates commit action and commits for error: $commitError', + async ({ commitError, expectedActions }) => { + store.state.commit.commitError = commitError('test message'); - getByText(document.body, 'Create new branch').click(); + await vm.$nextTick(); - await waitForPromises(); + getByText(document.body, 'Create new branch').click(); - expect(vm.$store.dispatch.mock.calls).toEqual([ - ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH], - ['commit/commitChanges', undefined], - ]); - }); + await waitForPromises(); + + expect(vm.$store.dispatch.mock.calls).toEqual(expectedActions); + }, + ); }); }); diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js index c9ac2ac423d..bcc98669427 100644 --- a/spec/frontend/ide/components/ide_review_spec.js +++ b/spec/frontend/ide/components/ide_review_spec.js @@ -1,14 +1,19 @@ import Vue from 'vue'; +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; import IdeReview from '~/ide/components/ide_review.vue'; +import EditorModeDropdown from '~/ide/components/editor_mode_dropdown.vue'; import { createStore } from '~/ide/stores'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { trimText } from '../../helpers/text_helper'; +import { keepAlive } from '../../helpers/keep_alive_component_helper'; import { file } from '../helpers'; import { projectData } from '../mock_data'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('IDE review mode', () => { - const Component = Vue.extend(IdeReview); - let vm; + let wrapper; let store; beforeEach(() => { @@ -21,15 +26,53 @@ describe('IDE review mode', () => { loading: false, }); - vm = createComponentWithStore(Component, store).$mount(); + wrapper = mount(keepAlive(IdeReview), { + store, + localVue, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders list of files', () => { - expect(vm.$el.textContent).toContain('fileName'); + expect(wrapper.text()).toContain('fileName'); + }); + + describe('activated', () => { + let inititializeSpy; + + beforeEach(async () => { + inititializeSpy = jest.spyOn(wrapper.find(IdeReview).vm, 'initialize'); + store.state.viewer = 'editor'; + + await wrapper.vm.reactivate(); + }); + + it('re initializes the component', () => { + expect(inititializeSpy).toHaveBeenCalled(); + }); + + it('updates viewer to "diff" by default', () => { + expect(store.state.viewer).toBe('diff'); + }); + + describe('merge request is defined', () => { + beforeEach(async () => { + store.state.currentMergeRequestId = '1'; + store.state.projects.abcproject.mergeRequests['1'] = { + iid: 123, + web_url: 'testing123', + }; + + await wrapper.vm.reactivate(); + }); + + it('updates viewer to "mrdiff"', async () => { + expect(store.state.viewer).toBe('mrdiff'); + }); + }); }); describe('merge request', () => { @@ -40,32 +83,27 @@ describe('IDE review mode', () => { web_url: 'testing123', }; - return vm.$nextTick(); + return wrapper.vm.$nextTick(); }); it('renders edit dropdown', () => { - expect(vm.$el.querySelector('.btn')).not.toBe(null); + expect(wrapper.find(EditorModeDropdown).exists()).toBe(true); }); - it('renders merge request link & IID', () => { + it('renders merge request link & IID', async () => { store.state.viewer = 'mrdiff'; - return vm.$nextTick(() => { - const link = vm.$el.querySelector('.ide-review-sub-header'); + await wrapper.vm.$nextTick(); - expect(link.querySelector('a').getAttribute('href')).toBe('testing123'); - expect(trimText(link.textContent)).toBe('Merge request (!123)'); - }); + expect(trimText(wrapper.text())).toContain('Merge request (!123)'); }); - it('changes text to latest changes when viewer is not mrdiff', () => { + it('changes text to latest changes when viewer is not mrdiff', async () => { store.state.viewer = 'diff'; - return vm.$nextTick(() => { - expect(trimText(vm.$el.querySelector('.ide-review-sub-header').textContent)).toBe( - 'Latest changes', - ); - }); + await wrapper.vm.$nextTick(); + + expect(wrapper.text()).toContain('Latest changes'); }); }); }); diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js index 67257b40879..86e4e8d8f89 100644 --- a/spec/frontend/ide/components/ide_side_bar_spec.js +++ b/spec/frontend/ide/components/ide_side_bar_spec.js @@ -1,57 +1,88 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { mount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlSkeletonLoading } from '@gitlab/ui'; import { createStore } from '~/ide/stores'; -import ideSidebar from '~/ide/components/ide_side_bar.vue'; +import IdeSidebar from '~/ide/components/ide_side_bar.vue'; +import IdeTree from '~/ide/components/ide_tree.vue'; +import RepoCommitSection from '~/ide/components/repo_commit_section.vue'; import { leftSidebarViews } from '~/ide/constants'; import { projectData } from '../mock_data'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('IdeSidebar', () => { - let vm; + let wrapper; let store; - beforeEach(() => { + function createComponent() { store = createStore(); - const Component = Vue.extend(ideSidebar); - store.state.currentProjectId = 'abcproject'; store.state.projects.abcproject = projectData; - vm = createComponentWithStore(Component, store).$mount(); - }); + return mount(IdeSidebar, { + store, + localVue, + }); + } afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); it('renders a sidebar', () => { - expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull(); + wrapper = createComponent(); + + expect(wrapper.find('[data-testid="ide-side-bar-inner"]').exists()).toBe(true); }); - it('renders loading icon component', done => { - vm.$store.state.loading = true; + it('renders loading components', async () => { + wrapper = createComponent(); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull(); - expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3); + store.state.loading = true; - done(); - }); + await wrapper.vm.$nextTick(); + + expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3); }); describe('activityBarComponent', () => { it('renders tree component', () => { - expect(vm.$el.querySelector('.ide-file-list')).not.toBeNull(); + wrapper = createComponent(); + + expect(wrapper.find(IdeTree).exists()).toBe(true); }); - it('renders commit component', done => { - vm.$store.state.currentActivityView = leftSidebarViews.commit.name; + it('renders commit component', async () => { + wrapper = createComponent(); + + store.state.currentActivityView = leftSidebarViews.commit.name; - vm.$nextTick(() => { - expect(vm.$el.querySelector('.multi-file-commit-panel-section')).not.toBeNull(); + await wrapper.vm.$nextTick(); - done(); - }); + expect(wrapper.find(RepoCommitSection).exists()).toBe(true); }); }); + + it('keeps the current activity view components alive', async () => { + wrapper = createComponent(); + + const ideTreeComponent = wrapper.find(IdeTree).element; + + store.state.currentActivityView = leftSidebarViews.commit.name; + + await wrapper.vm.$nextTick(); + + expect(wrapper.find(IdeTree).exists()).toBe(false); + expect(wrapper.find(RepoCommitSection).exists()).toBe(true); + + store.state.currentActivityView = leftSidebarViews.edit.name; + + await wrapper.vm.$nextTick(); + + // reference to the elements remains the same, meaning the components were kept alive + expect(wrapper.find(IdeTree).element).toEqual(ideTreeComponent); + }); }); diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js index 4593ef6049b..dd57a5c5f4d 100644 --- a/spec/frontend/ide/components/ide_tree_list_spec.js +++ b/spec/frontend/ide/components/ide_tree_list_spec.js @@ -38,15 +38,9 @@ describe('IDE tree list', () => { beforeEach(() => { bootstrapWithTree(); - jest.spyOn(vm, 'updateViewer'); - vm.$mount(); }); - it('updates viewer on mount', () => { - expect(vm.updateViewer).toHaveBeenCalledWith('edit'); - }); - it('renders loading indicator', done => { store.state.trees['abcproject/master'].loading = true; @@ -67,8 +61,6 @@ describe('IDE tree list', () => { beforeEach(() => { bootstrapWithTree(emptyBranchTree); - jest.spyOn(vm, 'updateViewer'); - vm.$mount(); }); diff --git a/spec/frontend/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js index 899daa0bf57..ad00dec2e48 100644 --- a/spec/frontend/ide/components/ide_tree_spec.js +++ b/spec/frontend/ide/components/ide_tree_spec.js @@ -1,19 +1,22 @@ import Vue from 'vue'; +import Vuex from 'vuex'; +import { mount, createLocalVue } from '@vue/test-utils'; import IdeTree from '~/ide/components/ide_tree.vue'; import { createStore } from '~/ide/stores'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { keepAlive } from '../../helpers/keep_alive_component_helper'; import { file } from '../helpers'; import { projectData } from '../mock_data'; -describe('IdeRepoTree', () => { +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('IdeTree', () => { let store; - let vm; + let wrapper; beforeEach(() => { store = createStore(); - const IdeRepoTree = Vue.extend(IdeTree); - store.state.currentProjectId = 'abcproject'; store.state.currentBranchId = 'master'; store.state.projects.abcproject = { ...projectData }; @@ -22,14 +25,36 @@ describe('IdeRepoTree', () => { loading: false, }); - vm = createComponentWithStore(IdeRepoTree, store).$mount(); + wrapper = mount(keepAlive(IdeTree), { + store, + localVue, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders list of files', () => { - expect(vm.$el.textContent).toContain('fileName'); + expect(wrapper.text()).toContain('fileName'); + }); + + describe('activated', () => { + let inititializeSpy; + + beforeEach(async () => { + inititializeSpy = jest.spyOn(wrapper.find(IdeTree).vm, 'initialize'); + store.state.viewer = 'diff'; + + await wrapper.vm.reactivate(); + }); + + it('re initializes the component', () => { + expect(inititializeSpy).toHaveBeenCalled(); + }); + + it('updates viewer to "editor" by default', () => { + expect(store.state.viewer).toBe('editor'); + }); }); }); 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 a65d9e6f78b..faa70982fac 100644 --- a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap +++ b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap @@ -16,8 +16,6 @@ exports[`IDE pipeline stage renders stage details & icon 1`] = ` build 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 42526590ebb..57174181a3d 100644 --- a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js +++ b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js @@ -31,7 +31,7 @@ describe('IDE job log scroll button', () => { }); it('returns proper title', () => { - expect(wrapper.attributes('data-original-title')).toBe(title); + expect(wrapper.attributes('title')).toBe(title); }); }); diff --git a/spec/frontend/ide/components/new_dropdown/upload_spec.js b/spec/frontend/ide/components/new_dropdown/upload_spec.js index ae497106f73..3f3784dbb3a 100644 --- a/spec/frontend/ide/components/new_dropdown/upload_spec.js +++ b/spec/frontend/ide/components/new_dropdown/upload_spec.js @@ -59,14 +59,11 @@ describe('new dropdown upload', () => { result: 'base64,cGxhaW4gdGV4dA==', }; const binaryTarget = { - result: 'base64,w4I=', + result: 'base64,8PDw8A==', // ðððð }; - const textFile = new File(['plain text'], 'textFile'); - const binaryFile = { - name: 'binaryFile', - type: 'image/png', - }; + const textFile = new File(['plain text'], 'textFile'); + const binaryFile = new File(['😺'], 'binaryFile'); beforeEach(() => { jest.spyOn(FileReader.prototype, 'readAsText'); @@ -92,16 +89,16 @@ describe('new dropdown upload', () => { .catch(done.fail); }); - it('splits content on base64 if binary', () => { + it('creates a blob URL for the content if binary', () => { vm.createFile(binaryTarget, binaryFile); - expect(FileReader.prototype.readAsText).not.toHaveBeenCalledWith(textFile); + expect(FileReader.prototype.readAsText).not.toHaveBeenCalled(); expect(vm.$emit).toHaveBeenCalledWith('create', { name: binaryFile.name, type: 'blob', - content: binaryTarget.result.split('base64,')[1], - rawPath: binaryTarget.result, + content: 'ðððð', + rawPath: 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b', }); }); }); diff --git a/spec/frontend/ide/components/repo_commit_section_spec.js b/spec/frontend/ide/components/repo_commit_section_spec.js index 3b837622720..096079308cd 100644 --- a/spec/frontend/ide/components/repo_commit_section_spec.js +++ b/spec/frontend/ide/components/repo_commit_section_spec.js @@ -1,6 +1,7 @@ import { mount } from '@vue/test-utils'; import { createStore } from '~/ide/stores'; import { createRouter } from '~/ide/ide_router'; +import { keepAlive } from '../../helpers/keep_alive_component_helper'; import RepoCommitSection from '~/ide/components/repo_commit_section.vue'; import EmptyState from '~/ide/components/commit_sidebar/empty_state.vue'; import { stageKeys } from '~/ide/constants'; @@ -14,7 +15,7 @@ describe('RepoCommitSection', () => { let store; function createComponent() { - wrapper = mount(RepoCommitSection, { store }); + wrapper = mount(keepAlive(RepoCommitSection), { store }); } function setupDefaultState() { @@ -64,6 +65,7 @@ describe('RepoCommitSection', () => { afterEach(() => { wrapper.destroy(); + wrapper = null; }); describe('empty state', () => { @@ -168,4 +170,21 @@ describe('RepoCommitSection', () => { expect(wrapper.find(EmptyState).exists()).toBe(false); }); }); + + describe('activated', () => { + let inititializeSpy; + + beforeEach(async () => { + createComponent(); + + inititializeSpy = jest.spyOn(wrapper.find(RepoCommitSection).vm, 'initialize'); + store.state.viewer = 'diff'; + + await wrapper.vm.reactivate(); + }); + + it('re initializes the component', () => { + expect(inititializeSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/ide/lib/errors_spec.js b/spec/frontend/ide/lib/errors_spec.js index 8c3fb378302..733d5a5da3c 100644 --- a/spec/frontend/ide/lib/errors_spec.js +++ b/spec/frontend/ide/lib/errors_spec.js @@ -2,6 +2,7 @@ import { createUnexpectedCommitError, createCodeownersCommitError, createBranchChangedCommitError, + branchAlreadyExistsCommitError, parseCommitError, } from '~/ide/lib/errors'; @@ -21,35 +22,22 @@ describe('~/ide/lib/errors', () => { }, }); - describe('createCodeownersCommitError', () => { - it('uses given message', () => { - expect(createCodeownersCommitError(TEST_MESSAGE)).toEqual({ - title: 'CODEOWNERS rule violation', - messageHTML: TEST_MESSAGE, - canCreateBranch: true, - }); - }); + const NEW_BRANCH_SUFFIX = `

Would you like to create a new branch?`; + const AUTOGENERATE_SUFFIX = `

Would you like to try auto-generating a branch name?`; - 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, - }); + it.each` + fn | title | message | messageHTML + ${createCodeownersCommitError} | ${'CODEOWNERS rule violation'} | ${TEST_MESSAGE} | ${TEST_MESSAGE} + ${createCodeownersCommitError} | ${'CODEOWNERS rule violation'} | ${TEST_SPECIAL} | ${TEST_SPECIAL_ESCAPED} + ${branchAlreadyExistsCommitError} | ${'Branch already exists'} | ${TEST_MESSAGE} | ${`${TEST_MESSAGE}${AUTOGENERATE_SUFFIX}`} + ${branchAlreadyExistsCommitError} | ${'Branch already exists'} | ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}${AUTOGENERATE_SUFFIX}`} + ${createBranchChangedCommitError} | ${'Branch changed'} | ${TEST_MESSAGE} | ${`${TEST_MESSAGE}${NEW_BRANCH_SUFFIX}`} + ${createBranchChangedCommitError} | ${'Branch changed'} | ${TEST_SPECIAL} | ${`${TEST_SPECIAL_ESCAPED}${NEW_BRANCH_SUFFIX}`} + `('$fn escapes and uses given message="$message"', ({ fn, title, message, messageHTML }) => { + expect(fn(message)).toEqual({ + title, + messageHTML, + primaryAction: { text: 'Create new branch', callback: expect.any(Function) }, }); }); @@ -60,7 +48,7 @@ describe('~/ide/lib/errors', () => { ${{}} | ${createUnexpectedCommitError()} ${{ response: {} }} | ${createUnexpectedCommitError()} ${{ response: { data: {} } }} | ${createUnexpectedCommitError()} - ${createResponseError('test')} | ${createUnexpectedCommitError()} + ${createResponseError(TEST_MESSAGE)} | ${createUnexpectedCommitError(TEST_MESSAGE)} ${createResponseError(CODEOWNERS_MESSAGE)} | ${createCodeownersCommitError(CODEOWNERS_MESSAGE)} ${createResponseError(CHANGED_MESSAGE)} | ${createBranchChangedCommitError(CHANGED_MESSAGE)} `('parses message into error object with "$message"', ({ message, expectation }) => { diff --git a/spec/frontend/ide/lib/languages/hcl_spec.js b/spec/frontend/ide/lib/languages/hcl_spec.js new file mode 100644 index 00000000000..a39673a3225 --- /dev/null +++ b/spec/frontend/ide/lib/languages/hcl_spec.js @@ -0,0 +1,290 @@ +import { editor } from 'monaco-editor'; +import { registerLanguages } from '~/ide/utils'; +import hcl from '~/ide/lib/languages/hcl'; + +describe('tokenization for .tf files', () => { + beforeEach(() => { + registerLanguages(hcl); + }); + + it.each([ + ['// Foo', [[{ language: 'hcl', offset: 0, type: 'comment.hcl' }]]], + ['/* Bar */', [[{ language: 'hcl', offset: 0, type: 'comment.hcl' }]]], + ['/*', [[{ language: 'hcl', offset: 0, type: 'comment.hcl' }]]], + [ + 'foo = "bar"', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'string.hcl' }, + ], + ], + ], + [ + 'variable "foo" {', + [ + [ + { language: 'hcl', offset: 0, type: 'type.hcl' }, + { language: 'hcl', offset: 8, type: '' }, + { language: 'hcl', offset: 9, type: 'string.hcl' }, + { language: 'hcl', offset: 14, type: '' }, + { language: 'hcl', offset: 15, type: 'delimiter.curly.hcl' }, + ], + ], + ], + [ + // eslint-disable-next-line no-template-curly-in-string + ' api_key = "${var.foo}"', + [ + [ + { language: 'hcl', offset: 0, type: '' }, + { language: 'hcl', offset: 2, type: 'variable.hcl' }, + { language: 'hcl', offset: 9, type: '' }, + { language: 'hcl', offset: 10, type: 'operator.hcl' }, + { language: 'hcl', offset: 11, type: '' }, + { language: 'hcl', offset: 12, type: 'string.hcl' }, + { language: 'hcl', offset: 13, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 15, type: 'keyword.var.hcl' }, + { language: 'hcl', offset: 18, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 19, type: 'variable.hcl' }, + { language: 'hcl', offset: 22, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 23, type: 'string.hcl' }, + ], + ], + ], + [ + 'resource "aws_security_group" "firewall" {', + [ + [ + { language: 'hcl', offset: 0, type: 'type.hcl' }, + { language: 'hcl', offset: 8, type: '' }, + { language: 'hcl', offset: 9, type: 'string.hcl' }, + { language: 'hcl', offset: 29, type: '' }, + { language: 'hcl', offset: 30, type: 'string.hcl' }, + { language: 'hcl', offset: 40, type: '' }, + { language: 'hcl', offset: 41, type: 'delimiter.curly.hcl' }, + ], + ], + ], + [ + ' network_interface {', + [ + [ + { language: 'hcl', offset: 0, type: '' }, + { language: 'hcl', offset: 2, type: 'identifier.hcl' }, + { language: 'hcl', offset: 20, type: 'delimiter.curly.hcl' }, + ], + ], + ], + [ + 'foo = [1, 2, "foo"]', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'delimiter.square.hcl' }, + { language: 'hcl', offset: 7, type: 'number.hcl' }, + { language: 'hcl', offset: 8, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 9, type: '' }, + { language: 'hcl', offset: 10, type: 'number.hcl' }, + { language: 'hcl', offset: 11, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 12, type: '' }, + { language: 'hcl', offset: 13, type: 'string.hcl' }, + { language: 'hcl', offset: 18, type: 'delimiter.square.hcl' }, + ], + ], + ], + [ + 'resource "foo" "bar" {}', + [ + [ + { language: 'hcl', offset: 0, type: 'type.hcl' }, + { language: 'hcl', offset: 8, type: '' }, + { language: 'hcl', offset: 9, type: 'string.hcl' }, + { language: 'hcl', offset: 14, type: '' }, + { language: 'hcl', offset: 15, type: 'string.hcl' }, + { language: 'hcl', offset: 20, type: '' }, + { language: 'hcl', offset: 21, type: 'delimiter.curly.hcl' }, + ], + ], + ], + [ + 'foo = "bar"', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'string.hcl' }, + ], + ], + ], + [ + 'bar = 7', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'number.hcl' }, + ], + ], + ], + [ + 'baz = [1,2,3]', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'delimiter.square.hcl' }, + { language: 'hcl', offset: 7, type: 'number.hcl' }, + { language: 'hcl', offset: 8, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 9, type: 'number.hcl' }, + { language: 'hcl', offset: 10, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 11, type: 'number.hcl' }, + { language: 'hcl', offset: 12, type: 'delimiter.square.hcl' }, + ], + ], + ], + [ + 'foo = -12', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'operator.hcl' }, + { language: 'hcl', offset: 7, type: 'number.hcl' }, + ], + ], + ], + [ + 'bar = 3.14159', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'number.float.hcl' }, + ], + ], + ], + [ + 'foo = true', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'keyword.true.hcl' }, + ], + ], + ], + [ + 'foo = false', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'keyword.false.hcl' }, + ], + ], + ], + [ + // eslint-disable-next-line no-template-curly-in-string + 'bar = "${file("bing/bong.txt")}"', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'operator.hcl' }, + { language: 'hcl', offset: 5, type: '' }, + { language: 'hcl', offset: 6, type: 'string.hcl' }, + { language: 'hcl', offset: 7, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 9, type: 'type.hcl' }, + { language: 'hcl', offset: 13, type: 'delimiter.parenthesis.hcl' }, + { language: 'hcl', offset: 14, type: 'string.hcl' }, + { language: 'hcl', offset: 29, type: 'delimiter.parenthesis.hcl' }, + { language: 'hcl', offset: 30, type: 'delimiter.hcl' }, + { language: 'hcl', offset: 31, type: 'string.hcl' }, + ], + ], + ], + [ + 'a = 1e-10', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 1, type: '' }, + { language: 'hcl', offset: 2, type: 'operator.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'number.float.hcl' }, + ], + ], + ], + [ + 'b = 1e+10', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 1, type: '' }, + { language: 'hcl', offset: 2, type: 'operator.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'number.float.hcl' }, + ], + ], + ], + [ + 'c = 1e10', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 1, type: '' }, + { language: 'hcl', offset: 2, type: 'operator.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'number.float.hcl' }, + ], + ], + ], + [ + 'd = 1.2e-10', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 1, type: '' }, + { language: 'hcl', offset: 2, type: 'operator.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'number.float.hcl' }, + ], + ], + ], + [ + 'e = 1.2e+10', + [ + [ + { language: 'hcl', offset: 0, type: 'variable.hcl' }, + { language: 'hcl', offset: 1, type: '' }, + { language: 'hcl', offset: 2, type: 'operator.hcl' }, + { language: 'hcl', offset: 3, type: '' }, + { language: 'hcl', offset: 4, type: 'number.float.hcl' }, + ], + ], + ], + ])('%s', (string, tokens) => { + expect(editor.tokenize(string, 'hcl')).toEqual(tokens); + }); +}); diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index 974c0715c06..8f7fcc25cf0 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -291,6 +291,20 @@ describe('IDE store file actions', () => { expect(store.state.openFiles[0].name).toBe(localFile.name); }); }); + + it('does not toggle loading if toggleLoading=false', () => { + expect(localFile.loading).toBe(false); + + return store + .dispatch('getFileData', { + path: localFile.path, + makeFileActive: false, + toggleLoading: false, + }) + .then(() => { + expect(localFile.loading).toBe(true); + }); + }); }); describe('Re-named success', () => { diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js index e24f08fa802..5ae87f5f9cd 100644 --- a/spec/frontend/ide/stores/getters_spec.js +++ b/spec/frontend/ide/stores/getters_spec.js @@ -449,16 +449,16 @@ describe('IDE store getters', () => { describe('getAvailableFileName', () => { it.each` path | newPath - ${'foo'} | ${'foo_1'} + ${'foo'} | ${'foo-1'} ${'foo__93.png'} | ${'foo__94.png'} - ${'foo/bar.png'} | ${'foo/bar_1.png'} + ${'foo/bar.png'} | ${'foo/bar-1.png'} ${'foo/bar--34.png'} | ${'foo/bar--35.png'} ${'foo/bar 2.png'} | ${'foo/bar 3.png'} ${'foo/bar-621.png'} | ${'foo/bar-622.png'} - ${'jquery.min.js'} | ${'jquery_1.min.js'} + ${'jquery.min.js'} | ${'jquery-1.min.js'} ${'my_spec_22.js.snap'} | ${'my_spec_23.js.snap'} - ${'subtitles5.mp4.srt'} | ${'subtitles_6.mp4.srt'} - ${'sample_file.mp3'} | ${'sample_file_1.mp3'} + ${'subtitles5.mp4.srt'} | ${'subtitles-6.mp4.srt'} + ${'sample-file.mp3'} | ${'sample-file-1.mp3'} ${'Screenshot 2020-05-26 at 10.53.08 PM.png'} | ${'Screenshot 2020-05-26 at 11.53.08 PM.png'} `('suffixes the path with a number if the path already exists', ({ path, newPath }) => { localState.entries[path] = file(); diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js index babc50e54f1..cfe2bddf76c 100644 --- a/spec/frontend/ide/stores/modules/commit/actions_spec.js +++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js @@ -76,59 +76,38 @@ describe('IDE commit module actions', () => { .then(done) .catch(done.fail); }); + }); - it('sets shouldCreateMR to true if "Create new MR" option is visible', done => { - Object.assign(store.state, { - shouldHideNewMrOption: false, - }); + describe('updateBranchName', () => { + let originalGon; - testAction( - actions.updateCommitAction, - {}, - store.state, - [ - { - type: mutationTypes.UPDATE_COMMIT_ACTION, - payload: { commitAction: expect.anything() }, - }, - { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: true }, - ], - [], - done, - ); + beforeEach(() => { + originalGon = window.gon; + window.gon = { current_username: 'johndoe' }; + + store.state.currentBranchId = 'master'; }); - it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => { - Object.assign(store.state, { - shouldHideNewMrOption: true, - }); + afterEach(() => { + window.gon = originalGon; + }); - testAction( - actions.updateCommitAction, - {}, - store.state, - [ - { - type: mutationTypes.UPDATE_COMMIT_ACTION, - payload: { commitAction: expect.anything() }, - }, - { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: false }, - ], - [], - done, - ); + it('updates store with new branch name', async () => { + await store.dispatch('commit/updateBranchName', 'branch-name'); + + expect(store.state.commit.newBranchName).toBe('branch-name'); }); }); - describe('updateBranchName', () => { - it('updates store with new branch name', done => { - store - .dispatch('commit/updateBranchName', 'branch-name') - .then(() => { - expect(store.state.commit.newBranchName).toBe('branch-name'); - }) - .then(done) - .catch(done.fail); + describe('addSuffixToBranchName', () => { + it('adds suffix to branchName', async () => { + jest.spyOn(Math, 'random').mockReturnValue(0.391352525); + + store.state.commit.newBranchName = 'branch-name'; + + await store.dispatch('commit/addSuffixToBranchName'); + + expect(store.state.commit.newBranchName).toBe('branch-name-39135'); }); }); @@ -318,13 +297,16 @@ describe('IDE commit module actions', () => { currentBranchId: 'master', projects: { abcproject: { + default_branch: 'master', web_url: 'webUrl', branches: { master: { + name: 'master', workingReference: '1', commit: { id: TEST_COMMIT_SHA, }, + can_push: true, }, }, userPermissions: { @@ -499,6 +481,16 @@ describe('IDE commit module actions', () => { .catch(done.fail); }); + it('does not redirect to merge request page if shouldCreateMR is checked, but branch is the default branch', async () => { + jest.spyOn(eventHub, '$on').mockImplementation(); + + store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + store.state.commit.shouldCreateMR = true; + + await store.dispatch('commit/commitChanges'); + expect(visitUrl).not.toHaveBeenCalled(); + }); + it('resets changed files before redirecting', () => { jest.spyOn(eventHub, '$on').mockImplementation(); diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js index b53e40be980..d303de6e9ef 100644 --- a/spec/frontend/ide/stores/mutations/file_spec.js +++ b/spec/frontend/ide/stores/mutations/file_spec.js @@ -39,20 +39,34 @@ describe('IDE store file mutations', () => { }); describe('TOGGLE_FILE_OPEN', () => { - beforeEach(() => { + it('adds into opened files', () => { mutations.TOGGLE_FILE_OPEN(localState, localFile.path); - }); - it('adds into opened files', () => { expect(localFile.opened).toBeTruthy(); expect(localState.openFiles.length).toBe(1); }); - it('removes from opened files', () => { + describe('if already open', () => { + it('removes from opened files', () => { + mutations.TOGGLE_FILE_OPEN(localState, localFile.path); + mutations.TOGGLE_FILE_OPEN(localState, localFile.path); + + expect(localFile.opened).toBeFalsy(); + expect(localState.openFiles.length).toBe(0); + }); + }); + + it.each` + entry | loading + ${{ opened: false }} | ${true} + ${{ opened: false, tempFile: true }} | ${false} + ${{ opened: true }} | ${false} + `('for state: $entry, sets loading=$loading', ({ entry, loading }) => { + Object.assign(localFile, entry); + mutations.TOGGLE_FILE_OPEN(localState, localFile.path); - expect(localFile.opened).toBeFalsy(); - expect(localState.openFiles.length).toBe(0); + expect(localFile.loading).toBe(loading); }); }); diff --git a/spec/frontend/ide/stores/utils_spec.js b/spec/frontend/ide/stores/utils_spec.js index d1eb4304c79..b185013050e 100644 --- a/spec/frontend/ide/stores/utils_spec.js +++ b/spec/frontend/ide/stores/utils_spec.js @@ -46,7 +46,7 @@ describe('Multi-file store utils', () => { path: 'added', tempFile: true, content: 'new file content', - rawPath: 'data:image/png;base64,abc', + rawPath: 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b', lastCommitSha: '123456789', }, { ...file('deletedFile'), path: 'deletedFile', deleted: true }, @@ -77,7 +77,8 @@ describe('Multi-file store utils', () => { { action: commitActionTypes.create, file_path: 'added', - content: 'new file content', + // atob("new file content") + content: 'bmV3IGZpbGUgY29udGVudA==', encoding: 'base64', last_commit_id: '123456789', previous_path: undefined, @@ -117,7 +118,7 @@ describe('Multi-file store utils', () => { path: 'added', tempFile: true, content: 'new file content', - rawPath: 'data:image/png;base64,abc', + rawPath: 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b', lastCommitSha: '123456789', }, ], @@ -148,7 +149,8 @@ describe('Multi-file store utils', () => { { action: commitActionTypes.create, file_path: 'added', - content: 'new file content', + // atob("new file content") + content: 'bmV3IGZpbGUgY29udGVudA==', encoding: 'base64', last_commit_id: '123456789', previous_path: undefined, diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js index 97dc8217ecc..6cd2128d356 100644 --- a/spec/frontend/ide/utils_spec.js +++ b/spec/frontend/ide/utils_spec.js @@ -9,6 +9,7 @@ import { getPathParents, getPathParent, readFileAsDataURL, + addNumericSuffix, } from '~/ide/utils'; describe('WebIDE utils', () => { @@ -291,4 +292,43 @@ describe('WebIDE utils', () => { }); }); }); + + /* + * hello-2425 -> hello-2425 + * hello.md -> hello-1.md + * hello_2.md -> hello_3.md + * hello_ -> hello_1 + * master-patch-22432 -> master-patch-22433 + * patch_332 -> patch_333 + */ + + describe('addNumericSuffix', () => { + it.each` + input | output + ${'hello'} | ${'hello-1'} + ${'hello2'} | ${'hello-3'} + ${'hello.md'} | ${'hello-1.md'} + ${'hello_2.md'} | ${'hello_3.md'} + ${'hello_'} | ${'hello_1'} + ${'master-patch-22432'} | ${'master-patch-22433'} + ${'patch_332'} | ${'patch_333'} + `('adds a numeric suffix to a given filename/branch name: $input', ({ input, output }) => { + expect(addNumericSuffix(input)).toBe(output); + }); + + it.each` + input | output + ${'hello'} | ${'hello-39135'} + ${'hello2'} | ${'hello-39135'} + ${'hello.md'} | ${'hello-39135.md'} + ${'hello_2.md'} | ${'hello_39135.md'} + ${'hello_'} | ${'hello_39135'} + ${'master-patch-22432'} | ${'master-patch-39135'} + ${'patch_332'} | ${'patch_39135'} + `('adds a random suffix if randomize=true is passed for name: $input', ({ input, output }) => { + jest.spyOn(Math, 'random').mockReturnValue(0.391352525); + + expect(addNumericSuffix(input, true)).toBe(output); + }); + }); }); -- cgit v1.2.3