diff options
Diffstat (limited to 'spec/frontend')
-rw-r--r-- | spec/frontend/autosave_spec.js | 11 | ||||
-rw-r--r-- | spec/frontend/cycle_analytics/stage_nav_item_spec.js | 177 | ||||
-rw-r--r-- | spec/frontend/lib/utils/url_utility_spec.js | 6 | ||||
-rw-r--r-- | spec/frontend/mocks/mocks_helper_spec.js | 4 | ||||
-rw-r--r-- | spec/frontend/notes/components/discussion_keyboard_navigator_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/vue_shared/components/changed_file_icon_spec.js | 123 | ||||
-rw-r--r-- | spec/frontend/wikis_spec.js | 74 |
7 files changed, 417 insertions, 5 deletions
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js index 4d9c8f96d62..33d402388c9 100644 --- a/spec/frontend/autosave_spec.js +++ b/spec/frontend/autosave_spec.js @@ -63,12 +63,15 @@ describe('Autosave', () => { expect(field.trigger).toHaveBeenCalled(); }); - it('triggers native event', done => { - autosave.field.get(0).addEventListener('change', () => { - done(); - }); + it('triggers native event', () => { + const fieldElement = autosave.field.get(0); + const eventHandler = jest.fn(); + fieldElement.addEventListener('change', eventHandler); Autosave.prototype.restore.call(autosave); + + expect(eventHandler).toHaveBeenCalledTimes(1); + fieldElement.removeEventListener('change', eventHandler); }); }); diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js new file mode 100644 index 00000000000..ff079082ca7 --- /dev/null +++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js @@ -0,0 +1,177 @@ +import { mount, shallowMount } from '@vue/test-utils'; +import StageNavItem from '~/cycle_analytics/components/stage_nav_item.vue'; + +describe('StageNavItem', () => { + let wrapper = null; + const title = 'Cool stage'; + const value = '1 day'; + + function createComponent(props, shallow = true) { + const func = shallow ? shallowMount : mount; + return func(StageNavItem, { + propsData: { + canEdit: false, + isActive: false, + isUserAllowed: false, + isDefaultStage: true, + title, + value, + ...props, + }, + }); + } + + function hasStageName() { + const stageName = wrapper.find('.stage-name'); + expect(stageName.exists()).toBe(true); + expect(stageName.text()).toEqual(title); + } + + it('renders stage name', () => { + wrapper = createComponent({ isUserAllowed: true }); + hasStageName(); + wrapper.destroy(); + }); + + describe('User has access', () => { + describe('with a value', () => { + beforeEach(() => { + wrapper = createComponent({ isUserAllowed: true }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + it('renders the value for median value', () => { + expect(wrapper.find('.stage-empty').exists()).toBe(false); + expect(wrapper.find('.not-available').exists()).toBe(false); + expect(wrapper.find('.stage-median').text()).toEqual(value); + }); + }); + + describe('without a value', () => { + beforeEach(() => { + wrapper = createComponent({ isUserAllowed: true, value: null }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('has the stage-empty class', () => { + expect(wrapper.find('.stage-empty').exists()).toBe(true); + }); + + it('renders Not enough data for the median value', () => { + expect(wrapper.find('.stage-median').text()).toEqual('Not enough data'); + }); + }); + }); + + describe('is active', () => { + beforeEach(() => { + wrapper = createComponent({ isActive: true }, false); + }); + + afterEach(() => { + wrapper.destroy(); + }); + it('has the active class', () => { + expect(wrapper.find('.stage-nav-item').classes('active')).toBe(true); + }); + }); + + describe('is not active', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + it('emits the `select` event when clicked', () => { + expect(wrapper.emitted().select).toBeUndefined(); + wrapper.trigger('click'); + expect(wrapper.emitted().select.length).toBe(1); + }); + }); + + describe('User does not have access', () => { + beforeEach(() => { + wrapper = createComponent({ isUserAllowed: false }, false); + }); + + afterEach(() => { + wrapper.destroy(); + }); + it('renders stage name', () => { + hasStageName(); + }); + + it('has class not-available', () => { + expect(wrapper.find('.stage-empty').exists()).toBe(false); + expect(wrapper.find('.not-available').exists()).toBe(true); + }); + + it('renders Not available for the median value', () => { + expect(wrapper.find('.stage-median').text()).toBe('Not available'); + }); + it('does not render options menu', () => { + expect(wrapper.find('.more-actions-toggle').exists()).toBe(false); + }); + }); + + describe('User can edit stages', () => { + beforeEach(() => { + wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false); + }); + + afterEach(() => { + wrapper.destroy(); + }); + it('renders stage name', () => { + hasStageName(); + }); + + it('renders options menu', () => { + expect(wrapper.find('.more-actions-toggle').exists()).toBe(true); + }); + + describe('Default stages', () => { + beforeEach(() => { + wrapper = createComponent( + { canEdit: true, isUserAllowed: true, isDefaultStage: true }, + false, + ); + }); + it('can hide the stage', () => { + expect(wrapper.text()).toContain('Hide stage'); + }); + it('can not edit the stage', () => { + expect(wrapper.text()).not.toContain('Edit stage'); + }); + it('can not remove the stage', () => { + expect(wrapper.text()).not.toContain('Remove stage'); + }); + }); + + describe('Custom stages', () => { + beforeEach(() => { + wrapper = createComponent( + { canEdit: true, isUserAllowed: true, isDefaultStage: false }, + false, + ); + }); + it('can edit the stage', () => { + expect(wrapper.text()).toContain('Edit stage'); + }); + it('can remove the stage', () => { + expect(wrapper.text()).toContain('Remove stage'); + }); + + it('can not hide the stage', () => { + expect(wrapper.text()).not.toContain('Hide stage'); + }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index a986bc49f28..b0bdd924921 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -94,6 +94,12 @@ describe('URL utility', () => { it('adds and updates encoded params', () => { expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag'); }); + + it('treats "+" as "%20"', () => { + expect(urlUtils.mergeUrlParams({ ref: 'bogus' }, '?a=lorem+ipsum&ref=charlie')).toBe( + '?a=lorem%20ipsum&ref=bogus', + ); + }); }); describe('removeParams', () => { diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js index 34be110a7e3..b8bb02c2f43 100644 --- a/spec/frontend/mocks/mocks_helper_spec.js +++ b/spec/frontend/mocks/mocks_helper_spec.js @@ -46,7 +46,9 @@ describe('mocks_helper.js', () => { readdir.sync.mockReturnValue([]); setupManualMocks(); - readdir.mock.calls.forEach(call => { + const readdirSpy = readdir.sync; + expect(readdirSpy).toHaveBeenCalled(); + readdirSpy.mock.calls.forEach(call => { expect(call[1].deep).toBeLessThan(100); }); }); diff --git a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js index 6d50713999d..8881bedf3cc 100644 --- a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js +++ b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js @@ -74,4 +74,31 @@ describe('notes/components/discussion_keyboard_navigator', () => { expect(wrapper.vm.currentDiscussionId).toEqual(expectedPrevId); }); }); + + describe('on destroy', () => { + beforeEach(() => { + jest.spyOn(Mousetrap, 'unbind'); + + createComponent(); + + wrapper.destroy(); + }); + + it('unbinds keys', () => { + expect(Mousetrap.unbind).toHaveBeenCalledWith('n'); + expect(Mousetrap.unbind).toHaveBeenCalledWith('p'); + }); + + it('does not call jumpToNextDiscussion when pressing `n`', () => { + Mousetrap.trigger('n'); + + expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled(); + }); + + it('does not call jumpToNextDiscussion when pressing `p`', () => { + Mousetrap.trigger('p'); + + expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js new file mode 100644 index 00000000000..806602877ef --- /dev/null +++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js @@ -0,0 +1,123 @@ +import { shallowMount } from '@vue/test-utils'; +import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +const changedFile = () => ({ changed: true }); +const stagedFile = () => ({ changed: false, staged: true }); +const changedAndStagedFile = () => ({ changed: true, staged: true }); +const newFile = () => ({ changed: true, tempFile: true }); +const unchangedFile = () => ({ changed: false, tempFile: false, staged: false, deleted: false }); + +describe('Changed file icon', () => { + let wrapper; + + const factory = (props = {}) => { + wrapper = shallowMount(ChangedFileIcon, { + propsData: { + file: changedFile(), + showTooltip: true, + ...props, + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findIcon = () => wrapper.find(Icon); + const findIconName = () => findIcon().props('name'); + const findIconClasses = () => + findIcon() + .props('cssClasses') + .split(' '); + const findTooltipText = () => wrapper.attributes('data-original-title'); + + it('with isCentered true, adds center class', () => { + factory({ + isCentered: true, + }); + + expect(wrapper.classes('ml-auto')).toBe(true); + }); + + it('with isCentered false, does not center', () => { + factory({ + isCentered: false, + }); + + expect(wrapper.classes('ml-auto')).toBe(false); + }); + + it('with showTooltip false, does not show tooltip', () => { + factory({ + showTooltip: false, + }); + + expect(findTooltipText()).toBeFalsy(); + }); + + describe.each` + file | iconName | tooltipText | desc + ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'} + ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'} + ${changedAndStagedFile()} | ${'file-modified'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'} + ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'} + `('$desc', ({ file, iconName, tooltipText }) => { + beforeEach(() => { + factory({ file }); + }); + + it('renders icon', () => { + expect(findIconName()).toBe(iconName); + expect(findIconClasses()).toContain(iconName); + }); + + it('renders tooltip text', () => { + expect(findTooltipText()).toBe(tooltipText); + }); + }); + + describe('with file unchanged', () => { + beforeEach(() => { + factory({ + file: unchangedFile(), + }); + }); + + it('does not show icon', () => { + expect(findIcon().exists()).toBe(false); + }); + + it('does not have tooltip text', () => { + expect(findTooltipText()).toBe(''); + }); + }); + + it('with size set, sets icon size', () => { + const size = 8; + + factory({ + file: changedFile(), + size, + }); + + expect(findIcon().props('size')).toBe(size); + }); + + // NOTE: It looks like 'showStagedIcon' behavior is backwards to what the name suggests + // https://gitlab.com/gitlab-org/gitlab-ce/issues/66071 + it.each` + showStagedIcon | iconName | desc + ${false} | ${'file-modified-solid'} | ${'with showStagedIcon false, renders staged icon'} + ${true} | ${'file-modified'} | ${'with showStagedIcon true, renders regular icon'} + `('$desc', ({ showStagedIcon, iconName }) => { + factory({ + file: stagedFile(), + showStagedIcon, + }); + + expect(findIconName()).toEqual(iconName); + }); +}); diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js new file mode 100644 index 00000000000..b2475488d97 --- /dev/null +++ b/spec/frontend/wikis_spec.js @@ -0,0 +1,74 @@ +import Wikis from '~/pages/projects/wikis/wikis'; +import { setHTMLFixture } from './helpers/fixtures'; + +describe('Wikis', () => { + describe('setting the commit message when the title changes', () => { + const editFormHtmlFixture = args => `<form class="wiki-form ${ + args.newPage ? 'js-new-wiki-page' : '' + }"> + <input type="text" id="wiki_title" value="My title" /> + <input type="text" id="wiki_message" /> + </form>`; + + let wikis; + let titleInput; + let messageInput; + + describe('when the wiki page is being created', () => { + const formHtmlFixture = editFormHtmlFixture({ newPage: true }); + + beforeEach(() => { + setHTMLFixture(formHtmlFixture); + + titleInput = document.getElementById('wiki_title'); + messageInput = document.getElementById('wiki_message'); + wikis = new Wikis(); + }); + + it('binds an event listener to the title input', () => { + wikis.handleWikiTitleChange = jest.fn(); + + titleInput.dispatchEvent(new Event('keyup')); + + expect(wikis.handleWikiTitleChange).toHaveBeenCalled(); + }); + + it('sets the commit message when title changes', () => { + titleInput.value = 'My title'; + messageInput.value = ''; + + titleInput.dispatchEvent(new Event('keyup')); + + expect(messageInput.value).toEqual('Create My title'); + }); + + it('replaces hyphens with spaces', () => { + titleInput.value = 'my-hyphenated-title'; + titleInput.dispatchEvent(new Event('keyup')); + + expect(messageInput.value).toEqual('Create my hyphenated title'); + }); + }); + + describe('when the wiki page is being updated', () => { + const formHtmlFixture = editFormHtmlFixture({ newPage: false }); + + beforeEach(() => { + setHTMLFixture(formHtmlFixture); + + titleInput = document.getElementById('wiki_title'); + messageInput = document.getElementById('wiki_message'); + wikis = new Wikis(); + }); + + it('sets the commit message when title changes, prefixing with "Update"', () => { + titleInput.value = 'My title'; + messageInput.value = ''; + + titleInput.dispatchEvent(new Event('keyup')); + + expect(messageInput.value).toEqual('Update My title'); + }); + }); + }); +}); |