diff options
Diffstat (limited to 'spec/frontend/ide')
17 files changed, 431 insertions, 223 deletions
diff --git a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js deleted file mode 100644 index 42e0a20bc7b..00000000000 --- a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { createStore } from '~/ide/stores'; -import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; -import { file } from '../../helpers'; -import { removeWhitespace } from '../../../helpers/text_helper'; - -describe('Multi-file editor commit sidebar list collapsed', () => { - let vm; - let store; - - beforeEach(() => { - store = createStore(); - - const Component = Vue.extend(listCollapsed); - - vm = createComponentWithStore(Component, store, { - files: [ - { - ...file('file1'), - tempFile: true, - }, - file('file2'), - ], - iconName: 'staged', - title: 'Staged', - }); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders added & modified files count', () => { - expect(removeWhitespace(vm.$el.textContent).trim()).toBe('1 1'); - }); - - describe('addedFilesLength', () => { - it('returns an length of temp files', () => { - expect(vm.addedFilesLength).toBe(1); - }); - }); - - describe('modifiedFilesLength', () => { - it('returns an length of modified files', () => { - expect(vm.modifiedFilesLength).toBe(1); - }); - }); - - describe('addedFilesIconClass', () => { - it('includes multi-file-addition when addedFiles is not empty', () => { - expect(vm.addedFilesIconClass).toContain('multi-file-addition'); - }); - - it('excludes multi-file-addition when addedFiles is empty', () => { - vm.files = []; - - expect(vm.addedFilesIconClass).not.toContain('multi-file-addition'); - }); - }); - - describe('modifiedFilesClass', () => { - it('includes multi-file-modified when addedFiles is not empty', () => { - expect(vm.modifiedFilesClass).toContain('multi-file-modified'); - }); - - it('excludes multi-file-modified when addedFiles is empty', () => { - vm.files = []; - - expect(vm.modifiedFilesClass).not.toContain('multi-file-modified'); - }); - }); -}); diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js index 2107ff96e95..636dfbf0b2a 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js @@ -16,7 +16,6 @@ describe('Multi-file editor commit sidebar list', () => { vm = createComponentWithStore(Component, store, { title: 'Staged', fileList: [], - iconName: 'staged', action: 'stageAllChanges', actionBtnText: 'stage all', actionBtnIcon: 'history', diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js index 86e4e8d8f89..72e9463945b 100644 --- a/spec/frontend/ide/components/ide_side_bar_spec.js +++ b/spec/frontend/ide/components/ide_side_bar_spec.js @@ -1,10 +1,12 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { GlSkeletonLoading } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; import { createStore } from '~/ide/stores'; 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 IdeReview from '~/ide/components/ide_review.vue'; import { leftSidebarViews } from '~/ide/constants'; import { projectData } from '../mock_data'; @@ -15,11 +17,12 @@ describe('IdeSidebar', () => { let wrapper; let store; - function createComponent() { + function createComponent({ view = leftSidebarViews.edit.name } = {}) { store = createStore(); store.state.currentProjectId = 'abcproject'; store.state.projects.abcproject = projectData; + store.state.currentActivityView = view; return mount(IdeSidebar, { store, @@ -48,22 +51,46 @@ describe('IdeSidebar', () => { expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3); }); - describe('activityBarComponent', () => { - it('renders tree component', () => { + describe('deferred rendering components', () => { + it('fetches components on demand', async () => { wrapper = createComponent(); expect(wrapper.find(IdeTree).exists()).toBe(true); - }); + expect(wrapper.find(IdeReview).exists()).toBe(false); + expect(wrapper.find(RepoCommitSection).exists()).toBe(false); - it('renders commit component', async () => { - wrapper = createComponent(); + store.state.currentActivityView = leftSidebarViews.review.name; + await waitForPromises(); + await wrapper.vm.$nextTick(); - store.state.currentActivityView = leftSidebarViews.commit.name; + expect(wrapper.find(IdeTree).exists()).toBe(false); + expect(wrapper.find(IdeReview).exists()).toBe(true); + expect(wrapper.find(RepoCommitSection).exists()).toBe(false); + store.state.currentActivityView = leftSidebarViews.commit.name; + await waitForPromises(); await wrapper.vm.$nextTick(); + expect(wrapper.find(IdeTree).exists()).toBe(false); + expect(wrapper.find(IdeReview).exists()).toBe(false); expect(wrapper.find(RepoCommitSection).exists()).toBe(true); }); + it.each` + view | tree | review | commit + ${leftSidebarViews.edit.name} | ${true} | ${false} | ${false} + ${leftSidebarViews.review.name} | ${false} | ${true} | ${false} + ${leftSidebarViews.commit.name} | ${false} | ${false} | ${true} + `('renders correct panels for $view', async ({ view, tree, review, commit } = {}) => { + wrapper = createComponent({ + view, + }); + await waitForPromises(); + await wrapper.vm.$nextTick(); + + expect(wrapper.find(IdeTree).exists()).toBe(tree); + expect(wrapper.find(IdeReview).exists()).toBe(review); + expect(wrapper.find(RepoCommitSection).exists()).toBe(commit); + }); }); it('keeps the current activity view components alive', async () => { @@ -72,7 +99,7 @@ describe('IdeSidebar', () => { const ideTreeComponent = wrapper.find(IdeTree).element; store.state.currentActivityView = leftSidebarViews.commit.name; - + await waitForPromises(); await wrapper.vm.$nextTick(); expect(wrapper.find(IdeTree).exists()).toBe(false); @@ -80,6 +107,7 @@ describe('IdeSidebar', () => { store.state.currentActivityView = leftSidebarViews.edit.name; + await waitForPromises(); await wrapper.vm.$nextTick(); // reference to the elements remains the same, meaning the components were kept alive diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js index a7b07a9f0e2..ff3852b6775 100644 --- a/spec/frontend/ide/components/ide_spec.js +++ b/spec/frontend/ide/components/ide_spec.js @@ -1,127 +1,165 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { createStore } from '~/ide/stores'; +import ErrorMessage from '~/ide/components/error_message.vue'; +import FindFile from '~/vue_shared/components/file_finder/index.vue'; +import CommitEditorHeader from '~/ide/components/commit_sidebar/editor_header.vue'; +import RepoTabs from '~/ide/components/repo_tabs.vue'; +import IdeStatusBar from '~/ide/components/ide_status_bar.vue'; +import RightPane from '~/ide/components/panes/right.vue'; +import NewModal from '~/ide/components/new_dropdown/modal.vue'; + import ide from '~/ide/components/ide.vue'; import { file } from '../helpers'; import { projectData } from '../mock_data'; -import extendStore from '~/ide/stores/extend'; - -let store; -function bootstrap(projData) { - store = createStore(); +const localVue = createLocalVue(); +localVue.use(Vuex); - extendStore(store, document.createElement('div')); +describe('WebIDE', () => { + const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; - const Component = Vue.extend(ide); + let wrapper; - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { ...projData }; - Vue.set(store.state.trees, 'abcproject/master', { - tree: [], - loading: false, - }); - - return createComponentWithStore(Component, store, { - emptyStateSvgPath: 'svg', - noChangesStateSvgPath: 'svg', - committedStateSvgPath: 'svg', - }); -} + function createComponent({ projData = emptyProjData, state = {} } = {}) { + const store = createStore(); -describe('ide component, empty repo', () => { - let vm; + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { ...projData }; + store.state.trees['abcproject/master'] = { + tree: [], + loading: false, + }; + Object.keys(state).forEach(key => { + store.state[key] = state[key]; + }); - beforeEach(() => { - const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; - vm = bootstrap(emptyProjData); - vm.$mount(); - }); + return shallowMount(ide, { + store, + localVue, + stubs: { + ErrorMessage, + GlButton, + GlLoadingIcon, + CommitEditorHeader, + RepoTabs, + IdeStatusBar, + FindFile, + RightPane, + NewModal, + }, + }); + } afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - it('renders "New file" button in empty repo', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).not.toBeNull(); - done(); + describe('ide component, empty repo', () => { + beforeEach(() => { + wrapper = createComponent({ + projData: { + empty_repo: true, + }, + }); }); - }); -}); - -describe('ide component, non-empty repo', () => { - let vm; - beforeEach(() => { - vm = bootstrap(projectData); - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); + it('renders "New file" button in empty repo', async () => { + expect(wrapper.find('[title="New file"]').exists()).toBe(true); + }); }); - it('shows error message when set', done => { - expect(vm.$el.querySelector('.gl-alert')).toBe(null); + describe('ide component, non-empty repo', () => { + describe('error message', () => { + it('does not show error message when it is not set', () => { + wrapper = createComponent({ + state: { + errorMessage: null, + }, + }); - vm.$store.state.errorMessage = { - text: 'error', - }; + expect(wrapper.find(ErrorMessage).exists()).toBe(false); + }); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.gl-alert')).not.toBe(null); + it('shows error message when set', () => { + wrapper = createComponent({ + state: { + errorMessage: { + text: 'error', + }, + }, + }); - done(); + expect(wrapper.find(ErrorMessage).exists()).toBe(true); + }); }); - }); - describe('onBeforeUnload', () => { - it('returns undefined when no staged files or changed files', () => { - expect(vm.onBeforeUnload()).toBe(undefined); - }); + describe('onBeforeUnload', () => { + it('returns undefined when no staged files or changed files', () => { + wrapper = createComponent(); + expect(wrapper.vm.onBeforeUnload()).toBe(undefined); + }); - it('returns warning text when their are changed files', () => { - vm.$store.state.changedFiles.push(file()); + it('returns warning text when their are changed files', () => { + wrapper = createComponent({ + state: { + changedFiles: [file()], + }, + }); - expect(vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); - }); + expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + }); - it('returns warning text when their are staged files', () => { - vm.$store.state.stagedFiles.push(file()); + it('returns warning text when their are staged files', () => { + wrapper = createComponent({ + state: { + stagedFiles: [file()], + }, + }); - expect(vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); - }); + expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + }); - it('updates event object', () => { - const event = {}; - vm.$store.state.stagedFiles.push(file()); + it('updates event object', () => { + const event = {}; + wrapper = createComponent({ + state: { + stagedFiles: [file()], + }, + }); - vm.onBeforeUnload(event); + wrapper.vm.onBeforeUnload(event); - expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?'); + expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?'); + }); }); - }); - describe('non-existent branch', () => { - it('does not render "New file" button for non-existent branch when repo is not empty', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); - done(); + describe('non-existent branch', () => { + it('does not render "New file" button for non-existent branch when repo is not empty', () => { + wrapper = createComponent({ + state: { + projects: {}, + }, + }); + + expect(wrapper.find('[title="New file"]').exists()).toBe(false); }); }); - }); - describe('branch with files', () => { - beforeEach(() => { - store.state.trees['abcproject/master'].tree = [file()]; - }); + describe('branch with files', () => { + beforeEach(() => { + wrapper = createComponent({ + projData: { + empty_repo: false, + }, + }); + }); - it('does not render "New file" button', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); - done(); + it('does not render "New file" button', () => { + expect(wrapper.find('[title="New file"]').exists()).toBe(false); }); }); }); diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index bb8165d1a52..02b5dc19bd8 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -6,17 +6,21 @@ import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync const TEST_FILE = { name: 'lorem.md', - editorRow: 3, - editorColumn: 23, - fileLanguage: 'markdown', content: 'abc\nndef', permalink: '/lorem.md', }; +const TEST_FILE_EDITOR = { + fileLanguage: 'markdown', + editorRow: 3, + editorColumn: 23, +}; +const TEST_EDITOR_POSITION = `${TEST_FILE_EDITOR.editorRow}:${TEST_FILE_EDITOR.editorColumn}`; const localVue = createLocalVue(); localVue.use(Vuex); describe('ide/components/ide_status_list', () => { + let activeFileEditor; let activeFile; let store; let wrapper; @@ -27,6 +31,14 @@ describe('ide/components/ide_status_list', () => { getters: { activeFile: () => activeFile, }, + modules: { + editor: { + namespaced: true, + getters: { + activeFileEditor: () => activeFileEditor, + }, + }, + }, }); wrapper = shallowMount(IdeStatusList, { @@ -38,6 +50,7 @@ describe('ide/components/ide_status_list', () => { beforeEach(() => { activeFile = TEST_FILE; + activeFileEditor = TEST_FILE_EDITOR; }); afterEach(() => { @@ -47,8 +60,6 @@ describe('ide/components/ide_status_list', () => { wrapper = null; }); - const getEditorPosition = file => `${file.editorRow}:${file.editorColumn}`; - describe('with regular file', () => { beforeEach(() => { createComponent(); @@ -65,11 +76,11 @@ describe('ide/components/ide_status_list', () => { }); it('shows file editor position', () => { - expect(wrapper.text()).toContain(getEditorPosition(TEST_FILE)); + expect(wrapper.text()).toContain(TEST_EDITOR_POSITION); }); it('shows file language', () => { - expect(wrapper.text()).toContain(TEST_FILE.fileLanguage); + expect(wrapper.text()).toContain(TEST_FILE_EDITOR.fileLanguage); }); }); @@ -81,7 +92,7 @@ describe('ide/components/ide_status_list', () => { }); it('does not show file editor position', () => { - expect(wrapper.text()).not.toContain(getEditorPosition(TEST_FILE)); + expect(wrapper.text()).not.toContain(TEST_EDITOR_POSITION); }); }); diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js index 7f083fa7c25..c1744fefe20 100644 --- a/spec/frontend/ide/components/pipelines/list_spec.js +++ b/spec/frontend/ide/components/pipelines/list_spec.js @@ -1,11 +1,10 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlTab } from '@gitlab/ui'; import { TEST_HOST } from 'helpers/test_constants'; import { pipelines } from 'jest/ide/mock_data'; import List from '~/ide/components/pipelines/list.vue'; import JobsList from '~/ide/components/jobs/list.vue'; -import Tab from '~/vue_shared/components/tabs/tab.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import IDEServices from '~/ide/services'; @@ -167,7 +166,7 @@ describe('IDE pipelines list', () => { createComponent({}, { ...withLatestPipelineState, stages, isLoadingJobs }); const jobProps = wrapper - .findAll(Tab) + .findAll(GlTab) .at(0) .find(JobsList) .props(); @@ -182,7 +181,7 @@ describe('IDE pipelines list', () => { createComponent({}, { ...withLatestPipelineState, isLoadingJobs }); const jobProps = wrapper - .findAll(Tab) + .findAll(GlTab) .at(1) .find(JobsList) .props(); diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index 9f4c9c1622a..71a4f08cfb4 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -55,7 +55,6 @@ describe('RepoEditor', () => { beforeEach(() => { const f = { ...file('file.txt'), - viewMode: FILE_VIEW_MODE_EDITOR, content: 'hello world', }; @@ -92,6 +91,8 @@ describe('RepoEditor', () => { }); const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder'); + const changeViewMode = viewMode => + store.dispatch('editor/updateFileEditor', { path: vm.file.path, data: { viewMode } }); describe('default', () => { beforeEach(() => { @@ -409,7 +410,7 @@ describe('RepoEditor', () => { describe('when files view mode is preview', () => { beforeEach(done => { jest.spyOn(vm.editor, 'updateDimensions').mockImplementation(); - vm.file.viewMode = FILE_VIEW_MODE_PREVIEW; + changeViewMode(FILE_VIEW_MODE_PREVIEW); vm.file.name = 'myfile.md'; vm.file.content = 'hello world'; @@ -423,7 +424,7 @@ describe('RepoEditor', () => { describe('when file view mode changes to editor', () => { it('should update dimensions', () => { - vm.file.viewMode = FILE_VIEW_MODE_EDITOR; + changeViewMode(FILE_VIEW_MODE_EDITOR); return vm.$nextTick().then(() => { expect(vm.editor.updateDimensions).toHaveBeenCalled(); diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js index f35726de27c..a44c8b4d5ee 100644 --- a/spec/frontend/ide/components/repo_tab_spec.js +++ b/spec/frontend/ide/components/repo_tab_spec.js @@ -100,6 +100,18 @@ describe('RepoTab', () => { expect(wrapper.find('.file-modified').exists()).toBe(true); }); + it.each` + tabProps | closeLabel + ${{}} | ${'Close foo.txt'} + ${{ changed: true }} | ${'foo.txt changed'} + `('close button has label ($closeLabel) with tab ($tabProps)', ({ tabProps, closeLabel }) => { + const tab = { ...file('foo.txt'), ...tabProps }; + + createComponent({ tab }); + + expect(wrapper.find('button').attributes('aria-label')).toBe(closeLabel); + }); + describe('locked file', () => { let f; @@ -122,9 +134,7 @@ describe('RepoTab', () => { }); it('renders a tooltip', () => { - expect(wrapper.find('span:nth-child(2)').attributes('data-original-title')).toContain( - 'Locked by testuser', - ); + expect(wrapper.find('span:nth-child(2)').attributes('title')).toBe('Locked by testuser'); }); }); diff --git a/spec/frontend/ide/components/terminal/empty_state_spec.js b/spec/frontend/ide/components/terminal/empty_state_spec.js index a3f2089608d..b62470f67b6 100644 --- a/spec/frontend/ide/components/terminal/empty_state_spec.js +++ b/spec/frontend/ide/components/terminal/empty_state_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue'; @@ -36,7 +36,7 @@ describe('IDE TerminalEmptyState', () => { const img = wrapper.find('.svg-content img'); expect(img.exists()).toBe(true); - expect(img.attributes('src')).toEqual(TEST_PATH); + expect(img.attributes('src')).toBe(TEST_PATH); }); it('when loading, shows loading icon', () => { @@ -71,24 +71,23 @@ describe('IDE TerminalEmptyState', () => { }, }); - button = wrapper.find('button'); + button = wrapper.find(GlButton); }); it('shows button', () => { - expect(button.text()).toEqual('Start Web Terminal'); - expect(button.attributes('disabled')).toBeFalsy(); + expect(button.text()).toBe('Start Web Terminal'); + expect(button.props('disabled')).toBe(false); }); it('emits start when button is clicked', () => { - expect(wrapper.emitted().start).toBeFalsy(); - - button.trigger('click'); + expect(wrapper.emitted().start).toBeUndefined(); + button.vm.$emit('click'); expect(wrapper.emitted().start).toHaveLength(1); }); it('shows help path link', () => { - expect(wrapper.find('a').attributes('href')).toEqual(TEST_HELP_PATH); + expect(wrapper.find('a').attributes('href')).toBe(TEST_HELP_PATH); }); }); @@ -101,7 +100,7 @@ describe('IDE TerminalEmptyState', () => { }, }); - expect(wrapper.find('button').attributes('disabled')).not.toBe(null); - expect(wrapper.find('.bs-callout').element.innerHTML).toEqual(TEST_HTML_MESSAGE); + expect(wrapper.find(GlButton).props('disabled')).toBe(true); + expect(wrapper.find(GlAlert).html()).toContain(TEST_HTML_MESSAGE); }); }); diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js index 0e85b523cbd..6b65dd96ef4 100644 --- a/spec/frontend/ide/helpers.js +++ b/spec/frontend/ide/helpers.js @@ -1,5 +1,6 @@ import * as pathUtils from 'path'; import { decorateData } from '~/ide/stores/utils'; +import { commitActionTypes } from '~/ide/constants'; export const file = (name = 'name', id = name, type = '', parent = null) => decorateData({ @@ -28,3 +29,17 @@ export const createEntriesFromPaths = paths => ...entries, }; }, {}); + +export const createTriggerChangeAction = payload => ({ + type: 'triggerFilesChange', + ...(payload ? { payload } : {}), +}); + +export const createTriggerRenamePayload = (path, newPath) => ({ + type: commitActionTypes.move, + path, + newPath, +}); + +export const createTriggerRenameAction = (path, newPath) => + createTriggerChangeAction(createTriggerRenamePayload(path, newPath)); diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index 8f7fcc25cf0..cc290fc526e 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -7,7 +7,7 @@ import * as types from '~/ide/stores/mutation_types'; import service from '~/ide/services'; import { createRouter } from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; -import { file } from '../../helpers'; +import { file, createTriggerRenameAction } from '../../helpers'; const ORIGINAL_CONTENT = 'original content'; const RELATIVE_URL_ROOT = '/gitlab'; @@ -785,13 +785,19 @@ describe('IDE store file actions', () => { }); describe('triggerFilesChange', () => { + const { payload: renamePayload } = createTriggerRenameAction('test', '123'); + beforeEach(() => { jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); }); - it('emits event that files have changed', () => { - return store.dispatch('triggerFilesChange').then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change'); + it.each` + args | payload + ${[]} | ${{}} + ${[renamePayload]} | ${renamePayload} + `('emits event that files have changed (args=$args)', ({ args, payload }) => { + return store.dispatch('triggerFilesChange', ...args).then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change', payload); }); }); }); diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js index ebf39df2f6f..04128c27e70 100644 --- a/spec/frontend/ide/stores/actions_spec.js +++ b/spec/frontend/ide/stores/actions_spec.js @@ -19,7 +19,7 @@ import { } from '~/ide/stores/actions'; import axios from '~/lib/utils/axios_utils'; import * as types from '~/ide/stores/mutation_types'; -import { file } from '../helpers'; +import { file, createTriggerRenameAction, createTriggerChangeAction } from '../helpers'; import testAction from '../../helpers/vuex_action_helper'; import eventHub from '~/ide/eventhub'; @@ -522,7 +522,7 @@ describe('Multi-file store actions', () => { 'path', store.state, [{ type: types.DELETE_ENTRY, payload: 'path' }], - [{ type: 'stageChange', payload: 'path' }, { type: 'triggerFilesChange' }], + [{ type: 'stageChange', payload: 'path' }, createTriggerChangeAction()], done, ); }); @@ -551,7 +551,7 @@ describe('Multi-file store actions', () => { [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }], [ { type: 'stageChange', payload: 'testFolder/entry-to-delete' }, - { type: 'triggerFilesChange' }, + createTriggerChangeAction(), ], done, ); @@ -614,7 +614,7 @@ describe('Multi-file store actions', () => { testEntry.path, store.state, [{ type: types.DELETE_ENTRY, payload: testEntry.path }], - [{ type: 'stageChange', payload: testEntry.path }, { type: 'triggerFilesChange' }], + [{ type: 'stageChange', payload: testEntry.path }, createTriggerChangeAction()], done, ); }); @@ -754,7 +754,7 @@ describe('Multi-file store actions', () => { payload: origEntry, }, ], - [{ type: 'triggerFilesChange' }], + [createTriggerRenameAction('renamed', 'orig')], done, ); }); @@ -767,7 +767,7 @@ describe('Multi-file store actions', () => { { path: 'orig', name: 'renamed' }, store.state, [expect.objectContaining({ type: types.RENAME_ENTRY })], - [{ type: 'triggerFilesChange' }], + [createTriggerRenameAction('orig', 'renamed')], done, ); }); diff --git a/spec/frontend/ide/stores/modules/editor/actions_spec.js b/spec/frontend/ide/stores/modules/editor/actions_spec.js new file mode 100644 index 00000000000..6a420ac32de --- /dev/null +++ b/spec/frontend/ide/stores/modules/editor/actions_spec.js @@ -0,0 +1,36 @@ +import testAction from 'helpers/vuex_action_helper'; +import * as types from '~/ide/stores/modules/editor/mutation_types'; +import * as actions from '~/ide/stores/modules/editor/actions'; +import { createTriggerRenamePayload } from '../../../helpers'; + +describe('~/ide/stores/modules/editor/actions', () => { + describe('updateFileEditor', () => { + it('commits with payload', () => { + const payload = {}; + + testAction(actions.updateFileEditor, payload, {}, [ + { type: types.UPDATE_FILE_EDITOR, payload }, + ]); + }); + }); + + describe('removeFileEditor', () => { + it('commits with payload', () => { + const payload = 'path/to/file.txt'; + + testAction(actions.removeFileEditor, payload, {}, [ + { type: types.REMOVE_FILE_EDITOR, payload }, + ]); + }); + }); + + describe('renameFileEditor', () => { + it('commits with payload', () => { + const payload = createTriggerRenamePayload('test', 'test123'); + + testAction(actions.renameFileEditor, payload, {}, [ + { type: types.RENAME_FILE_EDITOR, payload }, + ]); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/editor/getters_spec.js b/spec/frontend/ide/stores/modules/editor/getters_spec.js new file mode 100644 index 00000000000..55e1e31f66f --- /dev/null +++ b/spec/frontend/ide/stores/modules/editor/getters_spec.js @@ -0,0 +1,31 @@ +import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils'; +import * as getters from '~/ide/stores/modules/editor/getters'; + +const TEST_PATH = 'test/path.md'; +const TEST_FILE_EDITOR = { + ...createDefaultFileEditor(), + editorRow: 7, + editorColumn: 8, + fileLanguage: 'markdown', +}; + +describe('~/ide/stores/modules/editor/getters', () => { + describe('activeFileEditor', () => { + it.each` + activeFile | fileEditors | expected + ${null} | ${{}} | ${null} + ${{}} | ${{}} | ${createDefaultFileEditor()} + ${{ path: TEST_PATH }} | ${{}} | ${createDefaultFileEditor()} + ${{ path: TEST_PATH }} | ${{ bogus: createDefaultFileEditor(), [TEST_PATH]: TEST_FILE_EDITOR }} | ${TEST_FILE_EDITOR} + `( + 'with activeFile=$activeFile and fileEditors=$fileEditors', + ({ activeFile, fileEditors, expected }) => { + const rootGetters = { activeFile }; + const state = { fileEditors }; + const result = getters.activeFileEditor(state, {}, {}, rootGetters); + + expect(result).toEqual(expected); + }, + ); + }); +}); diff --git a/spec/frontend/ide/stores/modules/editor/mutations_spec.js b/spec/frontend/ide/stores/modules/editor/mutations_spec.js new file mode 100644 index 00000000000..e4b330b3174 --- /dev/null +++ b/spec/frontend/ide/stores/modules/editor/mutations_spec.js @@ -0,0 +1,78 @@ +import { createDefaultFileEditor } from '~/ide/stores/modules/editor/utils'; +import * as types from '~/ide/stores/modules/editor/mutation_types'; +import mutations from '~/ide/stores/modules/editor/mutations'; +import { createTriggerRenamePayload } from '../../../helpers'; + +const TEST_PATH = 'test/path.md'; + +describe('~/ide/stores/modules/editor/mutations', () => { + describe(types.UPDATE_FILE_EDITOR, () => { + it('with path that does not exist, should initialize with default values', () => { + const state = { fileEditors: {} }; + const data = { fileLanguage: 'markdown' }; + + mutations[types.UPDATE_FILE_EDITOR](state, { path: TEST_PATH, data }); + + expect(state.fileEditors).toEqual({ + [TEST_PATH]: { + ...createDefaultFileEditor(), + ...data, + }, + }); + }); + + it('with existing path, should overwrite values', () => { + const state = { + fileEditors: { + foo: {}, + [TEST_PATH]: { ...createDefaultFileEditor(), editorRow: 7, editorColumn: 7 }, + }, + }; + + mutations[types.UPDATE_FILE_EDITOR](state, { + path: TEST_PATH, + data: { fileLanguage: 'markdown' }, + }); + + expect(state).toEqual({ + fileEditors: { + foo: {}, + [TEST_PATH]: { + ...createDefaultFileEditor(), + editorRow: 7, + editorColumn: 7, + fileLanguage: 'markdown', + }, + }, + }); + }); + }); + + describe(types.REMOVE_FILE_EDITOR, () => { + it.each` + fileEditors | path | expected + ${{}} | ${'does/not/exist.txt'} | ${{}} + ${{ foo: {}, [TEST_PATH]: {} }} | ${TEST_PATH} | ${{ foo: {} }} + `('removes file $path', ({ fileEditors, path, expected }) => { + const state = { fileEditors }; + + mutations[types.REMOVE_FILE_EDITOR](state, path); + + expect(state).toEqual({ fileEditors: expected }); + }); + }); + + describe(types.RENAME_FILE_EDITOR, () => { + it.each` + fileEditors | payload | expected + ${{ foo: {} }} | ${createTriggerRenamePayload('does/not/exist', 'abc')} | ${{ foo: {} }} + ${{ foo: { a: 1 }, bar: {} }} | ${createTriggerRenamePayload('foo', 'abc/def')} | ${{ 'abc/def': { a: 1 }, bar: {} }} + `('renames fileEditor at $payload', ({ fileEditors, payload, expected }) => { + const state = { fileEditors }; + + mutations[types.RENAME_FILE_EDITOR](state, payload); + + expect(state).toEqual({ fileEditors: expected }); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/editor/setup_spec.js b/spec/frontend/ide/stores/modules/editor/setup_spec.js new file mode 100644 index 00000000000..71b5d7590c5 --- /dev/null +++ b/spec/frontend/ide/stores/modules/editor/setup_spec.js @@ -0,0 +1,44 @@ +import Vuex from 'vuex'; +import eventHub from '~/ide/eventhub'; +import { createStoreOptions } from '~/ide/stores'; +import { setupFileEditorsSync } from '~/ide/stores/modules/editor/setup'; +import { createTriggerRenamePayload } from '../../../helpers'; + +describe('~/ide/stores/modules/editor/setup', () => { + let store; + + beforeEach(() => { + store = new Vuex.Store(createStoreOptions()); + store.state.entries = { + foo: {}, + bar: {}, + }; + store.state.editor.fileEditors = { + foo: {}, + bizz: {}, + }; + + setupFileEditorsSync(store); + }); + + it('when files change is emitted, removes unused fileEditors', () => { + eventHub.$emit('ide.files.change'); + + expect(store.state.entries).toEqual({ + foo: {}, + bar: {}, + }); + expect(store.state.editor.fileEditors).toEqual({ + foo: {}, + }); + }); + + it('when files rename is emitted, renames fileEditor', () => { + eventHub.$emit('ide.files.change', createTriggerRenamePayload('foo', 'foo_new')); + + expect(store.state.editor.fileEditors).toEqual({ + foo_new: {}, + bizz: {}, + }); + }); +}); diff --git a/spec/frontend/ide/stores/mutations/file_spec.js b/spec/frontend/ide/stores/mutations/file_spec.js index d303de6e9ef..fd39cf21635 100644 --- a/spec/frontend/ide/stores/mutations/file_spec.js +++ b/spec/frontend/ide/stores/mutations/file_spec.js @@ -1,6 +1,5 @@ import mutations from '~/ide/stores/mutations/file'; import { createStore } from '~/ide/stores'; -import { FILE_VIEW_MODE_PREVIEW } from '~/ide/constants'; import { file } from '../../helpers'; describe('IDE store file mutations', () => { @@ -532,17 +531,6 @@ describe('IDE store file mutations', () => { }); }); - describe('SET_FILE_VIEWMODE', () => { - it('updates file view mode', () => { - mutations.SET_FILE_VIEWMODE(localState, { - file: localFile, - viewMode: FILE_VIEW_MODE_PREVIEW, - }); - - expect(localFile.viewMode).toBe(FILE_VIEW_MODE_PREVIEW); - }); - }); - describe('ADD_PENDING_TAB', () => { beforeEach(() => { const f = { ...file('openFile'), path: 'openFile', active: true, opened: true }; |