diff options
Diffstat (limited to 'spec/javascripts')
49 files changed, 1774 insertions, 1140 deletions
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ea7753c7a1d..68ad5f66676 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -3,6 +3,8 @@ import Cookies from 'js-cookie'; import AwardsHandler from '~/awards_handler'; +require('~/lib/utils/common_utils'); + (function() { var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu; @@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler'; loadFixtures('issues/issue_with_comment.html.raw'); awardsHandler = new AwardsHandler; spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { - return function(url, emoji, cb) { + return function(button, url, emoji, cb) { return cb(); }; })(this)); @@ -63,7 +65,7 @@ import AwardsHandler from '~/awards_handler'; $emojiMenu = $('.emoji-menu'); expect($emojiMenu.length).toBe(1); expect($emojiMenu.hasClass('is-visible')).toBe(true); - expect($emojiMenu.find('#emoji_search').length).toBe(1); + expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1); return expect($('.js-awards-block.current').length).toBe(1); }); }); @@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler'; return expect($emojiButton.next('.js-counter').text()).toBe('4'); }); }); + describe('::userAuthored', function() { + it('should update tooltip to user authored title', function() { + var $thumbsUpEmoji, $votesBlock; + $votesBlock = $('.js-awards-block').eq(0); + $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); + $thumbsUpEmoji.attr('data-title', 'sam'); + awardsHandler.userAuthored($thumbsUpEmoji); + return expect($thumbsUpEmoji.data("original-title")).toBe("You cannot vote on your own issue, MR and note"); + }); + it('should restore tooltip back to initial vote list', function() { + var $thumbsUpEmoji, $votesBlock; + jasmine.clock().install(); + $votesBlock = $('.js-awards-block').eq(0); + $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); + $thumbsUpEmoji.attr('data-title', 'sam'); + awardsHandler.userAuthored($thumbsUpEmoji); + jasmine.clock().tick(2801); + jasmine.clock().uninstall(); + return expect($thumbsUpEmoji.data("original-title")).toBe("sam"); + }); + }); describe('::getAwardUrl', function() { return it('returns the url for request', function() { return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); @@ -194,16 +217,35 @@ import AwardsHandler from '~/awards_handler'; return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); }); }); - describe('search', function() { - return it('should filter the emoji', function(done) { + describe('::searchEmojis', () => { + it('should filter the emoji', function(done) { return openAndWaitForEmojiMenu() .then(() => { expect($('[data-name=angel]').is(':visible')).toBe(true); expect($('[data-name=anger]').is(':visible')).toBe(true); - $('#emoji_search').val('ali').trigger('input'); + awardsHandler.searchEmojis('ali'); expect($('[data-name=angel]').is(':visible')).toBe(false); expect($('[data-name=anger]').is(':visible')).toBe(false); expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe('ali'); + }) + .then(done) + .catch((err) => { + done.fail(`Failed to open and build emoji menu: ${err.message}`); + }); + }); + it('should clear the search when searching for nothing', function(done) { + return openAndWaitForEmojiMenu() + .then(() => { + awardsHandler.searchEmojis('ali'); + expect($('[data-name=angel]').is(':visible')).toBe(false); + expect($('[data-name=anger]').is(':visible')).toBe(false); + expect($('[data-name=alien]').is(':visible')).toBe(true); + awardsHandler.searchEmojis(''); + expect($('[data-name=angel]').is(':visible')).toBe(true); + expect($('[data-name=anger]').is(':visible')).toBe(true); + expect($('[data-name=alien]').is(':visible')).toBe(true); + expect($('.js-emoji-menu-search').val()).toBe(''); }) .then(done) .catch((err) => { @@ -211,6 +253,7 @@ import AwardsHandler from '~/awards_handler'; }); }); }); + describe('emoji menu', function() { const emojiSelector = '[data-name="sunglasses"]'; const openEmojiMenuAndAddEmoji = function() { diff --git a/spec/javascripts/blob/blob_fork_suggestion_spec.js b/spec/javascripts/blob/blob_fork_suggestion_spec.js new file mode 100644 index 00000000000..d0d64d75957 --- /dev/null +++ b/spec/javascripts/blob/blob_fork_suggestion_spec.js @@ -0,0 +1,37 @@ +import BlobForkSuggestion from '~/blob/blob_fork_suggestion'; + +describe('BlobForkSuggestion', () => { + let blobForkSuggestion; + + const openButtons = [document.createElement('div')]; + const forkButtons = [document.createElement('a')]; + const cancelButtons = [document.createElement('div')]; + const suggestionSections = [document.createElement('div')]; + const actionTextPieces = [document.createElement('div')]; + + beforeEach(() => { + blobForkSuggestion = new BlobForkSuggestion({ + openButtons, + forkButtons, + cancelButtons, + suggestionSections, + actionTextPieces, + }); + }); + + afterEach(() => { + blobForkSuggestion.destroy(); + }); + + it('showSuggestionSection', () => { + blobForkSuggestion.showSuggestionSection('/foo', 'foo'); + expect(suggestionSections[0].classList.contains('hidden')).toEqual(false); + expect(forkButtons[0].getAttribute('href')).toEqual('/foo'); + expect(actionTextPieces[0].textContent).toEqual('foo'); + }); + + it('hideSuggestionSection', () => { + blobForkSuggestion.hideSuggestionSection(); + expect(suggestionSections[0].classList.contains('hidden')).toEqual(true); + }); +}); diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 0e4431548c4..79f40559817 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-new */ +/* eslint-disable no-new, promise/catch-or-return */ import JSZip from 'jszip'; import SketchLoader from '~/blob/sketch'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index a9d4c6ef76f..24a2da9f6b6 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -107,4 +107,44 @@ describe('List model', () => { expect(gl.boardService.moveIssue) .toHaveBeenCalledWith(issue.id, list.id, listDup.id, undefined, undefined); }); + + describe('page number', () => { + beforeEach(() => { + spyOn(list, 'getIssues'); + }); + + it('increase page number if current issue count is more than the page size', () => { + for (let i = 0; i < 30; i += 1) { + list.issues.push(new ListIssue({ + title: 'Testing', + iid: _.random(10000) + i, + confidential: false, + labels: [list.label] + })); + } + list.issuesSize = 50; + + expect(list.issues.length).toBe(30); + + list.nextPage(); + + expect(list.page).toBe(2); + expect(list.getIssues).toHaveBeenCalled(); + }); + + it('does not increase page number if issue count is less than the page size', () => { + list.issues.push(new ListIssue({ + title: 'Testing', + iid: _.random(10000), + confidential: false, + labels: [list.label] + })); + list.issuesSize = 2; + + list.nextPage(); + + expect(list.page).toBe(1); + expect(list.getIssues).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index edd4b3c1440..8ec96bdb583 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -1,11 +1,11 @@ /* eslint-disable no-new */ /* global Build */ - -require('~/lib/utils/datetime_utility'); -require('~/lib/utils/url_utility'); -require('~/build'); -require('~/breakpoints'); -require('vendor/jquery.nicescroll'); +import { bytesToKiB } from '~/lib/utils/number_utils'; +import '~/lib/utils/datetime_utility'; +import '~/lib/utils/url_utility'; +import '~/build'; +import '~/breakpoints'; +import 'vendor/jquery.nicescroll'; describe('Build', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`; @@ -98,9 +98,9 @@ describe('Build', () => { jasmine.clock().tick(4001); - expect($.ajax.calls.count()).toBe(2); + expect($.ajax.calls.count()).toBe(3); - args = $.ajax.calls.argsFor(1)[0]; + args = $.ajax.calls.argsFor(2)[0]; expect(args.url).toBe(`${BUILD_URL}/trace.json`); expect(args.dataType).toBe('json'); expect(args.data.state).toBe('newstate'); @@ -133,7 +133,7 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Update/); jasmine.clock().tick(4001); - args = $.ajax.calls.argsFor(1)[0]; + args = $.ajax.calls.argsFor(2)[0]; args.success.call($, { html: '<span>Different</span>', status: 'running', @@ -144,24 +144,6 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).toMatch(/Different/); }); - it('shows information about truncated log', () => { - jasmine.clock().tick(4001); - const [{ success }] = $.ajax.calls.argsFor(0); - - success.call($, { - html: '<span>Update</span>', - status: 'success', - append: false, - truncated: true, - size: '50', - }); - - expect( - $('#build-trace .js-truncated-info').text().trim(), - ).toContain('Showing last 50 KiB of log'); - expect($('#build-trace .js-truncated-info-size').text()).toMatch('50'); - }); - it('reloads the page when the build is done', () => { spyOn(gl.utils, 'visitUrl'); @@ -176,6 +158,107 @@ describe('Build', () => { expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL); }); + + describe('truncated information', () => { + describe('when size is less than total', () => { + it('shows information about truncated log', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).not.toContain('hidden'); + }); + + it('shows the size in KiB', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + const size = 50; + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(size)}`); + }); + + it('shows incremented size', () => { + jasmine.clock().tick(4001); + let args = $.ajax.calls.argsFor(0)[0]; + args.success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(50)}`); + + jasmine.clock().tick(4001); + args = $.ajax.calls.argsFor(2)[0]; + args.success.call($, { + html: '<span>Update</span>', + status: 'success', + append: true, + size: 10, + total: 100, + }); + + expect( + document.querySelector('.js-truncated-info-size').textContent.trim(), + ).toEqual(`${bytesToKiB(60)}`); + }); + + it('renders the raw link', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + }); + + expect( + document.querySelector('.js-raw-link').textContent.trim(), + ).toContain('Complete Raw'); + }); + }); + + describe('when size is equal than total', () => { + it('does not show the trunctated information', () => { + jasmine.clock().tick(4001); + const [{ success }] = $.ajax.calls.argsFor(0); + + success.call($, { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 100, + total: 100, + }); + + expect(document.querySelector('.js-truncated-info').classList).toContain('hidden'); + }); + }); + }); }); }); }); diff --git a/spec/javascripts/ci_status_icon_spec.js b/spec/javascripts/ci_status_icon_spec.js new file mode 100644 index 00000000000..c83416c15ef --- /dev/null +++ b/spec/javascripts/ci_status_icon_spec.js @@ -0,0 +1,44 @@ +import * as icons from '~/ci_status_icons'; + +describe('CI status icons', () => { + const statuses = [ + 'canceled', + 'created', + 'failed', + 'manual', + 'pending', + 'running', + 'skipped', + 'success', + 'warning', + ]; + + statuses.forEach((status) => { + it(`should export a ${status} svg`, () => { + const key = `${status.toUpperCase()}_SVG`; + + expect(Object.hasOwnProperty.call(icons, key)).toBe(true); + expect(icons[key]).toMatch(/^<svg/); + }); + }); + + describe('default export map', () => { + const entityIconNames = [ + 'icon_status_canceled', + 'icon_status_created', + 'icon_status_failed', + 'icon_status_manual', + 'icon_status_pending', + 'icon_status_running', + 'icon_status_skipped', + 'icon_status_success', + 'icon_status_warning', + ]; + + entityIconNames.forEach((iconName) => { + it(`should have a '${iconName}' key`, () => { + expect(Object.hasOwnProperty.call(icons.default, iconName)).toBe(true); + }); + }); + }); +}); diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 8cac3cad232..ad31448f81c 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -36,6 +36,7 @@ describe('Pipelines table in Commits and Merge requests', () => { setTimeout(() => { expect(this.component.$el.querySelector('.empty-state')).toBeDefined(); expect(this.component.$el.querySelector('.realtime-loading')).toBe(null); + expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null); done(); }, 1); }); @@ -67,6 +68,8 @@ describe('Pipelines table in Commits and Merge requests', () => { setTimeout(() => { expect(this.component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1); expect(this.component.$el.querySelector('.realtime-loading')).toBe(null); + expect(this.component.$el.querySelector('.empty-state')).toBe(null); + expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null); done(); }, 0); }); @@ -95,10 +98,12 @@ describe('Pipelines table in Commits and Merge requests', () => { this.component.$destroy(); }); - it('should render empty state', function (done) { + it('should render error state', function (done) { setTimeout(() => { expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBeDefined(); expect(this.component.$el.querySelector('.realtime-loading')).toBe(null); + expect(this.component.$el.querySelector('.js-empty-state')).toBe(null); + expect(this.component.$el.querySelector('table')).toBe(null); done(); }, 0); }); diff --git a/spec/javascripts/diff_comments_store_spec.js b/spec/javascripts/diff_comments_store_spec.js index 84cf98c930a..66ece7e4f41 100644 --- a/spec/javascripts/diff_comments_store_spec.js +++ b/spec/javascripts/diff_comments_store_spec.js @@ -5,129 +5,127 @@ require('~/diff_notes/models/discussion'); require('~/diff_notes/models/note'); require('~/diff_notes/stores/comments'); -(() => { - function createDiscussion(noteId = 1, resolved = true) { - CommentsStore.create({ - discussionId: 'a', - noteId, - canResolve: true, - resolved, - resolvedBy: 'test', - authorName: 'test', - authorAvatar: 'test', - noteTruncated: 'test...', - }); - } - - beforeEach(() => { - CommentsStore.state = {}; +function createDiscussion(noteId = 1, resolved = true) { + CommentsStore.create({ + discussionId: 'a', + noteId, + canResolve: true, + resolved, + resolvedBy: 'test', + authorName: 'test', + authorAvatar: 'test', + noteTruncated: 'test...', }); +} - describe('New discussion', () => { - it('creates new discussion', () => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - expect(Object.keys(CommentsStore.state).length).toBe(1); - }); +beforeEach(() => { + CommentsStore.state = {}; +}); - it('creates new note in discussion', () => { - createDiscussion(); - createDiscussion(2); +describe('New discussion', () => { + it('creates new discussion', () => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + expect(Object.keys(CommentsStore.state).length).toBe(1); + }); - const discussion = CommentsStore.state['a']; - expect(Object.keys(discussion.notes).length).toBe(2); - }); + it('creates new note in discussion', () => { + createDiscussion(); + createDiscussion(2); + + const discussion = CommentsStore.state['a']; + expect(Object.keys(discussion.notes).length).toBe(2); }); +}); - describe('Get note', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); +describe('Get note', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + }); - it('gets note by ID', () => { - const note = CommentsStore.get('a', 1); - expect(note).toBeDefined(); - expect(note.id).toBe(1); - }); + it('gets note by ID', () => { + const note = CommentsStore.get('a', 1); + expect(note).toBeDefined(); + expect(note.id).toBe(1); }); +}); - describe('Delete discussion', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); +describe('Delete discussion', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + }); - it('deletes discussion by ID', () => { - CommentsStore.delete('a', 1); - expect(Object.keys(CommentsStore.state).length).toBe(0); - }); + it('deletes discussion by ID', () => { + CommentsStore.delete('a', 1); + expect(Object.keys(CommentsStore.state).length).toBe(0); + }); - it('deletes discussion when no more notes', () => { - createDiscussion(); - createDiscussion(2); - expect(Object.keys(CommentsStore.state).length).toBe(1); - expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); + it('deletes discussion when no more notes', () => { + createDiscussion(); + createDiscussion(2); + expect(Object.keys(CommentsStore.state).length).toBe(1); + expect(Object.keys(CommentsStore.state['a'].notes).length).toBe(2); - CommentsStore.delete('a', 1); - CommentsStore.delete('a', 2); - expect(Object.keys(CommentsStore.state).length).toBe(0); - }); + CommentsStore.delete('a', 1); + CommentsStore.delete('a', 2); + expect(Object.keys(CommentsStore.state).length).toBe(0); }); +}); - describe('Update note', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); +describe('Update note', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + }); - it('updates note to be unresolved', () => { - CommentsStore.update('a', 1, false, 'test'); + it('updates note to be unresolved', () => { + CommentsStore.update('a', 1, false, 'test'); - const note = CommentsStore.get('a', 1); - expect(note.resolved).toBe(false); - }); + const note = CommentsStore.get('a', 1); + expect(note.resolved).toBe(false); }); +}); - describe('Discussion resolved', () => { - beforeEach(() => { - expect(Object.keys(CommentsStore.state).length).toBe(0); - createDiscussion(); - }); +describe('Discussion resolved', () => { + beforeEach(() => { + expect(Object.keys(CommentsStore.state).length).toBe(0); + createDiscussion(); + }); - it('is resolved with single note', () => { - const discussion = CommentsStore.state['a']; - expect(discussion.isResolved()).toBe(true); - }); + it('is resolved with single note', () => { + const discussion = CommentsStore.state['a']; + expect(discussion.isResolved()).toBe(true); + }); - it('is unresolved with 2 notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2, false); + it('is unresolved with 2 notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2, false); - expect(discussion.isResolved()).toBe(false); - }); + expect(discussion.isResolved()).toBe(false); + }); - it('is resolved with 2 notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2); + it('is resolved with 2 notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2); - expect(discussion.isResolved()).toBe(true); - }); + expect(discussion.isResolved()).toBe(true); + }); - it('resolve all notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2, false); + it('resolve all notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2, false); - discussion.resolveAllNotes(); - expect(discussion.isResolved()).toBe(true); - }); + discussion.resolveAllNotes(); + expect(discussion.isResolved()).toBe(true); + }); - it('unresolve all notes', () => { - const discussion = CommentsStore.state['a']; - createDiscussion(2); + it('unresolve all notes', () => { + const discussion = CommentsStore.state['a']; + createDiscussion(2); - discussion.unResolveAllNotes(); - expect(discussion.isResolved()).toBe(false); - }); + discussion.unResolveAllNotes(); + expect(discussion.isResolved()).toBe(false); }); -})(); +}); diff --git a/spec/javascripts/droplab/constants_spec.js b/spec/javascripts/droplab/constants_spec.js index 35239e4fb8e..fd153a49fcd 100644 --- a/spec/javascripts/droplab/constants_spec.js +++ b/spec/javascripts/droplab/constants_spec.js @@ -26,4 +26,10 @@ describe('constants', function () { expect(constants.ACTIVE_CLASS).toBe('droplab-item-active'); }); }); + + describe('IGNORE_CLASS', function () { + it('should be `droplab-item-ignore`', function() { + expect(constants.IGNORE_CLASS).toBe('droplab-item-ignore'); + }); + }); }); diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js index 802e2435672..7516b301917 100644 --- a/spec/javascripts/droplab/drop_down_spec.js +++ b/spec/javascripts/droplab/drop_down_spec.js @@ -2,7 +2,7 @@ import DropDown from '~/droplab/drop_down'; import utils from '~/droplab/utils'; -import { SELECTED_CLASS } from '~/droplab/constants'; +import { SELECTED_CLASS, IGNORE_CLASS } from '~/droplab/constants'; describe('DropDown', function () { describe('class constructor', function () { @@ -128,9 +128,10 @@ describe('DropDown', function () { describe('clickEvent', function () { beforeEach(function () { + this.classList = jasmine.createSpyObj('classList', ['contains']); this.list = { dispatchEvent: () => {} }; this.dropdown = { hide: () => {}, list: this.list, addSelectedClass: () => {} }; - this.event = { preventDefault: () => {}, target: {} }; + this.event = { preventDefault: () => {}, target: { classList: this.classList } }; this.customEvent = {}; this.closestElement = {}; @@ -140,6 +141,7 @@ describe('DropDown', function () { spyOn(this.event, 'preventDefault'); spyOn(window, 'CustomEvent').and.returnValue(this.customEvent); spyOn(utils, 'closest').and.returnValues(this.closestElement, undefined); + this.classList.contains.and.returnValue(false); DropDown.prototype.clickEvent.call(this.dropdown, this.event); }); @@ -164,15 +166,35 @@ describe('DropDown', function () { expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object)); }); + it('should call .classList.contains checking for IGNORE_CLASS', function () { + expect(this.classList.contains).toHaveBeenCalledWith(IGNORE_CLASS); + }); + it('should call .dispatchEvent with the customEvent', function () { expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent); }); describe('if the target is a UL element', function () { beforeEach(function () { - this.event = { preventDefault: () => {}, target: { tagName: 'UL' } }; + this.event = { preventDefault: () => {}, target: { tagName: 'UL', classList: this.classList } }; + + spyOn(this.event, 'preventDefault'); + utils.closest.calls.reset(); + + DropDown.prototype.clickEvent.call(this.dropdown, this.event); + }); + + it('should return immediately', function () { + expect(utils.closest).not.toHaveBeenCalled(); + }); + }); + + describe('if the target has the IGNORE_CLASS class', function () { + beforeEach(function () { + this.event = { preventDefault: () => {}, target: { tagName: 'LI', classList: this.classList } }; spyOn(this.event, 'preventDefault'); + this.classList.contains.and.returnValue(true); utils.closest.calls.reset(); DropDown.prototype.clickEvent.call(this.dropdown, this.event); diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js index 6348d97b0a5..676bf61cfd9 100644 --- a/spec/javascripts/environments/environment_actions_spec.js +++ b/spec/javascripts/environments/environment_actions_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import actionsComp from '~/environments/components/environment_actions'; +import actionsComp from '~/environments/components/environment_actions.vue'; describe('Actions Component', () => { let ActionsComponent; diff --git a/spec/javascripts/environments/environment_external_url_spec.js b/spec/javascripts/environments/environment_external_url_spec.js index 9af218a27ff..056d68a26e9 100644 --- a/spec/javascripts/environments/environment_external_url_spec.js +++ b/spec/javascripts/environments/environment_external_url_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import externalUrlComp from '~/environments/components/environment_external_url'; +import externalUrlComp from '~/environments/components/environment_external_url.vue'; describe('External URL Component', () => { let ExternalUrlComponent; diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 4d42de4d549..0e141adb628 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -1,6 +1,6 @@ import 'timeago.js'; import Vue from 'vue'; -import environmentItemComp from '~/environments/components/environment_item'; +import environmentItemComp from '~/environments/components/environment_item.vue'; describe('Environment item', () => { let EnvironmentItem; diff --git a/spec/javascripts/environments/environment_monitoring_spec.js b/spec/javascripts/environments/environment_monitoring_spec.js index fc451cce641..0f3dba66230 100644 --- a/spec/javascripts/environments/environment_monitoring_spec.js +++ b/spec/javascripts/environments/environment_monitoring_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import monitoringComp from '~/environments/components/environment_monitoring'; +import monitoringComp from '~/environments/components/environment_monitoring.vue'; describe('Monitoring Component', () => { let MonitoringComponent; diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js index 7cb39d9df03..25397714a76 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js +++ b/spec/javascripts/environments/environment_rollback_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import rollbackComp from '~/environments/components/environment_rollback'; +import rollbackComp from '~/environments/components/environment_rollback.vue'; describe('Rollback Component', () => { const retryURL = 'https://gitlab.com/retry'; diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 4431baa4b96..9762688af1a 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -83,9 +83,10 @@ describe('Environment', () => { it('should render a table with environments', (done) => { setTimeout(() => { + expect(component.$el.querySelectorAll('table')).toBeDefined(); expect( - component.$el.querySelectorAll('table tbody tr').length, - ).toEqual(1); + component.$el.querySelector('.environment-name').textContent.trim(), + ).toEqual(environment.name); done(); }, 0); }); diff --git a/spec/javascripts/environments/environment_stop_spec.js b/spec/javascripts/environments/environment_stop_spec.js index 01055e3f255..942e4aaabd4 100644 --- a/spec/javascripts/environments/environment_stop_spec.js +++ b/spec/javascripts/environments/environment_stop_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import stopComp from '~/environments/components/environment_stop'; +import stopComp from '~/environments/components/environment_stop.vue'; describe('Stop Component', () => { let StopComponent; diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js index 3df967848a7..effbc6c3ee1 100644 --- a/spec/javascripts/environments/environment_table_spec.js +++ b/spec/javascripts/environments/environment_table_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import environmentTableComp from '~/environments/components/environments_table'; +import environmentTableComp from '~/environments/components/environments_table.vue'; describe('Environment item', () => { preloadFixtures('static/environments/element.html.raw'); diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js index be2289edc2b..858472af4b6 100644 --- a/spec/javascripts/environments/environment_terminal_button_spec.js +++ b/spec/javascripts/environments/environment_terminal_button_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import terminalComp from '~/environments/components/environment_terminal_button'; +import terminalComp from '~/environments/components/environment_terminal_button.vue'; describe('Stop Component', () => { let TerminalComponent; diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 43a217a67f5..72f3db29a66 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -47,9 +47,10 @@ describe('Environments Folder View', () => { it('should render a table with environments', (done) => { setTimeout(() => { + expect(component.$el.querySelectorAll('table')).toBeDefined(); expect( - component.$el.querySelectorAll('table tbody tr').length, - ).toEqual(2); + component.$el.querySelector('.environment-name').textContent.trim(), + ).toEqual(environmentsList[0].name); done(); }, 0); }); diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index 2b1fe5e3eef..3f92fe4701e 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -3,69 +3,67 @@ require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown'); require('~/filtered_search/dropdown_user'); -(() => { - describe('Dropdown User', () => { - describe('getSearchInput', () => { - let dropdownUser; +describe('Dropdown User', () => { + describe('getSearchInput', () => { + let dropdownUser; - beforeEach(() => { - spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); - spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); - spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); + beforeEach(() => { + spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); - dropdownUser = new gl.DropdownUser(); - }); - - it('should not return the double quote found in value', () => { - spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ - lastToken: '"johnny appleseed', - }); + dropdownUser = new gl.DropdownUser(); + }); - expect(dropdownUser.getSearchInput()).toBe('johnny appleseed'); + it('should not return the double quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: '"johnny appleseed', }); - it('should not return the single quote found in value', () => { - spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ - lastToken: '\'larry boy', - }); + expect(dropdownUser.getSearchInput()).toBe('johnny appleseed'); + }); - expect(dropdownUser.getSearchInput()).toBe('larry boy'); + it('should not return the single quote found in value', () => { + spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({ + lastToken: '\'larry boy', }); + + expect(dropdownUser.getSearchInput()).toBe('larry boy'); }); + }); - describe('config AjaxFilter\'s endpoint', () => { - beforeEach(() => { - spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); - spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); - }); + describe('config AjaxFilter\'s endpoint', () => { + beforeEach(() => { + spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); + }); - it('should return endpoint', () => { - window.gon = { - relative_url_root: '', - }; - const dropdown = new gl.DropdownUser(); + it('should return endpoint', () => { + window.gon = { + relative_url_root: '', + }; + const dropdown = new gl.DropdownUser(); - expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); - }); + expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); + }); - it('should return endpoint when relative_url_root is undefined', () => { - const dropdown = new gl.DropdownUser(); + it('should return endpoint when relative_url_root is undefined', () => { + const dropdown = new gl.DropdownUser(); - expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); - }); + expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json'); + }); - it('should return endpoint with relative url when available', () => { - window.gon = { - relative_url_root: '/gitlab_directory', - }; - const dropdown = new gl.DropdownUser(); + it('should return endpoint with relative url when available', () => { + window.gon = { + relative_url_root: '/gitlab_directory', + }; + const dropdown = new gl.DropdownUser(); - expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json'); - }); + expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json'); + }); - afterEach(() => { - window.gon = {}; - }); + afterEach(() => { + window.gon = {}; }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index e6538020896..c820c955172 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -3,308 +3,306 @@ require('~/filtered_search/dropdown_utils'); require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown_manager'); -(() => { - describe('Dropdown Utils', () => { - describe('getEscapedText', () => { - it('should return same word when it has no space', () => { - const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace'); - expect(escaped).toBe('textWithoutSpace'); - }); +describe('Dropdown Utils', () => { + describe('getEscapedText', () => { + it('should return same word when it has no space', () => { + const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace'); + expect(escaped).toBe('textWithoutSpace'); + }); - it('should escape with double quotes', () => { - let escaped = gl.DropdownUtils.getEscapedText('text with space'); - expect(escaped).toBe('"text with space"'); + it('should escape with double quotes', () => { + let escaped = gl.DropdownUtils.getEscapedText('text with space'); + expect(escaped).toBe('"text with space"'); - escaped = gl.DropdownUtils.getEscapedText('won\'t fix'); - expect(escaped).toBe('"won\'t fix"'); - }); + escaped = gl.DropdownUtils.getEscapedText('won\'t fix'); + expect(escaped).toBe('"won\'t fix"'); + }); - it('should escape with single quotes', () => { - const escaped = gl.DropdownUtils.getEscapedText('won"t fix'); - expect(escaped).toBe('\'won"t fix\''); - }); + it('should escape with single quotes', () => { + const escaped = gl.DropdownUtils.getEscapedText('won"t fix'); + expect(escaped).toBe('\'won"t fix\''); + }); - it('should escape with single quotes by default', () => { - const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix'); - expect(escaped).toBe('\'won"t\' fix\''); - }); + it('should escape with single quotes by default', () => { + const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix'); + expect(escaped).toBe('\'won"t\' fix\''); }); + }); - describe('filterWithSymbol', () => { - let input; - const item = { - title: '@root', - }; + describe('filterWithSymbol', () => { + let input; + const item = { + title: '@root', + }; - beforeEach(() => { - setFixtures(` - <input type="text" id="test" /> - `); + beforeEach(() => { + setFixtures(` + <input type="text" id="test" /> + `); - input = document.getElementById('test'); - }); + input = document.getElementById('test'); + }); - it('should filter without symbol', () => { - input.value = 'roo'; + it('should filter without symbol', () => { + input.value = 'roo'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with symbol', () => { - input.value = '@roo'; + it('should filter with symbol', () => { + input.value = '@roo'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); + expect(updatedItem.droplab_hidden).toBe(false); + }); - describe('filters multiple word title', () => { - const multipleWordItem = { - title: 'Community Contributions', - }; + describe('filters multiple word title', () => { + const multipleWordItem = { + title: 'Community Contributions', + }; - it('should filter with double quote', () => { - input.value = '"'; + it('should filter with double quote', () => { + input.value = '"'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with double quote and symbol', () => { - input.value = '~"'; + it('should filter with double quote and symbol', () => { + input.value = '~"'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with double quote and multiple words', () => { - input.value = '"community con'; + it('should filter with double quote and multiple words', () => { + input.value = '"community con'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with double quote, symbol and multiple words', () => { - input.value = '~"community con'; + it('should filter with double quote, symbol and multiple words', () => { + input.value = '~"community con'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with single quote', () => { - input.value = '\''; + it('should filter with single quote', () => { + input.value = '\''; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with single quote and symbol', () => { - input.value = '~\''; + it('should filter with single quote and symbol', () => { + input.value = '~\''; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with single quote and multiple words', () => { - input.value = '\'community con'; + it('should filter with single quote and multiple words', () => { + input.value = '\'community con'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); - it('should filter with single quote, symbol and multiple words', () => { - input.value = '~\'community con'; + it('should filter with single quote, symbol and multiple words', () => { + input.value = '~\'community con'; - const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); - expect(updatedItem.droplab_hidden).toBe(false); - }); + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); }); }); + }); - describe('filterHint', () => { - let input; - - beforeEach(() => { - setFixtures(` - <ul class="tokens-container"> - <li class="input-token"> - <input class="filtered-search" type="text" id="test" /> - </li> - </ul> - `); - - input = document.getElementById('test'); - }); + describe('filterHint', () => { + let input; - it('should filter', () => { - input.value = 'l'; - let updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - }); - expect(updatedItem.droplab_hidden).toBe(false); + beforeEach(() => { + setFixtures(` + <ul class="tokens-container"> + <li class="input-token"> + <input class="filtered-search" type="text" id="test" /> + </li> + </ul> + `); - input.value = 'o'; - updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - }); - expect(updatedItem.droplab_hidden).toBe(true); - }); + input = document.getElementById('test'); + }); - it('should return droplab_hidden false when item has no hint', () => { - const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); - expect(updatedItem.droplab_hidden).toBe(false); + it('should filter', () => { + input.value = 'l'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', }); + expect(updatedItem.droplab_hidden).toBe(false); - it('should allow multiple if item.type is array', () => { - input.value = 'label:~first la'; - const updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'label', - type: 'array', - }); - expect(updatedItem.droplab_hidden).toBe(false); + input.value = 'o'; + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', }); + expect(updatedItem.droplab_hidden).toBe(true); + }); - it('should prevent multiple if item.type is not array', () => { - input.value = 'milestone:~first mile'; - let updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'milestone', - }); - expect(updatedItem.droplab_hidden).toBe(true); + it('should return droplab_hidden false when item has no hint', () => { + const updatedItem = gl.DropdownUtils.filterHint(input, {}, ''); + expect(updatedItem.droplab_hidden).toBe(false); + }); - updatedItem = gl.DropdownUtils.filterHint(input, { - hint: 'milestone', - type: 'string', - }); - expect(updatedItem.droplab_hidden).toBe(true); + it('should allow multiple if item.type is array', () => { + input.value = 'label:~first la'; + const updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'label', + type: 'array', }); + expect(updatedItem.droplab_hidden).toBe(false); }); - describe('setDataValueIfSelected', () => { - beforeEach(() => { - spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') - .and.callFake(() => {}); + it('should prevent multiple if item.type is not array', () => { + input.value = 'milestone:~first mile'; + let updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', }); + expect(updatedItem.droplab_hidden).toBe(true); - it('calls addWordToInput when dataValue exists', () => { - const selected = { - getAttribute: () => 'value', - }; - - gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1); + updatedItem = gl.DropdownUtils.filterHint(input, { + hint: 'milestone', + type: 'string', }); + expect(updatedItem.droplab_hidden).toBe(true); + }); + }); - it('returns true when dataValue exists', () => { - const selected = { - getAttribute: () => 'value', - }; + describe('setDataValueIfSelected', () => { + beforeEach(() => { + spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput') + .and.callFake(() => {}); + }); - const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(result).toBe(true); - }); + it('calls addWordToInput when dataValue exists', () => { + const selected = { + getAttribute: () => 'value', + }; - it('returns false when dataValue does not exist', () => { - const selected = { - getAttribute: () => null, - }; + gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1); + }); - const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); - expect(result).toBe(false); - }); + it('returns true when dataValue exists', () => { + const selected = { + getAttribute: () => 'value', + }; + + const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(result).toBe(true); }); - describe('getInputSelectionPosition', () => { - describe('word with trailing spaces', () => { - const value = 'label:none '; + it('returns false when dataValue does not exist', () => { + const selected = { + getAttribute: () => null, + }; + + const result = gl.DropdownUtils.setDataValueIfSelected(null, selected); + expect(result).toBe(false); + }); + }); - it('should return selectionStart when cursor is at the trailing space', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 11, - value, - }); + describe('getInputSelectionPosition', () => { + describe('word with trailing spaces', () => { + const value = 'label:none '; - expect(left).toBe(11); - expect(right).toBe(11); + it('should return selectionStart when cursor is at the trailing space', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 11, + value, }); - it('should return input when cursor is at the start of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 0, - value, - }); + expect(left).toBe(11); + expect(right).toBe(11); + }); - expect(left).toBe(0); - expect(right).toBe(10); + it('should return input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, }); - it('should return input when cursor is at the middle of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 7, - value, - }); + expect(left).toBe(0); + expect(right).toBe(10); + }); - expect(left).toBe(0); - expect(right).toBe(10); + it('should return input when cursor is at the middle of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 7, + value, }); - it('should return input when cursor is at the end of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 10, - value, - }); + expect(left).toBe(0); + expect(right).toBe(10); + }); - expect(left).toBe(0); - expect(right).toBe(10); + it('should return input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 10, + value, }); - }); - describe('multiple words', () => { - const value = 'label:~"Community Contribution"'; + expect(left).toBe(0); + expect(right).toBe(10); + }); + }); - it('should return input when cursor is after the first word', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 17, - value, - }); + describe('multiple words', () => { + const value = 'label:~"Community Contribution"'; - expect(left).toBe(0); - expect(right).toBe(31); + it('should return input when cursor is after the first word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 17, + value, }); - it('should return input when cursor is before the second word', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 18, - value, - }); + expect(left).toBe(0); + expect(right).toBe(31); + }); - expect(left).toBe(0); - expect(right).toBe(31); + it('should return input when cursor is before the second word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 18, + value, }); - }); - describe('incomplete multiple words', () => { - const value = 'label:~"Community Contribution'; + expect(left).toBe(0); + expect(right).toBe(31); + }); + }); - it('should return entire input when cursor is at the start of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 0, - value, - }); + describe('incomplete multiple words', () => { + const value = 'label:~"Community Contribution'; - expect(left).toBe(0); - expect(right).toBe(30); + it('should return entire input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, }); - it('should return entire input when cursor is at the end of input', () => { - const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ - selectionStart: 30, - value, - }); + expect(left).toBe(0); + expect(right).toBe(30); + }); - expect(left).toBe(0); - expect(right).toBe(30); + it('should return entire input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 30, + value, }); + + expect(left).toBe(0); + expect(right).toBe(30); }); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js index a1da3396d7b..17bf8932489 100644 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js @@ -3,99 +3,97 @@ require('~/filtered_search/filtered_search_visual_tokens'); require('~/filtered_search/filtered_search_tokenizer'); require('~/filtered_search/filtered_search_dropdown_manager'); -(() => { - describe('Filtered Search Dropdown Manager', () => { - describe('addWordToInput', () => { - function getInputValue() { - return document.querySelector('.filtered-search').value; - } - - function setInputValue(value) { - document.querySelector('.filtered-search').value = value; - } - - beforeEach(() => { - setFixtures(` - <ul class="tokens-container"> - <li class="input-token"> - <input class="filtered-search"> - </li> - </ul> - `); - }); +describe('Filtered Search Dropdown Manager', () => { + describe('addWordToInput', () => { + function getInputValue() { + return document.querySelector('.filtered-search').value; + } + + function setInputValue(value) { + document.querySelector('.filtered-search').value = value; + } + + beforeEach(() => { + setFixtures(` + <ul class="tokens-container"> + <li class="input-token"> + <input class="filtered-search"> + </li> + </ul> + `); + }); - describe('input has no existing value', () => { - it('should add just tokenName', () => { - gl.FilteredSearchDropdownManager.addWordToInput('milestone'); + describe('input has no existing value', () => { + it('should add just tokenName', () => { + gl.FilteredSearchDropdownManager.addWordToInput('milestone'); - const token = document.querySelector('.tokens-container .js-visual-token'); + const token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('milestone'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('milestone'); + expect(getInputValue()).toBe(''); + }); - it('should add tokenName and tokenValue', () => { - gl.FilteredSearchDropdownManager.addWordToInput('label'); + it('should add tokenName and tokenValue', () => { + gl.FilteredSearchDropdownManager.addWordToInput('label'); - let token = document.querySelector('.tokens-container .js-visual-token'); + let token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(getInputValue()).toBe(''); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(getInputValue()).toBe(''); - gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); - // We have to get that reference again - // Because gl.FilteredSearchDropdownManager deletes the previous token - token = document.querySelector('.tokens-container .js-visual-token'); + gl.FilteredSearchDropdownManager.addWordToInput('label', 'none'); + // We have to get that reference again + // Because gl.FilteredSearchDropdownManager deletes the previous token + token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(token.querySelector('.value').innerText).toBe('none'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.value').innerText).toBe('none'); + expect(getInputValue()).toBe(''); }); + }); - describe('input has existing value', () => { - it('should be able to just add tokenName', () => { - setInputValue('a'); - gl.FilteredSearchDropdownManager.addWordToInput('author'); + describe('input has existing value', () => { + it('should be able to just add tokenName', () => { + setInputValue('a'); + gl.FilteredSearchDropdownManager.addWordToInput('author'); - const token = document.querySelector('.tokens-container .js-visual-token'); + const token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('author'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('author'); + expect(getInputValue()).toBe(''); + }); - it('should replace tokenValue', () => { - gl.FilteredSearchDropdownManager.addWordToInput('author'); + it('should replace tokenValue', () => { + gl.FilteredSearchDropdownManager.addWordToInput('author'); - setInputValue('roo'); - gl.FilteredSearchDropdownManager.addWordToInput(null, '@root'); + setInputValue('roo'); + gl.FilteredSearchDropdownManager.addWordToInput(null, '@root'); - const token = document.querySelector('.tokens-container .js-visual-token'); + const token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('author'); - expect(token.querySelector('.value').innerText).toBe('@root'); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('author'); + expect(token.querySelector('.value').innerText).toBe('@root'); + expect(getInputValue()).toBe(''); + }); - it('should add tokenValues containing spaces', () => { - gl.FilteredSearchDropdownManager.addWordToInput('label'); + it('should add tokenValues containing spaces', () => { + gl.FilteredSearchDropdownManager.addWordToInput('label'); - setInputValue('"test '); - gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); + setInputValue('"test '); + gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\''); - const token = document.querySelector('.tokens-container .js-visual-token'); + const token = document.querySelector('.tokens-container .js-visual-token'); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toBe('label'); - expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); - expect(getInputValue()).toBe(''); - }); + expect(token.classList.contains('filtered-search-token')).toEqual(true); + expect(token.querySelector('.name').innerText).toBe('label'); + expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); + expect(getInputValue()).toBe(''); }); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index 97af681429b..6683489f63c 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -6,271 +6,269 @@ require('~/filtered_search/filtered_search_dropdown_manager'); require('~/filtered_search/filtered_search_manager'); const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper'); -(() => { - describe('Filtered Search Manager', () => { - let input; - let manager; - let tokensContainer; - const placeholder = 'Search or filter results...'; - - function dispatchBackspaceEvent(element, eventType) { - const backspaceKey = 8; - const event = new Event(eventType); - event.keyCode = backspaceKey; - element.dispatchEvent(event); - } +describe('Filtered Search Manager', () => { + let input; + let manager; + let tokensContainer; + const placeholder = 'Search or filter results...'; + + function dispatchBackspaceEvent(element, eventType) { + const backspaceKey = 8; + const event = new Event(eventType); + event.keyCode = backspaceKey; + element.dispatchEvent(event); + } + + function dispatchDeleteEvent(element, eventType) { + const deleteKey = 46; + const event = new Event(eventType); + event.keyCode = deleteKey; + element.dispatchEvent(event); + } + + beforeEach(() => { + setFixtures(` + <div class="filtered-search-box"> + <form> + <ul class="tokens-container list-unstyled"> + ${FilteredSearchSpecHelper.createInputHTML(placeholder)} + </ul> + <button class="clear-search" type="button"> + <i class="fa fa-times"></i> + </button> + </form> + </div> + `); + + spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); + spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {}); + spyOn(gl.utils, 'getParameterByName').and.returnValue(null); + spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough(); + + input = document.querySelector('.filtered-search'); + tokensContainer = document.querySelector('.tokens-container'); + manager = new gl.FilteredSearchManager(); + }); - function dispatchDeleteEvent(element, eventType) { - const deleteKey = 46; - const event = new Event(eventType); - event.keyCode = deleteKey; - element.dispatchEvent(event); - } + afterEach(() => { + manager.cleanup(); + }); - beforeEach(() => { - setFixtures(` - <div class="filtered-search-box"> - <form> - <ul class="tokens-container list-unstyled"> - ${FilteredSearchSpecHelper.createInputHTML(placeholder)} - </ul> - <button class="clear-search" type="button"> - <i class="fa fa-times"></i> - </button> - </form> - </div> - `); + describe('search', () => { + const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; - spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); - spyOn(gl.FilteredSearchManager.prototype, 'tokenChange').and.callFake(() => {}); - spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); - spyOn(gl.FilteredSearchDropdownManager.prototype, 'updateDropdownOffset').and.callFake(() => {}); - spyOn(gl.utils, 'getParameterByName').and.returnValue(null); - spyOn(gl.FilteredSearchVisualTokens, 'unselectTokens').and.callThrough(); + it('should search with a single word', (done) => { + input.value = 'searchTerm'; - input = document.querySelector('.filtered-search'); - tokensContainer = document.querySelector('.tokens-container'); - manager = new gl.FilteredSearchManager(); - }); + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=searchTerm`); + done(); + }); - afterEach(() => { - manager.cleanup(); + manager.search(); }); - describe('search', () => { - const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened'; - - it('should search with a single word', (done) => { - input.value = 'searchTerm'; + it('should search with multiple words', (done) => { + input.value = 'awesome search terms'; - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=searchTerm`); - done(); - }); - - manager.search(); + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); + done(); }); - it('should search with multiple words', (done) => { - input.value = 'awesome search terms'; + manager.search(); + }); - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); - done(); - }); + it('should search with special characters', (done) => { + input.value = '~!@#$%^&*()_+{}:<>,.?/'; - manager.search(); + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); + done(); }); - it('should search with special characters', (done) => { - input.value = '~!@#$%^&*()_+{}:<>,.?/'; + manager.search(); + }); - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); - done(); - }); + it('removes duplicated tokens', (done) => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} + `); - manager.search(); + spyOn(gl.utils, 'visitUrl').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&label_name[]=bug`); + done(); }); - it('removes duplicated tokens', (done) => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug')} - `); - - spyOn(gl.utils, 'visitUrl').and.callFake((url) => { - expect(url).toEqual(`${defaultParams}&label_name[]=bug`); - done(); - }); + manager.search(); + }); + }); - manager.search(); - }); + describe('handleInputPlaceholder', () => { + it('should render placeholder when there is no input', () => { + expect(input.placeholder).toEqual(placeholder); }); - describe('handleInputPlaceholder', () => { - it('should render placeholder when there is no input', () => { - expect(input.placeholder).toEqual(placeholder); - }); + it('should not render placeholder when there is input', () => { + input.value = 'test words'; + + const event = new Event('input'); + input.dispatchEvent(event); - it('should not render placeholder when there is input', () => { - input.value = 'test words'; + expect(input.placeholder).toEqual(''); + }); - const event = new Event('input'); - input.dispatchEvent(event); + it('should not render placeholder when there are tokens and no input', () => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( + FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), + ); - expect(input.placeholder).toEqual(''); - }); + const event = new Event('input'); + input.dispatchEvent(event); - it('should not render placeholder when there are tokens and no input', () => { + expect(input.placeholder).toEqual(''); + }); + }); + + describe('checkForBackspace', () => { + describe('tokens and no input', () => { + beforeEach(() => { tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), ); - - const event = new Event('input'); - input.dispatchEvent(event); - - expect(input.placeholder).toEqual(''); }); - }); - - describe('checkForBackspace', () => { - describe('tokens and no input', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug'), - ); - }); - it('removes last token', () => { - spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); - dispatchBackspaceEvent(input, 'keyup'); - - expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); - }); - - it('sets the input', () => { - spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); - dispatchDeleteEvent(input, 'keyup'); + it('removes last token', () => { + spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + dispatchBackspaceEvent(input, 'keyup'); - expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); - expect(input.value).toEqual('~bug'); - }); + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).toHaveBeenCalled(); }); - it('does not remove token or change input when there is existing input', () => { - spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + it('sets the input', () => { spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); - - input.value = 'text'; dispatchDeleteEvent(input, 'keyup'); - expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); - expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); - expect(input.value).toEqual('text'); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).toHaveBeenCalled(); + expect(input.value).toEqual('~bug'); }); }); - describe('removeSelectedToken', () => { - function getVisualTokens() { - return tokensContainer.querySelectorAll('.js-visual-token'); - } + it('does not remove token or change input when there is existing input', () => { + spyOn(gl.FilteredSearchVisualTokens, 'removeLastTokenPartial').and.callThrough(); + spyOn(gl.FilteredSearchVisualTokens, 'getLastTokenPartial').and.callThrough(); - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), - ); - }); + input.value = 'text'; + dispatchDeleteEvent(input, 'keyup'); - it('removes selected token when the backspace key is pressed', () => { - expect(getVisualTokens().length).toEqual(1); + expect(gl.FilteredSearchVisualTokens.removeLastTokenPartial).not.toHaveBeenCalled(); + expect(gl.FilteredSearchVisualTokens.getLastTokenPartial).not.toHaveBeenCalled(); + expect(input.value).toEqual('text'); + }); + }); - dispatchBackspaceEvent(document, 'keydown'); + describe('removeSelectedToken', () => { + function getVisualTokens() { + return tokensContainer.querySelectorAll('.js-visual-token'); + } - expect(getVisualTokens().length).toEqual(0); - }); + beforeEach(() => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( + FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', 'none', true), + ); + }); - it('removes selected token when the delete key is pressed', () => { - expect(getVisualTokens().length).toEqual(1); + it('removes selected token when the backspace key is pressed', () => { + expect(getVisualTokens().length).toEqual(1); - dispatchDeleteEvent(document, 'keydown'); + dispatchBackspaceEvent(document, 'keydown'); - expect(getVisualTokens().length).toEqual(0); - }); + expect(getVisualTokens().length).toEqual(0); + }); - it('updates the input placeholder after removal', () => { - manager.handleInputPlaceholder(); + it('removes selected token when the delete key is pressed', () => { + expect(getVisualTokens().length).toEqual(1); - expect(input.placeholder).toEqual(''); - expect(getVisualTokens().length).toEqual(1); + dispatchDeleteEvent(document, 'keydown'); - dispatchBackspaceEvent(document, 'keydown'); + expect(getVisualTokens().length).toEqual(0); + }); - expect(input.placeholder).not.toEqual(''); - expect(getVisualTokens().length).toEqual(0); - }); + it('updates the input placeholder after removal', () => { + manager.handleInputPlaceholder(); - it('updates the clear button after removal', () => { - manager.toggleClearSearchButton(); + expect(input.placeholder).toEqual(''); + expect(getVisualTokens().length).toEqual(1); - const clearButton = document.querySelector('.clear-search'); + dispatchBackspaceEvent(document, 'keydown'); - expect(clearButton.classList.contains('hidden')).toEqual(false); - expect(getVisualTokens().length).toEqual(1); + expect(input.placeholder).not.toEqual(''); + expect(getVisualTokens().length).toEqual(0); + }); - dispatchBackspaceEvent(document, 'keydown'); + it('updates the clear button after removal', () => { + manager.toggleClearSearchButton(); - expect(clearButton.classList.contains('hidden')).toEqual(true); - expect(getVisualTokens().length).toEqual(0); - }); + const clearButton = document.querySelector('.clear-search'); + + expect(clearButton.classList.contains('hidden')).toEqual(false); + expect(getVisualTokens().length).toEqual(1); + + dispatchBackspaceEvent(document, 'keydown'); + + expect(clearButton.classList.contains('hidden')).toEqual(true); + expect(getVisualTokens().length).toEqual(0); }); + }); - describe('unselects token', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')} - `); - }); + describe('unselects token', () => { + beforeEach(() => { + tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~bug', true)} + ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} + ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '~awesome')} + `); + }); - it('unselects token when input is clicked', () => { - const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); + it('unselects token when input is clicked', () => { + const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); - expect(selectedToken.classList.contains('selected')).toEqual(true); - expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); + expect(selectedToken.classList.contains('selected')).toEqual(true); + expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); - // Click directly on input attached to document - // so that the click event will propagate properly - document.querySelector('.filtered-search').click(); + // Click directly on input attached to document + // so that the click event will propagate properly + document.querySelector('.filtered-search').click(); - expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); - expect(selectedToken.classList.contains('selected')).toEqual(false); - }); + expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); + expect(selectedToken.classList.contains('selected')).toEqual(false); + }); - it('unselects token when document.body is clicked', () => { - const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); + it('unselects token when document.body is clicked', () => { + const selectedToken = tokensContainer.querySelector('.js-visual-token .selected'); - expect(selectedToken.classList.contains('selected')).toEqual(true); - expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); + expect(selectedToken.classList.contains('selected')).toEqual(true); + expect(gl.FilteredSearchVisualTokens.unselectTokens).not.toHaveBeenCalled(); - document.body.click(); + document.body.click(); - expect(selectedToken.classList.contains('selected')).toEqual(false); - expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); - }); + expect(selectedToken.classList.contains('selected')).toEqual(false); + expect(gl.FilteredSearchVisualTokens.unselectTokens).toHaveBeenCalled(); }); + }); - describe('toggleInputContainerFocus', () => { - it('toggles on focus', () => { - input.focus(); - expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true); - }); + describe('toggleInputContainerFocus', () => { + it('toggles on focus', () => { + input.focus(); + expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true); + }); - it('toggles on blur', () => { - input.blur(); - expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false); - }); + it('toggles on blur', () => { + input.blur(); + expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index cf409a7e509..6f9fa434c35 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -1,110 +1,108 @@ require('~/extensions/array'); require('~/filtered_search/filtered_search_token_keys'); -(() => { - describe('Filtered Search Token Keys', () => { - describe('get', () => { - let tokenKeys; - - beforeEach(() => { - tokenKeys = gl.FilteredSearchTokenKeys.get(); - }); - - it('should return tokenKeys', () => { - expect(tokenKeys !== null).toBe(true); - }); - - it('should return tokenKeys as an array', () => { - expect(tokenKeys instanceof Array).toBe(true); - }); - }); - - describe('getConditions', () => { - let conditions; - - beforeEach(() => { - conditions = gl.FilteredSearchTokenKeys.getConditions(); - }); - - it('should return conditions', () => { - expect(conditions !== null).toBe(true); - }); - - it('should return conditions as an array', () => { - expect(conditions instanceof Array).toBe(true); - }); - }); - - describe('searchByKey', () => { - it('should return null when key not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by key', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchBySymbol', () => { - it('should return null when symbol not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by symbol', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByKeyParam', () => { - it('should return null when key param not found', () => { - const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); - expect(tokenKey === null).toBe(true); - }); - - it('should return tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.get(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); - expect(result).toEqual(tokenKeys[0]); - }); - - it('should return alternative tokenKey when found by key param', () => { - const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); - const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); - expect(result).toEqual(tokenKeys[0]); - }); - }); - - describe('searchByConditionUrl', () => { - it('should return null when condition url not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null); - expect(condition === null).toBe(true); - }); - - it('should return condition when found by url', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); - expect(result).toBe(conditions[0]); - }); - }); - - describe('searchByConditionKeyValue', () => { - it('should return null when condition tokenKey and value not found', () => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); - expect(condition === null).toBe(true); - }); - - it('should return condition when found by tokenKey and value', () => { - const conditions = gl.FilteredSearchTokenKeys.getConditions(); - const result = gl.FilteredSearchTokenKeys - .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value); - expect(result).toEqual(conditions[0]); - }); +describe('Filtered Search Token Keys', () => { + describe('get', () => { + let tokenKeys; + + beforeEach(() => { + tokenKeys = gl.FilteredSearchTokenKeys.get(); + }); + + it('should return tokenKeys', () => { + expect(tokenKeys !== null).toBe(true); + }); + + it('should return tokenKeys as an array', () => { + expect(tokenKeys instanceof Array).toBe(true); + }); + }); + + describe('getConditions', () => { + let conditions; + + beforeEach(() => { + conditions = gl.FilteredSearchTokenKeys.getConditions(); + }); + + it('should return conditions', () => { + expect(conditions !== null).toBe(true); + }); + + it('should return conditions as an array', () => { + expect(conditions instanceof Array).toBe(true); + }); + }); + + describe('searchByKey', () => { + it('should return null when key not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by key', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchBySymbol', () => { + it('should return null when symbol not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by symbol', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByKeyParam', () => { + it('should return null when key param not found', () => { + const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam'); + expect(tokenKey === null).toBe(true); + }); + + it('should return tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.get(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); + + it('should return alternative tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); + }); + + describe('searchByConditionUrl', () => { + it('should return null when condition url not found', () => { + const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null); + expect(condition === null).toBe(true); + }); + + it('should return condition when found by url', () => { + const conditions = gl.FilteredSearchTokenKeys.getConditions(); + const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url); + expect(result).toBe(conditions[0]); + }); + }); + + describe('searchByConditionKeyValue', () => { + it('should return null when condition tokenKey and value not found', () => { + const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null); + expect(condition === null).toBe(true); + }); + + it('should return condition when found by tokenKey and value', () => { + const conditions = gl.FilteredSearchTokenKeys.getConditions(); + const result = gl.FilteredSearchTokenKeys + .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value); + expect(result).toEqual(conditions[0]); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js index cabbc694ec4..3e2e577f115 100644 --- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js @@ -2,134 +2,132 @@ require('~/extensions/array'); require('~/filtered_search/filtered_search_token_keys'); require('~/filtered_search/filtered_search_tokenizer'); -(() => { - describe('Filtered Search Tokenizer', () => { - describe('processTokens', () => { - it('returns for input containing only search value', () => { - const results = gl.FilteredSearchTokenizer.processTokens('searchTerm'); - expect(results.searchToken).toBe('searchTerm'); - expect(results.tokens.length).toBe(0); - expect(results.lastToken).toBe(results.searchToken); - }); - - it('returns for input containing only tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none'); - expect(results.searchToken).toBe(''); - expect(results.tokens.length).toBe(4); - expect(results.tokens[3]).toBe(results.lastToken); - - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); - - expect(results.tokens[1].key).toBe('label'); - expect(results.tokens[1].value).toBe('"Very Important"'); - expect(results.tokens[1].symbol).toBe('~'); - - expect(results.tokens[2].key).toBe('milestone'); - expect(results.tokens[2].value).toBe('v1.0'); - expect(results.tokens[2].symbol).toBe('%'); - - expect(results.tokens[3].key).toBe('assignee'); - expect(results.tokens[3].value).toBe('none'); - expect(results.tokens[3].symbol).toBe(''); - }); - - it('returns for input starting with search value and ending with tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('searchTerm anotherSearchTerm milestone:none'); - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0]).toBe(results.lastToken); - expect(results.tokens[0].key).toBe('milestone'); - expect(results.tokens[0].value).toBe('none'); - expect(results.tokens[0].symbol).toBe(''); - }); - - it('returns for input starting with tokens and ending with search value', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('assignee:@user searchTerm'); - - expect(results.searchToken).toBe('searchTerm'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0].key).toBe('assignee'); - expect(results.tokens[0].value).toBe('user'); - expect(results.tokens[0].symbol).toBe('@'); - expect(results.lastToken).toBe(results.searchToken); - }); - - it('returns for input containing search value wrapped between tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none'); - - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(3); - expect(results.tokens[2]).toBe(results.lastToken); - - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); - - expect(results.tokens[1].key).toBe('label'); - expect(results.tokens[1].value).toBe('"Won\'t fix"'); - expect(results.tokens[1].symbol).toBe('~'); - - expect(results.tokens[2].key).toBe('milestone'); - expect(results.tokens[2].value).toBe('none'); - expect(results.tokens[2].symbol).toBe(''); - }); - - it('returns for input containing search value in between tokens', () => { - const results = gl.FilteredSearchTokenizer - .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing'); - expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); - expect(results.tokens.length).toBe(3); - expect(results.tokens[2]).toBe(results.lastToken); - - expect(results.tokens[0].key).toBe('author'); - expect(results.tokens[0].value).toBe('root'); - expect(results.tokens[0].symbol).toBe('@'); - - expect(results.tokens[1].key).toBe('assignee'); - expect(results.tokens[1].value).toBe('none'); - expect(results.tokens[1].symbol).toBe(''); - - expect(results.tokens[2].key).toBe('label'); - expect(results.tokens[2].value).toBe('Doing'); - expect(results.tokens[2].symbol).toBe('~'); - }); - - it('returns search value for invalid tokens', () => { - const results = gl.FilteredSearchTokenizer.processTokens('fake:token'); - expect(results.lastToken).toBe('fake:token'); - expect(results.searchToken).toBe('fake:token'); - expect(results.tokens.length).toEqual(0); - }); - - it('returns search value and token for mix of valid and invalid tokens', () => { - const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token'); - expect(results.tokens.length).toEqual(1); - expect(results.tokens[0].key).toBe('label'); - expect(results.tokens[0].value).toBe('real'); - expect(results.tokens[0].symbol).toBe(''); - expect(results.lastToken).toBe('fake:token'); - expect(results.searchToken).toBe('fake:token'); - }); - - it('returns search value for invalid symbols', () => { - const results = gl.FilteredSearchTokenizer.processTokens('std::includes'); - expect(results.lastToken).toBe('std::includes'); - expect(results.searchToken).toBe('std::includes'); - }); - - it('removes duplicated values', () => { - const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo'); - expect(results.tokens.length).toBe(1); - expect(results.tokens[0].key).toBe('label'); - expect(results.tokens[0].value).toBe('foo'); - expect(results.tokens[0].symbol).toBe('~'); - }); +describe('Filtered Search Tokenizer', () => { + describe('processTokens', () => { + it('returns for input containing only search value', () => { + const results = gl.FilteredSearchTokenizer.processTokens('searchTerm'); + expect(results.searchToken).toBe('searchTerm'); + expect(results.tokens.length).toBe(0); + expect(results.lastToken).toBe(results.searchToken); + }); + + it('returns for input containing only tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none'); + expect(results.searchToken).toBe(''); + expect(results.tokens.length).toBe(4); + expect(results.tokens[3]).toBe(results.lastToken); + + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); + + expect(results.tokens[1].key).toBe('label'); + expect(results.tokens[1].value).toBe('"Very Important"'); + expect(results.tokens[1].symbol).toBe('~'); + + expect(results.tokens[2].key).toBe('milestone'); + expect(results.tokens[2].value).toBe('v1.0'); + expect(results.tokens[2].symbol).toBe('%'); + + expect(results.tokens[3].key).toBe('assignee'); + expect(results.tokens[3].value).toBe('none'); + expect(results.tokens[3].symbol).toBe(''); + }); + + it('returns for input starting with search value and ending with tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('searchTerm anotherSearchTerm milestone:none'); + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0]).toBe(results.lastToken); + expect(results.tokens[0].key).toBe('milestone'); + expect(results.tokens[0].value).toBe('none'); + expect(results.tokens[0].symbol).toBe(''); + }); + + it('returns for input starting with tokens and ending with search value', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('assignee:@user searchTerm'); + + expect(results.searchToken).toBe('searchTerm'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0].key).toBe('assignee'); + expect(results.tokens[0].value).toBe('user'); + expect(results.tokens[0].symbol).toBe('@'); + expect(results.lastToken).toBe(results.searchToken); + }); + + it('returns for input containing search value wrapped between tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none'); + + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(3); + expect(results.tokens[2]).toBe(results.lastToken); + + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); + + expect(results.tokens[1].key).toBe('label'); + expect(results.tokens[1].value).toBe('"Won\'t fix"'); + expect(results.tokens[1].symbol).toBe('~'); + + expect(results.tokens[2].key).toBe('milestone'); + expect(results.tokens[2].value).toBe('none'); + expect(results.tokens[2].symbol).toBe(''); + }); + + it('returns for input containing search value in between tokens', () => { + const results = gl.FilteredSearchTokenizer + .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing'); + expect(results.searchToken).toBe('searchTerm anotherSearchTerm'); + expect(results.tokens.length).toBe(3); + expect(results.tokens[2]).toBe(results.lastToken); + + expect(results.tokens[0].key).toBe('author'); + expect(results.tokens[0].value).toBe('root'); + expect(results.tokens[0].symbol).toBe('@'); + + expect(results.tokens[1].key).toBe('assignee'); + expect(results.tokens[1].value).toBe('none'); + expect(results.tokens[1].symbol).toBe(''); + + expect(results.tokens[2].key).toBe('label'); + expect(results.tokens[2].value).toBe('Doing'); + expect(results.tokens[2].symbol).toBe('~'); + }); + + it('returns search value for invalid tokens', () => { + const results = gl.FilteredSearchTokenizer.processTokens('fake:token'); + expect(results.lastToken).toBe('fake:token'); + expect(results.searchToken).toBe('fake:token'); + expect(results.tokens.length).toEqual(0); + }); + + it('returns search value and token for mix of valid and invalid tokens', () => { + const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token'); + expect(results.tokens.length).toEqual(1); + expect(results.tokens[0].key).toBe('label'); + expect(results.tokens[0].value).toBe('real'); + expect(results.tokens[0].symbol).toBe(''); + expect(results.lastToken).toBe('fake:token'); + expect(results.searchToken).toBe('fake:token'); + }); + + it('returns search value for invalid symbols', () => { + const results = gl.FilteredSearchTokenizer.processTokens('std::includes'); + expect(results.lastToken).toBe('std::includes'); + expect(results.searchToken).toBe('std::includes'); + }); + + it('removes duplicated values', () => { + const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo'); + expect(results.tokens.length).toBe(1); + expect(results.tokens[0].key).toBe('label'); + expect(results.tokens[0].value).toBe('foo'); + expect(results.tokens[0].symbol).toBe('~'); }); }); -})(); +}); diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js index 2a58fb3a7df..c255bf7c939 100644 --- a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js +++ b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js @@ -1,3 +1,5 @@ +/* eslint-disable promise/catch-or-return */ + import RecentSearchesService from '~/filtered_search/services/recent_searches_service'; describe('RecentSearchesService', () => { diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index fddeaaf504d..47d904b865b 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') } + let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) } let(:pipeline) do create( :ci_pipeline, @@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merge_request) end + it 'merge_requests/merged_merge_request.html.raw' do |example| + allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true) + allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true) + render_merge_request(example.description, merged_merge_request) + end + private def render_merge_request(fixture_file_name, merge_request) diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js index 806d728a874..03edbf9f947 100644 --- a/spec/javascripts/issue_show/issue_title_spec.js +++ b/spec/javascripts/issue_show/issue_title_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import issueTitle from '~/issue_show/issue_title'; +import issueTitle from '~/issue_show/issue_title.vue'; describe('Issue Title', () => { let IssueTitleComponent; diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index aabc8bea12f..9a2570ef7e9 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,18 +1,17 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */ +/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */ import Issue from '~/issue'; require('~/lib/utils/text_utility'); describe('Issue', function() { - var INVALID_URL = 'http://goesnowhere.nothing/whereami'; - var $boxClosed, $boxOpen, $btnClose, $btnReopen; + let $boxClosed, $boxOpen, $btnClose, $btnReopen; preloadFixtures('issues/closed-issue.html.raw'); preloadFixtures('issues/issue-with-task-list.html.raw'); preloadFixtures('issues/open-issue.html.raw'); function expectErrorMessage() { - var $flashMessage = $('div.flash-alert'); + const $flashMessage = $('div.flash-alert'); expect($flashMessage).toExist(); expect($flashMessage).toBeVisible(); expect($flashMessage).toHaveText('Unable to update this issue at this time.'); @@ -26,10 +25,28 @@ describe('Issue', function() { expectVisibility($btnReopen, !isIssueOpen); } - function expectPendingRequest(req, $triggeredButton) { - expect(req.type).toBe('PUT'); - expect(req.url).toBe($triggeredButton.attr('href')); - expect($triggeredButton).toHaveProp('disabled', true); + function expectNewBranchButtonState(isPending, canCreate) { + if (Issue.$btnNewBranch.length === 0) { + return; + } + + const $available = Issue.$btnNewBranch.find('.available'); + expect($available).toHaveText('New branch'); + + if (!isPending && canCreate) { + expect($available).toBeVisible(); + } else { + expect($available).toBeHidden(); + } + + const $unavailable = Issue.$btnNewBranch.find('.unavailable'); + expect($unavailable).toHaveText('New branch unavailable'); + + if (!isPending && !canCreate) { + expect($unavailable).toBeVisible(); + } else { + expect($unavailable).toBeHidden(); + } } function expectVisibility($element, shouldBeVisible) { @@ -81,100 +98,107 @@ describe('Issue', function() { }); }); - describe('close issue', function() { - beforeEach(function() { - loadFixtures('issues/open-issue.html.raw'); - findElements(); - this.issue = new Issue(); - - expectIssueState(true); - }); + [true, false].forEach((isIssueInitiallyOpen) => { + describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() { + const action = isIssueInitiallyOpen ? 'close' : 'reopen'; + + function ajaxSpy(req) { + if (req.url === this.$triggeredButton.attr('href')) { + expect(req.type).toBe('PUT'); + expect(this.$triggeredButton).toHaveProp('disabled', true); + expectNewBranchButtonState(true, false); + return this.issueStateDeferred; + } else if (req.url === Issue.$btnNewBranch.data('path')) { + expect(req.type).toBe('get'); + expectNewBranchButtonState(true, false); + return this.canCreateBranchDeferred; + } + + expect(req.url).toBe('unexpected'); + return null; + } + + beforeEach(function() { + if (isIssueInitiallyOpen) { + loadFixtures('issues/open-issue.html.raw'); + } else { + loadFixtures('issues/closed-issue.html.raw'); + } + + findElements(); + this.issue = new Issue(); + expectIssueState(isIssueInitiallyOpen); + this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen; + + this.$projectIssuesCounter = $('.issue_counter'); + this.$projectIssuesCounter.text('1,001'); + + this.issueStateDeferred = new jQuery.Deferred(); + this.canCreateBranchDeferred = new jQuery.Deferred(); + + spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this)); + }); - it('closes an issue', function() { - spyOn(jQuery, 'ajax').and.callFake(function(req) { - expectPendingRequest(req, $btnClose); - req.success({ + it(`${action}s the issue`, function() { + this.$triggeredButton.trigger('click'); + this.issueStateDeferred.resolve({ id: 34 }); - }); - - $btnClose.trigger('click'); + this.canCreateBranchDeferred.resolve({ + can_create_branch: !isIssueInitiallyOpen + }); - expectIssueState(false); - expect($btnClose).toHaveProp('disabled', false); - expect($('.issue_counter')).toHaveText(0); - }); + expectIssueState(!isIssueInitiallyOpen); + expect(this.$triggeredButton).toHaveProp('disabled', false); + expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002'); + expectNewBranchButtonState(false, !isIssueInitiallyOpen); + }); - it('fails to close an issue with success:false', function() { - spyOn(jQuery, 'ajax').and.callFake(function(req) { - expectPendingRequest(req, $btnClose); - req.success({ + it(`fails to ${action} the issue if saved:false`, function() { + this.$triggeredButton.trigger('click'); + this.issueStateDeferred.resolve({ saved: false }); - }); - - $btnClose.attr('href', INVALID_URL); - $btnClose.trigger('click'); - - expectIssueState(true); - expect($btnClose).toHaveProp('disabled', false); - expectErrorMessage(); - expect($('.issue_counter')).toHaveText(1); - }); + this.canCreateBranchDeferred.resolve({ + can_create_branch: isIssueInitiallyOpen + }); - it('fails to closes an issue with HTTP error', function() { - spyOn(jQuery, 'ajax').and.callFake(function(req) { - expectPendingRequest(req, $btnClose); - req.error(); + expectIssueState(isIssueInitiallyOpen); + expect(this.$triggeredButton).toHaveProp('disabled', false); + expectErrorMessage(); + expect(this.$projectIssuesCounter.text()).toBe('1,001'); + expectNewBranchButtonState(false, isIssueInitiallyOpen); }); - $btnClose.attr('href', INVALID_URL); - $btnClose.trigger('click'); - - expectIssueState(true); - expect($btnClose).toHaveProp('disabled', true); - expectErrorMessage(); - expect($('.issue_counter')).toHaveText(1); - }); - - it('updates counter', () => { - spyOn(jQuery, 'ajax').and.callFake(function(req) { - expectPendingRequest(req, $btnClose); - req.success({ - id: 34 + it(`fails to ${action} the issue if HTTP error occurs`, function() { + this.$triggeredButton.trigger('click'); + this.issueStateDeferred.reject(); + this.canCreateBranchDeferred.resolve({ + can_create_branch: isIssueInitiallyOpen }); - }); - expect($('.issue_counter')).toHaveText(1); - $('.issue_counter').text('1,001'); - expect($('.issue_counter').text()).toEqual('1,001'); - $btnClose.trigger('click'); - expect($('.issue_counter').text()).toEqual('1,000'); - }); - }); + expectIssueState(isIssueInitiallyOpen); + expect(this.$triggeredButton).toHaveProp('disabled', true); + expectErrorMessage(); + expect(this.$projectIssuesCounter.text()).toBe('1,001'); + expectNewBranchButtonState(false, isIssueInitiallyOpen); + }); - describe('reopen issue', function() { - beforeEach(function() { - loadFixtures('issues/closed-issue.html.raw'); - findElements(); - this.issue = new Issue(); + it('disables the new branch button if Ajax call fails', function() { + this.$triggeredButton.trigger('click'); + this.issueStateDeferred.reject(); + this.canCreateBranchDeferred.reject(); - expectIssueState(false); - }); - - it('reopens an issue', function() { - spyOn(jQuery, 'ajax').and.callFake(function(req) { - expectPendingRequest(req, $btnReopen); - req.success({ - id: 34 - }); + expectNewBranchButtonState(false, false); }); - $btnReopen.trigger('click'); + it('does not trigger Ajax call if new branch button is missing', function() { + Issue.$btnNewBranch = $(); + this.canCreateBranchDeferred = null; - expectIssueState(true); - expect($btnReopen).toHaveProp('disabled', false); - expect($('.issue_counter')).toHaveText(1); + this.$triggeredButton.trigger('click'); + this.issueStateDeferred.reject(); + }); }); }); }); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 03f3c206f44..a00efa10119 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -1,3 +1,5 @@ +/* eslint-disable promise/catch-or-return */ + require('~/lib/utils/common_utils'); (() => { @@ -313,7 +315,7 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setFavicon', () => { it('should set page favicon to provided favicon', () => { - const faviconName = 'custom_favicon'; + const faviconPath = '//custom_favicon'; const fakeLink = { setAttribute() {}, }; @@ -321,9 +323,9 @@ require('~/lib/utils/common_utils'); spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { expect(attr).toEqual('href'); - expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); + expect(val.indexOf(faviconPath) > -1).toBe(true); }); - gl.utils.setFavicon(faviconName); + gl.utils.setFavicon(faviconPath); }); }); @@ -345,13 +347,12 @@ require('~/lib/utils/common_utils'); describe('gl.utils.setCiStatusFavicon', () => { it('should set page favicon to CI status favicon based on provided status', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; - const FAVICON_PATH = 'ci_favicons/'; - const FAVICON = 'icon_status_success'; + const FAVICON_PATH = '//icon_status_success'; const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); spyOn($, 'ajax').and.callFake(function (options) { - options.success({ icon: FAVICON }); - expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); + options.success({ favicon: FAVICON_PATH }); + expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH); options.success(); expect(spyResetFavicon).toHaveBeenCalled(); options.error(); diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js index 5fde8be9123..90b12c9f115 100644 --- a/spec/javascripts/lib/utils/number_utility_spec.js +++ b/spec/javascripts/lib/utils/number_utility_spec.js @@ -1,4 +1,4 @@ -import { formatRelevantDigits } from '~/lib/utils/number_utils'; +import { formatRelevantDigits, bytesToKiB } from '~/lib/utils/number_utils'; describe('Number Utils', () => { describe('formatRelevantDigits', () => { @@ -38,4 +38,11 @@ describe('Number Utils', () => { expect(leftFromDecimal.length).toBe(3); }); }); + + describe('bytesToKiB', () => { + it('calculates KiB for the given bytes', () => { + expect(bytesToKiB(1024)).toEqual(1); + expect(bytesToKiB(1000)).toEqual(0.9765625); + }); + }); }); diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js index e3429c2a1cb..918b6d32c43 100644 --- a/spec/javascripts/lib/utils/poll_spec.js +++ b/spec/javascripts/lib/utils/poll_spec.js @@ -4,6 +4,20 @@ import Poll from '~/lib/utils/poll'; Vue.use(VueResource); +const waitForAllCallsToFinish = (service, waitForCount, successCallback) => { + const timer = () => { + setTimeout(() => { + if (service.fetch.calls.count() === waitForCount) { + successCallback(); + } else { + timer(); + } + }, 5); + }; + + timer(); +}; + class ServiceMock { constructor(endpoint) { this.service = Vue.resource(endpoint); @@ -16,6 +30,7 @@ class ServiceMock { describe('Poll', () => { let callbacks; + let service; beforeEach(() => { callbacks = { @@ -23,8 +38,11 @@ describe('Poll', () => { error: () => {}, }; + service = new ServiceMock('endpoint'); + spyOn(callbacks, 'success'); spyOn(callbacks, 'error'); + spyOn(service, 'fetch').and.callThrough(); }); it('calls the success callback when no header for interval is provided', (done) => { @@ -35,19 +53,20 @@ describe('Poll', () => { Vue.http.interceptors.push(successInterceptor); new Poll({ - resource: new ServiceMock('endpoint'), + resource: service, method: 'fetch', successCallback: callbacks.success, errorCallback: callbacks.error, }).makeRequest(); - setTimeout(() => { + waitForAllCallsToFinish(service, 1, () => { expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); + + Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor); + done(); }, 0); - - Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor); }); it('calls the error callback whe the http request returns an error', (done) => { @@ -58,19 +77,19 @@ describe('Poll', () => { Vue.http.interceptors.push(errorInterceptor); new Poll({ - resource: new ServiceMock('endpoint'), + resource: service, method: 'fetch', successCallback: callbacks.success, errorCallback: callbacks.error, }).makeRequest(); - setTimeout(() => { + waitForAllCallsToFinish(service, 1, () => { expect(callbacks.success).not.toHaveBeenCalled(); expect(callbacks.error).toHaveBeenCalled(); - done(); - }, 0); + Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor); - Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor); + done(); + }); }); it('should call the success callback when the interval header is -1', (done) => { @@ -81,7 +100,7 @@ describe('Poll', () => { Vue.http.interceptors.push(intervalInterceptor); new Poll({ - resource: new ServiceMock('endpoint'), + resource: service, method: 'fetch', successCallback: callbacks.success, errorCallback: callbacks.error, @@ -90,10 +109,11 @@ describe('Poll', () => { setTimeout(() => { expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); + + Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor); + done(); }, 0); - - Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor); }); it('starts polling when http status is 200 and interval header is provided', (done) => { @@ -103,26 +123,28 @@ describe('Poll', () => { Vue.http.interceptors.push(pollInterceptor); - const service = new ServiceMock('endpoint'); - spyOn(service, 'fetch').and.callThrough(); - - new Poll({ + const Polling = new Poll({ resource: service, method: 'fetch', data: { page: 1 }, successCallback: callbacks.success, errorCallback: callbacks.error, - }).makeRequest(); + }); + + Polling.makeRequest(); + + waitForAllCallsToFinish(service, 2, () => { + Polling.stop(); - setTimeout(() => { expect(service.fetch.calls.count()).toEqual(2); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(callbacks.success).toHaveBeenCalled(); expect(callbacks.error).not.toHaveBeenCalled(); - done(); - }, 5); - Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + + done(); + }); }); describe('stop', () => { @@ -133,9 +155,6 @@ describe('Poll', () => { Vue.http.interceptors.push(pollInterceptor); - const service = new ServiceMock('endpoint'); - spyOn(service, 'fetch').and.callThrough(); - const Polling = new Poll({ resource: service, method: 'fetch', @@ -150,14 +169,15 @@ describe('Poll', () => { Polling.makeRequest(); - setTimeout(() => { + waitForAllCallsToFinish(service, 1, () => { expect(service.fetch.calls.count()).toEqual(1); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(Polling.stop).toHaveBeenCalled(); - done(); - }, 100); - Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + + done(); + }); }); }); @@ -169,10 +189,6 @@ describe('Poll', () => { Vue.http.interceptors.push(pollInterceptor); - const service = new ServiceMock('endpoint'); - - spyOn(service, 'fetch').and.callThrough(); - const Polling = new Poll({ resource: service, method: 'fetch', @@ -187,17 +203,22 @@ describe('Poll', () => { }); spyOn(Polling, 'stop').and.callThrough(); + spyOn(Polling, 'restart').and.callThrough(); Polling.makeRequest(); - setTimeout(() => { + waitForAllCallsToFinish(service, 2, () => { + Polling.stop(); + expect(service.fetch.calls.count()).toEqual(2); expect(service.fetch).toHaveBeenCalledWith({ page: 1 }); expect(Polling.stop).toHaveBeenCalled(); - done(); - }, 10); + expect(Polling.restart).toHaveBeenCalled(); - Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor); + + done(); + }); }); }); }); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index 4200e943121..daef9b93fa5 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -1,110 +1,108 @@ require('~/lib/utils/text_utility'); -(() => { - describe('text_utility', () => { - describe('gl.text.getTextWidth', () => { - it('returns zero width when no text is passed', () => { - expect(gl.text.getTextWidth('')).toBe(0); - }); +describe('text_utility', () => { + describe('gl.text.getTextWidth', () => { + it('returns zero width when no text is passed', () => { + expect(gl.text.getTextWidth('')).toBe(0); + }); - it('returns zero width when no text is passed and font is passed', () => { - expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0); - }); + it('returns zero width when no text is passed and font is passed', () => { + expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0); + }); - it('returns width when text is passed', () => { - expect(gl.text.getTextWidth('foo') > 0).toBe(true); - }); + it('returns width when text is passed', () => { + expect(gl.text.getTextWidth('foo') > 0).toBe(true); + }); - it('returns bigger width when font is larger', () => { - const largeFont = gl.text.getTextWidth('foo', '100px sans-serif'); - const regular = gl.text.getTextWidth('foo', '10px sans-serif'); - expect(largeFont > regular).toBe(true); - }); + it('returns bigger width when font is larger', () => { + const largeFont = gl.text.getTextWidth('foo', '100px sans-serif'); + const regular = gl.text.getTextWidth('foo', '10px sans-serif'); + expect(largeFont > regular).toBe(true); }); + }); - describe('gl.text.pluralize', () => { - it('returns pluralized', () => { - expect(gl.text.pluralize('test', 2)).toBe('tests'); - }); + describe('gl.text.pluralize', () => { + it('returns pluralized', () => { + expect(gl.text.pluralize('test', 2)).toBe('tests'); + }); - it('returns pluralized when count is 0', () => { - expect(gl.text.pluralize('test', 0)).toBe('tests'); - }); + it('returns pluralized when count is 0', () => { + expect(gl.text.pluralize('test', 0)).toBe('tests'); + }); - it('does not return pluralized', () => { - expect(gl.text.pluralize('test', 1)).toBe('test'); - }); + it('does not return pluralized', () => { + expect(gl.text.pluralize('test', 1)).toBe('test'); }); + }); - describe('gl.text.highCountTrim', () => { - it('returns 99+ for count >= 100', () => { - expect(gl.text.highCountTrim(105)).toBe('99+'); - expect(gl.text.highCountTrim(100)).toBe('99+'); - }); + describe('gl.text.highCountTrim', () => { + it('returns 99+ for count >= 100', () => { + expect(gl.text.highCountTrim(105)).toBe('99+'); + expect(gl.text.highCountTrim(100)).toBe('99+'); + }); - it('returns exact number for count < 100', () => { - expect(gl.text.highCountTrim(45)).toBe(45); - }); + it('returns exact number for count < 100', () => { + expect(gl.text.highCountTrim(45)).toBe(45); }); + }); - describe('gl.text.insertText', () => { - let textArea; + describe('gl.text.insertText', () => { + let textArea; - beforeAll(() => { - textArea = document.createElement('textarea'); - document.querySelector('body').appendChild(textArea); - }); + beforeAll(() => { + textArea = document.createElement('textarea'); + document.querySelector('body').appendChild(textArea); + }); - afterAll(() => { - textArea.parentNode.removeChild(textArea); - }); + afterAll(() => { + textArea.parentNode.removeChild(textArea); + }); - describe('without selection', () => { - it('inserts the tag on an empty line', () => { - const initialValue = ''; + describe('without selection', () => { + it('inserts the tag on an empty line', () => { + const initialValue = ''; - textArea.value = initialValue; - textArea.selectionStart = 0; - textArea.selectionEnd = 0; + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; - gl.text.insertText(textArea, textArea.value, '*', null, '', false); + gl.text.insertText(textArea, textArea.value, '*', null, '', false); - expect(textArea.value).toEqual(`${initialValue}* `); - }); + expect(textArea.value).toEqual(`${initialValue}* `); + }); - it('inserts the tag on a new line if the current one is not empty', () => { - const initialValue = 'some text'; + it('inserts the tag on a new line if the current one is not empty', () => { + const initialValue = 'some text'; - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - gl.text.insertText(textArea, textArea.value, '*', null, '', false); + gl.text.insertText(textArea, textArea.value, '*', null, '', false); - expect(textArea.value).toEqual(`${initialValue}\n* `); - }); + expect(textArea.value).toEqual(`${initialValue}\n* `); + }); - it('inserts the tag on the same line if the current line only contains spaces', () => { - const initialValue = ' '; + it('inserts the tag on the same line if the current line only contains spaces', () => { + const initialValue = ' '; - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - gl.text.insertText(textArea, textArea.value, '*', null, '', false); + gl.text.insertText(textArea, textArea.value, '*', null, '', false); - expect(textArea.value).toEqual(`${initialValue}* `); - }); + expect(textArea.value).toEqual(`${initialValue}* `); + }); - it('inserts the tag on the same line if the current line only contains tabs', () => { - const initialValue = '\t\t\t'; + it('inserts the tag on the same line if the current line only contains tabs', () => { + const initialValue = '\t\t\t'; - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - gl.text.insertText(textArea, textArea.value, '*', null, '', false); + gl.text.insertText(textArea, textArea.value, '*', null, '', false); - expect(textArea.value).toEqual(`${initialValue}* `); - }); + expect(textArea.value).toEqual(`${initialValue}* `); }); }); }); -})(); +}); diff --git a/spec/javascripts/merged_buttons_spec.js b/spec/javascripts/merged_buttons_spec.js new file mode 100644 index 00000000000..b5c5e60dd97 --- /dev/null +++ b/spec/javascripts/merged_buttons_spec.js @@ -0,0 +1,44 @@ +/* global MergedButtons */ + +import '~/merged_buttons'; + +describe('MergedButtons', () => { + const fixturesPath = 'merge_requests/merged_merge_request.html.raw'; + preloadFixtures(fixturesPath); + + beforeEach(() => { + loadFixtures(fixturesPath); + this.mergedButtons = new MergedButtons(); + this.$removeBranchWidget = $('.remove_source_branch_widget:not(.failed)'); + this.$removeBranchProgress = $('.remove_source_branch_in_progress'); + this.$removeBranchFailed = $('.remove_source_branch_widget.failed'); + this.$removeBranchButton = $('.remove_source_branch'); + }); + + describe('removeSourceBranch', () => { + it('shows loader', () => { + $('.remove_source_branch').trigger('click'); + expect(this.$removeBranchProgress).toBeVisible(); + expect(this.$removeBranchWidget).not.toBeVisible(); + }); + }); + + describe('removeBranchSuccess', () => { + it('refreshes page when branch removed', () => { + spyOn(gl.utils, 'refreshCurrentPage').and.stub(); + const response = { status: 200 }; + this.$removeBranchButton.trigger('ajax:success', response, 'xhr'); + expect(gl.utils.refreshCurrentPage).toHaveBeenCalled(); + }); + }); + + describe('removeBranchError', () => { + it('shows error message', () => { + const response = { status: 500 }; + this.$removeBranchButton.trigger('ajax:error', response, 'xhr'); + expect(this.$removeBranchFailed).toBeVisible(); + expect(this.$removeBranchProgress).not.toBeVisible(); + expect(this.$removeBranchWidget).not.toBeVisible(); + }); + }); +}); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index d81a5bbb6a5..ca8ee04d955 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -72,5 +72,157 @@ require('~/lib/utils/text_utility'); expect(this.autoSizeSpy).toHaveBeenTriggered(); }); }); + + describe('renderNote', () => { + let notes; + let note; + let $notesList; + + beforeEach(() => { + note = { + discussion_html: null, + valid: true, + html: '<div></div>', + }; + $notesList = jasmine.createSpyObj('$notesList', ['find']); + + notes = jasmine.createSpyObj('notes', [ + 'refresh', + 'isNewNote', + 'collapseLongCommitList', + 'updateNotesCount', + ]); + notes.taskList = jasmine.createSpyObj('tasklist', ['init']); + notes.note_ids = []; + + spyOn(window, '$').and.returnValue($notesList); + spyOn(gl.utils, 'localTimeAgo'); + spyOn(Notes, 'animateAppendNote'); + notes.isNewNote.and.returnValue(true); + + Notes.prototype.renderNote.call(notes, note); + }); + + it('should query for the notes list', () => { + expect(window.$).toHaveBeenCalledWith('ul.main-notes-list'); + }); + + it('should call .animateAppendNote', () => { + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList); + }); + }); + + describe('renderDiscussionNote', () => { + let discussionContainer; + let note; + let notes; + let $form; + let row; + + beforeEach(() => { + note = { + html: '<li></li>', + discussion_html: '<div></div>', + discussion_id: 1, + discussion_resolvable: false, + diff_discussion_html: false, + }; + $form = jasmine.createSpyObj('$form', ['closest', 'find']); + row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']); + + notes = jasmine.createSpyObj('notes', [ + 'isNewNote', + 'isParallelView', + 'updateNotesCount', + ]); + notes.note_ids = []; + + spyOn(gl.utils, 'localTimeAgo'); + spyOn(Notes, 'animateAppendNote'); + notes.isNewNote.and.returnValue(true); + notes.isParallelView.and.returnValue(false); + row.prevAll.and.returnValue(row); + row.first.and.returnValue(row); + row.find.and.returnValue(row); + }); + + describe('Discussion root note', () => { + let $notesList; + let body; + + beforeEach(() => { + body = jasmine.createSpyObj('body', ['attr']); + discussionContainer = { length: 0 }; + + spyOn(window, '$').and.returnValues(discussionContainer, body, $notesList); + $form.closest.and.returnValues(row, $form); + $form.find.and.returnValues(discussionContainer); + body.attr.and.returnValue(''); + + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + }); + + it('should query for the notes list', () => { + expect(window.$.calls.argsFor(2)).toEqual(['ul.main-notes-list']); + }); + + it('should call Notes.animateAppendNote', () => { + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.discussion_html, $notesList); + }); + }); + + describe('Discussion sub note', () => { + beforeEach(() => { + discussionContainer = { length: 1 }; + + spyOn(window, '$').and.returnValues(discussionContainer); + $form.closest.and.returnValues(row); + + Notes.prototype.renderDiscussionNote.call(notes, note, $form); + }); + + it('should query foor the discussion container', () => { + expect(window.$).toHaveBeenCalledWith(`.notes[data-discussion-id="${note.discussion_id}"]`); + }); + + it('should call Notes.animateAppendNote', () => { + expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer); + }); + }); + }); + + describe('animateAppendNote', () => { + let noteHTML; + let $note; + let $notesList; + + beforeEach(() => { + noteHTML = '<div></div>'; + $note = jasmine.createSpyObj('$note', ['addClass', 'renderGFM', 'removeClass']); + $notesList = jasmine.createSpyObj('$notesList', ['append']); + + spyOn(window, '$').and.returnValue($note); + spyOn(window, 'setTimeout').and.callThrough(); + $note.addClass.and.returnValue($note); + $note.renderGFM.and.returnValue($note); + + Notes.animateAppendNote(noteHTML, $notesList); + }); + + it('should init the note jquery object', () => { + expect(window.$).toHaveBeenCalledWith(noteHTML); + }); + + it('should call addClass', () => { + expect($note.addClass).toHaveBeenCalledWith('fade-in'); + }); + it('should call renderGFM', () => { + expect($note.renderGFM).toHaveBeenCalledWith(); + }); + + it('should append note to the notes list', () => { + expect($notesList.append).toHaveBeenCalledWith($note); + }); + }); }); }).call(window); diff --git a/spec/javascripts/vue_pipelines_index/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js index bc8e504c413..28c9c7ab282 100644 --- a/spec/javascripts/vue_pipelines_index/async_button_spec.js +++ b/spec/javascripts/pipelines/async_button_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import asyncButtonComp from '~/vue_pipelines_index/components/async_button'; +import asyncButtonComp from '~/pipelines/components/async_button.vue'; describe('Pipelines Async Button', () => { let component; diff --git a/spec/javascripts/vue_pipelines_index/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js index 733337168dc..bb47a28d9fe 100644 --- a/spec/javascripts/vue_pipelines_index/empty_state_spec.js +++ b/spec/javascripts/pipelines/empty_state_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import emptyStateComp from '~/vue_pipelines_index/components/empty_state'; +import emptyStateComp from '~/pipelines/components/empty_state.vue'; describe('Pipelines Empty State', () => { let component; diff --git a/spec/javascripts/vue_pipelines_index/error_state_spec.js b/spec/javascripts/pipelines/error_state_spec.js index 524e018b1fa..f667d351f72 100644 --- a/spec/javascripts/vue_pipelines_index/error_state_spec.js +++ b/spec/javascripts/pipelines/error_state_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import errorStateComp from '~/vue_pipelines_index/components/error_state'; +import errorStateComp from '~/pipelines/components/error_state.vue'; describe('Pipelines Error State', () => { let component; diff --git a/spec/javascripts/vue_pipelines_index/mock_data.js b/spec/javascripts/pipelines/mock_data.js index 2365a662b9f..2365a662b9f 100644 --- a/spec/javascripts/vue_pipelines_index/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js diff --git a/spec/javascripts/vue_pipelines_index/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js index 659c4854a56..601eebce38a 100644 --- a/spec/javascripts/vue_pipelines_index/nav_controls_spec.js +++ b/spec/javascripts/pipelines/nav_controls_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import navControlsComp from '~/vue_pipelines_index/components/nav_controls'; +import navControlsComp from '~/pipelines/components/nav_controls'; describe('Pipelines Nav Controls', () => { let NavControlsComponent; diff --git a/spec/javascripts/vue_pipelines_index/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 96a2a37b5f7..53931d67ad7 100644 --- a/spec/javascripts/vue_pipelines_index/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import pipelineUrlComp from '~/vue_pipelines_index/components/pipeline_url'; +import pipelineUrlComp from '~/pipelines/components/pipeline_url'; describe('Pipeline Url Component', () => { let PipelineUrlComponent; diff --git a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index 0910df61915..c89dacbcd93 100644 --- a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import pipelinesActionsComp from '~/vue_pipelines_index/components/pipelines_actions'; +import pipelinesActionsComp from '~/pipelines/components/pipelines_actions'; describe('Pipelines Actions dropdown', () => { let component; diff --git a/spec/javascripts/vue_pipelines_index/pipelines_artifacts_spec.js b/spec/javascripts/pipelines/pipelines_artifacts_spec.js index f7f49649c1c..9724b63d957 100644 --- a/spec/javascripts/vue_pipelines_index/pipelines_artifacts_spec.js +++ b/spec/javascripts/pipelines/pipelines_artifacts_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import artifactsComp from '~/vue_pipelines_index/components/pipelines_artifacts'; +import artifactsComp from '~/pipelines/components/pipelines_artifacts'; describe('Pipelines Artifacts dropdown', () => { let component; diff --git a/spec/javascripts/vue_pipelines_index/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index 725f6cb2d7a..e9c05f74ce6 100644 --- a/spec/javascripts/vue_pipelines_index/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import pipelinesComp from '~/vue_pipelines_index/pipelines'; -import Store from '~/vue_pipelines_index/stores/pipelines_store'; +import pipelinesComp from '~/pipelines/pipelines'; +import Store from '~/pipelines/stores/pipelines_store'; import pipelinesData from './mock_data'; describe('Pipelines', () => { diff --git a/spec/javascripts/vue_pipelines_index/pipelines_store_spec.js b/spec/javascripts/pipelines/pipelines_store_spec.js index 5c0934404bb..10ff0c6bb84 100644 --- a/spec/javascripts/vue_pipelines_index/pipelines_store_spec.js +++ b/spec/javascripts/pipelines/pipelines_store_spec.js @@ -1,4 +1,4 @@ -import PipelineStore from '~/vue_pipelines_index/stores/pipelines_store'; +import PipelineStore from '~/pipelines/stores/pipelines_store'; describe('Pipelines Store', () => { let store; diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js new file mode 100644 index 00000000000..66b57a82363 --- /dev/null +++ b/spec/javascripts/pipelines/stage_spec.js @@ -0,0 +1,66 @@ +import Vue from 'vue'; +import { SUCCESS_SVG } from '~/ci_status_icons'; +import Stage from '~/pipelines/components/stage'; + +function minify(string) { + return string.replace(/\s/g, ''); +} + +describe('Pipelines Stage', () => { + describe('data', () => { + let stageReturnValue; + + beforeEach(() => { + stageReturnValue = Stage.data(); + }); + + it('should return object with .builds and .spinner', () => { + expect(stageReturnValue).toEqual({ + builds: '', + spinner: '<span class="fa fa-spinner fa-spin"></span>', + }); + }); + }); + + describe('computed', () => { + describe('svgHTML', function () { + let stage; + let svgHTML; + + beforeEach(() => { + stage = { stage: { status: { icon: 'icon_status_success' } } }; + + svgHTML = Stage.computed.svgHTML.call(stage); + }); + + it("should return the correct icon for the stage's status", () => { + expect(svgHTML).toBe(SUCCESS_SVG); + }); + }); + }); + + describe('when mounted', () => { + let StageComponent; + let renderedComponent; + let stage; + + beforeEach(() => { + stage = { status: { icon: 'icon_status_success' } }; + + StageComponent = Vue.extend(Stage); + + renderedComponent = new StageComponent({ + propsData: { + stage, + }, + }).$mount(); + }); + + it('should render the correct status svg', () => { + const minifiedComponent = minify(renderedComponent.$el.outerHTML); + const expectedSVG = minify(SUCCESS_SVG); + + expect(minifiedComponent).toContain(expectedSVG); + }); + }); +}); diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js new file mode 100644 index 00000000000..9b8373df29e --- /dev/null +++ b/spec/javascripts/shortcuts_spec.js @@ -0,0 +1,45 @@ +/* global Shortcuts */ +describe('Shortcuts', () => { + const fixtureName = 'issues/issue_with_comment.html.raw'; + const createEvent = (type, target) => $.Event(type, { + target, + }); + + preloadFixtures(fixtureName); + + describe('toggleMarkdownPreview', () => { + let sc; + + beforeEach(() => { + loadFixtures(fixtureName); + + spyOnEvent('.js-new-note-form .js-md-preview-button', 'focus'); + spyOnEvent('.edit-note .js-md-preview-button', 'focus'); + + sc = new Shortcuts(); + }); + + it('focuses preview button in form', () => { + sc.toggleMarkdownPreview( + createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text'), + )); + + expect('focus').toHaveBeenTriggeredOn('.js-new-note-form .js-md-preview-button'); + }); + + it('focues preview button inside edit comment form', (done) => { + document.querySelector('.js-note-edit').click(); + + setTimeout(() => { + sc.toggleMarkdownPreview( + createEvent('KeyboardEvent', document.querySelector('.edit-note .js-note-text'), + )); + + expect('focus').not.toHaveBeenTriggeredOn('.js-new-note-form .js-md-preview-button'); + expect('focus').toHaveBeenTriggeredOn('.edit-note .js-md-preview-button'); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/user_callout_spec.js b/spec/javascripts/user_callout_spec.js index c0375ebc61c..28d0c7dcd99 100644 --- a/spec/javascripts/user_callout_spec.js +++ b/spec/javascripts/user_callout_spec.js @@ -14,7 +14,6 @@ describe('UserCallout', function () { this.userCallout = new UserCallout(); this.closeButton = $('.js-close-callout.close'); this.userCalloutBtn = $('.js-close-callout:not(.close)'); - this.userCalloutContainer = $('.user-callout'); }); it('hides when user clicks on the dismiss-icon', (done) => { |