Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 18:40:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 18:40:28 +0300
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /spec/frontend/ide
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff)
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'spec/frontend/ide')
-rw-r--r--spec/frontend/ide/components/commit_sidebar/empty_state_spec.js26
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js56
-rw-r--r--spec/frontend/ide/components/commit_sidebar/success_message_spec.js30
-rw-r--r--spec/frontend/ide/components/ide_spec.js3
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js78
-rw-r--r--spec/frontend/ide/components/nav_dropdown_button_spec.js73
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js470
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js12
-rw-r--r--spec/frontend/ide/ide_router_spec.js3
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js3
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js3
-rw-r--r--spec/frontend/ide/stores/actions/tree_spec.js3
-rw-r--r--spec/frontend/ide/stores/actions_spec.js3
13 files changed, 478 insertions, 285 deletions
diff --git a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
index 4f81c0aa5d3..7c48c0e6f95 100644
--- a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
@@ -1,29 +1,21 @@
-import Vue from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import emptyState from '~/ide/components/commit_sidebar/empty_state.vue';
+import { shallowMount } from '@vue/test-utils';
+import EmptyState from '~/ide/components/commit_sidebar/empty_state.vue';
import { createStore } from '~/ide/stores';
-describe('IDE commit panel empty state', () => {
- let vm;
- let store;
+describe('IDE commit panel EmptyState component', () => {
+ let wrapper;
beforeEach(() => {
- store = createStore();
-
- const Component = Vue.extend(emptyState);
-
- Vue.set(store.state, 'noChangesStateSvgPath', 'no-changes');
-
- vm = createComponentWithStore(Component, store);
-
- vm.$mount();
+ const store = createStore();
+ store.state.noChangesStateSvgPath = 'no-changes';
+ wrapper = shallowMount(EmptyState, { store });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders no changes text when last commit message is empty', () => {
- expect(vm.$el.textContent).toContain('No changes');
+ expect(wrapper.find('h4').text()).toBe('No changes');
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index 1d42512c9ee..81c81fc0a9f 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -1,51 +1,47 @@
-import Vue, { nextTick } from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
-import { createStore } from '~/ide/stores';
+import { shallowMount } from '@vue/test-utils';
+import CommitSidebarList from '~/ide/components/commit_sidebar/list.vue';
+import ListItem from '~/ide/components/commit_sidebar/list_item.vue';
import { file } from '../../helpers';
describe('Multi-file editor commit sidebar list', () => {
- let store;
- let vm;
-
- beforeEach(() => {
- store = createStore();
-
- const Component = Vue.extend(commitSidebarList);
-
- vm = createComponentWithStore(Component, store, {
- title: 'Staged',
- fileList: [],
- action: 'stageAllChanges',
- actionBtnText: 'stage all',
- actionBtnIcon: 'history',
- activeFileKey: 'staged-testing',
- keyPrefix: 'staged',
+ let wrapper;
+
+ const mountComponent = ({ fileList }) =>
+ shallowMount(CommitSidebarList, {
+ propsData: {
+ title: 'Staged',
+ fileList,
+ action: 'stageAllChanges',
+ actionBtnText: 'stage all',
+ actionBtnIcon: 'history',
+ activeFileKey: 'staged-testing',
+ keyPrefix: 'staged',
+ },
});
- vm.$mount();
- });
-
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('with a list of files', () => {
beforeEach(async () => {
const f = file('file name');
f.changed = true;
- vm.fileList.push(f);
- await nextTick();
+ wrapper = mountComponent({ fileList: [f] });
});
it('renders list', () => {
- expect(vm.$el.querySelectorAll('.multi-file-commit-list > li').length).toBe(1);
+ expect(wrapper.findAllComponents(ListItem)).toHaveLength(1);
});
});
- describe('empty files array', () => {
- it('renders no changes text when empty', () => {
- expect(vm.$el.textContent).toContain('No changes');
+ describe('with empty files array', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({ fileList: [] });
+ });
+
+ it('renders no changes text ', () => {
+ expect(wrapper.text()).toContain('No changes');
});
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
index 52e35bdbb73..63d51953915 100644
--- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
@@ -1,32 +1,22 @@
-import Vue, { nextTick } from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
-import successMessage from '~/ide/components/commit_sidebar/success_message.vue';
+import { shallowMount } from '@vue/test-utils';
+import SuccessMessage from '~/ide/components/commit_sidebar/success_message.vue';
import { createStore } from '~/ide/stores';
describe('IDE commit panel successful commit state', () => {
- let vm;
- let store;
+ let wrapper;
beforeEach(() => {
- store = createStore();
-
- const Component = Vue.extend(successMessage);
-
- vm = createComponentWithStore(Component, store, {
- committedStateSvgPath: 'committed-state',
- });
-
- vm.$mount();
+ const store = createStore();
+ store.state.committedStateSvgPath = 'committed-state';
+ store.state.lastCommitMsg = 'testing commit message';
+ wrapper = shallowMount(SuccessMessage, { store });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
- it('renders last commit message when it exists', async () => {
- vm.$store.state.lastCommitMsg = 'testing commit message';
-
- await nextTick();
- expect(vm.$el.textContent).toContain('testing commit message');
+ it('renders last commit message when it exists', () => {
+ expect(wrapper.text()).toContain('testing commit message');
});
});
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index 37b42001a80..9172c69b10e 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue';
import ErrorMessage from '~/ide/components/error_message.vue';
import Ide from '~/ide/components/ide.vue';
@@ -40,6 +41,8 @@ describe('WebIDE', () => {
const findAlert = () => wrapper.findComponent(CannotPushCodeAlert);
beforeEach(() => {
+ stubPerformanceWebAPI();
+
store = createStore();
});
diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js
index a85c52f5e86..0f61aa80e53 100644
--- a/spec/frontend/ide/components/ide_tree_list_spec.js
+++ b/spec/frontend/ide/components/ide_tree_list_spec.js
@@ -1,82 +1,72 @@
-import Vue, { nextTick } from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { GlSkeletonLoader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import IdeTreeList from '~/ide/components/ide_tree_list.vue';
import { createStore } from '~/ide/stores';
+import FileTree from '~/vue_shared/components/file_tree.vue';
import { file } from '../helpers';
import { projectData } from '../mock_data';
-describe('IDE tree list', () => {
- const Component = Vue.extend(IdeTreeList);
- const normalBranchTree = [file('fileName')];
- const emptyBranchTree = [];
- let vm;
- let store;
+describe('IdeTreeList component', () => {
+ let wrapper;
- const bootstrapWithTree = (tree = normalBranchTree) => {
+ const mountComponent = ({ tree, loading = false } = {}) => {
+ const store = createStore();
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'main';
store.state.projects.abcproject = { ...projectData };
- Vue.set(store.state.trees, 'abcproject/main', {
- tree,
- loading: false,
- });
+ Vue.set(store.state.trees, 'abcproject/main', { tree, loading });
- vm = createComponentWithStore(Component, store, {
- viewerType: 'edit',
+ wrapper = shallowMount(IdeTreeList, {
+ propsData: {
+ viewerType: 'edit',
+ },
+ store,
});
};
- beforeEach(() => {
- store = createStore();
- });
-
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('normal branch', () => {
- beforeEach(() => {
- bootstrapWithTree();
-
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
-
- vm.$mount();
- });
+ const tree = [file('fileName')];
it('emits tree-ready event', () => {
- expect(vm.$emit).toHaveBeenCalledTimes(1);
- expect(vm.$emit).toHaveBeenCalledWith('tree-ready');
+ mountComponent({ tree });
+
+ expect(wrapper.emitted('tree-ready')).toEqual([[]]);
});
- it('renders loading indicator', async () => {
- store.state.trees['abcproject/main'].loading = true;
+ it('renders loading indicator', () => {
+ mountComponent({ tree, loading: true });
- await nextTick();
- expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
- expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
+ expect(wrapper.findAllComponents(GlSkeletonLoader)).toHaveLength(3);
});
it('renders list of files', () => {
- expect(vm.$el.textContent).toContain('fileName');
+ mountComponent({ tree });
+
+ expect(wrapper.findAllComponents(FileTree)).toHaveLength(1);
+ expect(wrapper.findComponent(FileTree).props('file')).toEqual(tree[0]);
});
});
describe('empty-branch state', () => {
beforeEach(() => {
- bootstrapWithTree(emptyBranchTree);
-
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
+ mountComponent({ tree: [] });
+ });
- vm.$mount();
+ it('emits tree-ready event', () => {
+ expect(wrapper.emitted('tree-ready')).toEqual([[]]);
});
- it('still emits tree-ready event', () => {
- expect(vm.$emit).toHaveBeenCalledWith('tree-ready');
+ it('does not render files', () => {
+ expect(wrapper.findAllComponents(FileTree)).toHaveLength(0);
});
- it('does not load files if the branch is empty', () => {
- expect(vm.$el.textContent).not.toContain('fileName');
- expect(vm.$el.textContent).toContain('No files');
+ it('renders empty state text', () => {
+ expect(wrapper.text()).toBe('No files');
});
});
});
diff --git a/spec/frontend/ide/components/nav_dropdown_button_spec.js b/spec/frontend/ide/components/nav_dropdown_button_spec.js
index 1c14685df68..8eebcdd9e08 100644
--- a/spec/frontend/ide/components/nav_dropdown_button_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_button_spec.js
@@ -1,81 +1,74 @@
-import Vue, { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
-import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
import { createStore } from '~/ide/stores';
+import { __ } from '~/locale';
-describe('NavDropdown', () => {
+describe('NavDropdownButton component', () => {
const TEST_BRANCH_ID = 'lorem-ipsum-dolar';
const TEST_MR_ID = '12345';
- let store;
- let vm;
-
- beforeEach(() => {
- store = createStore();
- });
+ let wrapper;
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
- const createComponent = (props = {}) => {
- vm = mountComponentWithStore(Vue.extend(NavDropdownButton), { props, store });
- vm.$mount();
+ const createComponent = ({ props = {}, state = {} } = {}) => {
+ const store = createStore();
+ store.replaceState(state);
+ wrapper = mountExtended(NavDropdownButton, { propsData: props, store });
};
- const findIcon = (name) => vm.$el.querySelector(`[data-testid="${name}-icon"]`);
- const findMRIcon = () => findIcon('merge-request');
- const findBranchIcon = () => findIcon('branch');
+ const findMRIcon = () => wrapper.findByLabelText(__('Merge request'));
+ const findBranchIcon = () => wrapper.findByLabelText(__('Current Branch'));
describe('normal', () => {
- beforeEach(() => {
+ it('renders empty placeholders, if state is falsey', () => {
createComponent();
- });
- it('renders empty placeholders, if state is falsey', () => {
- expect(trimText(vm.$el.textContent)).toEqual('- -');
+ expect(trimText(wrapper.text())).toBe('- -');
});
- it('renders branch name, if state has currentBranchId', async () => {
- vm.$store.state.currentBranchId = TEST_BRANCH_ID;
+ it('renders branch name, if state has currentBranchId', () => {
+ createComponent({ state: { currentBranchId: TEST_BRANCH_ID } });
- await nextTick();
- expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} -`);
+ expect(trimText(wrapper.text())).toBe(`${TEST_BRANCH_ID} -`);
});
- it('renders mr id, if state has currentMergeRequestId', async () => {
- vm.$store.state.currentMergeRequestId = TEST_MR_ID;
+ it('renders mr id, if state has currentMergeRequestId', () => {
+ createComponent({ state: { currentMergeRequestId: TEST_MR_ID } });
- await nextTick();
- expect(trimText(vm.$el.textContent)).toEqual(`- !${TEST_MR_ID}`);
+ expect(trimText(wrapper.text())).toBe(`- !${TEST_MR_ID}`);
});
- it('renders branch and mr, if state has both', async () => {
- vm.$store.state.currentBranchId = TEST_BRANCH_ID;
- vm.$store.state.currentMergeRequestId = TEST_MR_ID;
+ it('renders branch and mr, if state has both', () => {
+ createComponent({
+ state: { currentBranchId: TEST_BRANCH_ID, currentMergeRequestId: TEST_MR_ID },
+ });
- await nextTick();
- expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} !${TEST_MR_ID}`);
+ expect(trimText(wrapper.text())).toBe(`${TEST_BRANCH_ID} !${TEST_MR_ID}`);
});
it('shows icons', () => {
- expect(findBranchIcon()).toBeTruthy();
- expect(findMRIcon()).toBeTruthy();
+ createComponent();
+
+ expect(findBranchIcon().exists()).toBe(true);
+ expect(findMRIcon().exists()).toBe(true);
});
});
- describe('with showMergeRequests false', () => {
+ describe('when showMergeRequests=false', () => {
beforeEach(() => {
- createComponent({ showMergeRequests: false });
+ createComponent({ props: { showMergeRequests: false } });
});
it('shows single empty placeholder, if state is falsey', () => {
- expect(trimText(vm.$el.textContent)).toEqual('-');
+ expect(trimText(wrapper.text())).toBe('-');
});
it('shows only branch icon', () => {
- expect(findBranchIcon()).toBeTruthy();
- expect(findMRIcon()).toBe(null);
+ expect(findBranchIcon().exists()).toBe(true);
+ expect(findMRIcon().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index e8635444801..68cc08d2ebc 100644
--- a/spec/frontend/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,209 +1,419 @@
-import Vue, { nextTick } from 'vue';
-import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
+import { GlButton, GlModal } from '@gitlab/ui';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
-import modal from '~/ide/components/new_dropdown/modal.vue';
+import Modal from '~/ide/components/new_dropdown/modal.vue';
import { createStore } from '~/ide/stores';
+import { stubComponent } from 'helpers/stub_component';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createEntriesFromPaths } from '../../helpers';
jest.mock('~/flash');
+const NEW_NAME = 'babar';
+
describe('new file modal component', () => {
- const Component = Vue.extend(modal);
- let vm;
+ const showModal = jest.fn();
+ const toggleModal = jest.fn();
+
+ let store;
+ let wrapper;
+
+ const findForm = () => wrapper.findByTestId('file-name-form');
+ const findGlModal = () => wrapper.findComponent(GlModal);
+ const findInput = () => wrapper.findByTestId('file-name-field');
+ const findTemplateButtons = () => wrapper.findAllComponents(GlButton);
+ const findTemplateButtonsModel = () =>
+ findTemplateButtons().wrappers.map((x) => ({
+ text: x.text(),
+ variant: x.props('variant'),
+ category: x.props('category'),
+ }));
+
+ const open = (type, path) => {
+ // TODO: This component can not be passed props
+ // We have to interact with the open() method?
+ wrapper.vm.open(type, path);
+ };
+ const triggerSubmitForm = () => {
+ findForm().trigger('submit');
+ };
+ const triggerSubmitModal = () => {
+ findGlModal().vm.$emit('primary');
+ };
+ const triggerCancel = () => {
+ findGlModal().vm.$emit('cancel');
+ };
+
+ const mountComponent = () => {
+ const GlModalStub = stubComponent(GlModal);
+ jest.spyOn(GlModalStub.methods, 'show').mockImplementation(showModal);
+ jest.spyOn(GlModalStub.methods, 'toggle').mockImplementation(toggleModal);
+
+ wrapper = shallowMountExtended(Modal, {
+ store,
+ stubs: {
+ GlModal: GlModalStub,
+ },
+ // We need to attach to document for "focus" to work
+ attachTo: document.body,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+
+ Object.assign(
+ store.state.entries,
+ createEntriesFromPaths([
+ 'README.md',
+ 'src',
+ 'src/deleted.js',
+ 'src/parent_dir',
+ 'src/parent_dir/foo.js',
+ ]),
+ );
+ Object.assign(store.state.entries['src/deleted.js'], { deleted: true });
+
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ });
afterEach(() => {
- vm.$destroy();
+ store = null;
+ wrapper.destroy();
+ document.body.innerHTML = '';
});
- describe.each`
- entryType | modalTitle | btnTitle | showsFileTemplates
- ${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false}
- ${'blob'} | ${'Create new file'} | ${'Create file'} | ${true}
- `('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => {
+ describe('default', () => {
beforeEach(async () => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store).$mount();
- vm.open(entryType);
- vm.name = 'testing';
+ mountComponent();
+ // Not necessarily needed, but used to ensure that nothing extra is happening after the tick
await nextTick();
});
- afterEach(() => {
- vm.close();
+ it('renders modal', () => {
+ expect(findGlModal().props()).toMatchObject({
+ actionCancel: {
+ attributes: [{ variant: 'default' }],
+ text: 'Cancel',
+ },
+ actionPrimary: {
+ attributes: [{ variant: 'confirm' }],
+ text: 'Create file',
+ },
+ actionSecondary: null,
+ size: 'lg',
+ modalId: 'ide-new-entry',
+ title: 'Create new file',
+ });
});
- it(`sets modal title as ${entryType}`, () => {
- expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
+ it('renders name label', () => {
+ expect(wrapper.find('label').text()).toBe('Name');
});
- it(`sets button label as ${entryType}`, () => {
- expect(document.querySelector('.btn-confirm').textContent.trim()).toBe(btnTitle);
+ it('renders template buttons', () => {
+ const actual = findTemplateButtonsModel();
+
+ expect(actual.length).toBeGreaterThan(0);
+ expect(actual).toEqual(
+ store.getters['fileTemplates/templateTypes'].map((template) => ({
+ category: 'secondary',
+ text: template.name,
+ variant: 'dashed',
+ })),
+ );
});
- it(`sets form label as ${entryType}`, () => {
- expect(document.querySelector('.label-bold').textContent.trim()).toBe('Name');
+ // These negative ".not.toHaveBeenCalled" assertions complement the positive "toHaveBeenCalled"
+ // assertions that show up later in this spec. Without these, we're not guaranteed the "act"
+ // actually caused the change in behavior.
+ it('does not dispatch actions by default', () => {
+ expect(store.dispatch).not.toHaveBeenCalled();
});
- it(`shows file templates: ${showsFileTemplates}`, () => {
- const templateFilesEl = document.querySelector('.file-templates');
- expect(Boolean(templateFilesEl)).toBe(showsFileTemplates);
+ it('does not trigger modal by default', () => {
+ expect(showModal).not.toHaveBeenCalled();
+ expect(toggleModal).not.toHaveBeenCalled();
});
- });
- describe('rename entry', () => {
- beforeEach(() => {
- const store = createStore();
- store.state.entries = {
- 'test-path': {
- name: 'test',
- type: 'blob',
- path: 'test-path',
- },
- };
-
- vm = createComponentWithStore(Component, store).$mount();
+ it('does not focus input by default', () => {
+ expect(document.activeElement).toBe(document.body);
});
+ });
- it.each`
- entryType | modalTitle | btnTitle
- ${'tree'} | ${'Rename folder'} | ${'Rename folder'}
- ${'blob'} | ${'Rename file'} | ${'Rename file'}
- `(
- 'renders title and button for renaming $entryType',
- async ({ entryType, modalTitle, btnTitle }) => {
- vm.$store.state.entries['test-path'].type = entryType;
- vm.open('rename', 'test-path');
+ describe.each`
+ entryType | path | modalTitle | btnTitle | showsFileTemplates | inputValue | inputPlaceholder
+ ${'tree'} | ${''} | ${'Create new directory'} | ${'Create directory'} | ${false} | ${''} | ${'dir/'}
+ ${'blob'} | ${''} | ${'Create new file'} | ${'Create file'} | ${true} | ${''} | ${'dir/file_name'}
+ ${'blob'} | ${'foo/bar'} | ${'Create new file'} | ${'Create file'} | ${true} | ${'foo/bar/'} | ${'dir/file_name'}
+ `(
+ 'when opened as $entryType with path "$path"',
+ ({
+ entryType,
+ path,
+ modalTitle,
+ btnTitle,
+ showsFileTemplates,
+ inputValue,
+ inputPlaceholder,
+ }) => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open(entryType, path);
await nextTick();
- expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
- expect(document.querySelector('.btn-confirm').textContent.trim()).toBe(btnTitle);
- },
- );
+ });
- describe('entryName', () => {
- it('returns entries name', () => {
- vm.open('rename', 'test-path');
+ it('sets modal props', () => {
+ expect(findGlModal().props()).toMatchObject({
+ title: modalTitle,
+ actionPrimary: {
+ attributes: [{ variant: 'confirm' }],
+ text: btnTitle,
+ },
+ });
+ });
- expect(vm.entryName).toBe('test-path');
+ it('sets input attributes', () => {
+ expect(findInput().element.value).toBe(inputValue);
+ expect(findInput().attributes('placeholder')).toBe(inputPlaceholder);
});
- it('does not reset entryName to its old value if empty', () => {
- vm.entryName = 'hello';
- vm.entryName = '';
+ it(`shows file templates: ${showsFileTemplates}`, () => {
+ const actual = findTemplateButtonsModel().length > 0;
- expect(vm.entryName).toBe('');
+ expect(actual).toBe(showsFileTemplates);
+ });
+
+ it('shows modal', () => {
+ expect(showModal).toHaveBeenCalled();
});
- });
- describe('open', () => {
- it('sets entryName to path provided if modalType is rename', () => {
- vm.open('rename', 'test-path');
+ it('focus on input', () => {
+ expect(document.activeElement).toBe(findInput().element);
+ });
+
+ it('resets when canceled', async () => {
+ triggerCancel();
+
+ await nextTick();
- expect(vm.entryName).toBe('test-path');
+ // Resets input value
+ expect(findInput().element.value).toBe('');
+ // Resets to blob mode
+ expect(findGlModal().props('title')).toBe('Create new file');
});
+ },
+ );
+
+ describe.each`
+ modalType | name | expectedName
+ ${'blob'} | ${'foo/bar.js'} | ${'foo/bar.js'}
+ ${'blob'} | ${'foo /bar.js'} | ${'foo/bar.js'}
+ ${'tree'} | ${'foo/dir'} | ${'foo/dir'}
+ ${'tree'} | ${'foo /dir'} | ${'foo/dir'}
+ `('when submitting as $modalType with "$name"', ({ modalType, name, expectedName }) => {
+ describe('when using the modal primary button', () => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open(modalType, '');
+ await nextTick();
- it("appends '/' to the path if modalType isn't rename", () => {
- vm.open('blob', 'test-path');
+ findInput().setValue(name);
+ triggerSubmitModal();
+ });
- expect(vm.entryName).toBe('test-path/');
+ it('triggers createTempEntry action', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: expectedName,
+ type: modalType,
+ });
});
+ });
+
+ describe('when triggering form submit (pressing enter)', () => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open(modalType, '');
+ await nextTick();
- it('leaves entryName blank if no path is provided', () => {
- vm.open('blob');
+ findInput().setValue(name);
+ triggerSubmitForm();
+ });
- expect(vm.entryName).toBe('');
+ it('triggers createTempEntry action', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: expectedName,
+ type: modalType,
+ });
});
});
});
- describe('createFromTemplate', () => {
- let store;
+ describe('when creating from template type', () => {
+ beforeEach(async () => {
+ mountComponent();
- beforeEach(() => {
- store = createStore();
- store.state.entries = {
- 'test-path/test': {
- name: 'test',
- deleted: false,
- },
- };
+ open('blob', 'some_dir');
- vm = createComponentWithStore(Component, store).$mount();
- vm.open('blob');
+ await nextTick();
- jest.spyOn(vm, 'createTempEntry').mockImplementation();
+ // Set input, then trigger button
+ findInput().setValue('some_dir/foo.js');
+ findTemplateButtons().at(1).vm.$emit('click');
});
- it.each`
- entryName | newFilePath
- ${''} | ${'.gitignore'}
- ${'README.md'} | ${'.gitignore'}
- ${'test-path/test/'} | ${'test-path/test/.gitignore'}
- ${'test-path/test'} | ${'test-path/.gitignore'}
- ${'test-path/test/abc.md'} | ${'test-path/test/.gitignore'}
- `(
- 'creates a new file with the given template name in appropriate directory for path: $path',
- ({ entryName, newFilePath }) => {
- vm.entryName = entryName;
+ it('triggers createTempEntry action', () => {
+ const { name: expectedName } = store.getters['fileTemplates/templateTypes'][1];
- vm.createFromTemplate({ name: '.gitignore' });
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: `some_dir/${expectedName}`,
+ type: 'blob',
+ });
+ });
- expect(vm.createTempEntry).toHaveBeenCalledWith({
- name: newFilePath,
- type: 'blob',
- });
- },
- );
+ it('toggles modal', () => {
+ expect(toggleModal).toHaveBeenCalled();
+ });
});
- describe('submitForm', () => {
- let store;
+ describe.each`
+ origPath | title | inputValue | inputSelectionStart
+ ${'src/parent_dir'} | ${'Rename folder'} | ${'src/parent_dir'} | ${'src/'.length}
+ ${'README.md'} | ${'Rename file'} | ${'README.md'} | ${0}
+ `('when renaming for $origPath', ({ origPath, title, inputValue, inputSelectionStart }) => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open('rename', origPath);
+
+ await nextTick();
+ });
- beforeEach(() => {
- store = createStore();
- store.state.entries = {
- 'test-path/test': {
- name: 'test',
- deleted: false,
+ it('sets modal props for renaming', () => {
+ expect(findGlModal().props()).toMatchObject({
+ title,
+ actionPrimary: {
+ attributes: [{ variant: 'confirm' }],
+ text: title,
},
- };
+ });
+ });
+
+ it('sets input value', () => {
+ expect(findInput().element.value).toBe(inputValue);
+ });
- vm = createComponentWithStore(Component, store).$mount();
+ it(`does not show file templates`, () => {
+ expect(findTemplateButtonsModel()).toHaveLength(0);
});
- it('throws an error when target entry exists', () => {
- vm.open('rename', 'test-path/test');
+ it('shows modal when renaming', () => {
+ expect(showModal).toHaveBeenCalled();
+ });
- expect(createFlash).not.toHaveBeenCalled();
+ it('focus on input when renaming', () => {
+ expect(document.activeElement).toBe(findInput().element);
+ });
+
+ it('selects name part of the input', () => {
+ expect(findInput().element.selectionStart).toBe(inputSelectionStart);
+ expect(findInput().element.selectionEnd).toBe(origPath.length);
+ });
+
+ describe('when renames is submitted successfully', () => {
+ describe('when using the modal primary button', () => {
+ beforeEach(() => {
+ findInput().setValue(NEW_NAME);
+ triggerSubmitModal();
+ });
+
+ it('dispatches renameEntry event', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
+ path: origPath,
+ parentPath: '',
+ name: NEW_NAME,
+ });
+ });
+
+ it('does not trigger flash', () => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
- vm.submitForm();
+ describe('when triggering form submit (pressing enter)', () => {
+ beforeEach(() => {
+ findInput().setValue(NEW_NAME);
+ triggerSubmitForm();
+ });
+ it('dispatches renameEntry event', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
+ path: origPath,
+ parentPath: '',
+ name: NEW_NAME,
+ });
+ });
+
+ it('does not trigger flash', () => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('when renaming and file already exists', () => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open('rename', 'src/parent_dir');
+
+ await nextTick();
+
+ // Set to something that already exists!
+ findInput().setValue('src');
+ triggerSubmitModal();
+ });
+
+ it('creates flash', () => {
expect(createFlash).toHaveBeenCalledWith({
- message: 'The name "test-path/test" is already taken in this directory.',
+ message: 'The name "src" is already taken in this directory.',
fadeTransition: false,
addBodyClass: true,
});
});
- it('does not throw error when target entry does not exist', () => {
- jest.spyOn(vm, 'renameEntry').mockImplementation();
+ it('does not dispatch event', () => {
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+ });
- vm.open('rename', 'test-path/test');
- vm.entryName = 'test-path/test2';
- vm.submitForm();
+ describe('when renaming and file has been deleted', () => {
+ beforeEach(async () => {
+ mountComponent();
- expect(createFlash).not.toHaveBeenCalled();
- });
+ open('rename', 'src/parent_dir/foo.js');
- it('removes leading/trailing found in the new name', () => {
- vm.open('rename', 'test-path/test');
+ await nextTick();
- vm.entryName = 'test-path /test';
+ findInput().setValue('src/deleted.js');
+ triggerSubmitModal();
+ });
- vm.submitForm();
+ it('does not create flash', () => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
- expect(vm.entryName).toBe('test-path/test');
+ it('dispatches event', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
+ path: 'src/parent_dir/foo.js',
+ name: 'deleted.js',
+ parentPath: 'src',
+ });
});
});
});
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index b44651481e9..7a0bcda1b7a 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -1,3 +1,4 @@
+import { GlTab } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { editor as monacoEditor, Range } from 'monaco-editor';
import Vue, { nextTick } from 'vue';
@@ -5,6 +6,7 @@ import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import '~/behaviors/markdown/render_gfm';
import waitForPromises from 'helpers/wait_for_promises';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import { exampleConfigs, exampleFiles } from 'jest/ide/lib/editorconfig/mock_data';
import { EDITOR_CODE_INSTANCE_FN, EDITOR_DIFF_INSTANCE_FN } from '~/editor/constants';
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
@@ -125,10 +127,12 @@ describe('RepoEditor', () => {
};
const findEditor = () => wrapper.find('[data-testid="editor-container"]');
- const findTabs = () => wrapper.findAll('.ide-mode-tabs .nav-links li');
+ const findTabs = () => wrapper.findAllComponents(GlTab);
const findPreviewTab = () => wrapper.find('[data-testid="preview-tab"]');
beforeEach(() => {
+ stubPerformanceWebAPI();
+
createInstanceSpy = jest.spyOn(SourceEditor.prototype, EDITOR_CODE_INSTANCE_FN);
createDiffInstanceSpy = jest.spyOn(SourceEditor.prototype, EDITOR_DIFF_INSTANCE_FN);
createModelSpy = jest.spyOn(monacoEditor, 'createModel');
@@ -201,12 +205,12 @@ describe('RepoEditor', () => {
const tabs = findTabs();
expect(tabs).toHaveLength(2);
- expect(tabs.at(0).text()).toBe('Edit');
- expect(tabs.at(1).text()).toBe('Preview Markdown');
+ expect(tabs.at(0).element.dataset.testid).toBe('edit-tab');
+ expect(tabs.at(1).element.dataset.testid).toBe('preview-tab');
});
it('renders markdown for tempFile', async () => {
- findPreviewTab().trigger('click');
+ findPreviewTab().vm.$emit('click');
await waitForPromises();
expect(wrapper.find(ContentViewer).html()).toContain(dummyFile.text.content);
});
diff --git a/spec/frontend/ide/ide_router_spec.js b/spec/frontend/ide/ide_router_spec.js
index cd10812f8ea..adbdba1b11e 100644
--- a/spec/frontend/ide/ide_router_spec.js
+++ b/spec/frontend/ide/ide_router_spec.js
@@ -1,4 +1,5 @@
import waitForPromises from 'helpers/wait_for_promises';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import { createRouter } from '~/ide/ide_router';
import { createStore } from '~/ide/stores';
@@ -12,6 +13,8 @@ describe('IDE router', () => {
let router;
beforeEach(() => {
+ stubPerformanceWebAPI();
+
window.history.replaceState({}, '', '/');
store = createStore();
router = createRouter(store, DEFAULT_BRANCH);
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 45d1beea3f8..6c1dee1e5ca 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -7,6 +7,7 @@ import { createStore } from '~/ide/stores';
import * as actions from '~/ide/stores/actions/file';
import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import { file, createTriggerRenameAction, createTriggerUpdatePayload } from '../../helpers';
const ORIGINAL_CONTENT = 'original content';
@@ -19,6 +20,8 @@ describe('IDE store file actions', () => {
let router;
beforeEach(() => {
+ stubPerformanceWebAPI();
+
mock = new MockAdapter(axios);
originalGon = window.gon;
window.gon = {
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index 5592e2664c4..abc3ba5b0a2 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { range } from 'lodash';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
import createFlash from '~/flash';
@@ -35,6 +36,8 @@ describe('IDE store merge request actions', () => {
let mock;
beforeEach(() => {
+ stubPerformanceWebAPI();
+
store = createStore();
mock = new MockAdapter(axios);
diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js
index fc44cbb21ae..d43393875eb 100644
--- a/spec/frontend/ide/stores/actions/tree_spec.js
+++ b/spec/frontend/ide/stores/actions/tree_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
import { createRouter } from '~/ide/ide_router';
@@ -24,6 +25,8 @@ describe('Multi-file store tree actions', () => {
};
beforeEach(() => {
+ stubPerformanceWebAPI();
+
store = createStore();
router = createRouter(store);
jest.spyOn(router, 'push').mockImplementation();
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index 3889c4f11c3..f6d54491d77 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { stubPerformanceWebAPI } from 'helpers/performance';
import testAction from 'helpers/vuex_action_helper';
import eventHub from '~/ide/eventhub';
import { createRouter } from '~/ide/ide_router';
@@ -34,6 +35,8 @@ describe('Multi-file store actions', () => {
let router;
beforeEach(() => {
+ stubPerformanceWebAPI();
+
store = createStore();
router = createRouter(store);