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-05 21:08:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-05 21:08:43 +0300
commite129eff88309eca18f3902afd710e2e07393fe45 (patch)
tree2dd9399fdcfdee719d51e63cd821adc58165ccb3 /spec/frontend/ide
parent205b6baf2677879c35968d2b659225b58e8a1227 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/ide')
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js424
1 files changed, 294 insertions, 130 deletions
diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index e8635444801..8c72a37ec84 100644
--- a/spec/frontend/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,209 +1,373 @@
-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 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 triggerSubmit = () => {
+ 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);
});
- });
- describe('open', () => {
- it('sets entryName to path provided if modalType is rename', () => {
- vm.open('rename', 'test-path');
+ it('shows modal', () => {
+ expect(showModal).toHaveBeenCalled();
+ });
- expect(vm.entryName).toBe('test-path');
+ it('focus on input', () => {
+ expect(document.activeElement).toBe(findInput().element);
});
- it("appends '/' to the path if modalType isn't rename", () => {
- vm.open('blob', 'test-path');
+ 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 }) => {
+ beforeEach(async () => {
+ mountComponent();
+
+ open(modalType, '');
+ await nextTick();
- it('leaves entryName blank if no path is provided', () => {
- vm.open('blob');
+ findInput().setValue(name);
+ triggerSubmit();
+ });
- 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,
},
- };
+ });
+ });
- vm = createComponentWithStore(Component, store).$mount();
+ it('sets input value', () => {
+ expect(findInput().element.value).toBe(inputValue);
});
- it('throws an error when target entry exists', () => {
- vm.open('rename', 'test-path/test');
+ it(`does not show file templates`, () => {
+ expect(findTemplateButtonsModel()).toHaveLength(0);
+ });
- expect(createFlash).not.toHaveBeenCalled();
+ it('shows modal when renaming', () => {
+ expect(showModal).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', () => {
+ beforeEach(() => {
+ findInput().setValue(NEW_NAME);
+ triggerSubmit();
+ });
+
+ it('dispatches renameEntry event', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
+ path: origPath,
+ parentPath: '',
+ name: NEW_NAME,
+ });
+ });
- vm.submitForm();
+ 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');
+ triggerSubmit();
+ });
+ 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');
+ triggerSubmit();
+ });
- 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',
+ });
});
});
});