diff options
Diffstat (limited to 'spec/javascripts')
98 files changed, 0 insertions, 16263 deletions
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js deleted file mode 100644 index 02200f77ad7..00000000000 --- a/spec/javascripts/awards_handler_spec.js +++ /dev/null @@ -1,400 +0,0 @@ -import $ from 'jquery'; -import Cookies from 'js-cookie'; -import loadAwardsHandler from '~/awards_handler'; -import '~/lib/utils/common_utils'; - -window.gl = window.gl || {}; -window.gon = window.gon || {}; - -let openAndWaitForEmojiMenu; -let awardsHandler = null; -const urlRoot = gon.relative_url_root; - -const lazyAssert = function(done, assertFn) { - setTimeout(function() { - assertFn(); - done(); - // Maybe jasmine.clock here? - }, 333); -}; - -describe('AwardsHandler', function() { - preloadFixtures('snippets/show.html'); - beforeEach(function(done) { - loadFixtures('snippets/show.html'); - loadAwardsHandler(true) - .then(obj => { - awardsHandler = obj; - spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb()); - done(); - }) - .catch(fail); - - let isEmojiMenuBuilt = false; - openAndWaitForEmojiMenu = function() { - return new Promise(resolve => { - if (isEmojiMenuBuilt) { - resolve(); - } else { - $('.js-add-award') - .eq(0) - .click(); - const $menu = $('.emoji-menu'); - $menu.one('build-emoji-menu-finish', () => { - isEmojiMenuBuilt = true; - resolve(); - }); - } - }); - }; - }); - - afterEach(function() { - // restore original url root value - gon.relative_url_root = urlRoot; - - // Undo what we did to the shared <body> - $('body').removeAttr('data-page'); - - awardsHandler.destroy(); - }); - - describe('::showEmojiMenu', function() { - it('should show emoji menu when Add emoji button clicked', function(done) { - $('.js-add-award') - .eq(0) - .click(); - lazyAssert(done, function() { - const $emojiMenu = $('.emoji-menu'); - - expect($emojiMenu.length).toBe(1); - expect($emojiMenu.hasClass('is-visible')).toBe(true); - expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1); - expect($('.js-awards-block.current').length).toBe(1); - }); - }); - - it('should also show emoji menu for the smiley icon in notes', function(done) { - $('.js-add-award.note-action-button').click(); - lazyAssert(done, function() { - const $emojiMenu = $('.emoji-menu'); - - expect($emojiMenu.length).toBe(1); - }); - }); - - it('should remove emoji menu when body is clicked', function(done) { - $('.js-add-award') - .eq(0) - .click(); - lazyAssert(done, function() { - const $emojiMenu = $('.emoji-menu'); - $('body').click(); - - expect($emojiMenu.length).toBe(1); - expect($emojiMenu.hasClass('is-visible')).toBe(false); - expect($('.js-awards-block.current').length).toBe(0); - }); - }); - - it('should not remove emoji menu when search is clicked', function(done) { - $('.js-add-award') - .eq(0) - .click(); - lazyAssert(done, function() { - const $emojiMenu = $('.emoji-menu'); - $('.emoji-search').click(); - - expect($emojiMenu.length).toBe(1); - expect($emojiMenu.hasClass('is-visible')).toBe(true); - expect($('.js-awards-block.current').length).toBe(1); - }); - }); - }); - - describe('::addAwardToEmojiBar', function() { - it('should add emoji to votes block', function() { - const $votesBlock = $('.js-awards-block').eq(0); - awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); - const $emojiButton = $votesBlock.find('[data-name=heart]'); - - expect($emojiButton.length).toBe(1); - expect($emojiButton.next('.js-counter').text()).toBe('1'); - expect($votesBlock.hasClass('hidden')).toBe(false); - }); - - it('should remove the emoji when we click again', function() { - const $votesBlock = $('.js-awards-block').eq(0); - awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); - awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); - const $emojiButton = $votesBlock.find('[data-name=heart]'); - - expect($emojiButton.length).toBe(0); - }); - - it('should decrement the emoji counter', function() { - const $votesBlock = $('.js-awards-block').eq(0); - awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); - const $emojiButton = $votesBlock.find('[data-name=heart]'); - $emojiButton.next('.js-counter').text(5); - awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false); - - expect($emojiButton.length).toBe(1); - expect($emojiButton.next('.js-counter').text()).toBe('4'); - }); - }); - - describe('::userAuthored', function() { - it('should update tooltip to user authored title', function() { - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'sam'); - awardsHandler.userAuthored($thumbsUpEmoji); - - expect($thumbsUpEmoji.data('originalTitle')).toBe( - 'You cannot vote on your own issue, MR and note', - ); - }); - - it('should restore tooltip back to initial vote list', function() { - jasmine.clock().install(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'sam'); - awardsHandler.userAuthored($thumbsUpEmoji); - jasmine.clock().tick(2801); - jasmine.clock().uninstall(); - - expect($thumbsUpEmoji.data('originalTitle')).toBe('sam'); - }); - }); - - describe('::getAwardUrl', function() { - it('returns the url for request', function() { - expect(awardsHandler.getAwardUrl()).toBe('http://test.host/snippets/1/toggle_award_emoji'); - }); - }); - - describe('::addAward and ::checkMutuality', function() { - it('should handle :+1: and :-1: mutuality', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - const $thumbsDownEmoji = $votesBlock.find('[data-name=thumbsdown]').parent(); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); - - expect($thumbsUpEmoji.hasClass('active')).toBe(true); - expect($thumbsDownEmoji.hasClass('active')).toBe(false); - $thumbsUpEmoji.tooltip(); - $thumbsDownEmoji.tooltip(); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsdown', true); - - expect($thumbsUpEmoji.hasClass('active')).toBe(false); - expect($thumbsDownEmoji.hasClass('active')).toBe(true); - }); - }); - - describe('::removeEmoji', function() { - it('should remove emoji', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - awardsHandler.addAward($votesBlock, awardUrl, 'fire', false); - - expect($votesBlock.find('[data-name=fire]').length).toBe(1); - awardsHandler.removeEmoji($votesBlock.find('[data-name=fire]').closest('button')); - - expect($votesBlock.find('[data-name=fire]').length).toBe(0); - }); - }); - - describe('::addYouToUserList', function() { - it('should prepend "You" to the award tooltip', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy'); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); - $thumbsUpEmoji.tooltip(); - - expect($thumbsUpEmoji.data('originalTitle')).toBe('You, sam, jerry, max, and andy'); - }); - - it('handles the special case where "You" is not cleanly comma separated', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'sam'); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); - $thumbsUpEmoji.tooltip(); - - expect($thumbsUpEmoji.data('originalTitle')).toBe('You and sam'); - }); - }); - - describe('::removeYouToUserList', function() { - it('removes "You" from the front of the tooltip', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'You, sam, jerry, max, and andy'); - $thumbsUpEmoji.addClass('active'); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); - $thumbsUpEmoji.tooltip(); - - expect($thumbsUpEmoji.data('originalTitle')).toBe('sam, jerry, max, and andy'); - }); - - it('handles the special case where "You" is not cleanly comma separated', function() { - const awardUrl = awardsHandler.getAwardUrl(); - const $votesBlock = $('.js-awards-block').eq(0); - const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent(); - $thumbsUpEmoji.attr('data-title', 'You and sam'); - $thumbsUpEmoji.addClass('active'); - awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false); - $thumbsUpEmoji.tooltip(); - - expect($thumbsUpEmoji.data('originalTitle')).toBe('sam'); - }); - }); - - describe('::searchEmojis', () => { - it('should filter the emoji', function(done) { - openAndWaitForEmojiMenu() - .then(() => { - expect($('[data-name=angel]').is(':visible')).toBe(true); - expect($('[data-name=anger]').is(':visible')).toBe(true); - 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) { - 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 => { - done.fail(`Failed to open and build emoji menu: ${err.message}`); - }); - }); - }); - - describe('emoji menu', function() { - const emojiSelector = '[data-name="sunglasses"]'; - const openEmojiMenuAndAddEmoji = function() { - return openAndWaitForEmojiMenu().then(() => { - const $menu = $('.emoji-menu'); - const $block = $('.js-awards-block'); - const $emoji = $menu.find(`.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`); - - expect($emoji.length).toBe(1); - expect($block.find(emojiSelector).length).toBe(0); - $emoji.click(); - - expect($menu.hasClass('.is-visible')).toBe(false); - expect($block.find(emojiSelector).length).toBe(1); - }); - }; - - it('should add selected emoji to awards block', function(done) { - openEmojiMenuAndAddEmoji() - .then(done) - .catch(err => { - done.fail(`Failed to open and build emoji menu: ${err.message}`); - }); - }); - - it('should remove already selected emoji', function(done) { - openEmojiMenuAndAddEmoji() - .then(() => { - $('.js-add-award') - .eq(0) - .click(); - const $block = $('.js-awards-block'); - const $emoji = $('.emoji-menu').find( - `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`, - ); - $emoji.click(); - - expect($block.find(emojiSelector).length).toBe(0); - }) - .then(done) - .catch(err => { - done.fail(`Failed to open and build emoji menu: ${err.message}`); - }); - }); - }); - - describe('frequently used emojis', function() { - beforeEach(() => { - // Clear it out - Cookies.set('frequently_used_emojis', ''); - }); - - it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', function(done) { - return openAndWaitForEmojiMenu() - .then(() => { - const emojiMenu = document.querySelector('.emoji-menu'); - Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => { - expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used'); - }); - }) - .then(done) - .catch(err => { - done.fail(`Failed to open and build emoji menu: ${err.message}`); - }); - }); - - it('should have any frequently used section when there are frequently used emojis', function(done) { - awardsHandler.addEmojiToFrequentlyUsedList('8ball'); - - return openAndWaitForEmojiMenu() - .then(() => { - const emojiMenu = document.querySelector('.emoji-menu'); - const hasFrequentlyUsedHeading = Array.prototype.some.call( - emojiMenu.querySelectorAll('.emoji-menu-title'), - title => title.textContent.trim().toLowerCase() === 'frequently used', - ); - - expect(hasFrequentlyUsedHeading).toBe(true); - }) - .then(done) - .catch(err => { - done.fail(`Failed to open and build emoji menu: ${err.message}`); - }); - }); - - it('should disregard invalid frequently used emoji that are being attempted to be added', function() { - awardsHandler.addEmojiToFrequentlyUsedList('8ball'); - awardsHandler.addEmojiToFrequentlyUsedList('invalid_emoji'); - awardsHandler.addEmojiToFrequentlyUsedList('grinning'); - - expect(awardsHandler.getFrequentlyUsedEmojis()).toEqual(['8ball', 'grinning']); - }); - - it('should disregard invalid frequently used emoji already set in cookie', function() { - Cookies.set('frequently_used_emojis', '8ball,invalid_emoji,grinning'); - - expect(awardsHandler.getFrequentlyUsedEmojis()).toEqual(['8ball', 'grinning']); - }); - }); -}); diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js deleted file mode 100644 index 59abae479d4..00000000000 --- a/spec/javascripts/behaviors/autosize_spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import $ from 'jquery'; -import '~/behaviors/autosize'; - -function load() { - $(document).trigger('load'); -} - -describe('Autosize behavior', () => { - beforeEach(() => { - setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>'); - }); - - it('does not overwrite the resize property', () => { - load(); - - expect($('textarea')).toHaveCss({ - resize: 'vertical', - }); - }); -}); diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js deleted file mode 100644 index d653fca0988..00000000000 --- a/spec/javascripts/behaviors/copy_as_gfm_spec.js +++ /dev/null @@ -1,125 +0,0 @@ -import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm'; - -describe('CopyAsGFM', () => { - describe('CopyAsGFM.pasteGFM', () => { - function callPasteGFM() { - const e = { - originalEvent: { - clipboardData: { - getData(mimeType) { - // When GFM code is copied, we put the regular plain text - // on the clipboard as `text/plain`, and the GFM as `text/x-gfm`. - // This emulates the behavior of `getData` with that data. - if (mimeType === 'text/plain') { - return 'code'; - } - if (mimeType === 'text/x-gfm') { - return '`code`'; - } - return null; - }, - }, - }, - preventDefault() {}, - }; - - CopyAsGFM.pasteGFM(e); - } - - it('wraps pasted code when not already in code tags', () => { - spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => { - const insertedText = textFunc('This is code: ', ''); - - expect(insertedText).toEqual('`code`'); - }); - - callPasteGFM(); - }); - - it('does not wrap pasted code when already in code tags', () => { - spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => { - const insertedText = textFunc('This is code: `', '`'); - - expect(insertedText).toEqual('code'); - }); - - callPasteGFM(); - }); - }); - - describe('CopyAsGFM.copyGFM', () => { - // Stub getSelection to return a purpose-built object. - const stubSelection = (html, parentNode) => ({ - getRangeAt: () => ({ - commonAncestorContainer: { tagName: parentNode }, - cloneContents: () => { - const fragment = document.createDocumentFragment(); - const node = document.createElement('div'); - node.innerHTML = html; - Array.from(node.childNodes).forEach(item => fragment.appendChild(item)); - return fragment; - }, - }), - rangeCount: 1, - }); - - const clipboardData = { - setData() {}, - }; - - const simulateCopy = () => { - const e = { - originalEvent: { - clipboardData, - }, - preventDefault() {}, - stopPropagation() {}, - }; - CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); - return clipboardData; - }; - - beforeAll(done => { - initCopyAsGFM(); - - // Fake call to nodeToGfm so the import of lazy bundle happened - CopyAsGFM.nodeToGFM(document.createElement('div')) - .then(() => { - done(); - }) - .catch(done.fail); - }); - - beforeEach(() => spyOn(clipboardData, 'setData')); - - describe('list handling', () => { - it('uses correct gfm for unordered lists', done => { - const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'UL'); - - spyOn(window, 'getSelection').and.returnValue(selection); - simulateCopy(); - - setTimeout(() => { - const expectedGFM = '* List Item1\n* List Item2'; - - expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); - done(); - }); - }); - - it('uses correct gfm for ordered lists', done => { - const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'OL'); - - spyOn(window, 'getSelection').and.returnValue(selection); - simulateCopy(); - - setTimeout(() => { - const expectedGFM = '1. List Item1\n1. List Item2'; - - expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); - done(); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js deleted file mode 100644 index f656b97fec2..00000000000 --- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import getUnicodeSupportMap from '~/emoji/support/unicode_support_map'; -import AccessorUtilities from '~/lib/utils/accessor'; - -describe('Unicode Support Map', () => { - describe('getUnicodeSupportMap', () => { - const stringSupportMap = 'stringSupportMap'; - - beforeEach(() => { - spyOn(AccessorUtilities, 'isLocalStorageAccessSafe'); - spyOn(window.localStorage, 'getItem'); - spyOn(window.localStorage, 'setItem'); - spyOn(JSON, 'parse'); - spyOn(JSON, 'stringify').and.returnValue(stringSupportMap); - }); - - describe('if isLocalStorageAvailable is `true`', function() { - beforeEach(() => { - AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(true); - - getUnicodeSupportMap(); - }); - - it('should call .getItem and .setItem', () => { - const getArgs = window.localStorage.getItem.calls.allArgs(); - const setArgs = window.localStorage.setItem.calls.allArgs(); - - expect(getArgs[0][0]).toBe('gl-emoji-version'); - expect(getArgs[1][0]).toBe('gl-emoji-user-agent'); - - expect(setArgs[0][0]).toBe('gl-emoji-version'); - expect(setArgs[0][1]).toBe('0.2.0'); - expect(setArgs[1][0]).toBe('gl-emoji-user-agent'); - expect(setArgs[1][1]).toBe(navigator.userAgent); - expect(setArgs[2][0]).toBe('gl-emoji-unicode-support-map'); - expect(setArgs[2][1]).toBe(stringSupportMap); - }); - }); - - describe('if isLocalStorageAvailable is `false`', function() { - beforeEach(() => { - AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(false); - - getUnicodeSupportMap(); - }); - - it('should not call .getItem or .setItem', () => { - expect(window.localStorage.getItem.calls.count()).toBe(1); - expect(window.localStorage.setItem).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/spec/javascripts/behaviors/markdown/highlight_current_user_spec.js b/spec/javascripts/behaviors/markdown/highlight_current_user_spec.js deleted file mode 100644 index 3305ddc412d..00000000000 --- a/spec/javascripts/behaviors/markdown/highlight_current_user_spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; - -describe('highlightCurrentUser', () => { - let rootElement; - let elements; - - beforeEach(() => { - setFixtures(` - <div id="dummy-root-element"> - <div data-user="1">@first</div> - <div data-user="2">@second</div> - </div> - `); - rootElement = document.getElementById('dummy-root-element'); - elements = rootElement.querySelectorAll('[data-user]'); - }); - - describe('without current user', () => { - beforeEach(() => { - window.gon = window.gon || {}; - window.gon.current_user_id = null; - }); - - afterEach(() => { - delete window.gon.current_user_id; - }); - - it('does not highlight the user', () => { - const initialHtml = rootElement.outerHTML; - - highlightCurrentUser(elements); - - expect(rootElement.outerHTML).toBe(initialHtml); - }); - }); - - describe('with current user', () => { - beforeEach(() => { - window.gon = window.gon || {}; - window.gon.current_user_id = 2; - }); - - afterEach(() => { - delete window.gon.current_user_id; - }); - - it('highlights current user', () => { - highlightCurrentUser(elements); - - expect(elements.length).toBe(2); - expect(elements[0]).not.toHaveClass('current-user'); - expect(elements[1]).toHaveClass('current-user'); - }); - }); -}); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js deleted file mode 100644 index 617fe49b059..00000000000 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import $ from 'jquery'; -import '~/behaviors/requires_input'; - -describe('requiresInput', () => { - let submitButton; - preloadFixtures('branches/new_branch.html'); - - beforeEach(() => { - loadFixtures('branches/new_branch.html'); - submitButton = $('button[type="submit"]'); - }); - - it('disables submit when any field is required', () => { - $('.js-requires-input').requiresInput(); - - expect(submitButton).toBeDisabled(); - }); - - it('enables submit when no field is required', () => { - $('*[required=required]').prop('required', false); - $('.js-requires-input').requiresInput(); - - expect(submitButton).not.toBeDisabled(); - }); - - it('enables submit when all required fields are pre-filled', () => { - $('*[required=required]').remove(); - $('.js-requires-input').requiresInput(); - - expect($('.submit')).not.toBeDisabled(); - }); - - it('enables submit when all required fields receive input', () => { - $('.js-requires-input').requiresInput(); - $('#required1') - .val('input1') - .change(); - - expect(submitButton).toBeDisabled(); - - $('#optional1') - .val('input1') - .change(); - - expect(submitButton).toBeDisabled(); - - $('#required2') - .val('input2') - .change(); - $('#required3') - .val('input3') - .change(); - $('#required4') - .val('input4') - .change(); - $('#required5') - .val('1') - .change(); - - expect($('.submit')).not.toBeDisabled(); - }); -}); diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js deleted file mode 100644 index f6232026915..00000000000 --- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js +++ /dev/null @@ -1,315 +0,0 @@ -/* eslint-disable - no-underscore-dangle -*/ - -import $ from 'jquery'; -import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm'; -import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; - -const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form'; - -describe('ShortcutsIssuable', function() { - const fixtureName = 'snippets/show.html'; - preloadFixtures(fixtureName); - - beforeAll(done => { - initCopyAsGFM(); - - // Fake call to nodeToGfm so the import of lazy bundle happened - CopyAsGFM.nodeToGFM(document.createElement('div')) - .then(() => { - done(); - }) - .catch(done.fail); - }); - - beforeEach(() => { - loadFixtures(fixtureName); - $('body').append( - `<div class="js-main-target-form"> - <textare class="js-vue-comment-form"></textare> - </div>`, - ); - document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); - this.shortcut = new ShortcutsIssuable(true); - }); - - afterEach(() => { - $(FORM_SELECTOR).remove(); - }); - - describe('replyWithSelectedText', () => { - // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML. - const stubSelection = (html, invalidNode) => { - ShortcutsIssuable.__Rewire__('getSelectedFragment', () => { - const documentFragment = document.createDocumentFragment(); - const node = document.createElement('div'); - - node.innerHTML = html; - if (!invalidNode) node.className = 'md'; - - documentFragment.appendChild(node); - return documentFragment; - }); - }; - - describe('with empty selection', () => { - it('does not return an error', () => { - ShortcutsIssuable.replyWithSelectedText(true); - - expect($(FORM_SELECTOR).val()).toBe(''); - }); - - it('triggers `focus`', () => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - expect(spy).toHaveBeenCalled(); - }); - }); - - describe('with any selection', () => { - beforeEach(() => { - stubSelection('<p>Selected text.</p>'); - }); - - it('leaves existing input intact', done => { - $(FORM_SELECTOR).val('This text was already here.'); - - expect($(FORM_SELECTOR).val()).toBe('This text was already here.'); - - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe( - 'This text was already here.\n\n> Selected text.\n\n', - ); - done(); - }); - }); - - it('triggers `input`', done => { - let triggered = false; - $(FORM_SELECTOR).on('input', () => { - triggered = true; - }); - - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(triggered).toBe(true); - done(); - }); - }); - - it('triggers `focus`', done => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('with a one-line selection', () => { - it('quotes the selection', done => { - stubSelection('<p>This text has been selected.</p>'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n'); - done(); - }); - }); - }); - - describe('with a multi-line selection', () => { - it('quotes the selected lines as a group', done => { - stubSelection( - '<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>', - ); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe( - '> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n', - ); - done(); - }); - }); - }); - - describe('with an invalid selection', () => { - beforeEach(() => { - stubSelection('<p>Selected text.</p>', true); - }); - - it('does not add anything to the input', done => { - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe(''); - done(); - }); - }); - - it('triggers `focus`', done => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('with a semi-valid selection', () => { - beforeEach(() => { - stubSelection('<div class="md">Selected text.</div><p>Invalid selected text.</p>', true); - }); - - it('only adds the valid part to the input', done => { - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n'); - done(); - }); - }); - - it('triggers `focus`', done => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - done(); - }); - }); - - it('triggers `input`', done => { - let triggered = false; - $(FORM_SELECTOR).on('input', () => { - triggered = true; - }); - - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(triggered).toBe(true); - done(); - }); - }); - }); - - describe('with a selection in a valid block', () => { - beforeEach(() => { - ShortcutsIssuable.__Rewire__('getSelectedFragment', () => { - const documentFragment = document.createDocumentFragment(); - const node = document.createElement('div'); - const originalNode = document.createElement('body'); - originalNode.innerHTML = `<div class="issue"> - <div class="otherElem">Text...</div> - <div class="md"><p><em>Selected text.</em></p></div> - </div>`; - documentFragment.originalNodes = [originalNode.querySelector('em')]; - - node.innerHTML = '<em>Selected text.</em>'; - - documentFragment.appendChild(node); - - return documentFragment; - }); - }); - - it('adds the quoted selection to the input', done => { - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n'); - done(); - }); - }); - - it('triggers `focus`', done => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - done(); - }); - }); - - it('triggers `input`', done => { - let triggered = false; - $(FORM_SELECTOR).on('input', () => { - triggered = true; - }); - - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(triggered).toBe(true); - done(); - }); - }); - }); - - describe('with a selection in an invalid block', () => { - beforeEach(() => { - ShortcutsIssuable.__Rewire__('getSelectedFragment', () => { - const documentFragment = document.createDocumentFragment(); - const node = document.createElement('div'); - const originalNode = document.createElement('body'); - originalNode.innerHTML = `<div class="issue"> - <div class="otherElem"><div><b>Selected text.</b></div></div> - <div class="md"><p><em>Valid text</em></p></div> - </div>`; - documentFragment.originalNodes = [originalNode.querySelector('b')]; - - node.innerHTML = '<b>Selected text.</b>'; - - documentFragment.appendChild(node); - - return documentFragment; - }); - }); - - it('does not add anything to the input', done => { - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe(''); - done(); - }); - }); - - it('triggers `focus`', done => { - const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('with a valid selection with no text content', () => { - it('returns the proper markdown', done => { - stubSelection('<img src="foo" alt="image" />'); - ShortcutsIssuable.replyWithSelectedText(true); - - setTimeout(() => { - expect($(FORM_SELECTOR).val()).toBe('> ![image](http://localhost:9876/foo)\n\n'); - - done(); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/boards/board_list_common_spec.js b/spec/javascripts/boards/board_list_common_spec.js deleted file mode 100644 index b51a82f2a35..00000000000 --- a/spec/javascripts/boards/board_list_common_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -/* global List */ -/* global ListIssue */ - -import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; -import Sortable from 'sortablejs'; -import axios from '~/lib/utils/axios_utils'; -import BoardList from '~/boards/components/board_list.vue'; - -import '~/boards/models/issue'; -import '~/boards/models/list'; -import { listObj, boardsMockInterceptor } from './mock_data'; -import store from '~/boards/stores'; -import boardsStore from '~/boards/stores/boards_store'; - -window.Sortable = Sortable; - -export default function createComponent({ - done, - listIssueProps = {}, - componentProps = {}, - listProps = {}, -}) { - const el = document.createElement('div'); - - document.body.appendChild(el); - const mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - boardsStore.create(); - - const BoardListComp = Vue.extend(BoardList); - const list = new List({ ...listObj, ...listProps }); - const issue = new ListIssue({ - title: 'Testing', - id: 1, - iid: 1, - confidential: false, - labels: [], - assignees: [], - ...listIssueProps, - }); - if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) { - list.issuesSize = 1; - } - list.issues.push(issue); - - const component = new BoardListComp({ - el, - store, - propsData: { - disabled: false, - list, - issues: list.issues, - loading: false, - issueLinkBase: '/issues', - rootPath: '/', - ...componentProps, - }, - }).$mount(); - - Vue.nextTick(() => { - done(); - }); - - return { component, mock }; -} diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js deleted file mode 100644 index f2eb08fa198..00000000000 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ /dev/null @@ -1,171 +0,0 @@ -/* eslint-disable no-new */ -import { clone } from 'lodash'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Sidebar from '~/right_sidebar'; -import timeoutPromise from './helpers/set_timeout_promise_helper'; - -describe('Issuable right sidebar collapsed todo toggle', () => { - const fixtureName = 'issues/open-issue.html'; - const jsonFixtureName = 'todos/todos.json'; - let mock; - - preloadFixtures(fixtureName); - preloadFixtures(jsonFixtureName); - - beforeEach(() => { - const todoData = getJSONFixture(jsonFixtureName); - new Sidebar(); - loadFixtures(fixtureName); - - document.querySelector('.js-right-sidebar').classList.toggle('right-sidebar-expanded'); - document.querySelector('.js-right-sidebar').classList.toggle('right-sidebar-collapsed'); - - mock = new MockAdapter(axios); - - mock.onPost(`${gl.TEST_HOST}/frontend-fixtures/issues-project/todos`).reply(() => { - const response = clone(todoData); - - return [200, response]; - }); - - mock.onDelete(/(.*)\/dashboard\/todos\/\d+$/).reply(() => { - const response = clone(todoData); - delete response.delete_path; - - return [200, response]; - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('shows add todo button', () => { - expect(document.querySelector('.js-issuable-todo.sidebar-collapsed-icon')).not.toBeNull(); - - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg use') - .getAttribute('xlink:href'), - ).toContain('todo-add'); - - expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), - ).toBeNull(); - }); - - it('sets default tooltip title', () => { - expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('title'), - ).toBe('Add a To Do'); - }); - - it('toggle todo state', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - setTimeout(() => { - expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), - ).not.toBeNull(); - - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon svg.todo-undone use') - .getAttribute('xlink:href'), - ).toContain('todo-done'); - - done(); - }); - }); - - it('toggle todo state of expanded todo toggle', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - setTimeout(() => { - expect( - document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(), - ).toBe('Mark as done'); - - done(); - }); - }); - - it('toggles todo button tooltip', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - setTimeout(() => { - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon') - .getAttribute('data-original-title'), - ).toBe('Mark as done'); - - done(); - }); - }); - - it('marks todo as done', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - timeoutPromise() - .then(() => { - expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), - ).not.toBeNull(); - - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - }) - .then(timeoutPromise) - .then(() => { - expect( - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'), - ).toBeNull(); - - expect( - document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(), - ).toBe('Add a To Do'); - }) - .then(done) - .catch(done.fail); - }); - - it('updates aria-label to Mark as done', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - setTimeout(() => { - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon') - .getAttribute('aria-label'), - ).toBe('Mark as done'); - - done(); - }); - }); - - it('updates aria-label to add todo', done => { - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - - timeoutPromise() - .then(() => { - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon') - .getAttribute('aria-label'), - ).toBe('Mark as done'); - - document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click(); - }) - .then(timeoutPromise) - .then(() => { - expect( - document - .querySelector('.js-issuable-todo.sidebar-collapsed-icon') - .getAttribute('aria-label'), - ).toBe('Add a To Do'); - }) - .then(done) - .catch(done.fail); - }); -}); diff --git a/spec/javascripts/comment_type_toggle_spec.js b/spec/javascripts/comment_type_toggle_spec.js deleted file mode 100644 index 8b1217a000f..00000000000 --- a/spec/javascripts/comment_type_toggle_spec.js +++ /dev/null @@ -1,168 +0,0 @@ -import CommentTypeToggle from '~/comment_type_toggle'; -import InputSetter from '~/droplab/plugins/input_setter'; - -describe('CommentTypeToggle', function() { - describe('class constructor', function() { - beforeEach(function() { - this.dropdownTrigger = {}; - this.dropdownList = {}; - this.noteTypeInput = {}; - this.submitButton = {}; - this.closeButton = {}; - - this.commentTypeToggle = new CommentTypeToggle({ - dropdownTrigger: this.dropdownTrigger, - dropdownList: this.dropdownList, - noteTypeInput: this.noteTypeInput, - submitButton: this.submitButton, - closeButton: this.closeButton, - }); - }); - - it('should set .dropdownTrigger', function() { - expect(this.commentTypeToggle.dropdownTrigger).toBe(this.dropdownTrigger); - }); - - it('should set .dropdownList', function() { - expect(this.commentTypeToggle.dropdownList).toBe(this.dropdownList); - }); - - it('should set .noteTypeInput', function() { - expect(this.commentTypeToggle.noteTypeInput).toBe(this.noteTypeInput); - }); - - it('should set .submitButton', function() { - expect(this.commentTypeToggle.submitButton).toBe(this.submitButton); - }); - - it('should set .closeButton', function() { - expect(this.commentTypeToggle.closeButton).toBe(this.closeButton); - }); - - it('should set .reopenButton', function() { - expect(this.commentTypeToggle.reopenButton).toBe(this.reopenButton); - }); - }); - - describe('initDroplab', function() { - beforeEach(function() { - this.commentTypeToggle = { - dropdownTrigger: {}, - dropdownList: {}, - noteTypeInput: {}, - submitButton: {}, - closeButton: {}, - setConfig: () => {}, - }; - this.config = {}; - - this.droplab = jasmine.createSpyObj('droplab', ['init']); - - this.droplabConstructor = spyOnDependency(CommentTypeToggle, 'DropLab').and.returnValue( - this.droplab, - ); - spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config); - - CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle); - }); - - it('should instantiate a DropLab instance', function() { - expect(this.droplabConstructor).toHaveBeenCalled(); - }); - - it('should set .droplab', function() { - expect(this.commentTypeToggle.droplab).toBe(this.droplab); - }); - - it('should call .setConfig', function() { - expect(this.commentTypeToggle.setConfig).toHaveBeenCalled(); - }); - - it('should call DropLab.prototype.init', function() { - expect(this.droplab.init).toHaveBeenCalledWith( - this.commentTypeToggle.dropdownTrigger, - this.commentTypeToggle.dropdownList, - [InputSetter], - this.config, - ); - }); - }); - - describe('setConfig', function() { - describe('if no .closeButton is provided', function() { - beforeEach(function() { - this.commentTypeToggle = { - dropdownTrigger: {}, - dropdownList: {}, - noteTypeInput: {}, - submitButton: {}, - reopenButton: {}, - }; - - this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle); - }); - - it('should not add .closeButton related InputSetter config', function() { - expect(this.setConfig).toEqual({ - InputSetter: [ - { - input: this.commentTypeToggle.noteTypeInput, - valueAttribute: 'data-value', - }, - { - input: this.commentTypeToggle.submitButton, - valueAttribute: 'data-submit-text', - }, - { - input: this.commentTypeToggle.reopenButton, - valueAttribute: 'data-reopen-text', - }, - { - input: this.commentTypeToggle.reopenButton, - valueAttribute: 'data-reopen-text', - inputAttribute: 'data-alternative-text', - }, - ], - }); - }); - }); - - describe('if no .reopenButton is provided', function() { - beforeEach(function() { - this.commentTypeToggle = { - dropdownTrigger: {}, - dropdownList: {}, - noteTypeInput: {}, - submitButton: {}, - closeButton: {}, - }; - - this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle); - }); - - it('should not add .reopenButton related InputSetter config', function() { - expect(this.setConfig).toEqual({ - InputSetter: [ - { - input: this.commentTypeToggle.noteTypeInput, - valueAttribute: 'data-value', - }, - { - input: this.commentTypeToggle.submitButton, - valueAttribute: 'data-submit-text', - }, - { - input: this.commentTypeToggle.closeButton, - valueAttribute: 'data-close-text', - }, - { - input: this.commentTypeToggle.closeButton, - valueAttribute: 'data-close-text', - inputAttribute: 'data-alternative-text', - }, - ], - }); - }); - }); - }); -}); diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js deleted file mode 100644 index 22346c10547..00000000000 --- a/spec/javascripts/droplab/drop_down_spec.js +++ /dev/null @@ -1,650 +0,0 @@ -import DropDown from '~/droplab/drop_down'; -import utils from '~/droplab/utils'; -import { SELECTED_CLASS } from '~/droplab/constants'; - -describe('DropLab DropDown', function() { - describe('class constructor', function() { - beforeEach(function() { - spyOn(DropDown.prototype, 'getItems'); - spyOn(DropDown.prototype, 'initTemplateString'); - spyOn(DropDown.prototype, 'addEvents'); - - this.list = { innerHTML: 'innerHTML' }; - this.dropdown = new DropDown(this.list); - }); - - it('sets the .hidden property to true', function() { - expect(this.dropdown.hidden).toBe(true); - }); - - it('sets the .list property', function() { - expect(this.dropdown.list).toBe(this.list); - }); - - it('calls .getItems', function() { - expect(DropDown.prototype.getItems).toHaveBeenCalled(); - }); - - it('calls .initTemplateString', function() { - expect(DropDown.prototype.initTemplateString).toHaveBeenCalled(); - }); - - it('calls .addEvents', function() { - expect(DropDown.prototype.addEvents).toHaveBeenCalled(); - }); - - it('sets the .initialState property to the .list.innerHTML', function() { - expect(this.dropdown.initialState).toBe(this.list.innerHTML); - }); - - describe('if the list argument is a string', function() { - beforeEach(function() { - this.element = {}; - this.selector = '.selector'; - - spyOn(Document.prototype, 'querySelector').and.returnValue(this.element); - - this.dropdown = new DropDown(this.selector); - }); - - it('calls .querySelector with the selector string', function() { - expect(Document.prototype.querySelector).toHaveBeenCalledWith(this.selector); - }); - - it('sets the .list property element', function() { - expect(this.dropdown.list).toBe(this.element); - }); - }); - }); - - describe('getItems', function() { - beforeEach(function() { - this.list = { querySelectorAll: () => {} }; - this.dropdown = { list: this.list }; - this.nodeList = []; - - spyOn(this.list, 'querySelectorAll').and.returnValue(this.nodeList); - - this.getItems = DropDown.prototype.getItems.call(this.dropdown); - }); - - it('calls .querySelectorAll with a list item query', function() { - expect(this.list.querySelectorAll).toHaveBeenCalledWith('li'); - }); - - it('sets the .items property to the returned list items', function() { - expect(this.dropdown.items).toEqual(jasmine.any(Array)); - }); - - it('returns the .items', function() { - expect(this.getItems).toEqual(jasmine.any(Array)); - }); - }); - - describe('initTemplateString', function() { - beforeEach(function() { - this.items = [{ outerHTML: '<a></a>' }, { outerHTML: '<img>' }]; - this.dropdown = { items: this.items }; - - DropDown.prototype.initTemplateString.call(this.dropdown); - }); - - it('should set .templateString to the last items .outerHTML', function() { - expect(this.dropdown.templateString).toBe(this.items[1].outerHTML); - }); - - it('should not set .templateString to a non-last items .outerHTML', function() { - expect(this.dropdown.templateString).not.toBe(this.items[0].outerHTML); - }); - - describe('if .items is not set', function() { - beforeEach(function() { - this.dropdown = { getItems: () => {} }; - - spyOn(this.dropdown, 'getItems').and.returnValue([]); - - DropDown.prototype.initTemplateString.call(this.dropdown); - }); - - it('should call .getItems', function() { - expect(this.dropdown.getItems).toHaveBeenCalled(); - }); - }); - - describe('if items array is empty', function() { - beforeEach(function() { - this.dropdown = { items: [] }; - - DropDown.prototype.initTemplateString.call(this.dropdown); - }); - - it('should set .templateString to an empty string', function() { - expect(this.dropdown.templateString).toBe(''); - }); - }); - }); - - describe('clickEvent', function() { - beforeEach(function() { - this.classList = jasmine.createSpyObj('classList', ['contains']); - this.list = { dispatchEvent: () => {} }; - this.dropdown = { - hideOnClick: true, - hide: () => {}, - list: this.list, - addSelectedClass: () => {}, - }; - this.event = { - preventDefault: () => {}, - target: { - classList: this.classList, - closest: () => null, - }, - }; - this.customEvent = {}; - this.dummyListItem = document.createElement('li'); - spyOn(this.event.target, 'closest').and.callFake(selector => { - if (selector === 'li') { - return this.dummyListItem; - } - - return null; - }); - - spyOn(this.dropdown, 'hide'); - spyOn(this.dropdown, 'addSelectedClass'); - spyOn(this.list, 'dispatchEvent'); - spyOn(this.event, 'preventDefault'); - spyOn(window, 'CustomEvent').and.returnValue(this.customEvent); - this.classList.contains.and.returnValue(false); - }); - - it('should call event.target.closest', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.event.target.closest).toHaveBeenCalledWith('.droplab-item-ignore'); - expect(this.event.target.closest).toHaveBeenCalledWith('li'); - }); - - it('should call addSelectedClass', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.dummyListItem); - }); - - it('should call .preventDefault', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.event.preventDefault).toHaveBeenCalled(); - }); - - it('should call .hide', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.dropdown.hide).toHaveBeenCalled(); - }); - - it('should construct CustomEvent', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object)); - }); - - it('should call .dispatchEvent with the customEvent', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent); - }); - - describe('if the target is a UL element', function() { - beforeEach(function() { - this.event.target = document.createElement('ul'); - - spyOn(this.event.target, 'closest'); - }); - - it('should return immediately', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.event.target.closest).not.toHaveBeenCalled(); - expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); - }); - }); - - describe('if the target has the droplab-item-ignore class', function() { - beforeEach(function() { - this.ignoredButton = document.createElement('button'); - this.ignoredButton.classList.add('droplab-item-ignore'); - this.event.target = this.ignoredButton; - - spyOn(this.ignoredButton, 'closest').and.callThrough(); - }); - - it('does not select element', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.ignoredButton.closest.calls.count()).toBe(1); - expect(this.ignoredButton.closest).toHaveBeenCalledWith('.droplab-item-ignore'); - expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); - }); - }); - - describe('if no selected element exists', function() { - beforeEach(function() { - this.event.preventDefault.calls.reset(); - this.dummyListItem = null; - }); - - it('should return before .preventDefault is called', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.event.preventDefault).not.toHaveBeenCalled(); - expect(this.dropdown.addSelectedClass).not.toHaveBeenCalled(); - }); - }); - - describe('if hideOnClick is false', () => { - beforeEach(function() { - this.dropdown.hideOnClick = false; - this.dropdown.hide.calls.reset(); - }); - - it('should not call .hide', function() { - DropDown.prototype.clickEvent.call(this.dropdown, this.event); - - expect(this.dropdown.hide).not.toHaveBeenCalled(); - }); - }); - }); - - describe('addSelectedClass', function() { - beforeEach(function() { - this.items = Array(4).forEach((item, i) => { - this.items[i] = { classList: { add: () => {} } }; - spyOn(this.items[i].classList, 'add'); - }); - this.selected = { classList: { add: () => {} } }; - this.dropdown = { removeSelectedClasses: () => {} }; - - spyOn(this.dropdown, 'removeSelectedClasses'); - spyOn(this.selected.classList, 'add'); - - DropDown.prototype.addSelectedClass.call(this.dropdown, this.selected); - }); - - it('should call .removeSelectedClasses', function() { - expect(this.dropdown.removeSelectedClasses).toHaveBeenCalled(); - }); - - it('should call .classList.add', function() { - expect(this.selected.classList.add).toHaveBeenCalledWith(SELECTED_CLASS); - }); - }); - - describe('removeSelectedClasses', function() { - beforeEach(function() { - this.items = Array(4); - this.items.forEach((item, i) => { - this.items[i] = { classList: { add: () => {} } }; - spyOn(this.items[i].classList, 'add'); - }); - this.dropdown = { items: this.items }; - - DropDown.prototype.removeSelectedClasses.call(this.dropdown); - }); - - it('should call .classList.remove for all items', function() { - this.items.forEach((item, i) => { - expect(this.items[i].classList.add).toHaveBeenCalledWith(SELECTED_CLASS); - }); - }); - - describe('if .items is not set', function() { - beforeEach(function() { - this.dropdown = { getItems: () => {} }; - - spyOn(this.dropdown, 'getItems').and.returnValue([]); - - DropDown.prototype.removeSelectedClasses.call(this.dropdown); - }); - - it('should call .getItems', function() { - expect(this.dropdown.getItems).toHaveBeenCalled(); - }); - }); - }); - - describe('addEvents', function() { - beforeEach(function() { - this.list = { - addEventListener: () => {}, - querySelectorAll: () => [], - }; - this.dropdown = { - list: this.list, - clickEvent: () => {}, - closeDropdown: () => {}, - eventWrapper: {}, - }; - }); - - it('should call .addEventListener', function() { - spyOn(this.list, 'addEventListener'); - - DropDown.prototype.addEvents.call(this.dropdown); - - expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function)); - expect(this.list.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function)); - }); - }); - - describe('setData', function() { - beforeEach(function() { - this.dropdown = { render: () => {} }; - this.data = ['data']; - - spyOn(this.dropdown, 'render'); - - DropDown.prototype.setData.call(this.dropdown, this.data); - }); - - it('should set .data', function() { - expect(this.dropdown.data).toBe(this.data); - }); - - it('should call .render with the .data', function() { - expect(this.dropdown.render).toHaveBeenCalledWith(this.data); - }); - }); - - describe('addData', function() { - beforeEach(function() { - this.dropdown = { render: () => {}, data: ['data1'] }; - this.data = ['data2']; - - spyOn(this.dropdown, 'render'); - spyOn(Array.prototype, 'concat').and.callThrough(); - - DropDown.prototype.addData.call(this.dropdown, this.data); - }); - - it('should call .concat with data', function() { - expect(Array.prototype.concat).toHaveBeenCalledWith(this.data); - }); - - it('should set .data with concatination', function() { - expect(this.dropdown.data).toEqual(['data1', 'data2']); - }); - - it('should call .render with the .data', function() { - expect(this.dropdown.render).toHaveBeenCalledWith(['data1', 'data2']); - }); - - describe('if .data is undefined', function() { - beforeEach(function() { - this.dropdown = { render: () => {}, data: undefined }; - this.data = ['data2']; - - spyOn(this.dropdown, 'render'); - - DropDown.prototype.addData.call(this.dropdown, this.data); - }); - - it('should set .data with concatination', function() { - expect(this.dropdown.data).toEqual(['data2']); - }); - }); - }); - - describe('render', function() { - beforeEach(function() { - this.renderableList = {}; - this.list = { - querySelector: q => { - if (q === '.filter-dropdown-loading') { - return false; - } - return this.renderableList; - }, - dispatchEvent: () => {}, - }; - this.dropdown = { renderChildren: () => {}, list: this.list }; - this.data = [0, 1]; - this.customEvent = {}; - - spyOn(this.dropdown, 'renderChildren').and.callFake(data => data); - spyOn(this.list, 'dispatchEvent'); - spyOn(this.data, 'map').and.callThrough(); - spyOn(window, 'CustomEvent').and.returnValue(this.customEvent); - - DropDown.prototype.render.call(this.dropdown, this.data); - }); - - it('should call .map', function() { - expect(this.data.map).toHaveBeenCalledWith(jasmine.any(Function)); - }); - - it('should call .renderChildren for each data item', function() { - expect(this.dropdown.renderChildren.calls.count()).toBe(this.data.length); - }); - - it('sets the renderableList .innerHTML', function() { - expect(this.renderableList.innerHTML).toBe('01'); - }); - - it('should call render.dl', function() { - expect(window.CustomEvent).toHaveBeenCalledWith('render.dl', jasmine.any(Object)); - }); - - it('should call dispatchEvent with the customEvent', function() { - expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent); - }); - - describe('if no data argument is passed', function() { - beforeEach(function() { - this.data.map.calls.reset(); - this.dropdown.renderChildren.calls.reset(); - - DropDown.prototype.render.call(this.dropdown, undefined); - }); - - it('should not call .map', function() { - expect(this.data.map).not.toHaveBeenCalled(); - }); - - it('should not call .renderChildren', function() { - expect(this.dropdown.renderChildren).not.toHaveBeenCalled(); - }); - }); - - describe('if no dynamic list is present', function() { - beforeEach(function() { - this.list = { querySelector: () => {}, dispatchEvent: () => {} }; - this.dropdown = { renderChildren: () => {}, list: this.list }; - this.data = [0, 1]; - - spyOn(this.dropdown, 'renderChildren').and.callFake(data => data); - spyOn(this.list, 'querySelector'); - spyOn(this.data, 'map').and.callThrough(); - - DropDown.prototype.render.call(this.dropdown, this.data); - }); - - it('sets the .list .innerHTML', function() { - expect(this.list.innerHTML).toBe('01'); - }); - }); - }); - - describe('renderChildren', function() { - beforeEach(function() { - this.templateString = 'templateString'; - this.dropdown = { templateString: this.templateString }; - this.data = { droplab_hidden: true }; - this.html = 'html'; - this.template = { firstChild: { outerHTML: 'outerHTML', style: {} } }; - - spyOn(utils, 'template').and.returnValue(this.html); - spyOn(document, 'createElement').and.returnValue(this.template); - spyOn(DropDown, 'setImagesSrc'); - - this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data); - }); - - it('should call utils.t with .templateString and data', function() { - expect(utils.template).toHaveBeenCalledWith(this.templateString, this.data); - }); - - it('should call document.createElement', function() { - expect(document.createElement).toHaveBeenCalledWith('div'); - }); - - it('should set the templates .innerHTML to the HTML', function() { - expect(this.template.innerHTML).toBe(this.html); - }); - - it('should call .setImagesSrc with the template', function() { - expect(DropDown.setImagesSrc).toHaveBeenCalledWith(this.template); - }); - - it('should set the template display to none', function() { - expect(this.template.firstChild.style.display).toBe('none'); - }); - - it('should return the templates .firstChild.outerHTML', function() { - expect(this.renderChildren).toBe(this.template.firstChild.outerHTML); - }); - - describe('if droplab_hidden is false', function() { - beforeEach(function() { - this.data = { droplab_hidden: false }; - this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data); - }); - - it('should set the template display to block', function() { - expect(this.template.firstChild.style.display).toBe('block'); - }); - }); - }); - - describe('setImagesSrc', function() { - beforeEach(function() { - this.template = { querySelectorAll: () => {} }; - - spyOn(this.template, 'querySelectorAll').and.returnValue([]); - - DropDown.setImagesSrc(this.template); - }); - - it('should call .querySelectorAll', function() { - expect(this.template.querySelectorAll).toHaveBeenCalledWith('img[data-src]'); - }); - }); - - describe('show', function() { - beforeEach(function() { - this.list = { style: {} }; - this.dropdown = { list: this.list, hidden: true }; - - DropDown.prototype.show.call(this.dropdown); - }); - - it('it should set .list display to block', function() { - expect(this.list.style.display).toBe('block'); - }); - - it('it should set .hidden to false', function() { - expect(this.dropdown.hidden).toBe(false); - }); - - describe('if .hidden is false', function() { - beforeEach(function() { - this.list = { style: {} }; - this.dropdown = { list: this.list, hidden: false }; - - this.show = DropDown.prototype.show.call(this.dropdown); - }); - - it('should return undefined', function() { - expect(this.show).toEqual(undefined); - }); - - it('should not set .list display to block', function() { - expect(this.list.style.display).not.toEqual('block'); - }); - }); - }); - - describe('hide', function() { - beforeEach(function() { - this.list = { style: {} }; - this.dropdown = { list: this.list }; - - DropDown.prototype.hide.call(this.dropdown); - }); - - it('it should set .list display to none', function() { - expect(this.list.style.display).toBe('none'); - }); - - it('it should set .hidden to true', function() { - expect(this.dropdown.hidden).toBe(true); - }); - }); - - describe('toggle', function() { - beforeEach(function() { - this.hidden = true; - this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} }; - - spyOn(this.dropdown, 'show'); - spyOn(this.dropdown, 'hide'); - - DropDown.prototype.toggle.call(this.dropdown); - }); - - it('should call .show', function() { - expect(this.dropdown.show).toHaveBeenCalled(); - }); - - describe('if .hidden is false', function() { - beforeEach(function() { - this.hidden = false; - this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} }; - - spyOn(this.dropdown, 'show'); - spyOn(this.dropdown, 'hide'); - - DropDown.prototype.toggle.call(this.dropdown); - }); - - it('should call .hide', function() { - expect(this.dropdown.hide).toHaveBeenCalled(); - }); - }); - }); - - describe('destroy', function() { - beforeEach(function() { - this.list = { removeEventListener: () => {} }; - this.eventWrapper = { clickEvent: 'clickEvent' }; - this.dropdown = { list: this.list, hide: () => {}, eventWrapper: this.eventWrapper }; - - spyOn(this.list, 'removeEventListener'); - spyOn(this.dropdown, 'hide'); - - DropDown.prototype.destroy.call(this.dropdown); - }); - - it('it should call .hide', function() { - expect(this.dropdown.hide).toHaveBeenCalled(); - }); - - it('it should call .removeEventListener', function() { - expect(this.list.removeEventListener).toHaveBeenCalledWith( - 'click', - this.eventWrapper.clickEvent, - ); - }); - }); -}); diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js deleted file mode 100644 index 40470436f19..00000000000 --- a/spec/javascripts/droplab/hook_spec.js +++ /dev/null @@ -1,73 +0,0 @@ -import Hook from '~/droplab/hook'; - -describe('Hook', function() { - describe('class constructor', function() { - beforeEach(function() { - this.trigger = { id: 'id' }; - this.list = {}; - this.plugins = {}; - this.config = {}; - this.dropdown = {}; - - this.dropdownConstructor = spyOnDependency(Hook, 'DropDown').and.returnValue(this.dropdown); - - this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); - }); - - it('should set .trigger', function() { - expect(this.hook.trigger).toBe(this.trigger); - }); - - it('should set .list', function() { - expect(this.hook.list).toBe(this.dropdown); - }); - - it('should call DropDown constructor', function() { - expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config); - }); - - it('should set .type', function() { - expect(this.hook.type).toBe('Hook'); - }); - - it('should set .event', function() { - expect(this.hook.event).toBe('click'); - }); - - it('should set .plugins', function() { - expect(this.hook.plugins).toBe(this.plugins); - }); - - it('should set .config', function() { - expect(this.hook.config).toBe(this.config); - }); - - it('should set .id', function() { - expect(this.hook.id).toBe(this.trigger.id); - }); - - describe('if config argument is undefined', function() { - beforeEach(function() { - this.config = undefined; - - this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); - }); - - it('should set .config to an empty object', function() { - expect(this.hook.config).toEqual({}); - }); - }); - - describe('if plugins argument is undefined', function() { - beforeEach(function() { - this.plugins = undefined; - - this.hook = new Hook(this.trigger, this.list, this.plugins, this.config); - }); - - it('should set .plugins to an empty array', function() { - expect(this.hook.plugins).toEqual([]); - }); - }); - }); -}); diff --git a/spec/javascripts/droplab/plugins/input_setter_spec.js b/spec/javascripts/droplab/plugins/input_setter_spec.js deleted file mode 100644 index 711e0486bff..00000000000 --- a/spec/javascripts/droplab/plugins/input_setter_spec.js +++ /dev/null @@ -1,214 +0,0 @@ -import InputSetter from '~/droplab/plugins/input_setter'; - -describe('InputSetter', function() { - describe('init', function() { - beforeEach(function() { - this.config = { InputSetter: {} }; - this.hook = { config: this.config }; - this.inputSetter = jasmine.createSpyObj('inputSetter', ['addEvents']); - - InputSetter.init.call(this.inputSetter, this.hook); - }); - - it('should set .hook', function() { - expect(this.inputSetter.hook).toBe(this.hook); - }); - - it('should set .config', function() { - expect(this.inputSetter.config).toBe(this.config.InputSetter); - }); - - it('should set .eventWrapper', function() { - expect(this.inputSetter.eventWrapper).toEqual({}); - }); - - it('should call .addEvents', function() { - expect(this.inputSetter.addEvents).toHaveBeenCalled(); - }); - - describe('if config.InputSetter is not set', function() { - beforeEach(function() { - this.config = { InputSetter: undefined }; - this.hook = { config: this.config }; - - InputSetter.init.call(this.inputSetter, this.hook); - }); - - it('should set .config to an empty object', function() { - expect(this.inputSetter.config).toEqual({}); - }); - - it('should set hook.config to an empty object', function() { - expect(this.hook.config.InputSetter).toEqual({}); - }); - }); - }); - - describe('addEvents', function() { - beforeEach(function() { - this.hook = { list: { list: jasmine.createSpyObj('list', ['addEventListener']) } }; - this.inputSetter = { eventWrapper: {}, hook: this.hook, setInputs: () => {} }; - - InputSetter.addEvents.call(this.inputSetter); - }); - - it('should set .eventWrapper.setInputs', function() { - expect(this.inputSetter.eventWrapper.setInputs).toEqual(jasmine.any(Function)); - }); - - it('should call .addEventListener', function() { - expect(this.hook.list.list.addEventListener).toHaveBeenCalledWith( - 'click.dl', - this.inputSetter.eventWrapper.setInputs, - ); - }); - }); - - describe('removeEvents', function() { - beforeEach(function() { - this.hook = { list: { list: jasmine.createSpyObj('list', ['removeEventListener']) } }; - this.eventWrapper = jasmine.createSpyObj('eventWrapper', ['setInputs']); - this.inputSetter = { eventWrapper: this.eventWrapper, hook: this.hook }; - - InputSetter.removeEvents.call(this.inputSetter); - }); - - it('should call .removeEventListener', function() { - expect(this.hook.list.list.removeEventListener).toHaveBeenCalledWith( - 'click.dl', - this.eventWrapper.setInputs, - ); - }); - }); - - describe('setInputs', function() { - beforeEach(function() { - this.event = { detail: { selected: {} } }; - this.config = [0, 1]; - this.inputSetter = { config: this.config, setInput: () => {} }; - - spyOn(this.inputSetter, 'setInput'); - - InputSetter.setInputs.call(this.inputSetter, this.event); - }); - - it('should call .setInput for each config element', function() { - const allArgs = this.inputSetter.setInput.calls.allArgs(); - - expect(allArgs.length).toEqual(2); - - allArgs.forEach((args, i) => { - expect(args[0]).toBe(this.config[i]); - expect(args[1]).toBe(this.event.detail.selected); - }); - }); - - describe('if config isnt an array', function() { - beforeEach(function() { - this.inputSetter = { config: {}, setInput: () => {} }; - - InputSetter.setInputs.call(this.inputSetter, this.event); - }); - - it('should set .config to an array with .config as the first element', function() { - expect(this.inputSetter.config).toEqual([{}]); - }); - }); - }); - - describe('setInput', function() { - beforeEach(function() { - this.selectedItem = { getAttribute: () => {} }; - this.input = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} }; - this.config = { valueAttribute: {}, input: this.input }; - this.inputSetter = { hook: { trigger: {} } }; - this.newValue = 'newValue'; - - spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue); - spyOn(this.input, 'hasAttribute').and.returnValue(false); - - InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem); - }); - - it('should call .getAttribute', function() { - expect(this.selectedItem.getAttribute).toHaveBeenCalledWith(this.config.valueAttribute); - }); - - it('should call .hasAttribute', function() { - expect(this.input.hasAttribute).toHaveBeenCalledWith(undefined); - }); - - it('should set the value of the input', function() { - expect(this.input.value).toBe(this.newValue); - }); - - describe('if no config.input is provided', function() { - beforeEach(function() { - this.config = { valueAttribute: {} }; - this.trigger = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} }; - this.inputSetter = { hook: { trigger: this.trigger } }; - - InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem); - }); - - it('should set the value of the hook.trigger', function() { - expect(this.trigger.value).toBe(this.newValue); - }); - }); - - describe('if the input tag is not INPUT', function() { - beforeEach(function() { - this.input = { textContent: 'oldValue', tagName: 'SPAN', hasAttribute: () => {} }; - this.config = { valueAttribute: {}, input: this.input }; - - InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem); - }); - - it('should set the textContent of the input', function() { - expect(this.input.textContent).toBe(this.newValue); - }); - }); - - describe('if there is an inputAttribute', function() { - beforeEach(function() { - this.selectedItem = { getAttribute: () => {} }; - this.input = { id: 'oldValue', hasAttribute: () => {}, setAttribute: () => {} }; - this.inputSetter = { hook: { trigger: {} } }; - this.newValue = 'newValue'; - this.inputAttribute = 'id'; - this.config = { - valueAttribute: {}, - input: this.input, - inputAttribute: this.inputAttribute, - }; - - spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue); - spyOn(this.input, 'hasAttribute').and.returnValue(true); - spyOn(this.input, 'setAttribute'); - - InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem); - }); - - it('should call setAttribute', function() { - expect(this.input.setAttribute).toHaveBeenCalledWith(this.inputAttribute, this.newValue); - }); - - it('should not set the value or textContent of the input', function() { - expect(this.input.value).not.toBe('newValue'); - expect(this.input.textContent).not.toBe('newValue'); - }); - }); - }); - - describe('destroy', function() { - beforeEach(function() { - this.inputSetter = jasmine.createSpyObj('inputSetter', ['removeEvents']); - - InputSetter.destroy.call(this.inputSetter); - }); - - it('should call .removeEvents', function() { - expect(this.inputSetter.removeEvents).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/javascripts/dropzone_input_spec.js b/spec/javascripts/dropzone_input_spec.js deleted file mode 100644 index 6f6f20ccca2..00000000000 --- a/spec/javascripts/dropzone_input_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import $ from 'jquery'; -import { TEST_HOST } from 'spec/test_constants'; -import dropzoneInput from '~/dropzone_input'; -import PasteMarkdownTable from '~/behaviors/markdown/paste_markdown_table'; - -const TEST_FILE = new File([], 'somefile.jpg'); -TEST_FILE.upload = {}; - -const TEST_UPLOAD_PATH = `${TEST_HOST}/upload/file`; -const TEST_ERROR_MESSAGE = 'A big error occurred!'; -const TEMPLATE = `<form class="gfm-form" data-uploads-path="${TEST_UPLOAD_PATH}"> - <textarea class="js-gfm-input"></textarea> - <div class="uploading-error-message"></div> -</form>`; - -describe('dropzone_input', () => { - it('returns null when failed to initialize', () => { - const dropzone = dropzoneInput($('<form class="gfm-form"></form>')); - - expect(dropzone).toBeNull(); - }); - - it('returns valid dropzone when successfully initialize', () => { - const dropzone = dropzoneInput($(TEMPLATE)); - - expect(dropzone.version).toBeTruthy(); - }); - - describe('handlePaste', () => { - beforeEach(() => { - loadFixtures('issues/new-issue.html'); - - const form = $('#new_issue'); - form.data('uploads-path', TEST_UPLOAD_PATH); - dropzoneInput(form); - }); - - it('pastes Markdown tables', () => { - const event = $.Event('paste'); - const origEvent = new Event('paste'); - const pasteData = new DataTransfer(); - pasteData.setData('text/plain', 'Hello World'); - pasteData.setData('text/html', '<table><tr><td>Hello World</td></tr></table>'); - origEvent.clipboardData = pasteData; - event.originalEvent = origEvent; - - spyOn(PasteMarkdownTable.prototype, 'isTable').and.callThrough(); - spyOn(PasteMarkdownTable.prototype, 'convertToTableMarkdown').and.callThrough(); - - $('.js-gfm-input').trigger(event); - - expect(PasteMarkdownTable.prototype.isTable).toHaveBeenCalled(); - expect(PasteMarkdownTable.prototype.convertToTableMarkdown).toHaveBeenCalled(); - }); - }); - - describe('shows error message', () => { - let form; - let dropzone; - let xhr; - let oldXMLHttpRequest; - - beforeEach(() => { - form = $(TEMPLATE); - - dropzone = dropzoneInput(form); - - xhr = jasmine.createSpyObj(Object.keys(XMLHttpRequest.prototype)); - oldXMLHttpRequest = window.XMLHttpRequest; - window.XMLHttpRequest = () => xhr; - }); - - afterEach(() => { - window.XMLHttpRequest = oldXMLHttpRequest; - }); - - it('when AJAX fails with json', () => { - xhr = { - ...xhr, - statusCode: 400, - readyState: 4, - responseText: JSON.stringify({ message: TEST_ERROR_MESSAGE }), - getResponseHeader: () => 'application/json', - }; - - dropzone.processFile(TEST_FILE); - - xhr.onload(); - - expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE); - }); - - it('when AJAX fails with text', () => { - xhr = { - ...xhr, - statusCode: 400, - readyState: 4, - responseText: TEST_ERROR_MESSAGE, - getResponseHeader: () => 'text/plain', - }; - - dropzone.processFile(TEST_FILE); - - xhr.onload(); - - expect(form.find('.uploading-error-message').text()).toEqual(TEST_ERROR_MESSAGE); - }); - }); -}); diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js deleted file mode 100644 index 853f6b3b7b8..00000000000 --- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js +++ /dev/null @@ -1,126 +0,0 @@ -import $ from 'jquery'; -import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager'; - -describe('Filtered Search Dropdown Manager', () => { - beforeEach(() => { - spyOn($, 'ajax'); - }); - - 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', () => { - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'milestone' }); - - 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(''); - }); - - it('should add tokenName, tokenOperator, and tokenValue', () => { - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label' }); - - 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(''); - - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label', tokenOperator: '=' }); - - 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('.operator').innerText).toBe('='); - expect(getInputValue()).toBe(''); - - FilteredSearchDropdownManager.addWordToInput({ - tokenName: 'label', - tokenOperator: '=', - tokenValue: 'none', - }); - // We have to get that reference again - // Because 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('.operator').innerText).toBe('='); - 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'); - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author' }); - - 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(''); - }); - - it('should replace tokenValue', () => { - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author' }); - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'author', tokenOperator: '=' }); - - setInputValue('roo'); - FilteredSearchDropdownManager.addWordToInput({ - tokenName: null, - tokenOperator: '=', - tokenValue: '@root', - }); - - 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('.operator').innerText).toBe('='); - expect(token.querySelector('.value').innerText).toBe('@root'); - expect(getInputValue()).toBe(''); - }); - - it('should add tokenValues containing spaces', () => { - FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label' }); - - setInputValue('"test '); - FilteredSearchDropdownManager.addWordToInput({ - tokenName: 'label', - tokenOperator: '=', - tokenValue: '~\'"test me"\'', - }); - - 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('.operator').innerText).toBe('='); - expect(token.querySelector('.value').innerText).toBe('~\'"test me"\''); - expect(getInputValue()).toBe(''); - }); - }); - }); -}); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js deleted file mode 100644 index fda078bd41c..00000000000 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ /dev/null @@ -1,722 +0,0 @@ -import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens'; -import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; - -describe('Filtered Search Visual Tokens', () => { - const subject = FilteredSearchVisualTokens; - - const findElements = tokenElement => { - const tokenNameElement = tokenElement.querySelector('.name'); - const tokenOperatorElement = tokenElement.querySelector('.operator'); - const tokenValueContainer = tokenElement.querySelector('.value-container'); - const tokenValueElement = tokenValueContainer.querySelector('.value'); - return { tokenNameElement, tokenOperatorElement, tokenValueContainer, tokenValueElement }; - }; - - let tokensContainer; - let authorToken; - let bugLabelToken; - - beforeEach(() => { - setFixtures(` - <ul class="tokens-container"> - ${FilteredSearchSpecHelper.createInputHTML()} - </ul> - `); - tokensContainer = document.querySelector('.tokens-container'); - - authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user'); - bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug'); - }); - - describe('getLastVisualTokenBeforeInput', () => { - it('returns when there are no visual tokens', () => { - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - - expect(lastVisualToken).toEqual(null); - expect(isLastVisualTokenValid).toEqual(true); - }); - - describe('input is the last item in tokensContainer', () => { - it('returns when there is one visual token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - bugLabelToken.outerHTML, - ); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - - expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token')); - expect(isLastVisualTokenValid).toEqual(true); - }); - - it('returns when there is an incomplete visual token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('Author'), - ); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - - expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token')); - expect(isLastVisualTokenValid).toEqual(false); - }); - - it('returns when there are multiple visual tokens', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${bugLabelToken.outerHTML} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} - `); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - const items = document.querySelectorAll('.tokens-container .js-visual-token'); - - expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true); - expect(isLastVisualTokenValid).toEqual(true); - }); - - it('returns when there are multiple visual tokens and an incomplete visual token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${bugLabelToken.outerHTML} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('assignee')} - `); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - const items = document.querySelectorAll('.tokens-container .js-visual-token'); - - expect(lastVisualToken.isEqualNode(items[items.length - 1])).toEqual(true); - expect(isLastVisualTokenValid).toEqual(false); - }); - }); - - describe('input is a middle item in tokensContainer', () => { - it('returns last token before input', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${bugLabelToken.outerHTML} - ${FilteredSearchSpecHelper.createInputHTML()} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} - `); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - - expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token')); - expect(isLastVisualTokenValid).toEqual(true); - }); - - it('returns last partial token before input', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('label')} - ${FilteredSearchSpecHelper.createInputHTML()} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} - `); - - const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput(); - - expect(lastVisualToken).toEqual(document.querySelector('.filtered-search-token')); - expect(isLastVisualTokenValid).toEqual(false); - }); - }); - }); - - describe('getEndpointWithQueryParams', () => { - it('returns `endpoint` string as is when second param `endpointQueryParams` is undefined, null or empty string', () => { - const endpoint = 'foo/bar/-/labels.json'; - - expect(subject.getEndpointWithQueryParams(endpoint)).toBe(endpoint); - expect(subject.getEndpointWithQueryParams(endpoint, null)).toBe(endpoint); - expect(subject.getEndpointWithQueryParams(endpoint, '')).toBe(endpoint); - }); - - it('returns `endpoint` string with values of `endpointQueryParams`', () => { - const endpoint = 'foo/bar/-/labels.json'; - const singleQueryParams = '{"foo":"true"}'; - const multipleQueryParams = '{"foo":"true","bar":"true"}'; - - expect(subject.getEndpointWithQueryParams(endpoint, singleQueryParams)).toBe( - `${endpoint}?foo=true`, - ); - - expect(subject.getEndpointWithQueryParams(endpoint, multipleQueryParams)).toBe( - `${endpoint}?foo=true&bar=true`, - ); - }); - }); - - describe('unselectTokens', () => { - it('does nothing when there are no tokens', () => { - const beforeHTML = tokensContainer.innerHTML; - subject.unselectTokens(); - - expect(tokensContainer.innerHTML).toEqual(beforeHTML); - }); - - it('removes the selected class from buttons', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@author')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', '%123', true)} - `); - - const selected = tokensContainer.querySelector('.js-visual-token .selected'); - - expect(selected.classList.contains('selected')).toEqual(true); - - subject.unselectTokens(); - - expect(selected.classList.contains('selected')).toEqual(false); - }); - }); - - describe('selectToken', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${bugLabelToken.outerHTML} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', '~awesome')} - `); - }); - - it('removes the selected class if it has selected class', () => { - const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable'); - firstTokenButton.classList.add('selected'); - - subject.selectToken(firstTokenButton); - - expect(firstTokenButton.classList.contains('selected')).toEqual(false); - }); - - describe('has no selected class', () => { - it('adds selected class', () => { - const firstTokenButton = tokensContainer.querySelector('.js-visual-token .selectable'); - - subject.selectToken(firstTokenButton); - - expect(firstTokenButton.classList.contains('selected')).toEqual(true); - }); - - it('removes selected class from other tokens', () => { - const tokenButtons = tokensContainer.querySelectorAll('.js-visual-token .selectable'); - tokenButtons[1].classList.add('selected'); - - subject.selectToken(tokenButtons[0]); - - expect(tokenButtons[0].classList.contains('selected')).toEqual(true); - expect(tokenButtons[1].classList.contains('selected')).toEqual(false); - }); - }); - }); - - describe('removeSelectedToken', () => { - it('does not remove when there are no selected tokens', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none'), - ); - - expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null); - - subject.removeSelectedToken(); - - expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null); - }); - - it('removes selected token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'none', true), - ); - - expect(tokensContainer.querySelector('.js-visual-token .selectable')).not.toEqual(null); - - subject.removeSelectedToken(); - - expect(tokensContainer.querySelector('.js-visual-token .selectable')).toEqual(null); - }); - }); - - describe('createVisualTokenElementHTML', () => { - let tokenElement; - - beforeEach(() => { - setFixtures(` - <div class="test-area"> - ${subject.createVisualTokenElementHTML('custom-token')} - </div> - `); - - tokenElement = document.querySelector('.test-area').firstElementChild; - }); - - it('should add class name to token element', () => { - expect(document.querySelector('.test-area .custom-token')).toBeDefined(); - }); - - it('contains name div', () => { - expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything()); - }); - - it('contains value container div', () => { - expect(tokenElement.querySelector('.value-container')).toEqual(jasmine.anything()); - }); - - it('contains value div', () => { - expect(tokenElement.querySelector('.value-container .value')).toEqual(jasmine.anything()); - }); - - it('contains selectable class', () => { - expect(tokenElement.classList.contains('selectable')).toEqual(true); - }); - - it('contains button role', () => { - expect(tokenElement.getAttribute('role')).toEqual('button'); - }); - - describe('remove token', () => { - it('contains remove-token button', () => { - expect(tokenElement.querySelector('.value-container .remove-token')).toEqual( - jasmine.anything(), - ); - }); - - it('contains fa-close icon', () => { - expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(jasmine.anything()); - }); - }); - }); - - describe('addVisualTokenElement', () => { - it('renders search visual tokens', () => { - subject.addVisualTokenElement({ - name: 'search term', - operator: '=', - value: null, - options: { isSearchTerm: true }, - }); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('filtered-search-term')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('search term'); - expect(token.querySelector('.operator').innerText).toEqual('='); - expect(token.querySelector('.value')).toEqual(null); - }); - - it('renders filter visual token name', () => { - subject.addVisualTokenElement({ name: 'milestone' }); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('search-token-milestone')).toEqual(true); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('milestone'); - expect(token.querySelector('.value')).toEqual(null); - }); - - it('renders filter visual token name, operator, and value', () => { - subject.addVisualTokenElement({ name: 'label', operator: '!=', value: 'Frontend' }); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('search-token-label')).toEqual(true); - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('label'); - expect(token.querySelector('.operator').innerText).toEqual('!='); - expect(token.querySelector('.value').innerText).toEqual('Frontend'); - }); - - it('inserts visual token before input', () => { - tokensContainer.appendChild( - FilteredSearchSpecHelper.createFilterVisualToken('assignee', '=', '@root'), - ); - - subject.addVisualTokenElement({ name: 'label', operator: '!=', value: 'Frontend' }); - const tokens = tokensContainer.querySelectorAll('.js-visual-token'); - const labelToken = tokens[0]; - const assigneeToken = tokens[1]; - - expect(labelToken.classList.contains('search-token-label')).toEqual(true); - expect(labelToken.classList.contains('filtered-search-token')).toEqual(true); - expect(labelToken.querySelector('.name').innerText).toEqual('label'); - expect(labelToken.querySelector('.value').innerText).toEqual('Frontend'); - expect(labelToken.querySelector('.operator').innerText).toEqual('!='); - - expect(assigneeToken.classList.contains('search-token-assignee')).toEqual(true); - expect(assigneeToken.classList.contains('filtered-search-token')).toEqual(true); - expect(assigneeToken.querySelector('.name').innerText).toEqual('assignee'); - expect(assigneeToken.querySelector('.value').innerText).toEqual('@root'); - expect(assigneeToken.querySelector('.operator').innerText).toEqual('='); - }); - }); - - describe('addValueToPreviousVisualTokenElement', () => { - it('does not add when previous visual token element has no value', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root'), - ); - - const original = tokensContainer.innerHTML; - subject.addValueToPreviousVisualTokenElement('value'); - - expect(original).toEqual(tokensContainer.innerHTML); - }); - - it('does not add when previous visual token element is a search', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - `); - - const original = tokensContainer.innerHTML; - subject.addValueToPreviousVisualTokenElement('value'); - - expect(original).toEqual(tokensContainer.innerHTML); - }); - - it('adds value to previous visual filter token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label', '='), - ); - - const original = tokensContainer.innerHTML; - subject.addValueToPreviousVisualTokenElement('value'); - const updatedToken = tokensContainer.querySelector('.js-visual-token'); - - expect(updatedToken.querySelector('.name').innerText).toEqual('label'); - expect(updatedToken.querySelector('.value').innerText).toEqual('value'); - expect(original).not.toEqual(tokensContainer.innerHTML); - }); - }); - - describe('addFilterVisualToken', () => { - it('creates visual token with just tokenName', () => { - subject.addFilterVisualToken('milestone'); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('milestone'); - expect(token.querySelector('.operator')).toEqual(null); - expect(token.querySelector('.value')).toEqual(null); - }); - - it('creates visual token with just tokenValue', () => { - subject.addFilterVisualToken('milestone', '='); - subject.addFilterVisualToken('%8.17'); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('milestone'); - expect(token.querySelector('.operator').innerText).toEqual('='); - expect(token.querySelector('.value').innerText).toEqual('%8.17'); - }); - - it('creates full visual token', () => { - subject.addFilterVisualToken('assignee', '=', '@john'); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('filtered-search-token')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('assignee'); - expect(token.querySelector('.operator').innerText).toEqual('='); - expect(token.querySelector('.value').innerText).toEqual('@john'); - }); - }); - - describe('addSearchVisualToken', () => { - it('creates search visual token', () => { - subject.addSearchVisualToken('search term'); - const token = tokensContainer.querySelector('.js-visual-token'); - - expect(token.classList.contains('filtered-search-term')).toEqual(true); - expect(token.querySelector('.name').innerText).toEqual('search term'); - expect(token.querySelector('.value')).toEqual(null); - }); - - it('appends to previous search visual token if previous token was a search token', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('author', '=', '@root')} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search term')} - `); - - subject.addSearchVisualToken('append this'); - const token = tokensContainer.querySelector('.filtered-search-term'); - - expect(token.querySelector('.name').innerText).toEqual('search term append this'); - expect(token.querySelector('.value')).toEqual(null); - }); - }); - - describe('getLastTokenPartial', () => { - it('should get last token value', () => { - const value = '~bug'; - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - bugLabelToken.outerHTML, - ); - - expect(subject.getLastTokenPartial()).toEqual(value); - }); - - it('should get last token original value if available', () => { - const originalValue = '@user'; - const valueContainer = authorToken.querySelector('.value-container'); - valueContainer.dataset.originalValue = originalValue; - const avatar = document.createElement('img'); - const valueElement = valueContainer.querySelector('.value'); - valueElement.insertAdjacentElement('afterbegin', avatar); - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - authorToken.outerHTML, - ); - - const lastTokenValue = subject.getLastTokenPartial(); - - expect(lastTokenValue).toEqual(originalValue); - }); - - it('should get last token name if there is no value', () => { - const name = 'assignee'; - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameFilterVisualTokenHTML(name), - ); - - expect(subject.getLastTokenPartial()).toEqual(name); - }); - - it('should return empty when there are no tokens', () => { - expect(subject.getLastTokenPartial()).toEqual(''); - }); - }); - - describe('removeLastTokenPartial', () => { - it('should remove the last token value if it exists', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML( - 'label', - '=', - '~"Community Contribution"', - ), - ); - - expect(tokensContainer.querySelector('.js-visual-token .value')).not.toEqual(null); - - subject.removeLastTokenPartial(); - - expect(tokensContainer.querySelector('.js-visual-token .value')).toEqual(null); - }); - - it('should remove the last token name if there is no value', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameFilterVisualTokenHTML('milestone'), - ); - - expect(tokensContainer.querySelector('.js-visual-token .name')).not.toEqual(null); - - subject.removeLastTokenPartial(); - - expect(tokensContainer.querySelector('.js-visual-token .name')).toEqual(null); - }); - - it('should not remove anything when there are no tokens', () => { - const html = tokensContainer.innerHTML; - subject.removeLastTokenPartial(); - - expect(tokensContainer.innerHTML).toEqual(html); - }); - }); - - describe('tokenizeInput', () => { - it('does not do anything if there is no input', () => { - const original = tokensContainer.innerHTML; - subject.tokenizeInput(); - - expect(tokensContainer.innerHTML).toEqual(original); - }); - - it('adds search visual token if previous visual token is valid', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('assignee', '=', 'none'), - ); - - const input = document.querySelector('.filtered-search'); - input.value = 'some value'; - subject.tokenizeInput(); - - const newToken = tokensContainer.querySelector('.filtered-search-term'); - - expect(input.value).toEqual(''); - expect(newToken.querySelector('.name').innerText).toEqual('some value'); - expect(newToken.querySelector('.value')).toEqual(null); - }); - - it('adds value to previous visual token element if previous visual token is invalid', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('assignee', '='), - ); - - const input = document.querySelector('.filtered-search'); - input.value = '@john'; - subject.tokenizeInput(); - - const updatedToken = tokensContainer.querySelector('.filtered-search-token'); - - expect(input.value).toEqual(''); - expect(updatedToken.querySelector('.name').innerText).toEqual('assignee'); - expect(updatedToken.querySelector('.operator').innerText).toEqual('='); - expect(updatedToken.querySelector('.value').innerText).toEqual('@john'); - }); - }); - - describe('editToken', () => { - let input; - let token; - - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} - ${FilteredSearchSpecHelper.createSearchVisualTokenHTML('search')} - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('milestone', '=', 'upcoming')} - `); - - input = document.querySelector('.filtered-search'); - token = document.querySelector('.js-visual-token'); - }); - - it("tokenize's existing input", () => { - input.value = 'some text'; - spyOn(subject, 'tokenizeInput').and.callThrough(); - - subject.editToken(token); - - expect(subject.tokenizeInput).toHaveBeenCalled(); - expect(input.value).not.toEqual('some text'); - }); - - it('moves input to the token position', () => { - expect(tokensContainer.children[3].querySelector('.filtered-search')).not.toEqual(null); - - subject.editToken(token); - - expect(tokensContainer.children[1].querySelector('.filtered-search')).not.toEqual(null); - expect(tokensContainer.children[3].querySelector('.filtered-search')).toEqual(null); - }); - - it('input contains the visual token value', () => { - subject.editToken(token); - - expect(input.value).toEqual('none'); - }); - - it('input contains the original value if present', () => { - const originalValue = '@user'; - const valueContainer = token.querySelector('.value-container'); - valueContainer.dataset.originalValue = originalValue; - - subject.editToken(token); - - expect(input.value).toEqual(originalValue); - }); - - describe('selected token is a search term token', () => { - beforeEach(() => { - token = document.querySelector('.filtered-search-term'); - }); - - it('token is removed', () => { - expect(tokensContainer.querySelector('.filtered-search-term')).not.toEqual(null); - - subject.editToken(token); - - expect(tokensContainer.querySelector('.filtered-search-term')).toEqual(null); - }); - - it('input has the same value as removed token', () => { - expect(input.value).toEqual(''); - - subject.editToken(token); - - expect(input.value).toEqual('search'); - }); - }); - }); - - describe('moveInputTotheRight', () => { - it('does nothing if the input is already the right most element', () => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML( - FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none'), - ); - - spyOn(subject, 'tokenizeInput').and.callFake(() => {}); - spyOn(subject, 'getLastVisualTokenBeforeInput').and.callThrough(); - - subject.moveInputToTheRight(); - - expect(subject.tokenizeInput).toHaveBeenCalled(); - expect(subject.getLastVisualTokenBeforeInput).not.toHaveBeenCalled(); - }); - - it("tokenize's input", () => { - tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label', '=')} - ${FilteredSearchSpecHelper.createInputHTML()} - ${bugLabelToken.outerHTML} - `; - - tokensContainer.querySelector('.filtered-search').value = 'none'; - - subject.moveInputToTheRight(); - const value = tokensContainer.querySelector('.js-visual-token .value'); - - expect(value.innerText).toEqual('none'); - }); - - it('converts input into search term token if last token is valid', () => { - tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} - ${FilteredSearchSpecHelper.createInputHTML()} - ${bugLabelToken.outerHTML} - `; - - document.querySelector('.filtered-search').value = 'test'; - - subject.moveInputToTheRight(); - const searchValue = tokensContainer.querySelector('.filtered-search-term .name'); - - expect(searchValue.innerText).toEqual('test'); - }); - - it('moves the input to the right most element', () => { - tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} - ${FilteredSearchSpecHelper.createInputHTML()} - ${bugLabelToken.outerHTML} - `; - - subject.moveInputToTheRight(); - - expect(tokensContainer.children[2].querySelector('.filtered-search')).not.toEqual(null); - }); - - it('tokenizes input even if input is the right most element', () => { - tokensContainer.innerHTML = ` - ${FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none')} - ${FilteredSearchSpecHelper.createNameOperatorFilterVisualTokenHTML('label')} - ${FilteredSearchSpecHelper.createInputHTML('', '~bug')} - `; - - subject.moveInputToTheRight(); - - const token = tokensContainer.children[1]; - - expect(token.querySelector('.value').innerText).toEqual('~bug'); - }); - }); - - describe('renderVisualTokenValue', () => { - beforeEach(() => { - tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(` - ${authorToken.outerHTML} - ${bugLabelToken.outerHTML} - `); - }); - - it('renders a author token value element', () => { - const { tokenNameElement, tokenValueElement } = findElements(authorToken); - const tokenName = tokenNameElement.innerText; - const tokenValue = 'new value'; - - subject.renderVisualTokenValue(authorToken, tokenName, tokenValue); - - expect(tokenValueElement.innerText).toBe(tokenValue); - }); - }); -}); diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js deleted file mode 100644 index 06f76c581f2..00000000000 --- a/spec/javascripts/gl_dropdown_spec.js +++ /dev/null @@ -1,341 +0,0 @@ -/* eslint-disable no-param-reassign */ - -import $ from 'jquery'; -import GLDropdown from '~/gl_dropdown'; -import '~/lib/utils/common_utils'; - -describe('glDropdown', function describeDropdown() { - preloadFixtures('static/gl_dropdown.html'); - loadJSONFixtures('static/projects.json'); - - const NON_SELECTABLE_CLASSES = - '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item'; - const SEARCH_INPUT_SELECTOR = '.dropdown-input-field'; - const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; - const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; - - const ARROW_KEYS = { - DOWN: 40, - UP: 38, - ENTER: 13, - ESC: 27, - }; - - let remoteCallback; - - const navigateWithKeys = function navigateWithKeys(direction, steps, cb, i) { - i = i || 0; - if (!i) direction = direction.toUpperCase(); - $('body').trigger({ - type: 'keydown', - which: ARROW_KEYS[direction], - keyCode: ARROW_KEYS[direction], - }); - i += 1; - if (i <= steps) { - navigateWithKeys(direction, steps, cb, i); - } else { - cb(); - } - }; - - const remoteMock = function remoteMock(data, term, callback) { - remoteCallback = callback.bind({}, data); - }; - - function initDropDown(hasRemote, isFilterable, extraOpts = {}) { - const options = { - selectable: true, - filterable: isFilterable, - data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData, - search: { - fields: ['name'], - }, - text: project => project.name_with_namespace || project.name, - id: project => project.id, - ...extraOpts, - }; - this.dropdownButtonElement = $( - '#js-project-dropdown', - this.dropdownContainerElement, - ).glDropdown(options); - } - - beforeEach(() => { - loadFixtures('static/gl_dropdown.html'); - this.dropdownContainerElement = $('.dropdown.inline'); - this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); - this.projectsData = getJSONFixture('static/projects.json'); - }); - - afterEach(() => { - $('body').off('keydown'); - this.dropdownContainerElement.off('keyup'); - }); - - it('should open on click', () => { - initDropDown.call(this, false); - - expect(this.dropdownContainerElement).not.toHaveClass('show'); - this.dropdownButtonElement.click(); - - expect(this.dropdownContainerElement).toHaveClass('show'); - }); - - it('escapes HTML as text', () => { - this.projectsData[0].name_with_namespace = '<script>alert("testing");</script>'; - - initDropDown.call(this, false); - - this.dropdownButtonElement.click(); - - expect($('.dropdown-content li:first-child').text()).toBe('<script>alert("testing");</script>'); - }); - - it('should output HTML when highlighting', () => { - this.projectsData[0].name_with_namespace = 'testing'; - $('.dropdown-input .dropdown-input-field').val('test'); - - initDropDown.call(this, false, true, { - highlight: true, - }); - - this.dropdownButtonElement.click(); - - expect($('.dropdown-content li:first-child').text()).toBe('testing'); - - expect($('.dropdown-content li:first-child a').html()).toBe( - '<b>t</b><b>e</b><b>s</b><b>t</b>ing', - ); - }); - - describe('that is open', () => { - beforeEach(() => { - initDropDown.call(this, false, false); - this.dropdownButtonElement.click(); - }); - - it('should select a following item on DOWN keypress', () => { - expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0); - const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; - navigateWithKeys('down', randomIndex, () => { - expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); - expect($(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement)).toHaveClass( - 'is-focused', - ); - }); - }); - - it('should select a previous item on UP keypress', () => { - expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(0); - navigateWithKeys('down', this.projectsData.length - 1, () => { - expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); - const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 2)) + 0; - navigateWithKeys('up', randomIndex, () => { - expect($(FOCUSED_ITEM_SELECTOR, this.$dropdownMenuElement).length).toBe(1); - expect( - $( - `${ITEM_SELECTOR}:eq(${this.projectsData.length - 2 - randomIndex}) a`, - this.$dropdownMenuElement, - ), - ).toHaveClass('is-focused'); - }); - }); - }); - - it('should click the selected item on ENTER keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('show'); - const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; - navigateWithKeys('down', randomIndex, () => { - const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub(); - navigateWithKeys('enter', null, () => { - expect(this.dropdownContainerElement).not.toHaveClass('show'); - const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); - - expect(link).toHaveClass('is-active'); - const linkedLocation = link.attr('href'); - if (linkedLocation && linkedLocation !== '#') { - expect(visitUrl).toHaveBeenCalledWith(linkedLocation); - } - }); - }); - }); - - it('should close on ESC keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('show'); - this.dropdownContainerElement.trigger({ - type: 'keyup', - which: ARROW_KEYS.ESC, - keyCode: ARROW_KEYS.ESC, - }); - - expect(this.dropdownContainerElement).not.toHaveClass('show'); - }); - }); - - describe('opened and waiting for a remote callback', () => { - beforeEach(() => { - initDropDown.call(this, true, true); - this.dropdownButtonElement.click(); - }); - - it('should show loading indicator while search results are being fetched by backend', () => { - const dropdownMenu = document.querySelector('.dropdown-menu'); - - expect(dropdownMenu.className.indexOf('is-loading')).not.toBe(-1); - remoteCallback(); - - expect(dropdownMenu.className.indexOf('is-loading')).toBe(-1); - }); - - it('should not focus search input while remote task is not complete', () => { - expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR)); - remoteCallback(); - - expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); - }); - - it('should focus search input after remote task is complete', () => { - remoteCallback(); - - expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); - }); - - it('should focus on input when opening for the second time after transition', () => { - remoteCallback(); - this.dropdownContainerElement.trigger({ - type: 'keyup', - which: ARROW_KEYS.ESC, - keyCode: ARROW_KEYS.ESC, - }); - this.dropdownButtonElement.click(); - this.dropdownContainerElement.trigger('transitionend'); - - expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); - }); - }); - - describe('input focus with array data', () => { - it('should focus input when passing array data to drop down', () => { - initDropDown.call(this, false, true); - this.dropdownButtonElement.click(); - this.dropdownContainerElement.trigger('transitionend'); - - expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR)); - }); - }); - - it('should still have input value on close and restore', () => { - const $searchInput = $(SEARCH_INPUT_SELECTOR); - initDropDown.call(this, false, true); - $searchInput - .trigger('focus') - .val('g') - .trigger('input'); - - expect($searchInput.val()).toEqual('g'); - this.dropdownButtonElement.trigger('hidden.bs.dropdown'); - $searchInput.trigger('blur').trigger('focus'); - - expect($searchInput.val()).toEqual('g'); - }); - - describe('renderItem', () => { - function dropdownWithOptions(options) { - const $dropdownDiv = $('<div />'); - - $dropdownDiv.glDropdown(options); - - return $dropdownDiv.data('glDropdown'); - } - - function basicDropdown() { - return dropdownWithOptions({}); - } - - describe('without selected value', () => { - let dropdown; - - beforeEach(() => { - dropdown = basicDropdown(); - }); - - it('marks items without ID as active', () => { - const dummyData = {}; - - const html = dropdown.renderItem(dummyData, null, null); - - const link = html.querySelector('a'); - - expect(link).toHaveClass('is-active'); - }); - - it('does not mark items with ID as active', () => { - const dummyData = { - id: 'ea', - }; - - const html = dropdown.renderItem(dummyData, null, null); - - const link = html.querySelector('a'); - - expect(link).not.toHaveClass('is-active'); - }); - }); - - it('should return an empty .separator li when when appropriate', () => { - const dropdown = basicDropdown(); - const sep = { type: 'separator' }; - const li = dropdown.renderItem(sep); - - expect(li).toHaveClass('separator'); - expect(li.childNodes.length).toEqual(0); - }); - - it('should return an empty .divider li when when appropriate', () => { - const dropdown = basicDropdown(); - const div = { type: 'divider' }; - const li = dropdown.renderItem(div); - - expect(li).toHaveClass('divider'); - expect(li.childNodes.length).toEqual(0); - }); - - it('should return a .dropdown-header li with the correct content when when appropriate', () => { - const dropdown = basicDropdown(); - const text = 'My Header'; - const header = { type: 'header', content: text }; - const li = dropdown.renderItem(header); - - expect(li).toHaveClass('dropdown-header'); - expect(li.childNodes.length).toEqual(1); - expect(li.textContent).toEqual(text); - }); - }); - - it('should keep selected item after selecting a second time', () => { - const options = { - isSelectable(item, $el) { - return !$el.hasClass('is-active'); - }, - toggleLabel(item) { - return item && item.id; - }, - }; - initDropDown.call(this, false, false, options); - const $item = $(`${ITEM_SELECTOR}:first() a`, this.$dropdownMenuElement); - - // select item the first time - this.dropdownButtonElement.click(); - $item.click(); - - expect($item).toHaveClass('is-active'); - // select item the second time - this.dropdownButtonElement.click(); - $item.click(); - - expect($item).toHaveClass('is-active'); - - expect($('.dropdown-toggle-text')).toHaveText(this.projectsData[0].id.toString()); - }); -}); diff --git a/spec/javascripts/gl_form_spec.js b/spec/javascripts/gl_form_spec.js deleted file mode 100644 index 69b3dae743a..00000000000 --- a/spec/javascripts/gl_form_spec.js +++ /dev/null @@ -1,110 +0,0 @@ -import $ from 'jquery'; -import autosize from 'autosize'; -import GLForm from '~/gl_form'; -import '~/lib/utils/text_utility'; -import '~/lib/utils/common_utils'; - -describe('GLForm', () => { - describe('when instantiated', function() { - beforeEach(done => { - this.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>'); - this.textarea = this.form.find('textarea'); - spyOn($.prototype, 'off').and.returnValue(this.textarea); - spyOn($.prototype, 'on').and.returnValue(this.textarea); - spyOn($.prototype, 'css'); - - this.glForm = new GLForm(this.form, false); - setTimeout(() => { - $.prototype.off.calls.reset(); - $.prototype.on.calls.reset(); - $.prototype.css.calls.reset(); - done(); - }); - }); - - describe('setupAutosize', () => { - beforeEach(done => { - this.glForm.setupAutosize(); - setTimeout(() => { - done(); - }); - }); - - it('should register an autosize event handler on the textarea', () => { - expect($.prototype.off).toHaveBeenCalledWith('autosize:resized'); - expect($.prototype.on).toHaveBeenCalledWith('autosize:resized', jasmine.any(Function)); - }); - - it('should register a mouseup event handler on the textarea', () => { - expect($.prototype.off).toHaveBeenCalledWith('mouseup.autosize'); - expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', jasmine.any(Function)); - }); - - it('should set the resize css property to vertical', () => { - expect($.prototype.css).toHaveBeenCalledWith('resize', 'vertical'); - }); - }); - - describe('setHeightData', () => { - beforeEach(() => { - spyOn($.prototype, 'data'); - spyOn($.prototype, 'outerHeight').and.returnValue(200); - this.glForm.setHeightData(); - }); - - it('should set the height data attribute', () => { - expect($.prototype.data).toHaveBeenCalledWith('height', 200); - }); - - it('should call outerHeight', () => { - expect($.prototype.outerHeight).toHaveBeenCalled(); - }); - }); - - describe('destroyAutosize', () => { - describe('when called', () => { - beforeEach(() => { - spyOn($.prototype, 'data'); - spyOn($.prototype, 'outerHeight').and.returnValue(200); - spyOn(window, 'outerHeight').and.returnValue(400); - spyOn(autosize, 'destroy'); - - this.glForm.destroyAutosize(); - }); - - it('should call outerHeight', () => { - expect($.prototype.outerHeight).toHaveBeenCalled(); - }); - - it('should get data-height attribute', () => { - expect($.prototype.data).toHaveBeenCalledWith('height'); - }); - - it('should call autosize destroy', () => { - expect(autosize.destroy).toHaveBeenCalledWith(this.textarea); - }); - - it('should set the data-height attribute', () => { - expect($.prototype.data).toHaveBeenCalledWith('height', 200); - }); - - it('should set the outerHeight', () => { - expect($.prototype.outerHeight).toHaveBeenCalledWith(200); - }); - - it('should set the css', () => { - expect($.prototype.css).toHaveBeenCalledWith('max-height', window.outerHeight); - }); - }); - - it('should return undefined if the data-height equals the outerHeight', () => { - spyOn($.prototype, 'outerHeight').and.returnValue(200); - spyOn($.prototype, 'data').and.returnValue(200); - spyOn(autosize, 'destroy'); - - expect(this.glForm.destroyAutosize()).toBeUndefined(); - expect(autosize.destroy).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/spec/javascripts/helpers/scroll_into_view_promise.js b/spec/javascripts/helpers/scroll_into_view_promise.js deleted file mode 100644 index 0edea2103da..00000000000 --- a/spec/javascripts/helpers/scroll_into_view_promise.js +++ /dev/null @@ -1,28 +0,0 @@ -export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) { - return new Promise((resolve, reject) => { - let intersectionObserver; - let retry = 0; - - const intervalId = setInterval(() => { - if (retry >= maxTries) { - intersectionObserver.disconnect(); - clearInterval(intervalId); - reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`)); - } - retry += 1; - intersectionTarget.scrollIntoView(); - }, timeout); - - intersectionObserver = new IntersectionObserver(entries => { - if (entries[0].isIntersecting) { - intersectionObserver.disconnect(); - clearInterval(intervalId); - resolve(); - } - }); - - intersectionObserver.observe(intersectionTarget); - - intersectionTarget.scrollIntoView(); - }); -} diff --git a/spec/javascripts/helpers/vuex_action_helper_spec.js b/spec/javascripts/helpers/vuex_action_helper_spec.js deleted file mode 100644 index 09f0bd395c3..00000000000 --- a/spec/javascripts/helpers/vuex_action_helper_spec.js +++ /dev/null @@ -1,166 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import { TEST_HOST } from 'spec/test_constants'; -import axios from '~/lib/utils/axios_utils'; -import testAction from './vuex_action_helper'; - -describe('VueX test helper (testAction)', () => { - let originalExpect; - let assertion; - let mock; - const noop = () => {}; - - beforeAll(() => { - mock = new MockAdapter(axios); - /* - In order to test the helper properly, we need to overwrite the jasmine `expect` helper. - We test that the testAction helper properly passes the dispatched actions/committed mutations - to the jasmine helper. - */ - originalExpect = expect; - assertion = null; - global.expect = actual => ({ - toEqual: () => { - originalExpect(actual).toEqual(assertion); - }, - }); - }); - - afterAll(() => { - mock.restore(); - global.expect = originalExpect; - }); - - it('should properly pass on state and payload', () => { - const exampleState = { FOO: 12, BAR: 3 }; - const examplePayload = { BAZ: 73, BIZ: 55 }; - - const action = ({ state }, payload) => { - originalExpect(state).toEqual(exampleState); - originalExpect(payload).toEqual(examplePayload); - }; - - assertion = { mutations: [], actions: [] }; - - testAction(action, examplePayload, exampleState); - }); - - describe('should work with synchronous actions', () => { - it('committing mutation', () => { - const action = ({ commit }) => { - commit('MUTATION'); - }; - - assertion = { mutations: [{ type: 'MUTATION' }], actions: [] }; - - testAction(action, null, {}, assertion.mutations, assertion.actions, noop); - }); - - it('dispatching action', () => { - const action = ({ dispatch }) => { - dispatch('ACTION'); - }; - - assertion = { actions: [{ type: 'ACTION' }], mutations: [] }; - - testAction(action, null, {}, assertion.mutations, assertion.actions, noop); - }); - - it('work with jasmine done once finished', done => { - assertion = { mutations: [], actions: [] }; - - testAction(noop, null, {}, assertion.mutations, assertion.actions, done); - }); - - it('provide promise interface', done => { - assertion = { mutations: [], actions: [] }; - - testAction(noop, null, {}, assertion.mutations, assertion.actions) - .then(done) - .catch(done.fail); - }); - }); - - describe('should work with promise based actions (fetch action)', () => { - let lastError; - const data = { FOO: 'BAR' }; - - const promiseAction = ({ commit, dispatch }) => { - dispatch('ACTION'); - - return axios - .get(TEST_HOST) - .catch(error => { - commit('ERROR'); - lastError = error; - throw error; - }) - .then(() => { - commit('SUCCESS'); - return data; - }); - }; - - beforeEach(() => { - lastError = null; - }); - - it('work with jasmine done once finished', done => { - mock.onGet(TEST_HOST).replyOnce(200, 42); - - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; - - testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done); - }); - - it('return original data of successful promise while checking actions/mutations', done => { - mock.onGet(TEST_HOST).replyOnce(200, 42); - - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; - - testAction(promiseAction, null, {}, assertion.mutations, assertion.actions) - .then(res => { - originalExpect(res).toEqual(data); - done(); - }) - .catch(done.fail); - }); - - it('return original error of rejected promise while checking actions/mutations', done => { - mock.onGet(TEST_HOST).replyOnce(500, ''); - - assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] }; - - testAction(promiseAction, null, {}, assertion.mutations, assertion.actions) - .then(done.fail) - .catch(error => { - originalExpect(error).toBe(lastError); - done(); - }); - }); - }); - - it('should work with async actions not returning promises', done => { - const data = { FOO: 'BAR' }; - - const promiseAction = ({ commit, dispatch }) => { - dispatch('ACTION'); - - axios - .get(TEST_HOST) - .then(() => { - commit('SUCCESS'); - return data; - }) - .catch(error => { - commit('ERROR'); - throw error; - }); - }; - - mock.onGet(TEST_HOST).replyOnce(200, 42); - - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; - - testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done); - }); -}); diff --git a/spec/javascripts/helpers/wait_for_attribute_change.js b/spec/javascripts/helpers/wait_for_attribute_change.js deleted file mode 100644 index 8f22d569222..00000000000 --- a/spec/javascripts/helpers/wait_for_attribute_change.js +++ /dev/null @@ -1,16 +0,0 @@ -export default (domElement, attributes, timeout = 1500) => - new Promise((resolve, reject) => { - let observer; - const timeoutId = setTimeout(() => { - observer.disconnect(); - reject(new Error(`Could not see an attribute update within ${timeout} ms`)); - }, timeout); - - observer = new MutationObserver(() => { - clearTimeout(timeoutId); - observer.disconnect(); - resolve(); - }); - - observer.observe(domElement, { attributes: true, attributeFilter: attributes }); - }); diff --git a/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js b/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js deleted file mode 100644 index 53508f52b2f..00000000000 --- a/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js +++ /dev/null @@ -1,170 +0,0 @@ -import Vue from 'vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; -import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue'; - -describe('IDE commit message field', () => { - const Component = Vue.extend(CommitMessageField); - let vm; - - beforeEach(() => { - setFixtures('<div id="app"></div>'); - - vm = createComponent( - Component, - { - text: '', - placeholder: 'testing', - }, - '#app', - ); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('adds is-focused class on focus', done => { - vm.$el.querySelector('textarea').focus(); - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.is-focused')).not.toBeNull(); - - done(); - }); - }); - - it('removed is-focused class on blur', done => { - vm.$el.querySelector('textarea').focus(); - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.is-focused')).not.toBeNull(); - - vm.$el.querySelector('textarea').blur(); - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.$el.querySelector('.is-focused')).toBeNull(); - - done(); - }) - .then(done) - .catch(done.fail); - }); - - it('emits input event on input', () => { - spyOn(vm, '$emit'); - - const textarea = vm.$el.querySelector('textarea'); - textarea.value = 'testing'; - - textarea.dispatchEvent(new Event('input')); - - expect(vm.$emit).toHaveBeenCalledWith('input', 'testing'); - }); - - describe('highlights', () => { - describe('subject line', () => { - it('does not highlight less than 50 characters', done => { - vm.text = 'text less than 50 chars'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.highlights span').textContent).toContain( - 'text less than 50 chars', - ); - - expect(vm.$el.querySelector('mark').style.display).toBe('none'); - }) - .then(done) - .catch(done.fail); - }); - - it('highlights characters over 50 length', done => { - vm.text = - 'text less than 50 chars that should not highlighted. text more than 50 should be highlighted'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelector('.highlights span').textContent).toContain( - 'text less than 50 chars that should not highlighte', - ); - - expect(vm.$el.querySelector('mark').style.display).not.toBe('none'); - expect(vm.$el.querySelector('mark').textContent).toBe( - 'd. text more than 50 should be highlighted', - ); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('body text', () => { - it('does not highlight body text less tan 72 characters', done => { - vm.text = 'subject line\nbody content'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark')[1].style.display).toBe('none'); - }) - .then(done) - .catch(done.fail); - }); - - it('highlights body text more than 72 characters', done => { - vm.text = - 'subject line\nbody content that will be highlighted when it is more than 72 characters in length'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark')[1].style.display).not.toBe('none'); - expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length'); - }) - .then(done) - .catch(done.fail); - }); - - it('highlights body text & subject line', done => { - vm.text = - 'text less than 50 chars that should not highlighted\nbody content that will be highlighted when it is more than 72 characters in length'; - - vm.$nextTick() - .then(() => { - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark').length).toBe(2); - - expect(vm.$el.querySelectorAll('mark')[0].textContent).toContain('d'); - expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length'); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('scrolling textarea', () => { - it('updates transform of highlights', done => { - vm.text = 'subject line\n\n\n\n\n\n\n\n\n\n\nbody content'; - - vm.$nextTick() - .then(() => { - vm.$el.querySelector('textarea').scrollTo(0, 50); - - vm.handleScroll(); - }) - .then(vm.$nextTick) - .then(() => { - expect(vm.scrollTop).toBe(50); - expect(vm.$el.querySelector('.highlights').style.transform).toBe( - 'translate3d(0px, -50px, 0px)', - ); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/ide/components/jobs/detail_spec.js b/spec/javascripts/ide/components/jobs/detail_spec.js deleted file mode 100644 index a4e6b81acba..00000000000 --- a/spec/javascripts/ide/components/jobs/detail_spec.js +++ /dev/null @@ -1,184 +0,0 @@ -import Vue from 'vue'; -import JobDetail from '~/ide/components/jobs/detail.vue'; -import { createStore } from '~/ide/stores'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { jobs } from '../../mock_data'; - -describe('IDE jobs detail view', () => { - const Component = Vue.extend(JobDetail); - let vm; - - beforeEach(() => { - const store = createStore(); - - store.state.pipelines.detailJob = { - ...jobs[0], - isLoading: true, - output: 'testing', - rawPath: `${gl.TEST_HOST}/raw`, - }; - - vm = createComponentWithStore(Component, store); - - spyOn(vm, 'fetchJobTrace').and.returnValue(Promise.resolve()); - - vm = vm.$mount(); - - spyOn(vm.$refs.buildTrace, 'scrollTo'); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('calls fetchJobTrace on mount', () => { - expect(vm.fetchJobTrace).toHaveBeenCalled(); - }); - - it('scrolls to bottom on mount', done => { - setTimeout(() => { - expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalled(); - - done(); - }); - }); - - it('renders job output', () => { - expect(vm.$el.querySelector('.bash').textContent).toContain('testing'); - }); - - it('renders empty message output', done => { - vm.$store.state.pipelines.detailJob.output = ''; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.bash').textContent).toContain('No messages were logged'); - - done(); - }); - }); - - it('renders loading icon', () => { - expect(vm.$el.querySelector('.build-loader-animation')).not.toBe(null); - expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe(''); - }); - - it('hides output when loading', () => { - expect(vm.$el.querySelector('.bash')).not.toBe(null); - expect(vm.$el.querySelector('.bash').style.display).toBe('none'); - }); - - it('hide loading icon when isLoading is false', done => { - vm.$store.state.pipelines.detailJob.isLoading = false; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('none'); - - done(); - }); - }); - - it('resets detailJob when clicking header button', () => { - spyOn(vm, 'setDetailJob'); - - vm.$el.querySelector('.btn').click(); - - expect(vm.setDetailJob).toHaveBeenCalledWith(null); - }); - - it('renders raw path link', () => { - expect(vm.$el.querySelector('.controllers-buttons').getAttribute('href')).toBe( - `${gl.TEST_HOST}/raw`, - ); - }); - - describe('scroll buttons', () => { - it('triggers scrollDown when clicking down button', done => { - spyOn(vm, 'scrollDown'); - - vm.$el.querySelectorAll('.btn-scroll')[1].click(); - - vm.$nextTick(() => { - expect(vm.scrollDown).toHaveBeenCalled(); - - done(); - }); - }); - - it('triggers scrollUp when clicking up button', done => { - spyOn(vm, 'scrollUp'); - - vm.scrollPos = 1; - - vm.$nextTick() - .then(() => vm.$el.querySelector('.btn-scroll').click()) - .then(() => vm.$nextTick()) - .then(() => { - expect(vm.scrollUp).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('scrollDown', () => { - it('scrolls build trace to bottom', () => { - spyOnProperty(vm.$refs.buildTrace, 'scrollHeight').and.returnValue(1000); - - vm.scrollDown(); - - expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalledWith(0, 1000); - }); - }); - - describe('scrollUp', () => { - it('scrolls build trace to top', () => { - vm.scrollUp(); - - expect(vm.$refs.buildTrace.scrollTo).toHaveBeenCalledWith(0, 0); - }); - }); - - describe('scrollBuildLog', () => { - beforeEach(() => { - spyOnProperty(vm.$refs.buildTrace, 'offsetHeight').and.returnValue(100); - spyOnProperty(vm.$refs.buildTrace, 'scrollHeight').and.returnValue(200); - }); - - it('sets scrollPos to bottom when at the bottom', done => { - spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(100); - - vm.scrollBuildLog(); - - setTimeout(() => { - expect(vm.scrollPos).toBe(1); - - done(); - }); - }); - - it('sets scrollPos to top when at the top', done => { - spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(0); - vm.scrollPos = 1; - - vm.scrollBuildLog(); - - setTimeout(() => { - expect(vm.scrollPos).toBe(0); - - done(); - }); - }); - - it('resets scrollPos when not at top or bottom', done => { - spyOnProperty(vm.$refs.buildTrace, 'scrollTop').and.returnValue(10); - - vm.scrollBuildLog(); - - setTimeout(() => { - expect(vm.scrollPos).toBe(''); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js deleted file mode 100644 index 8db29011da7..00000000000 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ /dev/null @@ -1,512 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import '~/behaviors/markdown/render_gfm'; -import axios from '~/lib/utils/axios_utils'; -import store from '~/ide/stores'; -import repoEditor from '~/ide/components/repo_editor.vue'; -import Editor from '~/ide/lib/editor'; -import { leftSidebarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import setTimeoutPromise from '../../helpers/set_timeout_promise_helper'; -import { file, resetStore } from '../helpers'; - -describe('RepoEditor', () => { - let vm; - - beforeEach(done => { - const f = { - ...file(), - viewMode: FILE_VIEW_MODE_EDITOR, - }; - const RepoEditor = Vue.extend(repoEditor); - - vm = createComponentWithStore(RepoEditor, store, { - file: f, - }); - - f.active = true; - f.tempFile = true; - - vm.$store.state.openFiles.push(f); - vm.$store.state.projects = { - 'gitlab-org/gitlab': { - branches: { - master: { - name: 'master', - commit: { - id: 'abcdefgh', - }, - }, - }, - }, - }; - vm.$store.state.currentProjectId = 'gitlab-org/gitlab'; - vm.$store.state.currentBranchId = 'master'; - - Vue.set(vm.$store.state.entries, f.path, f); - - spyOn(vm, 'getFileData').and.returnValue(Promise.resolve()); - spyOn(vm, 'getRawFileData').and.returnValue(Promise.resolve()); - - vm.$mount(); - - Vue.nextTick(() => setTimeout(done)); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - - Editor.editorInstance.dispose(); - }); - - const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder'); - - it('sets renderWhitespace to `all`', () => { - vm.$store.state.renderWhitespaceInCode = true; - - expect(vm.editorOptions.renderWhitespace).toEqual('all'); - }); - - it('sets renderWhitespace to `none`', () => { - vm.$store.state.renderWhitespaceInCode = false; - - expect(vm.editorOptions.renderWhitespace).toEqual('none'); - }); - - it('renders an ide container', () => { - expect(vm.shouldHideEditor).toBeFalsy(); - expect(vm.showEditor).toBe(true); - expect(findEditor()).not.toHaveCss({ display: 'none' }); - }); - - it('renders only an edit tab', done => { - Vue.nextTick(() => { - const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li'); - - expect(tabs.length).toBe(1); - expect(tabs[0].textContent.trim()).toBe('Edit'); - - done(); - }); - }); - - describe('when file is markdown', () => { - beforeEach(done => { - vm.file.previewMode = { - id: 'markdown', - previewTitle: 'Preview Markdown', - }; - - vm.$nextTick(done); - }); - - it('renders an Edit and a Preview Tab', done => { - Vue.nextTick(() => { - const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li'); - - expect(tabs.length).toBe(2); - expect(tabs[0].textContent.trim()).toBe('Edit'); - expect(tabs[1].textContent.trim()).toBe('Preview Markdown'); - - done(); - }); - }); - }); - - describe('when file is markdown and viewer mode is review', () => { - let mock; - - beforeEach(done => { - mock = new MockAdapter(axios); - - vm.file.projectId = 'namespace/project'; - vm.file.previewMode = { - id: 'markdown', - previewTitle: 'Preview Markdown', - }; - vm.file.content = 'testing 123'; - vm.$store.state.viewer = 'diff'; - - mock.onPost(/(.*)\/preview_markdown/).reply(200, { - body: '<p>testing 123</p>', - }); - - vm.$nextTick(done); - }); - - afterEach(() => { - mock.restore(); - }); - - it('renders an Edit and a Preview Tab', done => { - Vue.nextTick(() => { - const tabs = vm.$el.querySelectorAll('.ide-mode-tabs .nav-links li'); - - expect(tabs.length).toBe(2); - expect(tabs[0].textContent.trim()).toBe('Review'); - expect(tabs[1].textContent.trim()).toBe('Preview Markdown'); - - done(); - }); - }); - - it('renders markdown for tempFile', done => { - vm.file.tempFile = true; - vm.file.path = `${vm.file.path}.md`; - vm.$store.state.entries[vm.file.path] = vm.file; - - vm.$nextTick() - .then(() => { - vm.$el.querySelectorAll('.ide-mode-tabs .nav-links a')[1].click(); - }) - .then(setTimeoutPromise) - .then(() => { - expect(vm.$el.querySelector('.preview-container').innerHTML).toContain( - '<p>testing 123</p>', - ); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('when open file is binary and not raw', () => { - beforeEach(done => { - vm.file.binary = true; - - vm.$nextTick(done); - }); - - it('does not render the IDE', () => { - expect(vm.shouldHideEditor).toBeTruthy(); - }); - }); - - describe('createEditorInstance', () => { - it('calls createInstance when viewer is editor', done => { - spyOn(vm.editor, 'createInstance'); - - vm.createEditorInstance(); - - vm.$nextTick(() => { - expect(vm.editor.createInstance).toHaveBeenCalled(); - - done(); - }); - }); - - it('calls createDiffInstance when viewer is diff', done => { - vm.$store.state.viewer = 'diff'; - - spyOn(vm.editor, 'createDiffInstance'); - - vm.createEditorInstance(); - - vm.$nextTick(() => { - expect(vm.editor.createDiffInstance).toHaveBeenCalled(); - - done(); - }); - }); - - it('calls createDiffInstance when viewer is a merge request diff', done => { - vm.$store.state.viewer = 'mrdiff'; - - spyOn(vm.editor, 'createDiffInstance'); - - vm.createEditorInstance(); - - vm.$nextTick(() => { - expect(vm.editor.createDiffInstance).toHaveBeenCalled(); - - done(); - }); - }); - }); - - describe('setupEditor', () => { - it('creates new model', () => { - spyOn(vm.editor, 'createModel').and.callThrough(); - - Editor.editorInstance.modelManager.dispose(); - - vm.setupEditor(); - - expect(vm.editor.createModel).toHaveBeenCalledWith(vm.file, null); - expect(vm.model).not.toBeNull(); - }); - - it('attaches model to editor', () => { - spyOn(vm.editor, 'attachModel').and.callThrough(); - - Editor.editorInstance.modelManager.dispose(); - - vm.setupEditor(); - - expect(vm.editor.attachModel).toHaveBeenCalledWith(vm.model); - }); - - it('attaches model to merge request editor', () => { - vm.$store.state.viewer = 'mrdiff'; - vm.file.mrChange = true; - spyOn(vm.editor, 'attachMergeRequestModel'); - - Editor.editorInstance.modelManager.dispose(); - - vm.setupEditor(); - - expect(vm.editor.attachMergeRequestModel).toHaveBeenCalledWith(vm.model); - }); - - it('does not attach model to merge request editor when not a MR change', () => { - vm.$store.state.viewer = 'mrdiff'; - vm.file.mrChange = false; - spyOn(vm.editor, 'attachMergeRequestModel'); - - Editor.editorInstance.modelManager.dispose(); - - vm.setupEditor(); - - expect(vm.editor.attachMergeRequestModel).not.toHaveBeenCalledWith(vm.model); - }); - - it('adds callback methods', () => { - spyOn(vm.editor, 'onPositionChange').and.callThrough(); - - Editor.editorInstance.modelManager.dispose(); - - vm.setupEditor(); - - expect(vm.editor.onPositionChange).toHaveBeenCalled(); - expect(vm.model.events.size).toBe(2); - }); - - it('updates state when model content changed', done => { - vm.model.setValue('testing 123\n'); - - setTimeout(() => { - expect(vm.file.content).toBe('testing 123\n'); - - done(); - }); - }); - - it('sets head model as staged file', () => { - spyOn(vm.editor, 'createModel').and.callThrough(); - - Editor.editorInstance.modelManager.dispose(); - - vm.$store.state.stagedFiles.push({ ...vm.file, key: 'staged' }); - vm.file.staged = true; - vm.file.key = `unstaged-${vm.file.key}`; - - vm.setupEditor(); - - expect(vm.editor.createModel).toHaveBeenCalledWith(vm.file, vm.$store.state.stagedFiles[0]); - }); - }); - - describe('editor updateDimensions', () => { - beforeEach(() => { - spyOn(vm.editor, 'updateDimensions').and.callThrough(); - spyOn(vm.editor, 'updateDiffView'); - }); - - it('calls updateDimensions when panelResizing is false', done => { - vm.$store.state.panelResizing = true; - - vm.$nextTick() - .then(() => { - vm.$store.state.panelResizing = false; - }) - .then(vm.$nextTick) - .then(() => { - expect(vm.editor.updateDimensions).toHaveBeenCalled(); - expect(vm.editor.updateDiffView).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not call updateDimensions when panelResizing is true', done => { - vm.$store.state.panelResizing = true; - - vm.$nextTick(() => { - expect(vm.editor.updateDimensions).not.toHaveBeenCalled(); - expect(vm.editor.updateDiffView).not.toHaveBeenCalled(); - - done(); - }); - }); - - it('calls updateDimensions when rightPane is opened', done => { - vm.$store.state.rightPane.isOpen = true; - - vm.$nextTick(() => { - expect(vm.editor.updateDimensions).toHaveBeenCalled(); - expect(vm.editor.updateDiffView).toHaveBeenCalled(); - - done(); - }); - }); - }); - - describe('show tabs', () => { - it('shows tabs in edit mode', () => { - expect(vm.$el.querySelector('.nav-links')).not.toBe(null); - }); - - it('hides tabs in review mode', done => { - vm.$store.state.currentActivityView = leftSidebarViews.review.name; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.nav-links')).toBe(null); - - done(); - }); - }); - - it('hides tabs in commit mode', done => { - vm.$store.state.currentActivityView = leftSidebarViews.commit.name; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.nav-links')).toBe(null); - - done(); - }); - }); - }); - - describe('when files view mode is preview', () => { - beforeEach(done => { - spyOn(vm.editor, 'updateDimensions'); - vm.file.viewMode = FILE_VIEW_MODE_PREVIEW; - vm.$nextTick(done); - }); - - it('should hide editor', () => { - expect(vm.showEditor).toBe(false); - expect(findEditor()).toHaveCss({ display: 'none' }); - }); - - describe('when file view mode changes to editor', () => { - beforeEach(done => { - vm.file.viewMode = FILE_VIEW_MODE_EDITOR; - - // one tick to trigger watch - vm.$nextTick() - // another tick needed until we can update dimensions - .then(() => vm.$nextTick()) - .then(done) - .catch(done.fail); - }); - - it('should update dimensions', () => { - expect(vm.editor.updateDimensions).toHaveBeenCalled(); - }); - }); - }); - - describe('initEditor', () => { - beforeEach(() => { - vm.file.tempFile = false; - spyOn(vm.editor, 'createInstance'); - spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); - }); - - it('does not fetch file information for temp entries', done => { - vm.file.tempFile = true; - - vm.initEditor(); - vm.$nextTick() - .then(() => { - expect(vm.getFileData).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('is being initialised for files without content even if shouldHideEditor is `true`', done => { - vm.file.content = ''; - vm.file.raw = ''; - - vm.initEditor(); - vm.$nextTick() - .then(() => { - expect(vm.getFileData).toHaveBeenCalled(); - expect(vm.getRawFileData).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not initialize editor for files already with content', done => { - vm.file.content = 'foo'; - - vm.initEditor(); - vm.$nextTick() - .then(() => { - expect(vm.getFileData).not.toHaveBeenCalled(); - expect(vm.getRawFileData).not.toHaveBeenCalled(); - expect(vm.editor.createInstance).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('updates on file changes', () => { - beforeEach(() => { - spyOn(vm, 'initEditor'); - }); - - it('calls removePendingTab when old file is pending', done => { - spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); - spyOn(vm, 'removePendingTab'); - - vm.file.pending = true; - - vm.$nextTick() - .then(() => { - vm.file = file('testing'); - vm.file.content = 'foo'; // need to prevent full cycle of initEditor - - return vm.$nextTick(); - }) - .then(() => { - expect(vm.removePendingTab).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not call initEditor if the file did not change', done => { - Vue.set(vm, 'file', vm.file); - - vm.$nextTick() - .then(() => { - expect(vm.initEditor).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('calls initEditor when file key is changed', done => { - expect(vm.initEditor).not.toHaveBeenCalled(); - - Vue.set(vm, 'file', { - ...vm.file, - key: 'new', - }); - - vm.$nextTick() - .then(() => { - expect(vm.initEditor).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js deleted file mode 100644 index 2c52780f316..00000000000 --- a/spec/javascripts/ide/helpers.js +++ /dev/null @@ -1 +0,0 @@ -export * from '../../frontend/ide/helpers'; diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js deleted file mode 100644 index 27f0ad01f54..00000000000 --- a/spec/javascripts/ide/mock_data.js +++ /dev/null @@ -1 +0,0 @@ -export * from '../../frontend/ide/mock_data'; diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js deleted file mode 100644 index ce09cf51ac5..00000000000 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ /dev/null @@ -1,510 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import store from '~/ide/stores'; -import actions, { - getMergeRequestData, - getMergeRequestChanges, - getMergeRequestVersions, - openMergeRequest, -} from '~/ide/stores/actions/merge_request'; -import service from '~/ide/services'; -import { leftSidebarViews, PERMISSION_READ_MR } from '~/ide/constants'; -import { resetStore } from '../../helpers'; - -const TEST_PROJECT = 'abcproject'; -const TEST_PROJECT_ID = 17; - -describe('IDE store merge request actions', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - - store.state.projects[TEST_PROJECT] = { - id: TEST_PROJECT_ID, - mergeRequests: {}, - userPermissions: { - [PERMISSION_READ_MR]: true, - }, - }; - }); - - afterEach(() => { - mock.restore(); - resetStore(store); - }); - - describe('getMergeRequestsForBranch', () => { - describe('success', () => { - const mrData = { iid: 2, source_branch: 'bar' }; - const mockData = [mrData]; - - describe('base case', () => { - beforeEach(() => { - spyOn(service, 'getProjectMergeRequests').and.callThrough(); - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, mockData); - }); - - it('calls getProjectMergeRequests service method', done => { - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) - .then(() => { - expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, { - source_branch: 'bar', - source_project_id: TEST_PROJECT_ID, - order_by: 'created_at', - per_page: 1, - }); - - done(); - }) - .catch(done.fail); - }); - - it('sets the "Merge Request" Object', done => { - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) - .then(() => { - expect(store.state.projects.abcproject.mergeRequests).toEqual({ - '2': jasmine.objectContaining(mrData), - }); - done(); - }) - .catch(done.fail); - }); - - it('sets "Current Merge Request" object to the most recent MR', done => { - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) - .then(() => { - expect(store.state.currentMergeRequestId).toEqual('2'); - done(); - }) - .catch(done.fail); - }); - - it('does nothing if user cannot read MRs', done => { - store.state.projects[TEST_PROJECT].userPermissions[PERMISSION_READ_MR] = false; - - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) - .then(() => { - expect(service.getProjectMergeRequests).not.toHaveBeenCalled(); - expect(store.state.currentMergeRequestId).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('no merge requests for branch available case', () => { - beforeEach(() => { - spyOn(service, 'getProjectMergeRequests').and.callThrough(); - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, []); - }); - - it('does not fail if there are no merge requests for current branch', done => { - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'foo' }) - .then(() => { - expect(store.state.projects[TEST_PROJECT].mergeRequests).toEqual({}); - expect(store.state.currentMergeRequestId).toEqual(''); - done(); - }) - .catch(done.fail); - }); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).networkError(); - }); - - it('flashes message, if error', done => { - const flashSpy = spyOnDependency(actions, 'flash'); - - store - .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) - .then(() => { - fail('Expected getMergeRequestsForBranch to throw an error'); - }) - .catch(() => { - expect(flashSpy).toHaveBeenCalled(); - expect(flashSpy.calls.argsFor(0)[0]).toEqual('Error fetching merge requests for bar'); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('getMergeRequestData', () => { - describe('success', () => { - beforeEach(() => { - spyOn(service, 'getProjectMergeRequestData').and.callThrough(); - - mock - .onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1/) - .reply(200, { title: 'mergerequest' }); - }); - - it('calls getProjectMergeRequestData service method', done => { - store - .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(service.getProjectMergeRequestData).toHaveBeenCalledWith(TEST_PROJECT, 1); - - done(); - }) - .catch(done.fail); - }); - - it('sets the Merge Request Object', done => { - store - .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(store.state.currentMergeRequestId).toBe(1); - expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].title).toBe( - 'mergerequest', - ); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1/).networkError(); - }); - - it('dispatches error action', done => { - const dispatch = jasmine.createSpy('dispatch'); - - getMergeRequestData( - { - commit() {}, - dispatch, - state: store.state, - }, - { projectId: TEST_PROJECT, mergeRequestId: 1 }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { - text: 'An error occurred while loading the merge request.', - action: jasmine.any(Function), - actionText: 'Please try again', - actionPayload: { - projectId: TEST_PROJECT, - mergeRequestId: 1, - force: false, - }, - }); - - done(); - }); - }); - }); - }); - - describe('getMergeRequestChanges', () => { - beforeEach(() => { - store.state.projects[TEST_PROJECT].mergeRequests['1'] = { changes: [] }; - }); - - describe('success', () => { - beforeEach(() => { - spyOn(service, 'getProjectMergeRequestChanges').and.callThrough(); - - mock - .onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1\/changes/) - .reply(200, { title: 'mergerequest' }); - }); - - it('calls getProjectMergeRequestChanges service method', done => { - store - .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith(TEST_PROJECT, 1); - - done(); - }) - .catch(done.fail); - }); - - it('sets the Merge Request Changes Object', done => { - store - .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].changes.title).toBe( - 'mergerequest', - ); - done(); - }) - .catch(done.fail); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1\/changes/).networkError(); - }); - - it('dispatches error action', done => { - const dispatch = jasmine.createSpy('dispatch'); - - getMergeRequestChanges( - { - commit() {}, - dispatch, - state: store.state, - }, - { projectId: TEST_PROJECT, mergeRequestId: 1 }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { - text: 'An error occurred while loading the merge request changes.', - action: jasmine.any(Function), - actionText: 'Please try again', - actionPayload: { - projectId: TEST_PROJECT, - mergeRequestId: 1, - force: false, - }, - }); - - done(); - }); - }); - }); - }); - - describe('getMergeRequestVersions', () => { - beforeEach(() => { - store.state.projects[TEST_PROJECT].mergeRequests['1'] = { versions: [] }; - }); - - describe('success', () => { - beforeEach(() => { - mock - .onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1\/versions/) - .reply(200, [{ id: 789 }]); - spyOn(service, 'getProjectMergeRequestVersions').and.callThrough(); - }); - - it('calls getProjectMergeRequestVersions service method', done => { - store - .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith(TEST_PROJECT, 1); - - done(); - }) - .catch(done.fail); - }); - - it('sets the Merge Request Versions Object', done => { - store - .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) - .then(() => { - expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].versions.length).toBe(1); - done(); - }) - .catch(done.fail); - }); - }); - - describe('error', () => { - beforeEach(() => { - mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests\/1\/versions/).networkError(); - }); - - it('dispatches error action', done => { - const dispatch = jasmine.createSpy('dispatch'); - - getMergeRequestVersions( - { - commit() {}, - dispatch, - state: store.state, - }, - { projectId: TEST_PROJECT, mergeRequestId: 1 }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { - text: 'An error occurred while loading the merge request version data.', - action: jasmine.any(Function), - actionText: 'Please try again', - actionPayload: { - projectId: TEST_PROJECT, - mergeRequestId: 1, - force: false, - }, - }); - - done(); - }); - }); - }); - }); - - describe('openMergeRequest', () => { - const mr = { - projectId: TEST_PROJECT, - targetProjectId: 'defproject', - mergeRequestId: 2, - }; - let testMergeRequest; - let testMergeRequestChanges; - - const mockGetters = { findBranch: () => ({ commit: { id: 'abcd2322' } }) }; - - beforeEach(() => { - testMergeRequest = { - source_branch: 'abcbranch', - }; - testMergeRequestChanges = { - changes: [], - }; - store.state.entries = { - foo: { - type: 'blob', - }, - bar: { - type: 'blob', - }, - }; - - store.state.currentProjectId = 'test/test'; - store.state.currentBranchId = 'master'; - - store.state.projects['test/test'] = { - branches: { - master: { - commit: { - id: '7297abc', - }, - }, - abcbranch: { - commit: { - id: '29020fc', - }, - }, - }, - }; - - const originalDispatch = store.dispatch; - - spyOn(store, 'dispatch').and.callFake((type, payload) => { - switch (type) { - case 'getMergeRequestData': - return Promise.resolve(testMergeRequest); - case 'getMergeRequestChanges': - return Promise.resolve(testMergeRequestChanges); - case 'getFiles': - case 'getMergeRequestVersions': - case 'getBranchData': - case 'setFileMrChange': - return Promise.resolve(); - default: - return originalDispatch(type, payload); - } - }); - spyOn(service, 'getFileData').and.callFake(() => - Promise.resolve({ - headers: {}, - }), - ); - }); - - it('dispatches actions for merge request data', done => { - openMergeRequest({ state: store.state, dispatch: store.dispatch, getters: mockGetters }, mr) - .then(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['getMergeRequestData', mr], - ['setCurrentBranchId', testMergeRequest.source_branch], - [ - 'getBranchData', - { - projectId: mr.projectId, - branchId: testMergeRequest.source_branch, - }, - ], - [ - 'getFiles', - { - projectId: mr.projectId, - branchId: testMergeRequest.source_branch, - ref: 'abcd2322', - }, - ], - ['getMergeRequestVersions', mr], - ['getMergeRequestChanges', mr], - ]); - }) - .then(done) - .catch(done.fail); - }); - - it('updates activity bar view and gets file data, if changes are found', done => { - store.state.entries.foo = { - url: 'test', - type: 'blob', - }; - store.state.entries.bar = { - url: 'test', - type: 'blob', - }; - - testMergeRequestChanges.changes = [ - { new_path: 'foo', path: 'foo' }, - { new_path: 'bar', path: 'bar' }, - ]; - - openMergeRequest({ state: store.state, dispatch: store.dispatch, getters: mockGetters }, mr) - .then(() => { - expect(store.dispatch).toHaveBeenCalledWith( - 'updateActivityBarView', - leftSidebarViews.review.name, - ); - - testMergeRequestChanges.changes.forEach((change, i) => { - expect(store.dispatch).toHaveBeenCalledWith('setFileMrChange', { - file: store.state.entries[change.new_path], - mrChange: change, - }); - - expect(store.dispatch).toHaveBeenCalledWith('getFileData', { - path: change.new_path, - makeFileActive: i === 0, - openFile: true, - }); - }); - - expect(store.state.openFiles.length).toBe(testMergeRequestChanges.changes.length); - }) - .then(done) - .catch(done.fail); - }); - - it('flashes message, if error', done => { - const flashSpy = spyOnDependency(actions, 'flash'); - store.dispatch.and.returnValue(Promise.reject()); - - openMergeRequest(store, mr) - .then(() => { - fail('Expected openMergeRequest to throw an error'); - }) - .catch(() => { - expect(flashSpy).toHaveBeenCalledWith(jasmine.any(String)); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js deleted file mode 100644 index e962224d1ad..00000000000 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ /dev/null @@ -1,404 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { - refreshLastCommitData, - showBranchNotFoundError, - createNewBranchFromDefault, - loadEmptyBranch, - openBranch, - loadFile, - loadBranch, -} from '~/ide/stores/actions'; -import { createStore } from '~/ide/stores'; -import service from '~/ide/services'; -import api from '~/api'; -import router from '~/ide/ide_router'; -import { resetStore } from '../../helpers'; -import testAction from '../../../helpers/vuex_action_helper'; - -const TEST_PROJECT_ID = 'abc/def'; - -describe('IDE store project actions', () => { - let mock; - let store; - - beforeEach(() => { - store = createStore(); - mock = new MockAdapter(axios); - - store.state.projects[TEST_PROJECT_ID] = { - branches: {}, - }; - }); - - afterEach(() => { - mock.restore(); - - resetStore(store); - }); - - describe('refreshLastCommitData', () => { - beforeEach(() => { - store.state.currentProjectId = 'abc/def'; - store.state.currentBranchId = 'master'; - store.state.projects['abc/def'] = { - id: 4, - branches: { - master: { - commit: null, - }, - }, - }; - spyOn(service, 'getBranchData').and.returnValue( - Promise.resolve({ - data: { - commit: { id: '123' }, - }, - }), - ); - }); - - it('calls the service', done => { - store - .dispatch('refreshLastCommitData', { - projectId: store.state.currentProjectId, - branchId: store.state.currentBranchId, - }) - .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'master'); - - done(); - }) - .catch(done.fail); - }); - - it('commits getBranchData', done => { - testAction( - refreshLastCommitData, - { - projectId: store.state.currentProjectId, - branchId: store.state.currentBranchId, - }, - store.state, - // mutations - [ - { - type: 'SET_BRANCH_COMMIT', - payload: { - projectId: TEST_PROJECT_ID, - branchId: 'master', - commit: { id: '123' }, - }, - }, - ], - // action - [], - done, - ); - }); - }); - - describe('showBranchNotFoundError', () => { - it('dispatches setErrorMessage', done => { - testAction( - showBranchNotFoundError, - 'master', - null, - [], - [ - { - type: 'setErrorMessage', - payload: { - text: "Branch <strong>master</strong> was not found in this project's repository.", - action: jasmine.any(Function), - actionText: 'Create branch', - actionPayload: 'master', - }, - }, - ], - done, - ); - }); - }); - - describe('createNewBranchFromDefault', () => { - it('calls API', done => { - spyOn(api, 'createBranch').and.returnValue(Promise.resolve()); - spyOn(router, 'push'); - - createNewBranchFromDefault( - { - state: { - currentProjectId: 'project-path', - }, - getters: { - currentProject: { - default_branch: 'master', - }, - }, - dispatch() {}, - }, - 'new-branch-name', - ) - .then(() => { - expect(api.createBranch).toHaveBeenCalledWith('project-path', { - ref: 'master', - branch: 'new-branch-name', - }); - }) - .then(done) - .catch(done.fail); - }); - - it('clears error message', done => { - const dispatchSpy = jasmine.createSpy('dispatch'); - spyOn(api, 'createBranch').and.returnValue(Promise.resolve()); - spyOn(router, 'push'); - - createNewBranchFromDefault( - { - state: { - currentProjectId: 'project-path', - }, - getters: { - currentProject: { - default_branch: 'master', - }, - }, - dispatch: dispatchSpy, - }, - 'new-branch-name', - ) - .then(() => { - expect(dispatchSpy).toHaveBeenCalledWith('setErrorMessage', null); - }) - .then(done) - .catch(done.fail); - }); - - it('reloads window', done => { - spyOn(api, 'createBranch').and.returnValue(Promise.resolve()); - spyOn(router, 'push'); - - createNewBranchFromDefault( - { - state: { - currentProjectId: 'project-path', - }, - getters: { - currentProject: { - default_branch: 'master', - }, - }, - dispatch() {}, - }, - 'new-branch-name', - ) - .then(() => { - expect(router.push).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('loadEmptyBranch', () => { - it('creates a blank tree and sets loading state to false', done => { - testAction( - loadEmptyBranch, - { projectId: TEST_PROJECT_ID, branchId: 'master' }, - store.state, - [ - { type: 'CREATE_TREE', payload: { treePath: `${TEST_PROJECT_ID}/master` } }, - { - type: 'TOGGLE_LOADING', - payload: { entry: store.state.trees[`${TEST_PROJECT_ID}/master`], forceValue: false }, - }, - ], - jasmine.any(Object), - done, - ); - }); - - it('does nothing, if tree already exists', done => { - const trees = { [`${TEST_PROJECT_ID}/master`]: [] }; - - testAction( - loadEmptyBranch, - { projectId: TEST_PROJECT_ID, branchId: 'master' }, - { trees }, - [], - [], - done, - ); - }); - }); - - describe('loadFile', () => { - beforeEach(() => { - Object.assign(store.state, { - entries: { - foo: { pending: false }, - 'foo/bar-pending': { pending: true }, - 'foo/bar': { pending: false }, - }, - }); - spyOn(store, 'dispatch'); - }); - - it('does nothing, if basePath is not given', () => { - loadFile(store, { basePath: undefined }); - - expect(store.dispatch).not.toHaveBeenCalled(); - }); - - it('handles tree entry action, if basePath is given and the entry is not pending', () => { - loadFile(store, { basePath: 'foo/bar/' }); - - expect(store.dispatch).toHaveBeenCalledWith( - 'handleTreeEntryAction', - store.state.entries['foo/bar'], - ); - }); - - it('does not handle tree entry action, if entry is pending', () => { - loadFile(store, { basePath: 'foo/bar-pending/' }); - - expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything()); - }); - - it('creates a new temp file supplied via URL if the file does not exist yet', () => { - loadFile(store, { basePath: 'not-existent.md' }); - - expect(store.dispatch.calls.count()).toBe(1); - - expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything()); - - expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { - name: 'not-existent.md', - type: 'blob', - }); - }); - }); - - describe('loadBranch', () => { - const projectId = TEST_PROJECT_ID; - const branchId = '123-lorem'; - const ref = 'abcd2322'; - - it('when empty repo, loads empty branch', done => { - const mockGetters = { emptyRepo: true }; - - testAction( - loadBranch, - { projectId, branchId }, - { ...store.state, ...mockGetters }, - [], - [{ type: 'loadEmptyBranch', payload: { projectId, branchId } }], - done, - ); - }); - - it('when branch already exists, does nothing', done => { - store.state.projects[projectId].branches[branchId] = {}; - - testAction(loadBranch, { projectId, branchId }, store.state, [], [], done); - }); - - it('fetches branch data', done => { - const mockGetters = { findBranch: () => ({ commit: { id: ref } }) }; - spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); - - loadBranch( - { getters: mockGetters, state: store.state, dispatch: store.dispatch }, - { projectId, branchId }, - ) - .then(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['getBranchData', { projectId, branchId }], - ['getMergeRequestsForBranch', { projectId, branchId }], - ['getFiles', { projectId, branchId, ref }], - ]); - }) - .then(done) - .catch(done.fail); - }); - - it('shows an error if branch can not be fetched', done => { - spyOn(store, 'dispatch').and.returnValue(Promise.reject()); - - loadBranch(store, { projectId, branchId }) - .then(done.fail) - .catch(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['getBranchData', { projectId, branchId }], - ['showBranchNotFoundError', branchId], - ]); - done(); - }); - }); - }); - - describe('openBranch', () => { - const projectId = TEST_PROJECT_ID; - const branchId = '123-lorem'; - - const branch = { - projectId, - branchId, - }; - - beforeEach(() => { - Object.assign(store.state, { - entries: { - foo: { pending: false }, - 'foo/bar-pending': { pending: true }, - 'foo/bar': { pending: false }, - }, - }); - }); - - describe('existing branch', () => { - beforeEach(() => { - spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); - }); - - it('dispatches branch actions', done => { - openBranch(store, branch) - .then(() => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branchId], - ['loadBranch', { projectId, branchId }], - ['loadFile', { basePath: undefined }], - ]); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('non-existent branch', () => { - beforeEach(() => { - spyOn(store, 'dispatch').and.returnValue(Promise.reject()); - }); - - it('dispatches correct branch actions', done => { - openBranch(store, branch) - .then(val => { - expect(store.dispatch.calls.allArgs()).toEqual([ - ['setCurrentBranchId', branchId], - ['loadBranch', { projectId, branchId }], - ]); - - expect(val).toEqual( - new Error( - `An error occurred while getting files for - <strong>${projectId}/${branchId}</strong>`, - ), - ); - }) - .then(done) - .catch(done.fail); - }); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js deleted file mode 100644 index 2201a3b4b57..00000000000 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ /dev/null @@ -1,216 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import testAction from 'spec/helpers/vuex_action_helper'; -import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree'; -import * as types from '~/ide/stores/mutation_types'; -import axios from '~/lib/utils/axios_utils'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import router from '~/ide/ide_router'; -import { file, resetStore, createEntriesFromPaths } from '../../helpers'; - -describe('Multi-file store tree actions', () => { - let projectTree; - let mock; - - const basicCallParameters = { - endpoint: 'rootEndpoint', - projectId: 'abcproject', - branch: 'master', - branchId: 'master', - ref: '12345678', - }; - - beforeEach(() => { - jasmine.clock().install(); - spyOn(router, 'push'); - - mock = new MockAdapter(axios); - - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - web_url: '', - path_with_namespace: 'foo/abcproject', - }; - }); - - afterEach(() => { - jasmine.clock().uninstall(); - mock.restore(); - resetStore(store); - }); - - describe('getFiles', () => { - describe('success', () => { - beforeEach(() => { - spyOn(service, 'getFiles').and.callThrough(); - - mock - .onGet(/(.*)/) - .replyOnce(200, [ - 'file.txt', - 'folder/fileinfolder.js', - 'folder/subfolder/fileinsubfolder.js', - ]); - }); - - it('calls service getFiles', done => { - store - .dispatch('getFiles', basicCallParameters) - .then(() => { - expect(service.getFiles).toHaveBeenCalledWith('foo/abcproject', '12345678'); - - done(); - }) - .catch(done.fail); - }); - - it('adds data into tree', done => { - store - .dispatch('getFiles', basicCallParameters) - .then(() => { - // The populating of the tree is deferred for performance reasons. - // See this merge request for details: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25700 - jasmine.clock().tick(1); - }) - .then(() => { - projectTree = store.state.trees['abcproject/master']; - - expect(projectTree.tree.length).toBe(2); - expect(projectTree.tree[0].type).toBe('tree'); - expect(projectTree.tree[0].tree[1].name).toBe('fileinfolder.js'); - expect(projectTree.tree[1].type).toBe('blob'); - expect(projectTree.tree[0].tree[0].tree[0].type).toBe('blob'); - expect(projectTree.tree[0].tree[0].tree[0].name).toBe('fileinsubfolder.js'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('error', () => { - it('dispatches error action', done => { - const dispatch = jasmine.createSpy('dispatchSpy'); - - store.state.projects = { - 'abc/def': { - web_url: `${gl.TEST_HOST}/files`, - branches: { - 'master-testing': { - commit: { - id: '12345', - }, - }, - }, - }, - }; - const getters = { - findBranch: () => store.state.projects['abc/def'].branches['master-testing'], - }; - - mock.onGet(/(.*)/).replyOnce(500); - - getFiles( - { - commit() {}, - dispatch, - state: store.state, - getters, - }, - { - projectId: 'abc/def', - branchId: 'master-testing', - }, - ) - .then(done.fail) - .catch(() => { - expect(dispatch).toHaveBeenCalledWith('setErrorMessage', { - text: 'An error occurred while loading all the files.', - action: jasmine.any(Function), - actionText: 'Please try again', - actionPayload: { projectId: 'abc/def', branchId: 'master-testing' }, - }); - done(); - }); - }); - }); - }); - - describe('toggleTreeOpen', () => { - let tree; - - beforeEach(() => { - tree = file('testing', '1', 'tree'); - store.state.entries[tree.path] = tree; - }); - - it('toggles the tree open', done => { - store - .dispatch('toggleTreeOpen', tree.path) - .then(() => { - expect(tree.opened).toBeTruthy(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('showTreeEntry', () => { - beforeEach(() => { - const paths = [ - 'grandparent', - 'ancestor', - 'grandparent/parent', - 'grandparent/aunt', - 'grandparent/parent/child.txt', - 'grandparent/aunt/cousing.txt', - ]; - - Object.assign(store.state.entries, createEntriesFromPaths(paths)); - }); - - it('opens the parents', done => { - testAction( - showTreeEntry, - 'grandparent/parent/child.txt', - store.state, - [{ type: types.SET_TREE_OPEN, payload: 'grandparent/parent' }], - [{ type: 'showTreeEntry', payload: 'grandparent/parent' }], - done, - ); - }); - }); - - describe('setDirectoryData', () => { - it('sets tree correctly if there are no opened files yet', done => { - const treeFile = file({ name: 'README.md' }); - store.state.trees['abcproject/master'] = {}; - - testAction( - setDirectoryData, - { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] }, - store.state, - [ - { - type: types.SET_DIRECTORY_DATA, - payload: { - treePath: 'abcproject/master', - data: [treeFile], - }, - }, - { - type: types.TOGGLE_LOADING, - payload: { - entry: {}, - forceValue: false, - }, - }, - ], - [], - done, - ); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js deleted file mode 100644 index 364c8421b6b..00000000000 --- a/spec/javascripts/ide/stores/actions_spec.js +++ /dev/null @@ -1,1116 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import actions, { - stageAllChanges, - unstageAllChanges, - toggleFileFinder, - setCurrentBranchId, - setEmptyStateSvgs, - updateActivityBarView, - updateTempFlagForEntry, - setErrorMessage, - deleteEntry, - renameEntry, - getBranchData, - createTempEntry, - discardAllChanges, -} from '~/ide/stores/actions'; -import axios from '~/lib/utils/axios_utils'; -import { createStore } from '~/ide/stores'; -import * as types from '~/ide/stores/mutation_types'; -import router from '~/ide/ide_router'; -import { file } from '../helpers'; -import testAction from '../../helpers/vuex_action_helper'; -import eventHub from '~/ide/eventhub'; - -describe('Multi-file store actions', () => { - let store; - - beforeEach(() => { - store = createStore(); - - spyOn(store, 'commit').and.callThrough(); - spyOn(store, 'dispatch').and.callThrough(); - spyOn(router, 'push'); - }); - - describe('redirectToUrl', () => { - it('calls visitUrl', done => { - const visitUrl = spyOnDependency(actions, 'visitUrl'); - - store - .dispatch('redirectToUrl', 'test') - .then(() => { - expect(visitUrl).toHaveBeenCalledWith('test'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('setInitialData', () => { - it('commits initial data', done => { - store - .dispatch('setInitialData', { canCommit: true }) - .then(() => { - expect(store.state.canCommit).toBeTruthy(); - done(); - }) - .catch(done.fail); - }); - }); - - describe('discardAllChanges', () => { - const paths = ['to_discard', 'another_one_to_discard']; - - beforeEach(() => { - paths.forEach(path => { - const f = file(path); - f.changed = true; - - store.state.openFiles.push(f); - store.state.changedFiles.push(f); - store.state.entries[f.path] = f; - }); - }); - - it('discards all changes in file', () => { - const expectedCalls = paths.map(path => ['restoreOriginalFile', path]); - - discardAllChanges(store); - - expect(store.dispatch.calls.allArgs()).toEqual(jasmine.arrayContaining(expectedCalls)); - }); - - it('removes all files from changedFiles state', done => { - store - .dispatch('discardAllChanges') - .then(() => { - expect(store.state.changedFiles.length).toBe(0); - expect(store.state.openFiles.length).toBe(2); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('closeAllFiles', () => { - beforeEach(() => { - const f = file('closeAll'); - store.state.openFiles.push(f); - store.state.openFiles[0].opened = true; - store.state.entries[f.path] = f; - }); - - it('closes all open files', done => { - store - .dispatch('closeAllFiles') - .then(() => { - expect(store.state.openFiles.length).toBe(0); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('createTempEntry', () => { - beforeEach(() => { - document.body.innerHTML += '<div class="flash-container"></div>'; - - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'mybranch'; - - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - store.state.projects.abcproject = { - web_url: '', - }; - }); - - afterEach(() => { - document.querySelector('.flash-container').remove(); - }); - - describe('tree', () => { - it('creates temp tree', done => { - store - .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, - name: 'test', - type: 'tree', - }) - .then(() => { - const entry = store.state.entries.test; - - expect(entry).not.toBeNull(); - expect(entry.type).toBe('tree'); - - done(); - }) - .catch(done.fail); - }); - - it('creates new folder inside another tree', done => { - const tree = { - type: 'tree', - name: 'testing', - path: 'testing', - tree: [], - }; - - store.state.entries[tree.path] = tree; - - store - .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, - name: 'testing/test', - type: 'tree', - }) - .then(() => { - expect(tree.tree[0].tempFile).toBeTruthy(); - expect(tree.tree[0].name).toBe('test'); - expect(tree.tree[0].type).toBe('tree'); - - done(); - }) - .catch(done.fail); - }); - - it('does not create new tree if already exists', done => { - const tree = { - type: 'tree', - path: 'testing', - tempFile: false, - tree: [], - }; - - store.state.entries[tree.path] = tree; - - store - .dispatch('createTempEntry', { - branchId: store.state.currentBranchId, - name: 'testing', - type: 'tree', - }) - .then(() => { - expect(store.state.entries[tree.path].tempFile).toEqual(false); - expect(document.querySelector('.flash-alert')).not.toBeNull(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('blob', () => { - it('creates temp file', done => { - const name = 'test'; - - store - .dispatch('createTempEntry', { - name, - branchId: 'mybranch', - type: 'blob', - }) - .then(() => { - const f = store.state.entries[name]; - - expect(f.tempFile).toBeTruthy(); - expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1); - - done(); - }) - .catch(done.fail); - }); - - it('adds tmp file to open files', done => { - const name = 'test'; - - store - .dispatch('createTempEntry', { - name, - branchId: 'mybranch', - type: 'blob', - }) - .then(() => { - const f = store.state.entries[name]; - - expect(store.state.openFiles.length).toBe(1); - expect(store.state.openFiles[0].name).toBe(f.name); - - done(); - }) - .catch(done.fail); - }); - - it('adds tmp file to staged files', done => { - const name = 'test'; - - store - .dispatch('createTempEntry', { - name, - branchId: 'mybranch', - type: 'blob', - }) - .then(() => { - expect(store.state.stagedFiles).toEqual([jasmine.objectContaining({ name })]); - - done(); - }) - .catch(done.fail); - }); - - it('sets tmp file as active', () => { - createTempEntry(store, { name: 'test', branchId: 'mybranch', type: 'blob' }); - - expect(store.dispatch).toHaveBeenCalledWith('setFileActive', 'test'); - }); - - it('creates flash message if file already exists', done => { - const f = file('test', '1', 'blob'); - store.state.trees['abcproject/mybranch'].tree = [f]; - store.state.entries[f.path] = f; - - store - .dispatch('createTempEntry', { - name: 'test', - branchId: 'mybranch', - type: 'blob', - }) - .then(() => { - expect(document.querySelector('.flash-alert')?.textContent.trim()).toEqual( - `The name "${f.name}" is already taken in this directory.`, - ); - - done(); - }) - .catch(done.fail); - }); - - it('bursts unused seal', done => { - store - .dispatch('createTempEntry', { - name: 'test', - branchId: 'mybranch', - type: 'blob', - }) - .then(() => { - expect(store.state.unusedSeal).toBe(false); - - done(); - }) - .catch(done.fail); - }); - }); - }); - - describe('scrollToTab', () => { - it('focuses the current active element', done => { - document.body.innerHTML += - '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>'; - const el = document.querySelector('.repo-tab'); - spyOn(el, 'focus'); - - store - .dispatch('scrollToTab') - .then(() => { - setTimeout(() => { - expect(el.focus).toHaveBeenCalled(); - - document.getElementById('tabs').remove(); - - done(); - }); - }) - .catch(done.fail); - }); - }); - - describe('stage/unstageAllChanges', () => { - let file1; - let file2; - - beforeEach(() => { - file1 = { ...file('test'), content: 'changed test', raw: 'test' }; - file2 = { ...file('test2'), content: 'changed test2', raw: 'test2' }; - - store.state.openFiles = [file1]; - store.state.changedFiles = [file1]; - store.state.stagedFiles = [{ ...file2, content: 'staged test' }]; - - store.state.entries = { - [file1.path]: { ...file1 }, - [file2.path]: { ...file2 }, - }; - }); - - describe('stageAllChanges', () => { - it('adds all files from changedFiles to stagedFiles', () => { - stageAllChanges(store); - - expect(store.commit.calls.allArgs()).toEqual([ - [types.SET_LAST_COMMIT_MSG, ''], - [types.STAGE_CHANGE, jasmine.objectContaining({ path: file1.path })], - ]); - }); - - it('opens pending tab if a change exists in that file', () => { - stageAllChanges(store); - - expect(store.dispatch.calls.allArgs()).toEqual([ - [ - 'openPendingTab', - { file: { ...file1, staged: true, changed: true }, keyPrefix: 'staged' }, - ], - ]); - }); - - it('does not open pending tab if no change exists in that file', () => { - store.state.entries[file1.path].content = 'test'; - store.state.stagedFiles = [file1]; - store.state.changedFiles = [store.state.entries[file1.path]]; - - stageAllChanges(store); - - expect(store.dispatch).not.toHaveBeenCalled(); - }); - }); - - describe('unstageAllChanges', () => { - it('removes all files from stagedFiles after unstaging', () => { - unstageAllChanges(store); - - expect(store.commit.calls.allArgs()).toEqual([ - [types.UNSTAGE_CHANGE, jasmine.objectContaining({ path: file2.path })], - ]); - }); - - it('opens pending tab if a change exists in that file', () => { - unstageAllChanges(store); - - expect(store.dispatch.calls.allArgs()).toEqual([ - ['openPendingTab', { file: file1, keyPrefix: 'unstaged' }], - ]); - }); - - it('does not open pending tab if no change exists in that file', () => { - store.state.entries[file1.path].content = 'test'; - store.state.stagedFiles = [file1]; - store.state.changedFiles = [store.state.entries[file1.path]]; - - unstageAllChanges(store); - - expect(store.dispatch).not.toHaveBeenCalled(); - }); - }); - }); - - describe('updateViewer', () => { - it('updates viewer state', done => { - store - .dispatch('updateViewer', 'diff') - .then(() => { - expect(store.state.viewer).toBe('diff'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('updateActivityBarView', () => { - it('commits UPDATE_ACTIVITY_BAR_VIEW', done => { - testAction( - updateActivityBarView, - 'test', - {}, - [{ type: 'UPDATE_ACTIVITY_BAR_VIEW', payload: 'test' }], - [], - done, - ); - }); - }); - - describe('setEmptyStateSvgs', () => { - it('commits setEmptyStateSvgs', done => { - testAction( - setEmptyStateSvgs, - 'svg', - {}, - [{ type: 'SET_EMPTY_STATE_SVGS', payload: 'svg' }], - [], - done, - ); - }); - }); - - describe('updateTempFlagForEntry', () => { - it('commits UPDATE_TEMP_FLAG', done => { - const f = { - ...file(), - path: 'test', - tempFile: true, - }; - store.state.entries[f.path] = f; - - testAction( - updateTempFlagForEntry, - { file: f, tempFile: false }, - store.state, - [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], - [], - done, - ); - }); - - it('commits UPDATE_TEMP_FLAG and dispatches for parent', done => { - const parent = { - ...file(), - path: 'testing', - }; - const f = { - ...file(), - path: 'test', - parentPath: 'testing', - }; - store.state.entries[parent.path] = parent; - store.state.entries[f.path] = f; - - testAction( - updateTempFlagForEntry, - { file: f, tempFile: false }, - store.state, - [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], - [{ type: 'updateTempFlagForEntry', payload: { file: parent, tempFile: false } }], - done, - ); - }); - - it('does not dispatch for parent, if parent does not exist', done => { - const f = { - ...file(), - path: 'test', - parentPath: 'testing', - }; - store.state.entries[f.path] = f; - - testAction( - updateTempFlagForEntry, - { file: f, tempFile: false }, - store.state, - [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], - [], - done, - ); - }); - }); - - describe('setCurrentBranchId', () => { - it('commits setCurrentBranchId', done => { - testAction( - setCurrentBranchId, - 'branchId', - {}, - [{ type: 'SET_CURRENT_BRANCH', payload: 'branchId' }], - [], - done, - ); - }); - }); - - describe('toggleFileFinder', () => { - it('commits TOGGLE_FILE_FINDER', done => { - testAction( - toggleFileFinder, - true, - null, - [{ type: 'TOGGLE_FILE_FINDER', payload: true }], - [], - done, - ); - }); - }); - - describe('setErrorMessage', () => { - it('commis error messsage', done => { - testAction( - setErrorMessage, - 'error', - null, - [{ type: types.SET_ERROR_MESSAGE, payload: 'error' }], - [], - done, - ); - }); - }); - - describe('deleteEntry', () => { - it('commits entry deletion', done => { - store.state.entries.path = 'testing'; - - testAction( - deleteEntry, - 'path', - store.state, - [{ type: types.DELETE_ENTRY, payload: 'path' }], - [{ type: 'stageChange', payload: 'path' }, { type: 'triggerFilesChange' }], - done, - ); - }); - - it('does not delete a folder after it is emptied', done => { - const testFolder = { - type: 'tree', - tree: [], - }; - const testEntry = { - path: 'testFolder/entry-to-delete', - parentPath: 'testFolder', - opened: false, - tree: [], - }; - testFolder.tree.push(testEntry); - store.state.entries = { - testFolder, - 'testFolder/entry-to-delete': testEntry, - }; - - testAction( - deleteEntry, - 'testFolder/entry-to-delete', - store.state, - [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }], - [ - { type: 'stageChange', payload: 'testFolder/entry-to-delete' }, - { type: 'triggerFilesChange' }, - ], - done, - ); - }); - - describe('when renamed', () => { - let testEntry; - - beforeEach(() => { - testEntry = { - path: 'test', - name: 'test', - prevPath: 'test_old', - prevName: 'test_old', - prevParentPath: '', - }; - - store.state.entries = { test: testEntry }; - }); - - describe('and previous does not exist', () => { - it('reverts the rename before deleting', done => { - testAction( - deleteEntry, - testEntry.path, - store.state, - [], - [ - { - type: 'renameEntry', - payload: { - path: testEntry.path, - name: testEntry.prevName, - parentPath: testEntry.prevParentPath, - }, - }, - { - type: 'deleteEntry', - payload: testEntry.prevPath, - }, - ], - done, - ); - }); - }); - - describe('and previous exists', () => { - beforeEach(() => { - const oldEntry = { - path: testEntry.prevPath, - name: testEntry.prevName, - }; - - store.state.entries[oldEntry.path] = oldEntry; - }); - - it('does not revert rename before deleting', done => { - testAction( - deleteEntry, - testEntry.path, - store.state, - [{ type: types.DELETE_ENTRY, payload: testEntry.path }], - [{ type: 'stageChange', payload: testEntry.path }, { type: 'triggerFilesChange' }], - done, - ); - }); - - it('when previous is deleted, it reverts rename before deleting', done => { - store.state.entries[testEntry.prevPath].deleted = true; - - testAction( - deleteEntry, - testEntry.path, - store.state, - [], - [ - { - type: 'renameEntry', - payload: { - path: testEntry.path, - name: testEntry.prevName, - parentPath: testEntry.prevParentPath, - }, - }, - { - type: 'deleteEntry', - payload: testEntry.prevPath, - }, - ], - done, - ); - }); - }); - }); - - it('bursts unused seal', done => { - store.state.entries.test = file('test'); - - store - .dispatch('deleteEntry', 'test') - .then(() => { - expect(store.state.unusedSeal).toBe(false); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('renameEntry', () => { - describe('purging of file model cache', () => { - beforeEach(() => { - spyOn(eventHub, '$emit'); - }); - - it('does not purge model cache for temporary entries that got renamed', done => { - Object.assign(store.state.entries, { - test: { - ...file('test'), - key: 'foo-key', - type: 'blob', - tempFile: true, - }, - }); - - store - .dispatch('renameEntry', { - path: 'test', - name: 'new', - }) - .then(() => { - expect(eventHub.$emit.calls.allArgs()).not.toContain( - 'editor.update.model.dispose.foo-bar', - ); - }) - .then(done) - .catch(done.fail); - }); - - it('purges model cache for renamed entry', done => { - Object.assign(store.state.entries, { - test: { - ...file('test'), - key: 'foo-key', - type: 'blob', - tempFile: false, - }, - }); - - store - .dispatch('renameEntry', { - path: 'test', - name: 'new', - }) - .then(() => { - expect(eventHub.$emit).toHaveBeenCalled(); - expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.foo-key`); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('single entry', () => { - let origEntry; - let renamedEntry; - - beforeEach(() => { - // Need to insert both because `testAction` doesn't actually call the mutation - origEntry = file('orig', 'orig', 'blob'); - renamedEntry = { - ...file('renamed', 'renamed', 'blob'), - prevKey: origEntry.key, - prevName: origEntry.name, - prevPath: origEntry.path, - }; - - Object.assign(store.state.entries, { - orig: origEntry, - renamed: renamedEntry, - }); - }); - - it('by default renames an entry and stages it', () => { - const dispatch = jasmine.createSpy(); - const commit = jasmine.createSpy(); - - renameEntry( - { dispatch, commit, state: store.state, getters: store.getters }, - { path: 'orig', name: 'renamed' }, - ); - - expect(commit.calls.allArgs()).toEqual([ - [types.RENAME_ENTRY, { path: 'orig', name: 'renamed', parentPath: undefined }], - [types.STAGE_CHANGE, jasmine.objectContaining({ path: 'renamed' })], - ]); - }); - - it('if not changed, completely unstages and discards entry if renamed to original', done => { - testAction( - renameEntry, - { path: 'renamed', name: 'orig' }, - store.state, - [ - { - type: types.RENAME_ENTRY, - payload: { - path: 'renamed', - name: 'orig', - parentPath: undefined, - }, - }, - { - type: types.REMOVE_FILE_FROM_STAGED_AND_CHANGED, - payload: origEntry, - }, - ], - [{ type: 'triggerFilesChange' }], - done, - ); - }); - - it('if already in changed, does not add to change', done => { - store.state.changedFiles.push(renamedEntry); - - testAction( - renameEntry, - { path: 'orig', name: 'renamed' }, - store.state, - [jasmine.objectContaining({ type: types.RENAME_ENTRY })], - [{ type: 'triggerFilesChange' }], - done, - ); - }); - - it('routes to the renamed file if the original file has been opened', done => { - Object.assign(store.state.entries.orig, { - opened: true, - url: '/foo-bar.md', - }); - - store - .dispatch('renameEntry', { - path: 'orig', - name: 'renamed', - }) - .then(() => { - expect(router.push.calls.count()).toBe(1); - expect(router.push).toHaveBeenCalledWith(`/project/foo-bar.md`); - }) - .then(done) - .catch(done.fail); - }); - - it('bursts unused seal', done => { - store - .dispatch('renameEntry', { - path: 'orig', - name: 'renamed', - }) - .then(() => { - expect(store.state.unusedSeal).toBe(false); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('folder', () => { - let folder; - let file1; - let file2; - - beforeEach(() => { - folder = file('folder', 'folder', 'tree'); - file1 = file('file-1', 'file-1', 'blob', folder); - file2 = file('file-2', 'file-2', 'blob', folder); - - folder.tree = [file1, file2]; - - Object.assign(store.state.entries, { - [folder.path]: folder, - [file1.path]: file1, - [file2.path]: file2, - }); - }); - - it('updates entries in a folder correctly, when folder is renamed', done => { - store - .dispatch('renameEntry', { - path: 'folder', - name: 'new-folder', - }) - .then(() => { - const keys = Object.keys(store.state.entries); - - expect(keys.length).toBe(3); - expect(keys.indexOf('new-folder')).toBe(0); - expect(keys.indexOf('new-folder/file-1')).toBe(1); - expect(keys.indexOf('new-folder/file-2')).toBe(2); - }) - .then(done) - .catch(done.fail); - }); - - it('discards renaming of an entry if the root folder is renamed back to a previous name', done => { - const rootFolder = file('old-folder', 'old-folder', 'tree'); - const testEntry = file('test', 'test', 'blob', rootFolder); - - Object.assign(store.state, { - entries: { - 'old-folder': { - ...rootFolder, - tree: [testEntry], - }, - 'old-folder/test': testEntry, - }, - }); - - store - .dispatch('renameEntry', { - path: 'old-folder', - name: 'new-folder', - }) - .then(() => { - const { entries } = store.state; - - expect(Object.keys(entries).length).toBe(2); - expect(entries['old-folder']).toBeUndefined(); - expect(entries['old-folder/test']).toBeUndefined(); - - expect(entries['new-folder']).toBeDefined(); - expect(entries['new-folder/test']).toEqual( - jasmine.objectContaining({ - path: 'new-folder/test', - name: 'test', - prevPath: 'old-folder/test', - prevName: 'test', - }), - ); - }) - .then(() => - store.dispatch('renameEntry', { - path: 'new-folder', - name: 'old-folder', - }), - ) - .then(() => { - const { entries } = store.state; - - expect(Object.keys(entries).length).toBe(2); - expect(entries['new-folder']).toBeUndefined(); - expect(entries['new-folder/test']).toBeUndefined(); - - expect(entries['old-folder']).toBeDefined(); - expect(entries['old-folder/test']).toEqual( - jasmine.objectContaining({ - path: 'old-folder/test', - name: 'test', - prevPath: undefined, - prevName: undefined, - }), - ); - }) - .then(done) - .catch(done.fail); - }); - - describe('with file in directory', () => { - const parentPath = 'original-dir'; - const newParentPath = 'new-dir'; - const fileName = 'test.md'; - const filePath = `${parentPath}/${fileName}`; - - let rootDir; - - beforeEach(() => { - const parentEntry = file(parentPath, parentPath, 'tree'); - const fileEntry = file(filePath, filePath, 'blob', parentEntry); - rootDir = { - tree: [], - }; - - Object.assign(store.state, { - entries: { - [parentPath]: { - ...parentEntry, - tree: [fileEntry], - }, - [filePath]: fileEntry, - }, - trees: { - '/': rootDir, - }, - }); - }); - - it('creates new directory', done => { - expect(store.state.entries[newParentPath]).toBeUndefined(); - - store - .dispatch('renameEntry', { path: filePath, name: fileName, parentPath: newParentPath }) - .then(() => { - expect(store.state.entries[newParentPath]).toEqual( - jasmine.objectContaining({ - path: newParentPath, - type: 'tree', - tree: jasmine.arrayContaining([ - store.state.entries[`${newParentPath}/${fileName}`], - ]), - }), - ); - }) - .then(done) - .catch(done.fail); - }); - - describe('when new directory exists', () => { - let newDir; - - beforeEach(() => { - newDir = file(newParentPath, newParentPath, 'tree'); - - store.state.entries[newDir.path] = newDir; - rootDir.tree.push(newDir); - }); - - it('inserts in new directory', done => { - expect(newDir.tree).toEqual([]); - - store - .dispatch('renameEntry', { - path: filePath, - name: fileName, - parentPath: newParentPath, - }) - .then(() => { - expect(newDir.tree).toEqual([store.state.entries[`${newParentPath}/${fileName}`]]); - }) - .then(done) - .catch(done.fail); - }); - - it('when new directory is deleted, it undeletes it', done => { - store.dispatch('deleteEntry', newParentPath); - - expect(store.state.entries[newParentPath].deleted).toBe(true); - expect(rootDir.tree.some(x => x.path === newParentPath)).toBe(false); - - store - .dispatch('renameEntry', { - path: filePath, - name: fileName, - parentPath: newParentPath, - }) - .then(() => { - expect(store.state.entries[newParentPath].deleted).toBe(false); - expect(rootDir.tree.some(x => x.path === newParentPath)).toBe(true); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - }); - }); - - describe('getBranchData', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('error', () => { - let dispatch; - let callParams; - - beforeEach(() => { - callParams = [ - { - commit() {}, - state: store.state, - }, - { - projectId: 'abc/def', - branchId: 'master-testing', - }, - ]; - dispatch = jasmine.createSpy('dispatchSpy'); - document.body.innerHTML += '<div class="flash-container"></div>'; - }); - - afterEach(() => { - document.querySelector('.flash-container').remove(); - }); - - it('passes the error further unchanged without dispatching any action when response is 404', done => { - mock.onGet(/(.*)/).replyOnce(404); - - getBranchData(...callParams) - .then(done.fail) - .catch(e => { - expect(dispatch.calls.count()).toEqual(0); - expect(e.response.status).toEqual(404); - expect(document.querySelector('.flash-alert')).toBeNull(); - done(); - }); - }); - - it('does not pass the error further and flashes an alert if error is not 404', done => { - mock.onGet(/(.*)/).replyOnce(418); - - getBranchData(...callParams) - .then(done.fail) - .catch(e => { - expect(dispatch.calls.count()).toEqual(0); - expect(e.response).toBeUndefined(); - expect(document.querySelector('.flash-alert')).not.toBeNull(); - done(); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js deleted file mode 100644 index fb8cb300209..00000000000 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ /dev/null @@ -1,603 +0,0 @@ -import { resetStore, file } from 'spec/ide/helpers'; -import rootActions from '~/ide/stores/actions'; -import { createStore } from '~/ide/stores'; -import service from '~/ide/services'; -import router from '~/ide/ide_router'; -import eventHub from '~/ide/eventhub'; -import consts from '~/ide/stores/modules/commit/constants'; -import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types'; -import * as actions from '~/ide/stores/modules/commit/actions'; -import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants'; -import testAction from '../../../../helpers/vuex_action_helper'; - -const TEST_COMMIT_SHA = '123456789'; -const store = createStore(); - -describe('IDE commit module actions', () => { - beforeEach(() => { - spyOn(router, 'push'); - }); - - afterEach(() => { - resetStore(store); - }); - - describe('updateCommitMessage', () => { - it('updates store with new commit message', done => { - store - .dispatch('commit/updateCommitMessage', 'testing') - .then(() => { - expect(store.state.commit.commitMessage).toBe('testing'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('discardDraft', () => { - it('resets commit message to blank', done => { - store.state.commit.commitMessage = 'testing'; - - store - .dispatch('commit/discardDraft') - .then(() => { - expect(store.state.commit.commitMessage).not.toBe('testing'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('updateCommitAction', () => { - it('updates store with new commit action', done => { - store - .dispatch('commit/updateCommitAction', '1') - .then(() => { - expect(store.state.commit.commitAction).toBe('1'); - }) - .then(done) - .catch(done.fail); - }); - - it('sets shouldCreateMR to true if "Create new MR" option is visible', done => { - Object.assign(store.state, { - shouldHideNewMrOption: false, - }); - - testAction( - actions.updateCommitAction, - {}, - store.state, - [ - { - type: mutationTypes.UPDATE_COMMIT_ACTION, - payload: { commitAction: jasmine.anything() }, - }, - { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: true }, - ], - [], - done, - ); - }); - - it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => { - Object.assign(store.state, { - shouldHideNewMrOption: true, - }); - - testAction( - actions.updateCommitAction, - {}, - store.state, - [ - { - type: mutationTypes.UPDATE_COMMIT_ACTION, - payload: { commitAction: jasmine.anything() }, - }, - { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: false }, - ], - [], - done, - ); - }); - }); - - describe('updateBranchName', () => { - it('updates store with new branch name', done => { - store - .dispatch('commit/updateBranchName', 'branch-name') - .then(() => { - expect(store.state.commit.newBranchName).toBe('branch-name'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('setLastCommitMessage', () => { - beforeEach(() => { - Object.assign(store.state, { - currentProjectId: 'abcproject', - projects: { - abcproject: { - web_url: 'http://testing', - }, - }, - }); - }); - - it('updates commit message with short_id', done => { - store - .dispatch('commit/setLastCommitMessage', { short_id: '123' }) - .then(() => { - expect(store.state.lastCommitMsg).toContain( - 'Your changes have been committed. Commit <a href="http://testing/-/commit/123" class="commit-sha">123</a>', - ); - }) - .then(done) - .catch(done.fail); - }); - - it('updates commit message with stats', done => { - store - .dispatch('commit/setLastCommitMessage', { - short_id: '123', - stats: { - additions: '1', - deletions: '2', - }, - }) - .then(() => { - expect(store.state.lastCommitMsg).toBe( - 'Your changes have been committed. Commit <a href="http://testing/-/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.', - ); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('updateFilesAfterCommit', () => { - const data = { - id: '123', - message: 'testing commit message', - committed_date: '123', - committer_name: 'root', - }; - const branch = 'master'; - let f; - - beforeEach(() => { - spyOn(eventHub, '$emit'); - - f = file('changedFile'); - Object.assign(f, { - active: true, - changed: true, - content: 'file content', - }); - - Object.assign(store.state, { - currentProjectId: 'abcproject', - currentBranchId: 'master', - projects: { - abcproject: { - web_url: 'web_url', - branches: { - master: { - workingReference: '', - commit: { - short_id: TEST_COMMIT_SHA, - }, - }, - }, - }, - }, - stagedFiles: [ - f, - { - ...file('changedFile2'), - changed: true, - }, - ], - openFiles: store.state.stagedFiles, - }); - - store.state.stagedFiles.forEach(stagedFile => { - store.state.entries[stagedFile.path] = stagedFile; - }); - }); - - it('updates stores working reference', done => { - store - .dispatch('commit/updateFilesAfterCommit', { - data, - branch, - }) - .then(() => { - expect(store.state.projects.abcproject.branches.master.workingReference).toBe(data.id); - }) - .then(done) - .catch(done.fail); - }); - - it('resets all files changed status', done => { - store - .dispatch('commit/updateFilesAfterCommit', { - data, - branch, - }) - .then(() => { - store.state.openFiles.forEach(entry => { - expect(entry.changed).toBeFalsy(); - }); - }) - .then(done) - .catch(done.fail); - }); - - it('sets files commit data', done => { - store - .dispatch('commit/updateFilesAfterCommit', { - data, - branch, - }) - .then(() => { - expect(f.lastCommitSha).toBe(data.id); - }) - .then(done) - .catch(done.fail); - }); - - it('updates raw content for changed file', done => { - store - .dispatch('commit/updateFilesAfterCommit', { - data, - branch, - }) - .then(() => { - expect(f.raw).toBe(f.content); - }) - .then(done) - .catch(done.fail); - }); - - it('emits changed event for file', done => { - store - .dispatch('commit/updateFilesAfterCommit', { - data, - branch, - }) - .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.content.${f.key}`, { - content: f.content, - changed: false, - }); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('commitChanges', () => { - let visitUrl; - - beforeEach(() => { - visitUrl = spyOnDependency(rootActions, 'visitUrl'); - - document.body.innerHTML += '<div class="flash-container"></div>'; - - const f = { - ...file('changed'), - type: 'blob', - active: true, - lastCommitSha: TEST_COMMIT_SHA, - content: '\n', - raw: '\n', - }; - - Object.assign(store.state, { - stagedFiles: [f], - changedFiles: [f], - openFiles: [f], - currentProjectId: 'abcproject', - currentBranchId: 'master', - projects: { - abcproject: { - web_url: 'webUrl', - branches: { - master: { - workingReference: '1', - commit: { - id: TEST_COMMIT_SHA, - }, - }, - }, - userPermissions: { - [PERMISSION_CREATE_MR]: true, - }, - }, - }, - }); - - store.state.commit.commitAction = '2'; - store.state.commit.commitMessage = 'testing 123'; - - store.state.openFiles.forEach(localF => { - store.state.entries[localF.path] = localF; - }); - }); - - afterEach(() => { - document.querySelector('.flash-container').remove(); - }); - - describe('success', () => { - const COMMIT_RESPONSE = { - id: '123456', - short_id: '123', - message: 'test message', - committed_date: 'date', - parent_ids: '321', - stats: { - additions: '1', - deletions: '2', - }, - }; - - beforeEach(() => { - spyOn(service, 'commit').and.returnValue( - Promise.resolve({ - data: COMMIT_RESPONSE, - }), - ); - }); - - it('calls service', done => { - store - .dispatch('commit/commitChanges') - .then(() => { - expect(service.commit).toHaveBeenCalledWith('abcproject', { - branch: jasmine.anything(), - commit_message: 'testing 123', - actions: [ - { - action: commitActionTypes.update, - file_path: jasmine.anything(), - content: '\n', - encoding: jasmine.anything(), - last_commit_id: undefined, - previous_path: undefined, - }, - ], - start_sha: TEST_COMMIT_SHA, - }); - - done(); - }) - .catch(done.fail); - }); - - it('sends lastCommit ID when not creating new branch', done => { - store.state.commit.commitAction = '1'; - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(service.commit).toHaveBeenCalledWith('abcproject', { - branch: jasmine.anything(), - commit_message: 'testing 123', - actions: [ - { - action: commitActionTypes.update, - file_path: jasmine.anything(), - content: '\n', - encoding: jasmine.anything(), - last_commit_id: TEST_COMMIT_SHA, - previous_path: undefined, - }, - ], - start_sha: undefined, - }); - - done(); - }) - .catch(done.fail); - }); - - it('sets last Commit Msg', done => { - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.state.lastCommitMsg).toBe( - 'Your changes have been committed. Commit <a href="webUrl/-/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.', - ); - - done(); - }) - .catch(done.fail); - }); - - it('adds commit data to files', done => { - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.state.entries[store.state.openFiles[0].path].lastCommitSha).toBe( - COMMIT_RESPONSE.id, - ); - - done(); - }) - .catch(done.fail); - }); - - it('resets stores commit actions', done => { - store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.state.commit.commitAction).not.toBe(consts.COMMIT_TO_NEW_BRANCH); - }) - .then(done) - .catch(done.fail); - }); - - it('removes all staged files', done => { - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.state.stagedFiles.length).toBe(0); - }) - .then(done) - .catch(done.fail); - }); - - describe('merge request', () => { - it('redirects to new merge request page', done => { - spyOn(eventHub, '$on'); - - store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; - store.state.commit.shouldCreateMR = true; - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(visitUrl).toHaveBeenCalledWith( - `webUrl/-/merge_requests/new?merge_request[source_branch]=${ - store.getters['commit/placeholderBranchName'] - }&merge_request[target_branch]=master&nav_source=webide`, - ); - - done(); - }) - .catch(done.fail); - }); - - it('does not redirect to new merge request page when shouldCreateMR is not checked', done => { - spyOn(eventHub, '$on'); - - store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; - store.state.commit.shouldCreateMR = false; - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(visitUrl).not.toHaveBeenCalled(); - done(); - }) - .catch(done.fail); - }); - - it('resets changed files before redirecting', done => { - visitUrl = visitUrl.and.callFake(() => { - expect(store.state.stagedFiles.length).toBe(0); - done(); - }); - - spyOn(eventHub, '$on'); - - store.state.commit.commitAction = '3'; - - store.dispatch('commit/commitChanges').catch(done.fail); - }); - }); - }); - - describe('failed', () => { - beforeEach(() => { - spyOn(service, 'commit').and.returnValue( - Promise.resolve({ - data: { - message: 'failed message', - }, - }), - ); - }); - - it('shows failed message', done => { - store - .dispatch('commit/commitChanges') - .then(() => { - const alert = document.querySelector('.flash-container'); - - expect(alert.textContent.trim()).toBe('failed message'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('first commit of a branch', () => { - const COMMIT_RESPONSE = { - id: '123456', - short_id: '123', - message: 'test message', - committed_date: 'date', - parent_ids: [], - stats: { - additions: '1', - deletions: '2', - }, - }; - - it('commits TOGGLE_EMPTY_STATE mutation on empty repo', done => { - spyOn(service, 'commit').and.returnValue( - Promise.resolve({ - data: COMMIT_RESPONSE, - }), - ); - - spyOn(store, 'commit').and.callThrough(); - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.commit.calls.allArgs()).toEqual( - jasmine.arrayContaining([ - ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)], - ]), - ); - done(); - }) - .catch(done.fail); - }); - - it('does not commmit TOGGLE_EMPTY_STATE mutation on existing project', done => { - COMMIT_RESPONSE.parent_ids.push('1234'); - spyOn(service, 'commit').and.returnValue( - Promise.resolve({ - data: COMMIT_RESPONSE, - }), - ); - spyOn(store, 'commit').and.callThrough(); - - store - .dispatch('commit/commitChanges') - .then(() => { - expect(store.commit.calls.allArgs()).not.toEqual( - jasmine.arrayContaining([ - ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)], - ]), - ); - done(); - }) - .catch(done.fail); - }); - }); - }); - - describe('toggleShouldCreateMR', () => { - it('commits both toggle and interacting with MR checkbox actions', done => { - testAction( - actions.toggleShouldCreateMR, - {}, - store.state, - [{ type: mutationTypes.TOGGLE_SHOULD_CREATE_MR }], - [], - done, - ); - }); - }); -}); diff --git a/spec/javascripts/importer_status_spec.js b/spec/javascripts/importer_status_spec.js deleted file mode 100644 index 90835e1cc21..00000000000 --- a/spec/javascripts/importer_status_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import { ImporterStatus } from '~/importer_status'; -import axios from '~/lib/utils/axios_utils'; - -describe('Importer Status', () => { - let instance; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('addToImport', () => { - const importUrl = '/import_url'; - - beforeEach(() => { - setFixtures(` - <tr id="repo_123"> - <td class="import-target"></td> - <td class="import-actions job-status"> - <button name="button" type="submit" class="btn btn-import js-add-to-import"> - </button> - </td> - </tr> - `); - spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {}); - spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {}); - instance = new ImporterStatus({ - jobsUrl: '', - importUrl, - }); - }); - - it('sets table row to active after post request', done => { - mock.onPost(importUrl).reply(200, { - id: 1, - full_path: '/full_path', - }); - - instance - .addToImport({ - currentTarget: document.querySelector('.js-add-to-import'), - }) - .then(() => { - expect(document.querySelector('tr').classList.contains('table-active')).toEqual(true); - done(); - }) - .catch(done.fail); - }); - - it('shows error message after failed POST request', done => { - appendSetFixtures('<div class="flash-container"></div>'); - - mock.onPost(importUrl).reply(422, { - errors: 'You forgot your lunch', - }); - - instance - .addToImport({ - currentTarget: document.querySelector('.js-add-to-import'), - }) - .then(() => { - const flashMessage = document.querySelector('.flash-text'); - - expect(flashMessage.textContent.trim()).toEqual( - 'An error occurred while importing project: You forgot your lunch', - ); - done(); - }) - .catch(done.fail); - }); - }); - - describe('autoUpdate', () => { - const jobsUrl = '/jobs_url'; - - beforeEach(() => { - const div = document.createElement('div'); - div.innerHTML = ` - <div id="project_1"> - <div class="job-status"> - </div> - </div> - `; - - document.body.appendChild(div); - - spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {}); - spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {}); - instance = new ImporterStatus({ - jobsUrl, - }); - }); - - function setupMock(importStatus) { - mock.onGet(jobsUrl).reply(200, [ - { - id: 1, - import_status: importStatus, - }, - ]); - } - - function expectJobStatus(done, status) { - instance - .autoUpdate() - .then(() => { - expect(document.querySelector('#project_1').innerText.trim()).toEqual(status); - done(); - }) - .catch(done.fail); - } - - it('sets the job status to done', done => { - setupMock('finished'); - expectJobStatus(done, 'Done'); - }); - - it('sets the job status to scheduled', done => { - setupMock('scheduled'); - expectJobStatus(done, 'Scheduled'); - }); - - it('sets the job status to started', done => { - setupMock('started'); - expectJobStatus(done, 'Started'); - }); - - it('sets the job status to custom status', done => { - setupMock('custom status'); - expectJobStatus(done, 'custom status'); - }); - }); -}); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js deleted file mode 100644 index 94e833ec83b..00000000000 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable no-new */ - -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import { shuffle } from 'lodash'; -import axios from '~/lib/utils/axios_utils'; -import IssuableContext from '~/issuable_context'; -import LabelsSelect from '~/labels_select'; - -import '~/gl_dropdown'; -import 'select2'; -import '~/api'; -import '~/create_label'; -import '~/users_select'; - -let saveLabelCount = 0; -let mock; - -function testLabelClicks(labelOrder, done) { - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - const labelsInDropdown = $('.dropdown-content a'); - - expect(labelsInDropdown.length).toBe(10); - - const arrayOfLabels = labelsInDropdown.get(); - const randomArrayOfLabels = shuffle(arrayOfLabels); - randomArrayOfLabels.forEach((label, i) => { - if (i < saveLabelCount) { - $(label).click(); - } - }); - - $('.edit-link') - .get(0) - .click(); - - setTimeout(() => { - expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder); - done(); - }, 0); - }, 0); -} - -describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html'); - - beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html'); - - mock = new MockAdapter(axios); - - new IssuableContext('{"id":1,"name":"Administrator","username":"root"}'); - new LabelsSelect(); - - mock.onGet('/root/test/labels.json').reply(() => { - const labels = Array(10) - .fill() - .map((_val, i) => ({ - id: i, - title: `test ${i}`, - color: '#5CB85C', - })); - - return [200, labels]; - }); - - mock.onPut('/root/test/issues/2.json').reply(() => { - const labels = Array(saveLabelCount) - .fill() - .map((_val, i) => ({ - id: i, - title: `test ${i}`, - color: '#5CB85C', - })); - - return [200, { labels }]; - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('changes collapsed tooltip when changing labels when less than 5', done => { - saveLabelCount = 5; - testLabelClicks('test 0, test 1, test 2, test 3, test 4', done); - }); - - it('changes collapsed tooltip when changing labels when more than 5', done => { - saveLabelCount = 6; - testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done); - }); -}); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js deleted file mode 100644 index 82ab73c2170..00000000000 --- a/spec/javascripts/lazy_loader_spec.js +++ /dev/null @@ -1,244 +0,0 @@ -import LazyLoader from '~/lazy_loader'; -import { TEST_HOST } from './test_constants'; -import scrollIntoViewPromise from './helpers/scroll_into_view_promise'; -import waitForPromises from './helpers/wait_for_promises'; -import waitForAttributeChange from './helpers/wait_for_attribute_change'; - -const execImmediately = callback => { - callback(); -}; - -describe('LazyLoader', function() { - let lazyLoader = null; - - preloadFixtures('issues/issue_with_comment.html'); - - describe('without IntersectionObserver', () => { - beforeEach(function() { - loadFixtures('issues/issue_with_comment.html'); - - lazyLoader = new LazyLoader({ - observerNode: 'foobar', - }); - - spyOn(LazyLoader, 'supportsIntersectionObserver').and.callFake(() => false); - - spyOn(LazyLoader, 'loadImage').and.callThrough(); - - spyOn(window, 'requestAnimationFrame').and.callFake(execImmediately); - spyOn(window, 'requestIdleCallback').and.callFake(execImmediately); - - // Doing everything that happens normally in onload - lazyLoader.register(); - }); - - afterEach(() => { - lazyLoader.unregister(); - }); - - it('should copy value from data-src to src for img 1', function(done) { - const img = document.querySelectorAll('img[data-src]')[0]; - const originalDataSrc = img.getAttribute('data-src'); - - Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should lazy load dynamically added data-src images', function(done) { - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - Promise.all([ - scrollIntoViewPromise(newImg), - waitForAttributeChange(newImg, ['data-src', 'src']), - ]) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should not alter normal images', function(done) { - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.setAttribute('src', testPath); - document.body.appendChild(newImg); - - scrollIntoViewPromise(newImg) - .then(waitForPromises) - .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should not load dynamically added pictures if content observer is turned off', done => { - lazyLoader.stopContentObserver(); - - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - scrollIntoViewPromise(newImg) - .then(waitForPromises) - .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should load dynamically added pictures if content observer is turned off and on again', done => { - lazyLoader.stopContentObserver(); - lazyLoader.startContentObserver(); - - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - Promise.all([ - scrollIntoViewPromise(newImg), - waitForAttributeChange(newImg, ['data-src', 'src']), - ]) - .then(waitForPromises) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - }); - - describe('with IntersectionObserver', () => { - beforeEach(function() { - loadFixtures('issues/issue_with_comment.html'); - - lazyLoader = new LazyLoader({ - observerNode: 'foobar', - }); - - spyOn(LazyLoader, 'loadImage').and.callThrough(); - - spyOn(window, 'requestAnimationFrame').and.callFake(execImmediately); - spyOn(window, 'requestIdleCallback').and.callFake(execImmediately); - - // Doing everything that happens normally in onload - lazyLoader.register(); - }); - - afterEach(() => { - lazyLoader.unregister(); - }); - - it('should copy value from data-src to src for img 1', function(done) { - const img = document.querySelectorAll('img[data-src]')[0]; - const originalDataSrc = img.getAttribute('data-src'); - - Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalledWith(img); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should lazy load dynamically added data-src images', function(done) { - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - Promise.all([ - scrollIntoViewPromise(newImg), - waitForAttributeChange(newImg, ['data-src', 'src']), - ]) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should not alter normal images', function(done) { - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.setAttribute('src', testPath); - document.body.appendChild(newImg); - - scrollIntoViewPromise(newImg) - .then(waitForPromises) - .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should not load dynamically added pictures if content observer is turned off', done => { - lazyLoader.stopContentObserver(); - - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - scrollIntoViewPromise(newImg) - .then(waitForPromises) - .then(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - - it('should load dynamically added pictures if content observer is turned off and on again', done => { - lazyLoader.stopContentObserver(); - lazyLoader.startContentObserver(); - - const newImg = document.createElement('img'); - const testPath = `${TEST_HOST}/img/testimg.png`; - newImg.className = 'lazy'; - newImg.setAttribute('data-src', testPath); - document.body.appendChild(newImg); - - Promise.all([ - scrollIntoViewPromise(newImg), - waitForAttributeChange(newImg, ['data-src', 'src']), - ]) - .then(() => { - expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js deleted file mode 100644 index bedab0fd003..00000000000 --- a/spec/javascripts/line_highlighter_spec.js +++ /dev/null @@ -1,261 +0,0 @@ -/* eslint-disable dot-notation, no-return-assign, no-new, no-underscore-dangle */ - -import $ from 'jquery'; -import LineHighlighter from '~/line_highlighter'; - -describe('LineHighlighter', function() { - preloadFixtures('static/line_highlighter.html'); - const clickLine = function(number, eventData = {}) { - if ($.isEmptyObject(eventData)) { - return $(`#L${number}`).click(); - } - const e = $.Event('click', eventData); - return $(`#L${number}`).trigger(e); - }; - beforeEach(function() { - loadFixtures('static/line_highlighter.html'); - this['class'] = new LineHighlighter(); - this.css = this['class'].highlightLineClass; - return (this.spies = { - __setLocationHash__: spyOn(this['class'], '__setLocationHash__').and.callFake(function() {}), - }); - }); - - describe('behavior', function() { - it('highlights one line given in the URL hash', function() { - new LineHighlighter({ hash: '#L13' }); - - expect($('#LC13')).toHaveClass(this.css); - }); - - it('highlights one line given in the URL hash with given CSS class name', function() { - const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' }); - - expect(hiliter.highlightLineClass).toBe('hilite'); - expect($('#LC13')).toHaveClass('hilite'); - expect($('#LC13')).not.toHaveClass('hll'); - }); - - it('highlights a range of lines given in the URL hash', function() { - new LineHighlighter({ hash: '#L5-25' }); - - expect($(`.${this.css}`).length).toBe(21); - for (let line = 5; line <= 25; line += 1) { - expect($(`#LC${line}`)).toHaveClass(this.css); - } - }); - - it('scrolls to the first highlighted line on initial load', function() { - const spy = spyOn($, 'scrollTo'); - new LineHighlighter({ hash: '#L5-25' }); - - expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything()); - }); - - it('discards click events', function() { - const spy = spyOnEvent('a[data-line-number]', 'click'); - clickLine(13); - - expect(spy).toHaveBeenPrevented(); - }); - - it('handles garbage input from the hash', function() { - const func = function() { - return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' }); - }; - - expect(func).not.toThrow(); - }); - - it('handles hashchange event', () => { - const highlighter = new LineHighlighter(); - - spyOn(highlighter, 'highlightHash'); - - window.dispatchEvent(new Event('hashchange'), 'L15'); - - expect(highlighter.highlightHash).toHaveBeenCalled(); - }); - }); - - describe('clickHandler', function() { - it('handles clicking on a child icon element', function() { - const spy = spyOn(this['class'], 'setHash').and.callThrough(); - $('#L13 i') - .mousedown() - .click(); - - expect(spy).toHaveBeenCalledWith(13); - expect($('#LC13')).toHaveClass(this.css); - }); - - describe('without shiftKey', function() { - it('highlights one line when clicked', function() { - clickLine(13); - - expect($('#LC13')).toHaveClass(this.css); - }); - - it('unhighlights previously highlighted lines', function() { - clickLine(13); - clickLine(20); - - expect($('#LC13')).not.toHaveClass(this.css); - expect($('#LC20')).toHaveClass(this.css); - }); - - it('sets the hash', function() { - const spy = spyOn(this['class'], 'setHash').and.callThrough(); - clickLine(13); - - expect(spy).toHaveBeenCalledWith(13); - }); - }); - - describe('with shiftKey', function() { - it('sets the hash', function() { - const spy = spyOn(this['class'], 'setHash').and.callThrough(); - clickLine(13); - clickLine(20, { - shiftKey: true, - }); - - expect(spy).toHaveBeenCalledWith(13); - expect(spy).toHaveBeenCalledWith(13, 20); - }); - - describe('without existing highlight', function() { - it('highlights the clicked line', function() { - clickLine(13, { - shiftKey: true, - }); - - expect($('#LC13')).toHaveClass(this.css); - expect($(`.${this.css}`).length).toBe(1); - }); - - it('sets the hash', function() { - const spy = spyOn(this['class'], 'setHash'); - clickLine(13, { - shiftKey: true, - }); - - expect(spy).toHaveBeenCalledWith(13); - }); - }); - - describe('with existing single-line highlight', function() { - it('uses existing line as last line when target is lesser', function() { - clickLine(20); - clickLine(15, { - shiftKey: true, - }); - - expect($(`.${this.css}`).length).toBe(6); - for (let line = 15; line <= 20; line += 1) { - expect($(`#LC${line}`)).toHaveClass(this.css); - } - }); - - it('uses existing line as first line when target is greater', function() { - clickLine(5); - clickLine(10, { - shiftKey: true, - }); - - expect($(`.${this.css}`).length).toBe(6); - for (let line = 5; line <= 10; line += 1) { - expect($(`#LC${line}`)).toHaveClass(this.css); - } - }); - }); - - describe('with existing multi-line highlight', function() { - beforeEach(function() { - clickLine(10, { - shiftKey: true, - }); - clickLine(13, { - shiftKey: true, - }); - }); - - it('uses target as first line when it is less than existing first line', function() { - clickLine(5, { - shiftKey: true, - }); - - expect($(`.${this.css}`).length).toBe(6); - for (let line = 5; line <= 10; line += 1) { - expect($(`#LC${line}`)).toHaveClass(this.css); - } - }); - - it('uses target as last line when it is greater than existing first line', function() { - clickLine(15, { - shiftKey: true, - }); - - expect($(`.${this.css}`).length).toBe(6); - for (let line = 10; line <= 15; line += 1) { - expect($(`#LC${line}`)).toHaveClass(this.css); - } - }); - }); - }); - }); - - describe('hashToRange', function() { - beforeEach(function() { - this.subject = this['class'].hashToRange; - }); - - it('extracts a single line number from the hash', function() { - expect(this.subject('#L5')).toEqual([5, null]); - }); - - it('extracts a range of line numbers from the hash', function() { - expect(this.subject('#L5-15')).toEqual([5, 15]); - }); - - it('returns [null, null] when the hash is not a line number', function() { - expect(this.subject('#foo')).toEqual([null, null]); - }); - }); - - describe('highlightLine', function() { - beforeEach(function() { - this.subject = this['class'].highlightLine; - }); - - it('highlights the specified line', function() { - this.subject(13); - - expect($('#LC13')).toHaveClass(this.css); - }); - - it('accepts a String-based number', function() { - this.subject('13'); - - expect($('#LC13')).toHaveClass(this.css); - }); - }); - - describe('setHash', function() { - beforeEach(function() { - this.subject = this['class'].setHash; - }); - - it('sets the location hash for a single line', function() { - this.subject(5); - - expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5'); - }); - - it('sets the location hash for a range', function() { - this.subject(5, 15); - - expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15'); - }); - }); -}); diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js deleted file mode 100644 index b6173b9b171..00000000000 --- a/spec/javascripts/merge_request_spec.js +++ /dev/null @@ -1,187 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import MergeRequest from '~/merge_request'; -import CloseReopenReportToggle from '~/close_reopen_report_toggle'; -import IssuablesHelper from '~/helpers/issuables_helper'; - -describe('MergeRequest', function() { - describe('task lists', function() { - let mock; - - preloadFixtures('merge_requests/merge_request_with_task_list.html'); - beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html'); - - spyOn(axios, 'patch').and.callThrough(); - mock = new MockAdapter(axios); - - mock - .onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`) - .reply(200, {}); - - this.merge = new MergeRequest(); - return this.merge; - }); - - afterEach(() => { - mock.restore(); - }); - - it('modifies the Markdown field', done => { - spyOn($, 'ajax').and.stub(); - const changeEvent = document.createEvent('HTMLEvents'); - changeEvent.initEvent('change', true, true); - $('input[type=checkbox]') - .first() - .attr('checked', true)[0] - .dispatchEvent(changeEvent); - setTimeout(() => { - expect($('.js-task-list-field').val()).toBe( - '- [x] Task List Item\n- [ ] \n- [ ] Task List Item 2\n', - ); - done(); - }); - }); - - it('ensure that task with only spaces does not get checked incorrectly', done => { - // fixed in 'deckar01-task_list', '2.2.1' gem - spyOn($, 'ajax').and.stub(); - const changeEvent = document.createEvent('HTMLEvents'); - changeEvent.initEvent('change', true, true); - $('input[type=checkbox]') - .last() - .attr('checked', true)[0] - .dispatchEvent(changeEvent); - setTimeout(() => { - expect($('.js-task-list-field').val()).toBe( - '- [ ] Task List Item\n- [ ] \n- [x] Task List Item 2\n', - ); - done(); - }); - }); - - describe('tasklist', () => { - const lineNumber = 8; - const lineSource = '- [ ] item 8'; - const index = 3; - const checked = true; - - it('submits an ajax request on tasklist:changed', done => { - $('.js-task-list-field').trigger({ - type: 'tasklist:changed', - detail: { lineNumber, lineSource, index, checked }, - }); - - setTimeout(() => { - expect(axios.patch).toHaveBeenCalledWith( - `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`, - { - merge_request: { - description: '- [ ] Task List Item\n- [ ] \n- [ ] Task List Item 2\n', - lock_version: 0, - update_task: { line_number: lineNumber, line_source: lineSource, index, checked }, - }, - }, - ); - - done(); - }); - }); - - // https://gitlab.com/gitlab-org/gitlab/issues/34861 - // eslint-disable-next-line jasmine/no-disabled-tests - xit('shows an error notification when tasklist update failed', done => { - mock - .onPatch( - `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`, - ) - .reply(409, {}); - - $('.js-task-list-field').trigger({ - type: 'tasklist:changed', - detail: { lineNumber, lineSource, index, checked }, - }); - - setTimeout(() => { - expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( - 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.', - ); - - done(); - }); - }); - }); - }); - - describe('class constructor', () => { - beforeEach(() => { - spyOn($, 'ajax').and.stub(); - }); - - it('calls .initCloseReopenReport', () => { - spyOn(IssuablesHelper, 'initCloseReopenReport'); - - new MergeRequest(); // eslint-disable-line no-new - - expect(IssuablesHelper.initCloseReopenReport).toHaveBeenCalled(); - }); - - it('calls .initDroplab', () => { - const container = jasmine.createSpyObj('container', ['querySelector']); - const dropdownTrigger = {}; - const dropdownList = {}; - const button = {}; - - spyOn(CloseReopenReportToggle.prototype, 'initDroplab'); - spyOn(document, 'querySelector').and.returnValue(container); - container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button); - - new MergeRequest(); // eslint-disable-line no-new - - expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu'); - expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button'); - expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled(); - }); - }); - - describe('hideCloseButton', () => { - describe('merge request of another user', () => { - beforeEach(() => { - loadFixtures('merge_requests/merge_request_with_task_list.html'); - this.el = document.querySelector('.js-issuable-actions'); - new MergeRequest(); // eslint-disable-line no-new - MergeRequest.hideCloseButton(); - }); - - it('hides the dropdown close item and selects the next item', () => { - const closeItem = this.el.querySelector('li.close-item'); - const smallCloseItem = this.el.querySelector('.js-close-item'); - const reportItem = this.el.querySelector('li.report-item'); - - expect(closeItem).toHaveClass('hidden'); - expect(smallCloseItem).toHaveClass('hidden'); - expect(reportItem).toHaveClass('droplab-item-selected'); - expect(reportItem).not.toHaveClass('hidden'); - }); - }); - - describe('merge request of current_user', () => { - beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html'); - this.el = document.querySelector('.js-issuable-actions'); - MergeRequest.hideCloseButton(); - }); - - it('hides the close button', () => { - const closeButton = this.el.querySelector('.btn-close'); - const smallCloseItem = this.el.querySelector('.js-close-item'); - - expect(closeButton).toHaveClass('hidden'); - expect(smallCloseItem).toHaveClass('hidden'); - }); - }); - }); -}); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js deleted file mode 100644 index cbb61333d77..00000000000 --- a/spec/javascripts/merge_request_tabs_spec.js +++ /dev/null @@ -1,283 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import MergeRequestTabs from '~/merge_request_tabs'; -import '~/commit/pipelines/pipelines_bundle'; -import '~/lib/utils/common_utils'; -import 'vendor/jquery.scrollTo'; -import initMrPage from './helpers/init_vue_mr_page_helper'; - -describe('MergeRequestTabs', function() { - let mrPageMock; - const stubLocation = {}; - const setLocation = function(stubs) { - const defaults = { - pathname: '', - search: '', - hash: '', - }; - $.extend(stubLocation, defaults, stubs || {}); - }; - - preloadFixtures( - 'merge_requests/merge_request_with_task_list.html', - 'merge_requests/diff_comment.html', - ); - - beforeEach(function() { - mrPageMock = initMrPage(); - this.class = new MergeRequestTabs({ stubLocation }); - setLocation(); - - this.spies = { - history: spyOn(window.history, 'replaceState').and.callFake(function() {}), - }; - }); - - afterEach(function() { - this.class.unbindEvents(); - this.class.destroyPipelinesView(); - mrPageMock.restore(); - $('.js-merge-request-test').remove(); - }); - - describe('opensInNewTab', function() { - const windowTarget = '_blank'; - let clickTabParams; - let tabUrl; - - beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html'); - - tabUrl = $('.commits-tab a').attr('href'); - - clickTabParams = { - metaKey: false, - ctrlKey: false, - which: 1, - stopImmediatePropagation() {}, - preventDefault() {}, - currentTarget: { - getAttribute(attr) { - return attr === 'href' ? tabUrl : null; - }, - }, - }; - }); - - describe('meta click', () => { - let metakeyEvent; - - beforeEach(function() { - metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); - }); - - it('opens page when commits link is clicked', function() { - spyOn(window, 'open').and.callFake(function(url, name) { - expect(url).toEqual(tabUrl); - expect(name).toEqual(windowTarget); - }); - - this.class.bindEvents(); - $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent); - - expect(window.open).toHaveBeenCalled(); - }); - - it('opens page when commits badge is clicked', function() { - spyOn(window, 'open').and.callFake(function(url, name) { - expect(url).toEqual(tabUrl); - expect(name).toEqual(windowTarget); - }); - - this.class.bindEvents(); - $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent); - - expect(window.open).toHaveBeenCalled(); - }); - }); - - it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function() { - spyOn(window, 'open').and.callFake(function(url, name) { - expect(url).toEqual(tabUrl); - expect(name).toEqual(windowTarget); - }); - - this.class.clickTab({ ...clickTabParams, metaKey: true }); - - expect(window.open).toHaveBeenCalled(); - }); - - it('opens page tab in a new browser tab with Cmd+Click - Mac', function() { - spyOn(window, 'open').and.callFake(function(url, name) { - expect(url).toEqual(tabUrl); - expect(name).toEqual(windowTarget); - }); - - this.class.clickTab({ ...clickTabParams, ctrlKey: true }); - - expect(window.open).toHaveBeenCalled(); - }); - - it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() { - spyOn(window, 'open').and.callFake(function(url, name) { - expect(url).toEqual(tabUrl); - expect(name).toEqual(windowTarget); - }); - - this.class.clickTab({ ...clickTabParams, which: 2 }); - - expect(window.open).toHaveBeenCalled(); - }); - }); - - describe('setCurrentAction', function() { - let mock; - - beforeEach(function() { - mock = new MockAdapter(axios); - mock.onAny().reply({ data: {} }); - this.subject = this.class.setCurrentAction; - }); - - afterEach(() => { - mock.restore(); - }); - - it('changes from commits', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1/commits', - }); - - expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1'); - expect(this.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs'); - }); - - it('changes from diffs', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1/diffs', - }); - - expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1'); - expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits'); - }); - - it('changes from diffs.html', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1/diffs.html', - }); - - expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1'); - expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits'); - }); - - it('changes from notes', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1', - }); - - expect(this.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs'); - expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits'); - }); - - it('includes search parameters and hash string', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1/diffs', - search: '?view=parallel', - hash: '#L15-35', - }); - - expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1?view=parallel#L15-35'); - }); - - it('replaces the current history state', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1', - }); - const newState = this.subject('commits'); - - expect(this.spies.history).toHaveBeenCalledWith( - { - url: newState, - }, - document.title, - newState, - ); - }); - - it('treats "show" like "notes"', function() { - setLocation({ - pathname: '/foo/bar/-/merge_requests/1/commits', - }); - - expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1'); - }); - }); - - describe('expandViewContainer', function() { - beforeEach(() => { - $('body').append( - '<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>', - ); - }); - - afterEach(() => { - $('.content-wrapper').remove(); - }); - - it('removes container-limited from containers', function() { - this.class.expandViewContainer(); - - expect($('.content-wrapper')).not.toContainElement('.container-limited'); - }); - - it('does not add container-limited when fluid layout is prefered', function() { - $('.content-wrapper .container-fluid').removeClass('container-limited'); - - this.class.expandViewContainer(false); - - expect($('.content-wrapper')).not.toContainElement('.container-limited'); - }); - - it('does remove container-limited from breadcrumbs', function() { - $('.container-limited').addClass('breadcrumbs'); - this.class.expandViewContainer(); - - expect($('.content-wrapper')).toContainElement('.container-limited'); - }); - }); - - describe('tabShown', function() { - const mainContent = document.createElement('div'); - const tabContent = document.createElement('div'); - - beforeEach(function() { - spyOn(mainContent, 'getBoundingClientRect').and.returnValue({ top: 10 }); - spyOn(tabContent, 'getBoundingClientRect').and.returnValue({ top: 100 }); - spyOn(document, 'querySelector').and.callFake(function(selector) { - return selector === '.content-wrapper' ? mainContent : tabContent; - }); - this.class.currentAction = 'commits'; - }); - - it('calls window scrollTo with options if document has scrollBehavior', function() { - document.documentElement.style.scrollBehavior = ''; - - spyOn(window, 'scrollTo'); - - this.class.tabShown('commits', 'foobar'); - - expect(window.scrollTo.calls.first().args[0]).toEqual({ top: 39, behavior: 'smooth' }); - }); - - it('calls window scrollTo with two args if document does not have scrollBehavior', function() { - spyOnProperty(document.documentElement, 'style', 'get').and.returnValue({}); - - spyOn(window, 'scrollTo'); - - this.class.tabShown('commits', 'foobar'); - - expect(window.scrollTo.calls.first().args).toEqual([0, 39]); - }); - }); -}); diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js deleted file mode 100644 index aa4a376caf7..00000000000 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; -import timeoutPromise from './helpers/set_timeout_promise_helper'; - -describe('Mini Pipeline Graph Dropdown', () => { - preloadFixtures('static/mini_dropdown_graph.html'); - - beforeEach(() => { - loadFixtures('static/mini_dropdown_graph.html'); - }); - - describe('When is initialized', () => { - it('should initialize without errors when no options are given', () => { - const miniPipelineGraph = new MiniPipelineGraph(); - - expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container'); - }); - - it('should set the container as the given prop', () => { - const container = '.foo'; - - const miniPipelineGraph = new MiniPipelineGraph({ container }); - - expect(miniPipelineGraph.container).toEqual(container); - }); - }); - - describe('When dropdown is clicked', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should call getBuildsList', () => { - const getBuildsListSpy = spyOn(MiniPipelineGraph.prototype, 'getBuildsList').and.callFake( - function() {}, - ); - - new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); - - document.querySelector('.js-builds-dropdown-button').click(); - - expect(getBuildsListSpy).toHaveBeenCalled(); - }); - - it('should make a request to the endpoint provided in the html', () => { - const ajaxSpy = spyOn(axios, 'get').and.callThrough(); - - mock.onGet('foobar').reply(200, { - html: '', - }); - - new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); - - document.querySelector('.js-builds-dropdown-button').click(); - - expect(ajaxSpy.calls.allArgs()[0][0]).toEqual('foobar'); - }); - - it('should not close when user uses cmd/ctrl + click', done => { - mock.onGet('foobar').reply(200, { - html: `<li> - <a class="mini-pipeline-graph-dropdown-item" href="#"> - <span class="ci-status-icon ci-status-icon-failed"></span> - <span class="ci-build-text">build</span> - </a> - <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a> - </li>`, - }); - new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); - - document.querySelector('.js-builds-dropdown-button').click(); - - timeoutPromise() - .then(() => { - document.querySelector('a.mini-pipeline-graph-dropdown-item').click(); - }) - .then(timeoutPromise) - .then(() => { - expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true); - }) - .then(done) - .catch(done.fail); - }); - - it('should close the dropdown when request returns an error', done => { - mock.onGet('foobar').networkError(); - - new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); - - document.querySelector('.js-builds-dropdown-button').click(); - - setTimeout(() => { - expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js deleted file mode 100644 index c95a8400c6c..00000000000 --- a/spec/javascripts/pager_spec.js +++ /dev/null @@ -1,162 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Pager from '~/pager'; - -describe('pager', () => { - let axiosMock; - - beforeEach(() => { - axiosMock = new MockAdapter(axios); - }); - - afterEach(() => { - axiosMock.restore(); - }); - - describe('init', () => { - const originalHref = window.location.href; - - beforeEach(() => { - setFixtures('<div class="content_list"></div><div class="loading"></div>'); - spyOn($.fn, 'endlessScroll').and.stub(); - }); - - afterEach(() => { - window.history.replaceState({}, null, originalHref); - }); - - it('should use data-href attribute from list element', () => { - const href = `${gl.TEST_HOST}/some_list.json`; - setFixtures(`<div class="content_list" data-href="${href}"></div>`); - Pager.init(); - - expect(Pager.url).toBe(href); - }); - - it('should use current url if data-href attribute not provided', () => { - const href = `${gl.TEST_HOST}/some_list`; - spyOnDependency(Pager, 'removeParams').and.returnValue(href); - Pager.init(); - - expect(Pager.url).toBe(href); - }); - - it('should get initial offset from query parameter', () => { - window.history.replaceState({}, null, '?offset=100'); - Pager.init(); - - expect(Pager.offset).toBe(100); - }); - - it('keeps extra query parameters from url', () => { - window.history.replaceState({}, null, '?filter=test&offset=100'); - const href = `${gl.TEST_HOST}/some_list?filter=test`; - const removeParams = spyOnDependency(Pager, 'removeParams').and.returnValue(href); - Pager.init(); - - expect(removeParams).toHaveBeenCalledWith(['limit', 'offset']); - expect(Pager.url).toEqual(href); - }); - }); - - describe('getOld', () => { - const urlRegex = /(.*)some_list(.*)$/; - - function mockSuccess(count = 0) { - axiosMock.onGet(urlRegex).reply(200, { - count, - html: '', - }); - } - - function mockError() { - axiosMock.onGet(urlRegex).networkError(); - } - - beforeEach(() => { - setFixtures( - '<div class="content_list" data-href="/some_list"></div><div class="loading"></div>', - ); - spyOn(axios, 'get').and.callThrough(); - - Pager.init(); - }); - - it('shows loader while loading next page', done => { - mockSuccess(); - - spyOn(Pager.loading, 'show'); - Pager.getOld(); - - setTimeout(() => { - expect(Pager.loading.show).toHaveBeenCalled(); - - done(); - }); - }); - - it('hides loader on success', done => { - mockSuccess(); - - spyOn(Pager.loading, 'hide'); - Pager.getOld(); - - setTimeout(() => { - expect(Pager.loading.hide).toHaveBeenCalled(); - - done(); - }); - }); - - it('hides loader on error', done => { - mockError(); - - spyOn(Pager.loading, 'hide'); - Pager.getOld(); - - setTimeout(() => { - expect(Pager.loading.hide).toHaveBeenCalled(); - - done(); - }); - }); - - it('sends request to url with offset and limit params', done => { - Pager.offset = 100; - Pager.limit = 20; - Pager.getOld(); - - setTimeout(() => { - const [url, params] = axios.get.calls.argsFor(0); - - expect(params).toEqual({ - params: { - limit: 20, - offset: 100, - }, - }); - - expect(url).toBe('/some_list'); - - done(); - }); - }); - - it('disables if return count is less than limit', done => { - Pager.offset = 0; - Pager.limit = 20; - - mockSuccess(1); - spyOn(Pager.loading, 'hide'); - Pager.getOld(); - - setTimeout(() => { - expect(Pager.loading.hide).toHaveBeenCalled(); - expect(Pager.disable).toBe(true); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/pdf/index_spec.js b/spec/javascripts/pdf/index_spec.js deleted file mode 100644 index 39cd4dacd70..00000000000 --- a/spec/javascripts/pdf/index_spec.js +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue'; - -import { FIXTURES_PATH } from 'spec/test_constants'; -import PDFLab from '~/pdf/index.vue'; - -const pdf = `${FIXTURES_PATH}/blob/pdf/test.pdf`; - -const Component = Vue.extend(PDFLab); - -describe('PDF component', () => { - let vm; - - const checkLoaded = done => { - if (vm.loading) { - setTimeout(() => { - checkLoaded(done); - }, 100); - } else { - done(); - } - }; - - describe('without PDF data', () => { - beforeEach(done => { - vm = new Component({ - propsData: { - pdf: '', - }, - }); - - vm.$mount(); - - checkLoaded(done); - }); - - it('does not render', () => { - expect(vm.$el.tagName).toBeUndefined(); - }); - }); - - describe('with PDF data', () => { - beforeEach(done => { - vm = new Component({ - propsData: { - pdf, - }, - }); - - vm.$mount(); - - checkLoaded(done); - }); - - it('renders pdf component', () => { - expect(vm.$el.tagName).toBeDefined(); - }); - }); -}); diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js deleted file mode 100644 index cc2cc204ee3..00000000000 --- a/spec/javascripts/pdf/page_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import Vue from 'vue'; -import pdfjsLib from 'pdfjs-dist/webpack'; - -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { FIXTURES_PATH } from 'spec/test_constants'; -import PageComponent from '~/pdf/page/index.vue'; - -const testPDF = `${FIXTURES_PATH}/blob/pdf/test.pdf`; - -describe('Page component', () => { - const Component = Vue.extend(PageComponent); - let vm; - let testPage; - - beforeEach(done => { - pdfjsLib - .getDocument(testPDF) - .promise.then(pdf => pdf.getPage(1)) - .then(page => { - testPage = page; - }) - .then(done) - .catch(done.fail); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders the page when mounting', done => { - const promise = Promise.resolve(); - spyOn(testPage, 'render').and.returnValue({ promise }); - - vm = mountComponent(Component, { - page: testPage, - number: 1, - }); - - expect(vm.rendering).toBe(true); - - promise - .then(() => { - expect(testPage.render).toHaveBeenCalledWith(vm.renderContext); - expect(vm.rendering).toBe(false); - }) - .then(done) - .catch(done.fail); - }); -}); diff --git a/spec/javascripts/performance_bar/index_spec.js b/spec/javascripts/performance_bar/index_spec.js deleted file mode 100644 index 3957edce9e0..00000000000 --- a/spec/javascripts/performance_bar/index_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import '~/performance_bar/components/performance_bar_app.vue'; -import performanceBar from '~/performance_bar'; -import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; - -describe('performance bar wrapper', () => { - let mock; - let vm; - - beforeEach(() => { - const peekWrapper = document.createElement('div'); - - peekWrapper.setAttribute('id', 'js-peek'); - peekWrapper.setAttribute('data-env', 'development'); - peekWrapper.setAttribute('data-request-id', '123'); - peekWrapper.setAttribute('data-peek-url', '/-/peek/results'); - peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true'); - - document.body.appendChild(peekWrapper); - - mock = new MockAdapter(axios); - - mock.onGet('/-/peek/results').reply( - 200, - { - data: { - gc: { - invokes: 0, - invoke_time: '0.00', - use_size: 0, - total_size: 0, - total_object: 0, - gc_time: '0.00', - }, - host: { hostname: 'web-01' }, - }, - }, - {}, - ); - - vm = performanceBar({ container: '#js-peek' }); - }); - - afterEach(() => { - vm.$destroy(); - mock.restore(); - }); - - describe('loadRequestDetails', () => { - beforeEach(() => { - spyOn(vm.store, 'addRequest').and.callThrough(); - }); - - it('does nothing if the request cannot be tracked', () => { - spyOn(vm.store, 'canTrackRequest').and.callFake(() => false); - - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).not.toHaveBeenCalled(); - }); - - it('adds the request immediately', () => { - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).toHaveBeenCalledWith('123', 'https://gitlab.com/'); - }); - - it('makes an HTTP request for the request details', () => { - spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough(); - - vm.loadRequestDetails('456', 'https://gitlab.com/'); - - expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith( - '/-/peek/results', - '456', - ); - }); - }); -}); diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js deleted file mode 100644 index d4cb92cacfd..00000000000 --- a/spec/javascripts/persistent_user_callout_spec.js +++ /dev/null @@ -1,175 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import setTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; -import axios from '~/lib/utils/axios_utils'; -import PersistentUserCallout from '~/persistent_user_callout'; - -describe('PersistentUserCallout', () => { - const dismissEndpoint = '/dismiss'; - const featureName = 'feature'; - - function createFixture() { - const fixture = document.createElement('div'); - fixture.innerHTML = ` - <div - class="container" - data-dismiss-endpoint="${dismissEndpoint}" - data-feature-id="${featureName}" - > - <button type="button" class="js-close"></button> - </div> - `; - - return fixture; - } - - function createDeferredLinkFixture() { - const fixture = document.createElement('div'); - fixture.innerHTML = ` - <div - class="container" - data-dismiss-endpoint="${dismissEndpoint}" - data-feature-id="${featureName}" - data-defer-links="true" - > - <button type="button" class="js-close"></button> - <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a> - <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a> - </div> - `; - - return fixture; - } - - describe('dismiss', () => { - let button; - let mockAxios; - let persistentUserCallout; - - beforeEach(() => { - const fixture = createFixture(); - const container = fixture.querySelector('.container'); - button = fixture.querySelector('.js-close'); - mockAxios = new MockAdapter(axios); - persistentUserCallout = new PersistentUserCallout(container); - spyOn(persistentUserCallout.container, 'remove'); - }); - - afterEach(() => { - mockAxios.restore(); - }); - - it('POSTs endpoint and removes container when clicking close', done => { - mockAxios.onPost(dismissEndpoint).replyOnce(200); - - button.click(); - - setTimeoutPromise() - .then(() => { - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - expect(mockAxios.history.post[0].data).toBe( - JSON.stringify({ feature_name: featureName }), - ); - }) - .then(done) - .catch(done.fail); - }); - - it('invokes Flash when the dismiss request fails', done => { - const Flash = spyOnDependency(PersistentUserCallout, 'Flash'); - mockAxios.onPost(dismissEndpoint).replyOnce(500); - - button.click(); - - setTimeoutPromise() - .then(() => { - expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); - expect(Flash).toHaveBeenCalledWith( - 'An error occurred while dismissing the alert. Refresh the page and try again.', - ); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('deferred links', () => { - let button; - let deferredLink; - let normalLink; - let mockAxios; - let persistentUserCallout; - let windowSpy; - - beforeEach(() => { - const fixture = createDeferredLinkFixture(); - const container = fixture.querySelector('.container'); - button = fixture.querySelector('.js-close'); - deferredLink = fixture.querySelector('.deferred-link'); - normalLink = fixture.querySelector('.normal-link'); - mockAxios = new MockAdapter(axios); - persistentUserCallout = new PersistentUserCallout(container); - spyOn(persistentUserCallout.container, 'remove'); - windowSpy = spyOn(window, 'open').and.callFake(() => {}); - }); - - afterEach(() => { - mockAxios.restore(); - }); - - it('defers loading of a link until callout is dismissed', done => { - const { href, target } = deferredLink; - mockAxios.onPost(dismissEndpoint).replyOnce(200); - - deferredLink.click(); - - setTimeoutPromise() - .then(() => { - expect(windowSpy).toHaveBeenCalledWith(href, target); - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - expect(mockAxios.history.post[0].data).toBe( - JSON.stringify({ feature_name: featureName }), - ); - }) - .then(done) - .catch(done.fail); - }); - - it('does not dismiss callout on non-deferred links', done => { - normalLink.click(); - - setTimeoutPromise() - .then(() => { - expect(windowSpy).not.toHaveBeenCalled(); - expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('does not follow link when notification is closed', done => { - mockAxios.onPost(dismissEndpoint).replyOnce(200); - - button.click(); - - setTimeoutPromise() - .then(() => { - expect(windowSpy).not.toHaveBeenCalled(); - expect(persistentUserCallout.container.remove).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('factory', () => { - it('returns an instance of PersistentUserCallout with the provided container property', () => { - const fixture = createFixture(); - - expect(PersistentUserCallout.factory(fixture) instanceof PersistentUserCallout).toBe(true); - }); - - it('returns undefined if container is falsey', () => { - expect(PersistentUserCallout.factory()).toBe(undefined); - }); - }); -}); diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js deleted file mode 100644 index d1d01272403..00000000000 --- a/spec/javascripts/read_more_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import initReadMore from '~/read_more'; - -describe('Read more click-to-expand functionality', () => { - const fixtureName = 'projects/overview.html'; - - preloadFixtures(fixtureName); - - beforeEach(() => { - loadFixtures(fixtureName); - }); - - describe('expands target element', () => { - it('adds "is-expanded" class to target element', () => { - const target = document.querySelector('.read-more-container'); - const trigger = document.querySelector('.js-read-more-trigger'); - initReadMore(); - - trigger.click(); - - expect(target.classList.contains('is-expanded')).toEqual(true); - }); - }); -}); diff --git a/spec/javascripts/releases/components/app_index_spec.js b/spec/javascripts/releases/components/app_index_spec.js deleted file mode 100644 index 020937d07e5..00000000000 --- a/spec/javascripts/releases/components/app_index_spec.js +++ /dev/null @@ -1,184 +0,0 @@ -import { range as rge } from 'lodash'; -import Vue from 'vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import app from '~/releases/components/app_index.vue'; -import createStore from '~/releases/stores'; -import listModule from '~/releases/stores/modules/list'; -import api from '~/api'; -import { resetStore } from '../stores/modules/list/helpers'; -import { - pageInfoHeadersWithoutPagination, - pageInfoHeadersWithPagination, - release, - releases, -} from '../mock_data'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import waitForPromises from 'spec/helpers/wait_for_promises'; - -describe('Releases App ', () => { - const Component = Vue.extend(app); - let store; - let vm; - let releasesPagination; - - const props = { - projectId: 'gitlab-ce', - documentationPath: 'help/releases', - illustrationPath: 'illustration/path', - }; - - beforeEach(() => { - store = createStore({ modules: { list: listModule } }); - releasesPagination = rge(21).map(index => ({ - ...convertObjectPropsToCamelCase(release, { deep: true }), - tagName: `${index}.00`, - })); - }); - - afterEach(() => { - resetStore(store); - vm.$destroy(); - }); - - describe('while loading', () => { - beforeEach(() => { - spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [], headers: {} })); - vm = mountComponentWithStore(Component, { props, store }); - }); - - it('renders loading icon', done => { - expect(vm.$el.querySelector('.js-loading')).not.toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); - - waitForPromises() - .then(done) - .catch(done.fail); - }); - }); - - describe('with successful request', () => { - beforeEach(() => { - spyOn(api, 'releases').and.returnValue( - Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }), - ); - vm = mountComponentWithStore(Component, { props, store }); - }); - - it('renders success state', done => { - waitForPromises() - .then(() => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('with successful request and pagination', () => { - beforeEach(() => { - spyOn(api, 'releases').and.returnValue( - Promise.resolve({ data: releasesPagination, headers: pageInfoHeadersWithPagination }), - ); - vm = mountComponentWithStore(Component, { props, store }); - }); - - it('renders success state', done => { - waitForPromises() - .then(() => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).not.toBeNull(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('with empty request', () => { - beforeEach(() => { - spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [], headers: {} })); - vm = mountComponentWithStore(Component, { props, store }); - }); - - it('renders empty state', done => { - waitForPromises() - .then(() => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('"New release" button', () => { - const findNewReleaseButton = () => vm.$el.querySelector('.js-new-release-btn'); - - beforeEach(() => { - spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [], headers: {} })); - }); - - const factory = additionalProps => { - vm = mountComponentWithStore(Component, { - props: { - ...props, - ...additionalProps, - }, - store, - }); - }; - - describe('when the user is allowed to create a new Release', () => { - const newReleasePath = 'path/to/new/release'; - - beforeEach(() => { - factory({ newReleasePath }); - }); - - it('renders the "New release" button', done => { - waitForPromises() - .then(() => { - expect(findNewReleaseButton()).not.toBeNull(); - - done(); - }) - .catch(done.fail); - }); - - it('renders the "New release" button with the correct href', done => { - waitForPromises() - .then(() => { - expect(findNewReleaseButton().getAttribute('href')).toBe(newReleasePath); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('when the user is not allowed to create a new Release', () => { - beforeEach(() => factory()); - - it('does not render the "New release" button', done => { - waitForPromises() - .then(() => { - expect(findNewReleaseButton()).toBeNull(); - - done(); - }) - .catch(done.fail); - }); - }); - }); -}); diff --git a/spec/javascripts/releases/mock_data.js b/spec/javascripts/releases/mock_data.js deleted file mode 100644 index 72875dff172..00000000000 --- a/spec/javascripts/releases/mock_data.js +++ /dev/null @@ -1,148 +0,0 @@ -export const pageInfoHeadersWithoutPagination = { - 'X-NEXT-PAGE': '', - 'X-PAGE': '1', - 'X-PER-PAGE': '20', - 'X-PREV-PAGE': '', - 'X-TOTAL': '19', - 'X-TOTAL-PAGES': '1', -}; - -export const pageInfoHeadersWithPagination = { - 'X-NEXT-PAGE': '2', - 'X-PAGE': '1', - 'X-PER-PAGE': '20', - 'X-PREV-PAGE': '', - 'X-TOTAL': '21', - 'X-TOTAL-PAGES': '2', -}; - -export const release = { - name: 'Bionic Beaver', - tag_name: '18.04', - description: '## changelog\n\n* line 1\n* line2', - description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', - author_name: 'Release bot', - author_email: 'release-bot@example.com', - created_at: '2012-05-28T05:00:00-07:00', - commit: { - id: '2695effb5807a22ff3d138d593fd856244e155e7', - short_id: '2695effb', - title: 'Initial commit', - created_at: '2017-07-26T11:08:53.000+02:00', - parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], - message: 'Initial commit', - author: { - avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', - id: 482476, - name: 'John Doe', - path: '/johndoe', - state: 'active', - status_tooltip_html: null, - username: 'johndoe', - web_url: 'https://gitlab.com/johndoe', - }, - authored_date: '2012-05-28T04:42:42-07:00', - committer_name: 'Jack Smith', - committer_email: 'jack@example.com', - committed_date: '2012-05-28T04:42:42-07:00', - }, - assets: { - count: 6, - sources: [ - { - format: 'zip', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', - }, - { - format: 'tar.gz', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', - }, - { - format: 'tar.bz2', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', - }, - { - format: 'tar', - url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', - }, - ], - links: [ - { - name: 'release-18.04.dmg', - url: 'https://my-external-hosting.example.com/scrambled-url/', - external: true, - }, - { - name: 'binary-linux-amd64', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', - external: false, - }, - ], - }, -}; - -export const releases = [ - release, - { - name: 'JoJos Bizarre Adventure', - tag_name: '19.00', - description: '## changelog\n\n* line 1\n* line2', - description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', - author_name: 'Release bot', - author_email: 'release-bot@example.com', - created_at: '2012-05-28T05:00:00-07:00', - commit: { - id: '2695effb5807a22ff3d138d593fd856244e155e7', - short_id: '2695effb', - title: 'Initial commit', - created_at: '2017-07-26T11:08:53.000+02:00', - parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], - message: 'Initial commit', - author: { - avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', - id: 482476, - name: 'John Doe', - path: '/johndoe', - state: 'active', - status_tooltip_html: null, - username: 'johndoe', - web_url: 'https://gitlab.com/johndoe', - }, - authored_date: '2012-05-28T04:42:42-07:00', - committer_name: 'Jack Smith', - committer_email: 'jack@example.com', - committed_date: '2012-05-28T04:42:42-07:00', - }, - assets: { - count: 4, - sources: [ - { - format: 'tar.gz', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', - }, - { - format: 'tar.bz2', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', - }, - { - format: 'tar', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', - }, - ], - links: [ - { - name: 'binary-linux-amd64', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', - external: false, - }, - ], - }, - }, -]; diff --git a/spec/javascripts/releases/stores/modules/list/actions_spec.js b/spec/javascripts/releases/stores/modules/list/actions_spec.js deleted file mode 100644 index bf85e18997b..00000000000 --- a/spec/javascripts/releases/stores/modules/list/actions_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import testAction from 'spec/helpers/vuex_action_helper'; -import { - requestReleases, - fetchReleases, - receiveReleasesSuccess, - receiveReleasesError, -} from '~/releases/stores/modules/list/actions'; -import state from '~/releases/stores/modules/list/state'; -import * as types from '~/releases/stores/modules/list/mutation_types'; -import api from '~/api'; -import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { pageInfoHeadersWithoutPagination, releases as originalReleases } from '../../../mock_data'; - -describe('Releases State actions', () => { - let mockedState; - let pageInfo; - let releases; - - beforeEach(() => { - mockedState = state(); - pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); - releases = convertObjectPropsToCamelCase(originalReleases, { deep: true }); - }); - - describe('requestReleases', () => { - it('should commit REQUEST_RELEASES mutation', done => { - testAction(requestReleases, null, mockedState, [{ type: types.REQUEST_RELEASES }], [], done); - }); - }); - - describe('fetchReleases', () => { - describe('success', () => { - it('dispatches requestReleases and receiveReleasesSuccess', done => { - spyOn(api, 'releases').and.callFake((id, options) => { - expect(id).toEqual(1); - expect(options.page).toEqual('1'); - return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); - }); - - testAction( - fetchReleases, - { projectId: 1 }, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, - type: 'receiveReleasesSuccess', - }, - ], - done, - ); - }); - - it('dispatches requestReleases and receiveReleasesSuccess on page two', done => { - spyOn(api, 'releases').and.callFake((_, options) => { - expect(options.page).toEqual('2'); - return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); - }); - - testAction( - fetchReleases, - { page: '2', projectId: 1 }, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, - type: 'receiveReleasesSuccess', - }, - ], - done, - ); - }); - }); - - describe('error', () => { - it('dispatches requestReleases and receiveReleasesError', done => { - spyOn(api, 'releases').and.returnValue(Promise.reject()); - - testAction( - fetchReleases, - { projectId: null }, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - type: 'receiveReleasesError', - }, - ], - done, - ); - }); - }); - }); - - describe('receiveReleasesSuccess', () => { - it('should commit RECEIVE_RELEASES_SUCCESS mutation', done => { - testAction( - receiveReleasesSuccess, - { data: releases, headers: pageInfoHeadersWithoutPagination }, - mockedState, - [{ type: types.RECEIVE_RELEASES_SUCCESS, payload: { pageInfo, data: releases } }], - [], - done, - ); - }); - }); - - describe('receiveReleasesError', () => { - it('should commit RECEIVE_RELEASES_ERROR mutation', done => { - testAction( - receiveReleasesError, - null, - mockedState, - [{ type: types.RECEIVE_RELEASES_ERROR }], - [], - done, - ); - }); - }); -}); diff --git a/spec/javascripts/releases/stores/modules/list/helpers.js b/spec/javascripts/releases/stores/modules/list/helpers.js deleted file mode 100644 index 435ca36047e..00000000000 --- a/spec/javascripts/releases/stores/modules/list/helpers.js +++ /dev/null @@ -1,6 +0,0 @@ -import state from '~/releases/stores/modules/list/state'; - -// eslint-disable-next-line import/prefer-default-export -export const resetStore = store => { - store.replaceState(state()); -}; diff --git a/spec/javascripts/releases/stores/modules/list/mutations_spec.js b/spec/javascripts/releases/stores/modules/list/mutations_spec.js deleted file mode 100644 index 3035b916ff6..00000000000 --- a/spec/javascripts/releases/stores/modules/list/mutations_spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import state from '~/releases/stores/modules/list/state'; -import mutations from '~/releases/stores/modules/list/mutations'; -import * as types from '~/releases/stores/modules/list/mutation_types'; -import { parseIntPagination } from '~/lib/utils/common_utils'; -import { pageInfoHeadersWithoutPagination, releases } from '../../../mock_data'; - -describe('Releases Store Mutations', () => { - let stateCopy; - let pageInfo; - - beforeEach(() => { - stateCopy = state(); - pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); - }); - - describe('REQUEST_RELEASES', () => { - it('sets isLoading to true', () => { - mutations[types.REQUEST_RELEASES](stateCopy); - - expect(stateCopy.isLoading).toEqual(true); - }); - }); - - describe('RECEIVE_RELEASES_SUCCESS', () => { - beforeEach(() => { - mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, { pageInfo, data: releases }); - }); - - it('sets is loading to false', () => { - expect(stateCopy.isLoading).toEqual(false); - }); - - it('sets hasError to false', () => { - expect(stateCopy.hasError).toEqual(false); - }); - - it('sets data', () => { - expect(stateCopy.releases).toEqual(releases); - }); - - it('sets pageInfo', () => { - expect(stateCopy.pageInfo).toEqual(pageInfo); - }); - }); - - describe('RECEIVE_RELEASES_ERROR', () => { - it('resets data', () => { - mutations[types.RECEIVE_RELEASES_ERROR](stateCopy); - - expect(stateCopy.isLoading).toEqual(false); - expect(stateCopy.releases).toEqual([]); - expect(stateCopy.pageInfo).toEqual({}); - }); - }); -}); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js deleted file mode 100644 index 9565e3ce546..00000000000 --- a/spec/javascripts/right_sidebar_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import '~/commons/bootstrap'; -import axios from '~/lib/utils/axios_utils'; -import Sidebar from '~/right_sidebar'; - -let $aside = null; -let $toggle = null; -let $icon = null; -let $page = null; -let $labelsIcon = null; - -const assertSidebarState = function(state) { - const shouldBeExpanded = state === 'expanded'; - const shouldBeCollapsed = state === 'collapsed'; - expect($aside.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded); - expect($page.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded); - expect($icon.hasClass('fa-angle-double-right')).toBe(shouldBeExpanded); - expect($aside.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed); - expect($page.hasClass('right-sidebar-collapsed')).toBe(shouldBeCollapsed); - expect($icon.hasClass('fa-angle-double-left')).toBe(shouldBeCollapsed); -}; - -describe('RightSidebar', function() { - describe('fixture tests', () => { - const fixtureName = 'issues/open-issue.html'; - preloadFixtures(fixtureName); - loadJSONFixtures('todos/todos.json'); - let mock; - - beforeEach(function() { - loadFixtures(fixtureName); - mock = new MockAdapter(axios); - new Sidebar(); // eslint-disable-line no-new - $aside = $('.right-sidebar'); - $page = $('.layout-page'); - $icon = $aside.find('i'); - $toggle = $aside.find('.js-sidebar-toggle'); - $labelsIcon = $aside.find('.sidebar-collapsed-icon'); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should expand/collapse the sidebar when arrow is clicked', function() { - assertSidebarState('expanded'); - $toggle.click(); - assertSidebarState('collapsed'); - $toggle.click(); - assertSidebarState('expanded'); - }); - - it('should float over the page and when sidebar icons clicked', function() { - $labelsIcon.click(); - assertSidebarState('expanded'); - }); - - it('should collapse when the icon arrow clicked while it is floating on page', function() { - $labelsIcon.click(); - assertSidebarState('expanded'); - $toggle.click(); - assertSidebarState('collapsed'); - }); - - it('should broadcast todo:toggle event when add todo clicked', function(done) { - const todos = getJSONFixture('todos/todos.json'); - mock.onPost(/(.*)\/todos$/).reply(200, todos); - - const todoToggleSpy = spyOnEvent(document, 'todo:toggle'); - - $('.issuable-sidebar-header .js-issuable-todo').click(); - - setTimeout(() => { - expect(todoToggleSpy.calls.count()).toEqual(1); - - done(); - }); - }); - - it('should not hide collapsed icons', () => { - [].forEach.call(document.querySelectorAll('.sidebar-collapsed-icon'), el => { - expect(el.querySelector('.fa, svg').classList.contains('hidden')).toBeFalsy(); - }); - }); - }); -}); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js deleted file mode 100644 index 4f42d4880e8..00000000000 --- a/spec/javascripts/search_autocomplete_spec.js +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */ - -import $ from 'jquery'; -import '~/gl_dropdown'; -import initSearchAutocomplete from '~/search_autocomplete'; -import '~/lib/utils/common_utils'; - -describe('Search autocomplete dropdown', () => { - let widget = null; - - const userName = 'root'; - - const userId = 1; - - const dashboardIssuesPath = '/dashboard/issues'; - - const dashboardMRsPath = '/dashboard/merge_requests'; - - const projectIssuesPath = '/gitlab-org/gitlab-foss/issues'; - - const projectMRsPath = '/gitlab-org/gitlab-foss/-/merge_requests'; - - const groupIssuesPath = '/groups/gitlab-org/-/issues'; - - const groupMRsPath = '/groups/gitlab-org/-/merge_requests'; - - const projectName = 'GitLab Community Edition'; - - const groupName = 'Gitlab Org'; - - const removeBodyAttributes = function() { - const $body = $('body'); - - $body.removeAttr('data-page'); - $body.removeAttr('data-project'); - $body.removeAttr('data-group'); - }; - - // Add required attributes to body before starting the test. - // section would be dashboard|group|project - const addBodyAttributes = function(section) { - if (section == null) { - section = 'dashboard'; - } - - const $body = $('body'); - removeBodyAttributes(); - switch (section) { - case 'dashboard': - return $body.attr('data-page', 'root:index'); - case 'group': - $body.attr('data-page', 'groups:show'); - return $body.data('group', 'gitlab-org'); - case 'project': - $body.attr('data-page', 'projects:show'); - return $body.data('project', 'gitlab-ce'); - } - }; - - const disableProjectIssues = function() { - document.querySelector('.js-search-project-options').setAttribute('data-issues-disabled', true); - }; - - // Mock `gl` object in window for dashboard specific page. App code will need it. - const mockDashboardOptions = function() { - window.gl || (window.gl = {}); - return (window.gl.dashboardOptions = { - issuesPath: dashboardIssuesPath, - mrPath: dashboardMRsPath, - }); - }; - - // Mock `gl` object in window for project specific page. App code will need it. - const mockProjectOptions = function() { - window.gl || (window.gl = {}); - return (window.gl.projectOptions = { - 'gitlab-ce': { - issuesPath: projectIssuesPath, - mrPath: projectMRsPath, - projectName, - }, - }); - }; - - const mockGroupOptions = function() { - window.gl || (window.gl = {}); - return (window.gl.groupOptions = { - 'gitlab-org': { - issuesPath: groupIssuesPath, - mrPath: groupMRsPath, - projectName: groupName, - }, - }); - }; - - const assertLinks = function(list, issuesPath, mrsPath) { - if (issuesPath) { - const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`; - const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`; - - expect(list.find(issuesAssignedToMeLink).length).toBe(1); - expect(list.find(issuesAssignedToMeLink).text()).toBe('Issues assigned to me'); - expect(list.find(issuesIHaveCreatedLink).length).toBe(1); - expect(list.find(issuesIHaveCreatedLink).text()).toBe("Issues I've created"); - } - const mrsAssignedToMeLink = `a[href="${mrsPath}/?assignee_username=${userName}"]`; - const mrsIHaveCreatedLink = `a[href="${mrsPath}/?author_username=${userName}"]`; - - expect(list.find(mrsAssignedToMeLink).length).toBe(1); - expect(list.find(mrsAssignedToMeLink).text()).toBe('Merge requests assigned to me'); - expect(list.find(mrsIHaveCreatedLink).length).toBe(1); - expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); - }; - - preloadFixtures('static/search_autocomplete.html'); - beforeEach(function() { - loadFixtures('static/search_autocomplete.html'); - - window.gon = {}; - window.gon.current_user_id = userId; - window.gon.current_username = userName; - - return (widget = initSearchAutocomplete()); - }); - - afterEach(function() { - // Undo what we did to the shared <body> - removeBodyAttributes(); - window.gon = {}; - }); - - it('should show Dashboard specific dropdown menu', function() { - addBodyAttributes(); - mockDashboardOptions(); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, dashboardIssuesPath, dashboardMRsPath); - }); - - it('should show Group specific dropdown menu', function() { - addBodyAttributes('group'); - mockGroupOptions(); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, groupIssuesPath, groupMRsPath); - }); - - it('should show Project specific dropdown menu', function() { - addBodyAttributes('project'); - mockProjectOptions(); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - return assertLinks(list, projectIssuesPath, projectMRsPath); - }); - - it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() { - addBodyAttributes('project'); - disableProjectIssues(); - mockProjectOptions(); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - assertLinks(list, null, projectMRsPath); - }); - - it('should not show category related menu if there is text in the input', function() { - addBodyAttributes('project'); - mockProjectOptions(); - widget.searchInput.val('help'); - widget.searchInput.triggerHandler('focus'); - const list = widget.wrap.find('.dropdown-menu').find('ul'); - const link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`; - - expect(list.find(link).length).toBe(0); - }); - - it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { - const ENTER = 13; - const DOWN = 40; - addBodyAttributes(); - mockDashboardOptions(true); - const submitSpy = spyOnEvent('form', 'submit'); - widget.searchInput.triggerHandler('focus'); - widget.wrap.trigger($.Event('keydown', { which: DOWN })); - const enterKeyEvent = $.Event('keydown', { which: ENTER }); - widget.searchInput.trigger(enterKeyEvent); - // This does not currently catch failing behavior. For security reasons, - // browsers will not trigger default behavior (form submit, in this - // example) on JavaScript-created keypresses. - expect(submitSpy).not.toHaveBeenTriggered(); - }); - - describe('disableAutocomplete', function() { - beforeEach(function() { - widget.enableAutocomplete(); - }); - - it('should close the Dropdown', function() { - const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown'); - - widget.dropdown.addClass('show'); - widget.disableAutocomplete(); - - expect(toggleSpy).toHaveBeenCalledWith('toggle'); - }); - }); - - describe('enableAutocomplete', function() { - it('should open the Dropdown', function() { - const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown'); - widget.enableAutocomplete(); - - expect(toggleSpy).toHaveBeenCalledWith('toggle'); - }); - }); -}); diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js deleted file mode 100644 index df7012bb659..00000000000 --- a/spec/javascripts/shortcuts_spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import $ from 'jquery'; -import Shortcuts from '~/behaviors/shortcuts/shortcuts'; - -describe('Shortcuts', () => { - const fixtureName = 'snippets/show.html'; - const createEvent = (type, target) => - $.Event(type, { - target, - }); - - preloadFixtures(fixtureName); - - describe('toggleMarkdownPreview', () => { - beforeEach(() => { - loadFixtures(fixtureName); - - spyOnEvent('.js-new-note-form .js-md-preview-button', 'focus'); - spyOnEvent('.edit-note .js-md-preview-button', 'focus'); - - new Shortcuts(); // eslint-disable-line no-new - }); - - it('focuses preview button in form', () => { - Shortcuts.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(() => { - Shortcuts.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/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js deleted file mode 100644 index 966ae55ce14..00000000000 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ /dev/null @@ -1,216 +0,0 @@ -import AccessorUtilities from '~/lib/utils/accessor'; -import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; -import trackData from '~/pages/sessions/new/index'; -import Tracking from '~/tracking'; - -describe('SigninTabsMemoizer', () => { - const fixtureTemplate = 'static/signin_tabs.html'; - const tabSelector = 'ul.new-session-tabs'; - const currentTabKey = 'current_signin_tab'; - let memo; - - function createMemoizer() { - memo = new SigninTabsMemoizer({ - currentTabKey, - tabSelector, - }); - return memo; - } - - preloadFixtures(fixtureTemplate); - - beforeEach(() => { - loadFixtures(fixtureTemplate); - - spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true); - }); - - it('does nothing if no tab was previously selected', () => { - createMemoizer(); - - expect(document.querySelector(`${tabSelector} > li.active a`).getAttribute('href')).toEqual( - '#ldap', - ); - }); - - it('shows last selected tab on boot', () => { - createMemoizer().saveData('#ldap'); - const fakeTab = { - click: () => {}, - }; - spyOn(document, 'querySelector').and.returnValue(fakeTab); - spyOn(fakeTab, 'click'); - - memo.bootstrap(); - - // verify that triggers click on the last selected tab - expect(document.querySelector).toHaveBeenCalledWith(`${tabSelector} a[href="#ldap"]`); - expect(fakeTab.click).toHaveBeenCalled(); - }); - - it('clicks the first tab if value in local storage is bad', () => { - createMemoizer().saveData('#bogus'); - const fakeTab = { - click: () => {}, - }; - spyOn(document, 'querySelector').and.callFake(selector => - selector === `${tabSelector} a[href="#bogus"]` ? null : fakeTab, - ); - spyOn(fakeTab, 'click'); - - memo.bootstrap(); - - // verify that triggers click on stored selector and fallback - expect(document.querySelector.calls.allArgs()).toEqual([ - ['ul.new-session-tabs a[href="#bogus"]'], - ['ul.new-session-tabs a'], - ]); - - expect(fakeTab.click).toHaveBeenCalled(); - }); - - it('saves last selected tab on change', () => { - createMemoizer(); - - document.querySelector('a[href="#login-pane"]').click(); - - expect(memo.readData()).toEqual('#login-pane'); - }); - - it('overrides last selected tab with hash tag when given', () => { - window.location.hash = '#ldap'; - createMemoizer(); - - expect(memo.readData()).toEqual('#ldap'); - }); - - describe('class constructor', () => { - beforeEach(() => { - memo = createMemoizer(); - }); - - it('should set .isLocalStorageAvailable', () => { - expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); - expect(memo.isLocalStorageAvailable).toBe(true); - }); - }); - - describe('trackData', () => { - beforeEach(() => { - spyOn(Tracking, 'event'); - }); - - describe('with tracking data', () => { - beforeEach(() => { - gon.tracking_data = { - category: 'Growth::Acquisition::Experiment::SignUpFlow', - action: 'start', - label: 'uuid', - property: 'control_group', - }; - trackData(); - }); - - it('should track data when the "click" event of the register tab is triggered', () => { - document.querySelector('a[href="#register-pane"]').click(); - - expect(Tracking.event).toHaveBeenCalledWith( - 'Growth::Acquisition::Experiment::SignUpFlow', - 'start', - { - label: 'uuid', - property: 'control_group', - }, - ); - }); - }); - - describe('without tracking data', () => { - beforeEach(() => { - gon.tracking_data = undefined; - trackData(); - }); - - it('should not track data when the "click" event of the register tab is triggered', () => { - document.querySelector('a[href="#register-pane"]').click(); - - expect(Tracking.event).not.toHaveBeenCalled(); - }); - }); - }); - - describe('saveData', () => { - beforeEach(() => { - memo = { - currentTabKey, - }; - - spyOn(localStorage, 'setItem'); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(function() { - memo.isLocalStorageAvailable = false; - - SigninTabsMemoizer.prototype.saveData.call(memo); - }); - - it('should not call .setItem', () => { - expect(localStorage.setItem).not.toHaveBeenCalled(); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - const value = 'value'; - - beforeEach(function() { - memo.isLocalStorageAvailable = true; - - SigninTabsMemoizer.prototype.saveData.call(memo, value); - }); - - it('should call .setItem', () => { - expect(localStorage.setItem).toHaveBeenCalledWith(currentTabKey, value); - }); - }); - }); - - describe('readData', () => { - const itemValue = 'itemValue'; - let readData; - - beforeEach(() => { - memo = { - currentTabKey, - }; - - spyOn(localStorage, 'getItem').and.returnValue(itemValue); - }); - - describe('if .isLocalStorageAvailable is `false`', () => { - beforeEach(function() { - memo.isLocalStorageAvailable = false; - - readData = SigninTabsMemoizer.prototype.readData.call(memo); - }); - - it('should not call .getItem and should return `null`', () => { - expect(localStorage.getItem).not.toHaveBeenCalled(); - expect(readData).toBe(null); - }); - }); - - describe('if .isLocalStorageAvailable is `true`', () => { - beforeEach(function() { - memo.isLocalStorageAvailable = true; - - readData = SigninTabsMemoizer.prototype.readData.call(memo); - }); - - it('should call .getItem and return the localStorage value', () => { - expect(window.localStorage.getItem).toHaveBeenCalledWith(currentTabKey); - expect(readData).toBe(itemValue); - }); - }); - }); -}); diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js deleted file mode 100644 index dc3c547c632..00000000000 --- a/spec/javascripts/todos_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import Todos from '~/pages/dashboard/todos/index/todos'; -import '~/lib/utils/common_utils'; -import '~/gl_dropdown'; -import axios from '~/lib/utils/axios_utils'; -import { addDelimiter } from '~/lib/utils/text_utility'; - -const TEST_COUNT_BIG = 2000; -const TEST_DONE_COUNT_BIG = 7300; - -describe('Todos', () => { - preloadFixtures('todos/todos.html'); - let todoItem; - let mock; - - beforeEach(() => { - loadFixtures('todos/todos.html'); - todoItem = document.querySelector('.todos-list .todo'); - mock = new MockAdapter(axios); - - return new Todos(); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('goToTodoUrl', () => { - it('opens the todo url', done => { - const todoLink = todoItem.dataset.url; - - spyOnDependency(Todos, 'visitUrl').and.callFake(url => { - expect(url).toEqual(todoLink); - done(); - }); - - todoItem.click(); - }); - - describe('meta click', () => { - let visitUrlSpy; - let windowOpenSpy; - let metakeyEvent; - - beforeEach(() => { - metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); - visitUrlSpy = spyOnDependency(Todos, 'visitUrl').and.callFake(() => {}); - windowOpenSpy = spyOn(window, 'open').and.callFake(() => {}); - }); - - it('opens the todo url in another tab', () => { - const todoLink = todoItem.dataset.url; - - $('.todos-list .todo').trigger(metakeyEvent); - - expect(visitUrlSpy).not.toHaveBeenCalled(); - expect(windowOpenSpy).toHaveBeenCalledWith(todoLink, '_blank'); - }); - - it('run native funcionality when avatar is clicked', () => { - $('.todos-list a').on('click', e => e.preventDefault()); - $('.todos-list img').trigger(metakeyEvent); - - expect(visitUrlSpy).not.toHaveBeenCalled(); - expect(windowOpenSpy).not.toHaveBeenCalled(); - }); - }); - - describe('on done todo click', () => { - let onToggleSpy; - - beforeEach(done => { - const el = document.querySelector('.js-done-todo'); - const path = el.dataset.href; - - // Arrange - mock - .onDelete(path) - .replyOnce(200, { count: TEST_COUNT_BIG, done_count: TEST_DONE_COUNT_BIG }); - onToggleSpy = jasmine.createSpy('onToggle'); - $(document).on('todo:toggle', onToggleSpy); - - // Act - el.click(); - - // Wait for axios and HTML to udpate - setImmediate(done); - }); - - it('dispatches todo:toggle', () => { - expect(onToggleSpy).toHaveBeenCalledWith(jasmine.anything(), TEST_COUNT_BIG); - }); - - it('updates pending text', () => { - expect(document.querySelector('.todos-pending .badge').innerHTML).toEqual( - addDelimiter(TEST_COUNT_BIG), - ); - }); - - it('updates done text', () => { - expect(document.querySelector('.todos-done .badge').innerHTML).toEqual( - addDelimiter(TEST_DONE_COUNT_BIG), - ); - }); - }); - }); -}); diff --git a/spec/javascripts/toggle_buttons_spec.js b/spec/javascripts/toggle_buttons_spec.js deleted file mode 100644 index 09756ff76ec..00000000000 --- a/spec/javascripts/toggle_buttons_spec.js +++ /dev/null @@ -1,127 +0,0 @@ -import $ from 'jquery'; -import setupToggleButtons from '~/toggle_buttons'; -import getSetTimeoutPromise from './helpers/set_timeout_promise_helper'; - -function generateMarkup(isChecked = true) { - return ` - <button type="button" class="${isChecked ? 'is-checked' : ''} js-project-feature-toggle"> - <input type="hidden" class="js-project-feature-toggle-input" value="${isChecked}" /> - </button> - `; -} - -function setupFixture(isChecked, clickCallback) { - const wrapper = document.createElement('div'); - wrapper.innerHTML = generateMarkup(isChecked); - - setupToggleButtons(wrapper, clickCallback); - - return wrapper; -} - -describe('ToggleButtons', () => { - describe('when input value is true', () => { - it('should initialize as checked', () => { - const wrapper = setupFixture(true); - - expect( - wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked'), - ).toEqual(true); - - expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true'); - }); - - it('should toggle to unchecked when clicked', done => { - const wrapper = setupFixture(true); - const toggleButton = wrapper.querySelector('.js-project-feature-toggle'); - - toggleButton.click(); - - getSetTimeoutPromise() - .then(() => { - expect(toggleButton.classList.contains('is-checked')).toEqual(false); - expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('when input value is false', () => { - it('should initialize as unchecked', () => { - const wrapper = setupFixture(false); - - expect( - wrapper.querySelector('.js-project-feature-toggle').classList.contains('is-checked'), - ).toEqual(false); - - expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false'); - }); - - it('should toggle to checked when clicked', done => { - const wrapper = setupFixture(false); - const toggleButton = wrapper.querySelector('.js-project-feature-toggle'); - - toggleButton.click(); - - getSetTimeoutPromise() - .then(() => { - expect(toggleButton.classList.contains('is-checked')).toEqual(true); - expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true'); - }) - .then(done) - .catch(done.fail); - }); - }); - - it('should emit `trigger-change` event', done => { - const changeSpy = jasmine.createSpy('changeEventHandler'); - const wrapper = setupFixture(false); - const toggleButton = wrapper.querySelector('.js-project-feature-toggle'); - const input = wrapper.querySelector('.js-project-feature-toggle-input'); - - $(input).on('trigger-change', changeSpy); - - toggleButton.click(); - - getSetTimeoutPromise() - .then(() => { - expect(changeSpy).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - describe('clickCallback', () => { - it('should show loading indicator while waiting', done => { - const isChecked = true; - const clickCallback = (newValue, toggleButton) => { - const input = toggleButton.querySelector('.js-project-feature-toggle-input'); - - expect(newValue).toEqual(false); - - // Check for the loading state - expect(toggleButton.classList.contains('is-checked')).toEqual(false); - expect(toggleButton.classList.contains('is-loading')).toEqual(true); - expect(toggleButton.disabled).toEqual(true); - expect(input.value).toEqual('true'); - - // After the callback finishes, check that the loading state is gone - getSetTimeoutPromise() - .then(() => { - expect(toggleButton.classList.contains('is-checked')).toEqual(false); - expect(toggleButton.classList.contains('is-loading')).toEqual(false); - expect(toggleButton.disabled).toEqual(false); - expect(input.value).toEqual('false'); - }) - .then(done) - .catch(done.fail); - }; - - const wrapper = setupFixture(isChecked, clickCallback); - const toggleButton = wrapper.querySelector('.js-project-feature-toggle'); - - toggleButton.click(); - }); - }); -}); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js deleted file mode 100644 index 6ac22fca2d3..00000000000 --- a/spec/javascripts/user_popovers_spec.js +++ /dev/null @@ -1,95 +0,0 @@ -import initUserPopovers from '~/user_popovers'; -import UsersCache from '~/lib/utils/users_cache'; - -describe('User Popovers', () => { - const fixtureTemplate = 'merge_requests/merge_request_with_mentions.html'; - preloadFixtures(fixtureTemplate); - - const selector = '.js-user-link, .gfm-project_member'; - - const dummyUser = { name: 'root' }; - const dummyUserStatus = { message: 'active' }; - - let popovers; - - const triggerEvent = (eventName, el) => { - const event = new MouseEvent(eventName, { - bubbles: true, - cancelable: true, - view: window, - }); - - el.dispatchEvent(event); - }; - - beforeEach(() => { - loadFixtures(fixtureTemplate); - - const usersCacheSpy = () => Promise.resolve(dummyUser); - spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId)); - - const userStatusCacheSpy = () => Promise.resolve(dummyUserStatus); - spyOn(UsersCache, 'retrieveStatusById').and.callFake(userId => userStatusCacheSpy(userId)); - - popovers = initUserPopovers(document.querySelectorAll(selector)); - }); - - it('initializes a popover for each user link with a user id', () => { - const linksWithUsers = Array.from(document.querySelectorAll(selector)).filter( - ({ dataset }) => dataset.user || dataset.userId, - ); - - expect(linksWithUsers.length).toBe(popovers.length); - }); - - it('does not initialize the user popovers twice for the same element', () => { - const newPopovers = initUserPopovers(document.querySelectorAll(selector)); - const samePopovers = popovers.every((popover, index) => newPopovers[index] === popover); - - expect(samePopovers).toBe(true); - }); - - describe('when user link emits mouseenter event', () => { - let userLink; - - beforeEach(() => { - userLink = document.querySelector(selector); - - triggerEvent('mouseenter', userLink); - }); - - it('removes title attribute from user links', () => { - expect(userLink.getAttribute('title')).toBeFalsy(); - expect(userLink.dataset.originalTitle).toBeFalsy(); - }); - - it('populates popovers with preloaded user data', () => { - const { name, userId, username } = userLink.dataset; - const [firstPopover] = popovers; - - expect(firstPopover.$props.user).toEqual( - jasmine.objectContaining({ - name, - userId, - username, - }), - ); - }); - - it('fetches user info and status from the user cache', () => { - const { userId } = userLink.dataset; - - expect(UsersCache.retrieveById).toHaveBeenCalledWith(userId); - expect(UsersCache.retrieveStatusById).toHaveBeenCalledWith(userId); - }); - }); - - it('removes aria-describedby attribute from the user link on mouseleave', () => { - const userLink = document.querySelector(selector); - - userLink.setAttribute('aria-describedby', 'popover'); - triggerEvent('mouseleave', userLink); - - expect(userLink.getAttribute('aria-describedby')).toBe(null); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js deleted file mode 100644 index f78fcfb52b4..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_alert_message_spec.js +++ /dev/null @@ -1,76 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlLink } from '@gitlab/ui'; -import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue'; - -describe('MrWidgetAlertMessage', () => { - let wrapper; - - beforeEach(() => { - const localVue = createLocalVue(); - - wrapper = shallowMount(localVue.extend(MrWidgetAlertMessage), { - propsData: {}, - localVue, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when type is not provided', () => { - it('should render a red message', done => { - wrapper.vm.$nextTick(() => { - expect(wrapper.classes()).toContain('danger_message'); - expect(wrapper.classes()).not.toContain('warning_message'); - done(); - }); - }); - }); - - describe('when type === "danger"', () => { - it('should render a red message', done => { - wrapper.setProps({ type: 'danger' }); - wrapper.vm.$nextTick(() => { - expect(wrapper.classes()).toContain('danger_message'); - expect(wrapper.classes()).not.toContain('warning_message'); - done(); - }); - }); - }); - - describe('when type === "warning"', () => { - it('should render a red message', done => { - wrapper.setProps({ type: 'warning' }); - wrapper.vm.$nextTick(() => { - expect(wrapper.classes()).toContain('warning_message'); - expect(wrapper.classes()).not.toContain('danger_message'); - done(); - }); - }); - }); - - describe('when helpPath is not provided', () => { - it('should not render a help icon/link', done => { - wrapper.vm.$nextTick(() => { - const link = wrapper.find(GlLink); - - expect(link.exists()).toBe(false); - done(); - }); - }); - }); - - describe('when helpPath is provided', () => { - it('should render a help icon/link', done => { - wrapper.setProps({ helpPath: '/path/to/help/docs' }); - wrapper.vm.$nextTick(() => { - const link = wrapper.find(GlLink); - - expect(link.exists()).toBe(true); - expect(link.attributes().href).toBe('/path/to/help/docs'); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js deleted file mode 100644 index a942a9dec87..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; - -describe('MrWidgetAuthor', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(MrWidgetAuthor); - - vm = mountComponent(Component, { - author: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders link with the author web url', () => { - expect(vm.$el.getAttribute('href')).toEqual('http://localhost:3000/root'); - }); - - it('renders image with avatar url', () => { - expect(vm.$el.querySelector('img').getAttribute('src')).toEqual( - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - ); - }); - - it('renders author name', () => { - expect(vm.$el.textContent.trim()).toEqual('Administrator'); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js deleted file mode 100644 index 55af2baa924..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_time_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue'; - -describe('MrWidgetAuthorTime', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(MrWidgetAuthorTime); - - vm = mountComponent(Component, { - actionText: 'Merged by', - author: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - dateTitle: '2017-03-23T23:02:00.807Z', - dateReadable: '12 hours ago', - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders provided action text', () => { - expect(vm.$el.textContent).toContain('Merged by'); - }); - - it('renders author', () => { - expect(vm.$el.textContent).toContain('Administrator'); - }); - - it('renders provided time', () => { - expect(vm.$el.querySelector('time').getAttribute('data-original-title')).toEqual( - '2017-03-23T23:02:00.807Z', - ); - - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('12 hours ago'); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js deleted file mode 100644 index 3cbaa47c832..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ /dev/null @@ -1,313 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue'; - -describe('MRWidgetHeader', () => { - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(headerComponent); - }); - - afterEach(() => { - vm.$destroy(); - gon.relative_url_root = ''; - }); - - const expectDownloadDropdownItems = () => { - const downloadEmailPatchesEl = vm.$el.querySelector('.js-download-email-patches'); - const downloadPlainDiffEl = vm.$el.querySelector('.js-download-plain-diff'); - - expect(downloadEmailPatchesEl.textContent.trim()).toEqual('Email patches'); - expect(downloadEmailPatchesEl.getAttribute('href')).toEqual('/mr/email-patches'); - expect(downloadPlainDiffEl.textContent.trim()).toEqual('Plain diff'); - expect(downloadPlainDiffEl.getAttribute('href')).toEqual('/mr/plainDiffPath'); - }; - - describe('computed', () => { - describe('shouldShowCommitsBehindText', () => { - it('return true when there are divergedCommitsCount', () => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>', - targetBranch: 'master', - statusPath: 'abc', - }, - }); - - expect(vm.shouldShowCommitsBehindText).toEqual(true); - }); - - it('returns false where there are no divergedComits count', () => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 0, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>', - targetBranch: 'master', - statusPath: 'abc', - }, - }); - - expect(vm.shouldShowCommitsBehindText).toEqual(false); - }); - }); - - describe('commitsBehindText', () => { - it('returns singular when there is one commit', () => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 1, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>', - targetBranch: 'master', - targetBranchPath: '/foo/bar/master', - statusPath: 'abc', - }, - }); - - expect(vm.commitsBehindText).toEqual( - 'The source branch is <a href="/foo/bar/master">1 commit behind</a> the target branch', - ); - }); - - it('returns plural when there is more than one commit', () => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 2, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">Link</a>', - targetBranch: 'master', - targetBranchPath: '/foo/bar/master', - statusPath: 'abc', - }, - }); - - expect(vm.commitsBehindText).toEqual( - 'The source branch is <a href="/foo/bar/master">2 commits behind</a> the target branch', - ); - }); - }); - }); - - describe('template', () => { - describe('common elements', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: true, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - statusPath: 'abc', - }, - }); - }); - - it('renders source branch link', () => { - expect(vm.$el.querySelector('.js-source-branch').innerHTML).toEqual( - '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - ); - }); - - it('renders clipboard button', () => { - expect(vm.$el.querySelector('.btn-clipboard')).not.toEqual(null); - }); - - it('renders target branch', () => { - expect(vm.$el.querySelector('.js-target-branch').textContent.trim()).toEqual('master'); - }); - }); - - describe('with an open merge request', () => { - const mrDefaultOptions = { - iid: 1, - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: true, - canPushToSourceBranch: true, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - statusPath: 'abc', - sourceProjectFullPath: 'root/gitlab-ce', - targetProjectFullPath: 'gitlab-org/gitlab-ce', - }; - - afterEach(() => { - vm.$destroy(); - }); - - beforeEach(() => { - vm = mountComponent(Component, { - mr: { ...mrDefaultOptions }, - }); - }); - - it('renders checkout branch button with modal trigger', () => { - const button = vm.$el.querySelector('.js-check-out-branch'); - - expect(button.textContent.trim()).toEqual('Check out branch'); - expect(button.getAttribute('data-target')).toEqual('#modal_merge_info'); - expect(button.getAttribute('data-toggle')).toEqual('modal'); - }); - - it('renders web ide button', () => { - const button = vm.$el.querySelector('.js-web-ide'); - - expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.classList.contains('disabled')).toBe(false); - expect(button.getAttribute('href')).toEqual( - '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce', - ); - }); - - it('renders web ide button in disabled state with no href', () => { - const mr = { ...mrDefaultOptions, canPushToSourceBranch: false }; - vm = mountComponent(Component, { mr }); - - const link = vm.$el.querySelector('.js-web-ide'); - - expect(link.classList.contains('disabled')).toBe(true); - expect(link.getAttribute('href')).toBeNull(); - }); - - it('renders web ide button with blank query string if target & source project branch', done => { - vm.mr.targetProjectFullPath = 'root/gitlab-ce'; - - vm.$nextTick(() => { - const button = vm.$el.querySelector('.js-web-ide'); - - expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual( - '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=', - ); - - done(); - }); - }); - - it('renders web ide button with relative URL', done => { - gon.relative_url_root = '/gitlab'; - vm.mr.iid = 2; - - vm.$nextTick(() => { - const button = vm.$el.querySelector('.js-web-ide'); - - expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual( - '/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce', - ); - - done(); - }); - }); - - it('renders download dropdown with links', () => { - expectDownloadDropdownItems(); - }); - }); - - describe('with a closed merge request', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: false, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - statusPath: 'abc', - }, - }); - }); - - it('does not render checkout branch button with modal trigger', () => { - const button = vm.$el.querySelector('.js-check-out-branch'); - - expect(button).toEqual(null); - }); - - it('renders download dropdown with links', () => { - expectDownloadDropdownItems(); - }); - }); - - describe('without diverged commits', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 0, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: true, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - statusPath: 'abc', - }, - }); - }); - - it('does not render diverged commits info', () => { - expect(vm.$el.querySelector('.diverged-commits-count')).toEqual(null); - }); - }); - - describe('with diverged commits', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - divergedCommitsCount: 12, - sourceBranch: 'mr-widget-refactor', - sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', - sourceBranchRemoved: false, - targetBranchPath: 'foo/bar/commits-path', - targetBranchTreePath: 'foo/bar/tree/path', - targetBranch: 'master', - isOpen: true, - emailPatchesPath: '/mr/email-patches', - plainDiffPath: '/mr/plainDiffPath', - statusPath: 'abc', - }, - }); - }); - - it('renders diverged commits info', () => { - expect(vm.$el.querySelector('.diverged-commits-count').textContent).toEqual( - 'The source branch is 12 commits behind the target branch', - ); - - expect(vm.$el.querySelector('.diverged-commits-count a').textContent).toEqual( - '12 commits behind', - ); - - expect(vm.$el.querySelector('.diverged-commits-count a')).toHaveAttr( - 'href', - vm.mr.targetBranchPath, - ); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js deleted file mode 100644 index d15c3552b4a..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ /dev/null @@ -1,223 +0,0 @@ -import Vue from 'vue'; -import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; -import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; - -const url = '/root/acets-review-apps/environments/15/deployments/1/metrics'; -const monitoringUrl = '/root/acets-review-apps/environments/15/metrics'; - -const metricsMockData = { - success: true, - metrics: { - memory_before: [ - { - metric: {}, - value: [1495785220.607, '9572875.906976745'], - }, - ], - memory_after: [ - { - metric: {}, - value: [1495787020.607, '4485853.130206379'], - }, - ], - memory_values: [ - { - metric: {}, - values: [[1493716685, '4.30859375']], - }, - ], - }, - last_update: '2017-05-02T12:34:49.628Z', - deployment_time: 1493718485, -}; - -const createComponent = () => { - const Component = Vue.extend(MemoryUsage); - - return new Component({ - el: document.createElement('div'), - propsData: { - metricsUrl: url, - metricsMonitoringUrl: monitoringUrl, - memoryMetrics: [], - deploymentTime: 0, - hasMetrics: false, - loadFailed: false, - loadingMetrics: true, - backOffRequestCounter: 0, - }, - }); -}; - -const messages = { - loadingMetrics: 'Loading deployment statistics', - hasMetrics: 'Memory usage is unchanged at 0MB', - loadFailed: 'Failed to load deployment statistics', - metricsUnavailable: 'Deployment statistics are not available currently', -}; - -describe('MemoryUsage', () => { - let vm; - let el; - - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); - - describe('data', () => { - it('should have default data', () => { - const data = MemoryUsage.data(); - - expect(Array.isArray(data.memoryMetrics)).toBeTruthy(); - expect(data.memoryMetrics.length).toBe(0); - - expect(typeof data.deploymentTime).toBe('number'); - expect(data.deploymentTime).toBe(0); - - expect(typeof data.hasMetrics).toBe('boolean'); - expect(data.hasMetrics).toBeFalsy(); - - expect(typeof data.loadFailed).toBe('boolean'); - expect(data.loadFailed).toBeFalsy(); - - expect(typeof data.loadingMetrics).toBe('boolean'); - expect(data.loadingMetrics).toBeTruthy(); - - expect(typeof data.backOffRequestCounter).toBe('number'); - expect(data.backOffRequestCounter).toBe(0); - }); - }); - - describe('computed', () => { - describe('memoryChangeMessage', () => { - it('should contain "increased" if memoryFrom value is less than memoryTo value', () => { - vm.memoryFrom = 4.28; - vm.memoryTo = 9.13; - - expect(vm.memoryChangeMessage.indexOf('increased')).not.toEqual('-1'); - }); - - it('should contain "decreased" if memoryFrom value is less than memoryTo value', () => { - vm.memoryFrom = 9.13; - vm.memoryTo = 4.28; - - expect(vm.memoryChangeMessage.indexOf('decreased')).not.toEqual('-1'); - }); - - it('should contain "unchanged" if memoryFrom value equal to memoryTo value', () => { - vm.memoryFrom = 1; - vm.memoryTo = 1; - - expect(vm.memoryChangeMessage.indexOf('unchanged')).not.toEqual('-1'); - }); - }); - }); - - describe('methods', () => { - const { metrics, deployment_time } = metricsMockData; - - describe('getMegabytes', () => { - it('should return Megabytes from provided Bytes value', () => { - const memoryInBytes = '9572875.906976745'; - - expect(vm.getMegabytes(memoryInBytes)).toEqual('9.13'); - }); - }); - - describe('computeGraphData', () => { - it('should populate sparkline graph', () => { - vm.computeGraphData(metrics, deployment_time); - const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm; - - expect(hasMetrics).toBeTruthy(); - expect(memoryMetrics.length).toBeGreaterThan(0); - expect(deploymentTime).toEqual(deployment_time); - expect(memoryFrom).toEqual('9.13'); - expect(memoryTo).toEqual('4.28'); - }); - }); - - describe('loadMetrics', () => { - const returnServicePromise = () => - new Promise(resolve => { - resolve({ - data: metricsMockData, - }); - }); - - it('should load metrics data using MRWidgetService', done => { - spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(returnServicePromise(true)); - spyOn(vm, 'computeGraphData'); - - vm.loadMetrics(); - setTimeout(() => { - expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); - expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); - done(); - }, 333); - }); - }); - }); - - describe('template', () => { - it('should render template elements correctly', () => { - expect(el.classList.contains('mr-memory-usage')).toBeTruthy(); - expect(el.querySelector('.js-usage-info')).toBeDefined(); - }); - - it('should show loading metrics message while metrics are being loaded', done => { - vm.loadingMetrics = true; - vm.hasMetrics = false; - vm.loadFailed = false; - - Vue.nextTick(() => { - expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); - - expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); - - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadingMetrics); - done(); - }); - }); - - it('should show deployment memory usage when metrics are loaded', done => { - vm.loadingMetrics = false; - vm.hasMetrics = true; - vm.loadFailed = false; - vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values; - - Vue.nextTick(() => { - expect(el.querySelector('.memory-graph-container')).toBeDefined(); - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); - done(); - }); - }); - - it('should show failure message when metrics loading failed', done => { - vm.loadingMetrics = false; - vm.hasMetrics = false; - vm.loadFailed = true; - - Vue.nextTick(() => { - expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); - - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); - done(); - }); - }); - - it('should show metrics unavailable message when metrics loading failed', done => { - vm.loadingMetrics = false; - vm.hasMetrics = false; - vm.loadFailed = false; - - Vue.nextTick(() => { - expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); - - expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js deleted file mode 100644 index b566876fe1d..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_merge_help_spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue'; - -describe('MRWidgetMergeHelp', () => { - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(mergeHelpComponent); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('with missing branch', () => { - beforeEach(() => { - vm = mountComponent(Component, { - missingBranch: 'this-is-not-the-branch-you-are-looking-for', - }); - }); - - it('renders missing branch information', () => { - expect( - vm.$el.textContent - .trim() - .replace(/[\r\n]+/g, ' ') - .replace(/\s\s+/g, ' '), - ).toEqual( - 'If the this-is-not-the-branch-you-are-looking-for branch exists in your local repository, you can merge this merge request manually using the command line', - ); - }); - - it('renders button to open help modal', () => { - expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual( - '#modal_merge_info', - ); - - expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual( - 'modal', - ); - }); - }); - - describe('without missing branch', () => { - beforeEach(() => { - vm = mountComponent(Component); - }); - - it('renders information about how to merge manually', () => { - expect( - vm.$el.textContent - .trim() - .replace(/[\r\n]+/g, ' ') - .replace(/\s\s+/g, ' '), - ).toEqual('You can merge this merge request manually using the command line'); - }); - - it('renders element to open a modal', () => { - expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-target')).toEqual( - '#modal_merge_info', - ); - - expect(vm.$el.querySelector('.js-open-modal-help').getAttribute('data-toggle')).toEqual( - 'modal', - ); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js deleted file mode 100644 index 883c41085fa..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ /dev/null @@ -1,326 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { trimText } from 'spec/helpers/text_helper'; -import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; -import mockData from '../mock_data'; - -describe('MRWidgetPipeline', () => { - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(pipelineComponent); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('hasPipeline', () => { - it('should return true when there is a pipeline', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - ciStatus: 'success', - hasCi: true, - troubleshootingDocsPath: 'help', - }); - - expect(vm.hasPipeline).toEqual(true); - }); - - it('should return false when there is no pipeline', () => { - vm = mountComponent(Component, { - pipeline: {}, - troubleshootingDocsPath: 'help', - }); - - expect(vm.hasPipeline).toEqual(false); - }); - }); - - describe('hasCIError', () => { - it('should return false when there is no CI error', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - }); - - expect(vm.hasCIError).toEqual(false); - }); - - it('should return true when there is a CI error', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - hasCi: true, - ciStatus: null, - troubleshootingDocsPath: 'help', - }); - - expect(vm.hasCIError).toEqual(true); - }); - }); - - describe('coverageDeltaClass', () => { - it('should return no class if there is no coverage change', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - pipelineCoverageDelta: '0', - troubleshootingDocsPath: 'help', - }); - - expect(vm.coverageDeltaClass).toEqual(''); - }); - - it('should return text-success if the coverage increased', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - pipelineCoverageDelta: '10', - troubleshootingDocsPath: 'help', - }); - - expect(vm.coverageDeltaClass).toEqual('text-success'); - }); - - it('should return text-danger if the coverage decreased', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - pipelineCoverageDelta: '-12', - troubleshootingDocsPath: 'help', - }); - - expect(vm.coverageDeltaClass).toEqual('text-danger'); - }); - }); - }); - - describe('rendered output', () => { - it('should render CI error', () => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - hasCi: true, - troubleshootingDocsPath: 'help', - }); - - expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.', - ); - }); - - it('should render CI error when no pipeline is provided', () => { - vm = mountComponent(Component, { - pipeline: {}, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - }); - - expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.', - ); - }); - - it('should render CI error when no CI is provided and pipeline must succeed is turned on', () => { - vm = mountComponent(Component, { - pipeline: {}, - hasCi: false, - pipelineMustSucceed: true, - troubleshootingDocsPath: 'help', - }); - - expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - 'No pipeline has been run for this commit.', - ); - }); - - describe('with a pipeline', () => { - beforeEach(() => { - vm = mountComponent(Component, { - pipeline: mockData.pipeline, - hasCi: true, - ciStatus: 'success', - pipelineCoverageDelta: mockData.pipelineCoverageDelta, - troubleshootingDocsPath: 'help', - }); - }); - - it('should render pipeline ID', () => { - expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id}`, - ); - }); - - it('should render pipeline status and commit id', () => { - expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - mockData.pipeline.details.status.label, - ); - - expect(vm.$el.querySelector('.js-commit-link').textContent.trim()).toEqual( - mockData.pipeline.commit.short_id, - ); - - expect(vm.$el.querySelector('.js-commit-link').getAttribute('href')).toEqual( - mockData.pipeline.commit.commit_path, - ); - }); - - it('should render pipeline graph', () => { - expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined(); - expect(vm.$el.querySelectorAll('.stage-container').length).toEqual( - mockData.pipeline.details.stages.length, - ); - }); - - it('should render coverage information', () => { - expect(vm.$el.querySelector('.media-body').textContent).toContain( - `Coverage ${mockData.pipeline.coverage}`, - ); - }); - - it('should render pipeline coverage delta information', () => { - expect(vm.$el.querySelector('.js-pipeline-coverage-delta.text-danger')).toBeDefined(); - expect(vm.$el.querySelector('.js-pipeline-coverage-delta').textContent).toContain( - `(${mockData.pipelineCoverageDelta}%)`, - ); - }); - }); - - describe('without commit path', () => { - beforeEach(() => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.commit; - - vm = mountComponent(Component, { - pipeline: mockCopy.pipeline, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - }); - }); - - it('should render pipeline ID', () => { - expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id}`, - ); - }); - - it('should render pipeline status', () => { - expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - mockData.pipeline.details.status.label, - ); - - expect(vm.$el.querySelector('.js-commit-link')).toBeNull(); - }); - - it('should render pipeline graph', () => { - expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined(); - expect(vm.$el.querySelectorAll('.stage-container').length).toEqual( - mockData.pipeline.details.stages.length, - ); - }); - - it('should render coverage information', () => { - expect(vm.$el.querySelector('.media-body').textContent).toContain( - `Coverage ${mockData.pipeline.coverage}`, - ); - }); - }); - - describe('without coverage', () => { - it('should not render a coverage', () => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.coverage; - - vm = mountComponent(Component, { - pipeline: mockCopy.pipeline, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - }); - - expect(vm.$el.querySelector('.media-body').textContent).not.toContain('Coverage'); - }); - }); - - describe('without a pipeline graph', () => { - it('should not render a pipeline graph', () => { - const mockCopy = JSON.parse(JSON.stringify(mockData)); - delete mockCopy.pipeline.details.stages; - - vm = mountComponent(Component, { - pipeline: mockCopy.pipeline, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - }); - - expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null); - }); - }); - - describe('for each type of pipeline', () => { - let pipeline; - - beforeEach(() => { - ({ pipeline } = JSON.parse(JSON.stringify(mockData))); - - pipeline.details.name = 'Pipeline'; - pipeline.merge_request_event_type = undefined; - pipeline.ref.tag = false; - pipeline.ref.branch = false; - }); - - const factory = () => { - vm = mountComponent(Component, { - pipeline, - hasCi: true, - ciStatus: 'success', - troubleshootingDocsPath: 'help', - sourceBranchLink: mockData.source_branch_link, - }); - }; - - describe('for a branch pipeline', () => { - it('renders a pipeline widget that reads "Pipeline <ID> <status> for <SHA> on <branch>"', () => { - pipeline.ref.branch = true; - - factory(); - - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockData.source_branch_link}`; - const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); - - expect(actual).toBe(expected); - }); - }); - - describe('for a tag pipeline', () => { - it('renders a pipeline widget that reads "Pipeline <ID> <status> for <SHA> on <branch>"', () => { - pipeline.ref.tag = true; - - factory(); - - const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; - const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); - - expect(actual).toBe(expected); - }); - }); - - describe('for a detached merge request pipeline', () => { - it('renders a pipeline widget that reads "Detached merge request pipeline <ID> <status> for <SHA>"', () => { - pipeline.details.name = 'Detached merge request pipeline'; - pipeline.merge_request_event_type = 'detached'; - - factory(); - - const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`; - const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); - - expect(actual).toBe(expected); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js deleted file mode 100644 index 5b293862b16..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js +++ /dev/null @@ -1,141 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue'; - -describe('Merge request widget rebase component', () => { - let Component; - let vm; - beforeEach(() => { - Component = Vue.extend(component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('While rebasing', () => { - it('should show progress message', () => { - vm = mountComponent(Component, { - mr: { rebaseInProgress: true }, - service: {}, - }); - - expect( - vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(), - ).toContain('Rebase in progress'); - }); - }); - - describe('With permissions', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: true, - }, - service: {}, - }); - }); - - it('it should render rebase button and warning message', () => { - const text = vm.$el - .querySelector('.rebase-state-find-class-convention span') - .textContent.trim(); - - expect(text).toContain('Fast-forward merge is not possible.'); - expect(text.replace(/\s\s+/g, ' ')).toContain( - 'Rebase the source branch onto the target branch or merge target', - ); - - expect(text).toContain('branch into source branch to allow this merge request to be merged.'); - }); - - it('it should render error message when it fails', done => { - vm.rebasingError = 'Something went wrong!'; - - Vue.nextTick(() => { - expect( - vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(), - ).toContain('Something went wrong!'); - done(); - }); - }); - }); - - describe('Without permissions', () => { - it('should render a message explaining user does not have permissions', () => { - vm = mountComponent(Component, { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch: 'foo', - }, - service: {}, - }); - - const text = vm.$el - .querySelector('.rebase-state-find-class-convention span') - .textContent.trim(); - - expect(text).toContain('Fast-forward merge is not possible.'); - expect(text).toContain('Rebase the source branch onto'); - expect(text).toContain('foo'); - expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.'); - }); - - it('should render the correct target branch name', () => { - const targetBranch = 'fake-branch-to-test-with'; - vm = mountComponent(Component, { - mr: { - rebaseInProgress: false, - canPushToSourceBranch: false, - targetBranch, - }, - service: {}, - }); - - const elem = vm.$el.querySelector('.rebase-state-find-class-convention span'); - - expect(elem.innerHTML).toContain( - `Fast-forward merge is not possible. Rebase the source branch onto <span class="label-branch">${targetBranch}</span> to allow this merge request to be merged.`, - ); - }); - }); - - describe('methods', () => { - it('checkRebaseStatus', done => { - spyOn(eventHub, '$emit'); - vm = mountComponent(Component, { - mr: {}, - service: { - rebase() { - return Promise.resolve(); - }, - poll() { - return Promise.resolve({ - data: { - rebase_in_progress: false, - merge_error: null, - }, - }); - }, - }, - }); - - vm.rebase(); - - // Wait for the rebase request - vm.$nextTick() - // Wait for the polling request - .then(vm.$nextTick()) - // Wait for the eventHub to be called - .then(vm.$nextTick()) - .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js deleted file mode 100644 index a152bd01916..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_related_links_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue'; - -describe('MRWidgetRelatedLinks', () => { - let vm; - - const createComponent = data => { - const Component = Vue.extend(relatedLinksComponent); - - return mountComponent(Component, data); - }; - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('closesText', () => { - it('returns Closes text for open merge request', () => { - vm = createComponent({ state: 'open', relatedLinks: {} }); - - expect(vm.closesText).toEqual('Closes'); - }); - - it('returns correct text for closed merge request', () => { - vm = createComponent({ state: 'closed', relatedLinks: {} }); - - expect(vm.closesText).toEqual('Did not close'); - }); - - it('returns correct tense for merged request', () => { - vm = createComponent({ state: 'merged', relatedLinks: {} }); - - expect(vm.closesText).toEqual('Closed'); - }); - }); - }); - - it('should have only have closing issues text', () => { - vm = createComponent({ - relatedLinks: { - closing: '<a href="#">#23</a> and <a>#42</a>', - }, - }); - const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - - expect(content).toContain('Closes #23 and #42'); - expect(content).not.toContain('Mentions'); - }); - - it('should have only have mentioned issues text', () => { - vm = createComponent({ - relatedLinks: { - mentioned: '<a href="#">#7</a>', - }, - }); - - expect(vm.$el.innerText).toContain('Mentions #7'); - expect(vm.$el.innerText).not.toContain('Closes'); - }); - - it('should have closing and mentioned issues at the same time', () => { - vm = createComponent({ - relatedLinks: { - closing: '<a href="#">#7</a>', - mentioned: '<a href="#">#23</a> and <a>#42</a>', - }, - }); - const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim(); - - expect(content).toContain('Closes #7'); - expect(content).toContain('Mentions #23 and #42'); - }); - - it('should have assing issues link', () => { - vm = createComponent({ - relatedLinks: { - assignToMe: '<a href="#">Assign yourself to these issues</a>', - }, - }); - - expect(vm.$el.innerText).toContain('Assign yourself to these issues'); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js deleted file mode 100644 index 20bda024d89..00000000000 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; - -describe('MR widget status icon component', () => { - let vm; - let Component; - - beforeEach(() => { - Component = Vue.extend(mrStatusIcon); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('while loading', () => { - it('renders loading icon', () => { - vm = mountComponent(Component, { status: 'loading' }); - - expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner'); - }); - }); - - describe('with status icon', () => { - it('renders ci status icon', () => { - vm = mountComponent(Component, { status: 'failed' }); - - expect(vm.$el.querySelector('.js-ci-status-icon-failed')).not.toBeNull(); - }); - }); - - describe('with disabled button', () => { - it('renders a disabled button', () => { - vm = mountComponent(Component, { status: 'failed', showDisabledButton: true }); - - expect(vm.$el.querySelector('.js-disabled-merge-button').textContent.trim()).toEqual('Merge'); - }); - }); - - describe('without disabled button', () => { - it('does not render a disabled button', () => { - vm = mountComponent(Component, { status: 'failed' }); - - expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js deleted file mode 100644 index 242193c7b3d..00000000000 --- a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import Vue from 'vue'; -import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper'; -import component from '~/vue_merge_request_widget/components/review_app_link.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('review app link', () => { - const Component = Vue.extend(component); - const props = { - link: '/review', - cssClass: 'js-link', - display: { - text: 'View app', - tooltip: '', - }, - }; - let vm; - let el; - - beforeEach(() => { - vm = mountComponent(Component, props); - el = vm.$el; - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders provided link as href attribute', () => { - expect(el.getAttribute('href')).toEqual(props.link); - }); - - it('renders provided cssClass as class attribute', () => { - expect(el.getAttribute('class')).toEqual(props.cssClass); - }); - - it('renders View app text', () => { - expect(el.textContent.trim()).toEqual('View app'); - }); - - it('renders svg icon', () => { - expect(el.querySelector('svg')).not.toBeNull(); - }); - - it('tracks an event when clicked', () => { - const spy = mockTracking('_category_', el, spyOn); - triggerEvent(el); - - expect(spy).toHaveBeenCalledWith('_category_', 'open_review_app', { - label: 'review_app', - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js deleted file mode 100644 index 29a257b0e24..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue'; - -describe('MRWidgetArchived', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(archivedComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders a ci status failed icon', () => { - expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull(); - }); - - it('renders a disabled button', () => { - expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge'); - }); - - it('renders information', () => { - expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual( - 'This project is archived, write access has been disabled', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js deleted file mode 100644 index 73b65178ecf..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ /dev/null @@ -1,230 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { trimText } from 'spec/helpers/text_helper'; -import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue'; -import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; - -describe('MRWidgetAutoMergeEnabled', () => { - let vm; - const targetBranchPath = '/foo/bar'; - const targetBranch = 'foo'; - const sha = '1EA2EZ34'; - - beforeEach(() => { - const Component = Vue.extend(autoMergeEnabledComponent); - spyOn(eventHub, '$emit'); - - vm = mountComponent(Component, { - mr: { - shouldRemoveSourceBranch: false, - canRemoveSourceBranch: true, - canCancelAutomaticMerge: true, - mergeUserId: 1, - currentUserId: 1, - setToAutoMergeBy: {}, - sha, - targetBranchPath, - targetBranch, - autoMergeStrategy: MWPS_MERGE_STRATEGY, - }, - service: new MRWidgetService({}), - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('canRemoveSourceBranch', () => { - it('should return true when user is able to remove source branch', () => { - expect(vm.canRemoveSourceBranch).toBeTruthy(); - }); - - it('should return false when user id is not the same with who set the MWPS', () => { - vm.mr.mergeUserId = 2; - - expect(vm.canRemoveSourceBranch).toBeFalsy(); - - vm.mr.currentUserId = 2; - - expect(vm.canRemoveSourceBranch).toBeTruthy(); - - vm.mr.currentUserId = 3; - - expect(vm.canRemoveSourceBranch).toBeFalsy(); - }); - - it('should return false when shouldRemoveSourceBranch set to false', () => { - vm.mr.shouldRemoveSourceBranch = true; - - expect(vm.canRemoveSourceBranch).toBeFalsy(); - }); - - it('should return false if user is not able to remove the source branch', () => { - vm.mr.canRemoveSourceBranch = false; - - expect(vm.canRemoveSourceBranch).toBeFalsy(); - }); - }); - - describe('statusTextBeforeAuthor', () => { - it('should return "Set by" if the MWPS is selected', () => { - Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY); - - expect(vm.statusTextBeforeAuthor).toBe('Set by'); - }); - }); - - describe('statusTextAfterAuthor', () => { - it('should return "to be merged automatically..." if MWPS is selected', () => { - Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY); - - expect(vm.statusTextAfterAuthor).toBe( - 'to be merged automatically when the pipeline succeeds', - ); - }); - }); - - describe('cancelButtonText', () => { - it('should return "Cancel automatic merge" if MWPS is selected', () => { - Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY); - - expect(vm.cancelButtonText).toBe('Cancel automatic merge'); - }); - }); - }); - - describe('methods', () => { - describe('cancelAutomaticMerge', () => { - it('should set flag and call service then tell main component to update the widget with data', done => { - const mrObj = { - is_new_mr_data: true, - }; - spyOn(vm.service, 'cancelAutomaticMerge').and.returnValue( - new Promise(resolve => { - resolve({ - data: mrObj, - }); - }), - ); - - vm.cancelAutomaticMerge(); - setTimeout(() => { - expect(vm.isCancellingAutoMerge).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - done(); - }, 333); - }); - }); - - describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', done => { - spyOn(vm.service, 'merge').and.returnValue( - Promise.resolve({ - data: { - status: MWPS_MERGE_STRATEGY, - }, - }), - ); - - vm.removeSourceBranch(); - setTimeout(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(vm.service.merge).toHaveBeenCalledWith({ - sha, - auto_merge_strategy: MWPS_MERGE_STRATEGY, - should_remove_source_branch: true, - }); - done(); - }, 333); - }); - }); - }); - - describe('template', () => { - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.innerText).toContain('to be merged automatically when the pipeline succeeds'); - - expect(vm.$el.innerText).toContain('The changes will be merged into'); - expect(vm.$el.innerText).toContain(targetBranch); - expect(vm.$el.innerText).toContain('The source branch will not be deleted'); - expect(vm.$el.querySelector('.js-cancel-auto-merge').innerText).toContain( - 'Cancel automatic merge', - ); - - expect(vm.$el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy(); - expect(vm.$el.querySelector('.js-remove-source-branch').innerText).toContain( - 'Delete source branch', - ); - - expect(vm.$el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeFalsy(); - }); - - it('should disable cancel auto merge button when the action is in progress', done => { - vm.isCancellingAutoMerge = true; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeTruthy(); - done(); - }); - }); - - it('should show source branch will be deleted text when it source branch set to remove', done => { - vm.mr.shouldRemoveSourceBranch = true; - - Vue.nextTick(() => { - const normalizedText = vm.$el.innerText.replace(/\s+/g, ' '); - - expect(normalizedText).toContain('The source branch will be deleted'); - expect(normalizedText).not.toContain('The source branch will not be deleted'); - done(); - }); - }); - - it('should not show delete source branch button when user not able to delete source branch', done => { - vm.mr.currentUserId = 4; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-remove-source-branch')).toEqual(null); - done(); - }); - }); - - it('should disable delete source branch button when the action is in progress', done => { - vm.isRemovingSourceBranch = true; - - Vue.nextTick(() => { - expect( - vm.$el.querySelector('.js-remove-source-branch').getAttribute('disabled'), - ).toBeTruthy(); - done(); - }); - }); - - it('should render the status text as "...to merged automatically" if MWPS is selected', done => { - Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY); - - Vue.nextTick(() => { - const statusText = trimText(vm.$el.querySelector('.js-status-text-after-author').innerText); - - expect(statusText).toBe('to be merged automatically when the pipeline succeeds'); - done(); - }); - }); - - it('should render the cancel button as "Cancel automatic merge" if MWPS is selected', done => { - Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY); - - Vue.nextTick(() => { - const cancelButtonText = trimText(vm.$el.querySelector('.js-cancel-auto-merge').innerText); - - expect(cancelButtonText).toBe('Cancel automatic merge'); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js deleted file mode 100644 index efccd507fe2..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue'; - -describe('MRWidgetChecking', () => { - let Component; - let vm; - - beforeEach(() => { - Component = Vue.extend(checkingComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders disabled button', () => { - expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - }); - - it('renders loading icon', () => { - expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner'); - }); - - it('renders information about merging', () => { - expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual( - 'Checking ability to merge automatically…', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js deleted file mode 100644 index bbbaed0d2f5..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue'; - -describe('MRWidgetClosed', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(closedComponent); - vm = mountComponent(Component, { - mr: { - metrics: { - mergedBy: {}, - closedBy: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - mergedAt: 'Jan 24, 2018 1:02pm GMT+0000', - closedAt: 'Jan 24, 2018 1:02pm GMT+0000', - readableMergedAt: '', - readableClosedAt: 'less than a minute ago', - }, - targetBranchPath: '/twitter/flight/commits/so_long_jquery', - targetBranch: 'so_long_jquery', - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders warning icon', () => { - expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull(); - }); - - it('renders closed by information with author and time', () => { - expect( - vm.$el - .querySelector('.js-mr-widget-author') - .textContent.trim() - .replace(/\s\s+/g, ' '), - ).toContain('Closed by Administrator less than a minute ago'); - }); - - it('links to the user that closed the MR', () => { - expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual( - 'http://localhost:3000/root', - ); - }); - - it('renders information about the changes not being merged', () => { - expect( - vm.$el - .querySelector('.mr-info-list') - .textContent.trim() - .replace(/\s\s+/g, ' '), - ).toContain('The changes were not merged into so_long_jquery'); - }); - - it('renders link for target branch', () => { - expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual( - '/twitter/flight/commits/so_long_jquery', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js deleted file mode 100644 index 9035bc6f65d..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ /dev/null @@ -1,225 +0,0 @@ -import $ from 'jquery'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { removeBreakLine } from 'spec/helpers/text_helper'; -import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue'; - -describe('MRWidgetConflicts', () => { - let vm; - const path = '/conflicts'; - - function createComponent(propsData = {}) { - const localVue = createLocalVue(); - - vm = shallowMount(localVue.extend(ConflictsComponent), { - propsData, - }); - } - - beforeEach(() => { - spyOn($.fn, 'popover').and.callThrough(); - }); - - afterEach(() => { - vm.destroy(); - }); - - // There are two permissions we need to consider: - // - // 1. Is the user allowed to merge to the target branch? - // 2. Is the user allowed to push to the source branch? - // - // This yields 4 possible permutations that we need to test, and - // we test them below. A user who can push to the source - // branch should be allowed to resolve conflicts. This is - // consistent with what the backend does. - describe('when allowed to merge but not allowed to push to source branch', () => { - beforeEach(() => { - createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: false, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(vm.text()).toContain('There are merge conflicts'); - expect(vm.text()).not.toContain('ask someone with write access'); - }); - - it('should not allow you to resolve the conflicts', () => { - expect(vm.text()).not.toContain('Resolve conflicts'); - }); - - it('should have merge buttons', () => { - const mergeLocallyButton = vm.find('.js-merge-locally-button'); - - expect(mergeLocallyButton.text()).toContain('Merge locally'); - }); - }); - - describe('when not allowed to merge but allowed to push to source branch', () => { - beforeEach(() => { - createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts', () => { - expect(vm.text()).toContain('There are merge conflicts'); - expect(vm.text()).toContain('ask someone with write access'); - }); - - it('should allow you to resolve the conflicts', () => { - const resolveButton = vm.find('.js-resolve-conflicts-button'); - - expect(resolveButton.text()).toContain('Resolve conflicts'); - expect(resolveButton.attributes('href')).toEqual(path); - }); - - it('should not have merge buttons', () => { - expect(vm.text()).not.toContain('Merge locally'); - }); - }); - - describe('when allowed to merge and push to source branch', () => { - beforeEach(() => { - createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: path, - conflictsDocsPath: '', - }, - }); - }); - - it('should tell you about conflicts without bothering other people', () => { - expect(vm.text()).toContain('There are merge conflicts'); - expect(vm.text()).not.toContain('ask someone with write access'); - }); - - it('should allow you to resolve the conflicts', () => { - const resolveButton = vm.find('.js-resolve-conflicts-button'); - - expect(resolveButton.text()).toContain('Resolve conflicts'); - expect(resolveButton.attributes('href')).toEqual(path); - }); - - it('should have merge buttons', () => { - const mergeLocallyButton = vm.find('.js-merge-locally-button'); - - expect(mergeLocallyButton.text()).toContain('Merge locally'); - }); - }); - - describe('when user does not have permission to push to source branch', () => { - it('should show proper message', () => { - createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); - - expect( - vm - .text() - .trim() - .replace(/\s\s+/g, ' '), - ).toContain('ask someone with write access'); - }); - - it('should not have action buttons', () => { - createComponent({ - mr: { - canMerge: false, - canPushToSourceBranch: false, - conflictsDocsPath: '', - }, - }); - - expect(vm.contains('.js-resolve-conflicts-button')).toBe(false); - expect(vm.contains('.js-merge-locally-button')).toBe(false); - }); - - it('should not have resolve button when no conflict resolution path', () => { - createComponent({ - mr: { - canMerge: true, - conflictResolutionPath: null, - conflictsDocsPath: '', - }, - }); - - expect(vm.contains('.js-resolve-conflicts-button')).toBe(false); - }); - }); - - describe('when fast-forward or semi-linear merge enabled', () => { - it('should tell you to rebase locally', () => { - createComponent({ - mr: { - shouldBeRebased: true, - conflictsDocsPath: '', - }, - }); - - expect(removeBreakLine(vm.text()).trim()).toContain( - 'Fast-forward merge is not possible. To merge this request, first rebase locally.', - ); - }); - }); - - describe('when source branch protected', () => { - beforeEach(() => { - createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: gl.TEST_HOST, - sourceBranchProtected: true, - conflictsDocsPath: '', - }, - }); - }); - - it('sets resolve button as disabled', () => { - expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('disabled'); - }); - - it('renders popover', () => { - expect($.fn.popover).toHaveBeenCalled(); - }); - }); - - describe('when source branch not protected', () => { - beforeEach(() => { - createComponent({ - mr: { - canMerge: true, - canPushToSourceBranch: true, - conflictResolutionPath: gl.TEST_HOST, - sourceBranchProtected: false, - conflictsDocsPath: '', - }, - }); - }); - - it('sets resolve button as disabled', () => { - expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined); - }); - - it('renders popover', () => { - expect($.fn.popover).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js deleted file mode 100644 index ef76e617c07..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ /dev/null @@ -1,156 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import failedToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -describe('MRWidgetFailedToMerge', () => { - const dummyIntervalId = 1337; - let Component; - let mr; - let vm; - - beforeEach(() => { - Component = Vue.extend(failedToMergeComponent); - spyOn(eventHub, '$emit'); - spyOn(window, 'setInterval').and.returnValue(dummyIntervalId); - spyOn(window, 'clearInterval').and.stub(); - mr = { - mergeError: 'Merge error happened', - }; - vm = mountComponent(Component, { - mr, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('sets interval to refresh', () => { - expect(window.setInterval).toHaveBeenCalledWith(vm.updateTimer, 1000); - expect(vm.intervalId).toBe(dummyIntervalId); - }); - - it('clears interval when destroying ', () => { - vm.$destroy(); - - expect(window.clearInterval).toHaveBeenCalledWith(dummyIntervalId); - }); - - describe('computed', () => { - describe('timerText', () => { - it('should return correct timer text', () => { - expect(vm.timerText).toEqual('Refreshing in 10 seconds to show the updated status...'); - - vm.timer = 1; - - expect(vm.timerText).toEqual('Refreshing in a second to show the updated status...'); - }); - }); - - describe('mergeError', () => { - it('removes forced line breaks', done => { - mr.mergeError = 'contains<br />line breaks<br />'; - - Vue.nextTick() - .then(() => { - expect(vm.mergeError).toBe('contains line breaks'); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('created', () => { - it('should disable polling', () => { - expect(eventHub.$emit).toHaveBeenCalledWith('DisablePolling'); - }); - }); - - describe('methods', () => { - describe('refresh', () => { - it('should emit event to request component refresh', () => { - expect(vm.isRefreshing).toEqual(false); - - vm.refresh(); - - expect(vm.isRefreshing).toEqual(true); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('EnablePolling'); - }); - }); - - describe('updateTimer', () => { - it('should update timer and emit event when timer end', () => { - spyOn(vm, 'refresh'); - - expect(vm.timer).toEqual(10); - - for (let i = 0; i < 10; i += 1) { - expect(vm.timer).toEqual(10 - i); - vm.updateTimer(); - } - - expect(vm.refresh).toHaveBeenCalled(); - }); - }); - }); - - describe('while it is refreshing', () => { - it('renders Refresing now', done => { - vm.isRefreshing = true; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-refresh-label').textContent.trim()).toEqual( - 'Refreshing now', - ); - done(); - }); - }); - }); - - describe('while it is not regresing', () => { - it('renders warning icon and disabled merge button', () => { - expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull(); - expect(vm.$el.querySelector('.js-disabled-merge-button').getAttribute('disabled')).toEqual( - 'disabled', - ); - }); - - it('renders given error', () => { - expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual( - 'Merge error happened', - ); - }); - - it('renders refresh button', () => { - expect(vm.$el.querySelector('.js-refresh-button').textContent.trim()).toEqual('Refresh now'); - }); - - it('renders remaining time', () => { - expect(vm.$el.querySelector('.has-custom-error').textContent.trim()).toEqual( - 'Refreshing in 10 seconds to show the updated status...', - ); - }); - }); - - it('should just generic merge failed message if merge_error is not available', done => { - vm.mr.mergeError = null; - - Vue.nextTick(() => { - expect(vm.$el.innerText).toContain('Merge failed.'); - expect(vm.$el.innerText).not.toContain('Merge error happened.'); - done(); - }); - }); - - it('should show refresh label when refresh requested', done => { - vm.refresh(); - Vue.nextTick(() => { - expect(vm.$el.innerText).not.toContain('Merge failed. Refreshing'); - expect(vm.$el.innerText).toContain('Refreshing now'); - done(); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js deleted file mode 100644 index 423c800bfbc..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ /dev/null @@ -1,219 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -describe('MRWidgetMerged', () => { - let vm; - const targetBranch = 'foo'; - const selectors = { - get copyMergeShaButton() { - return vm.$el.querySelector('button.js-mr-merged-copy-sha'); - }, - get mergeCommitShaLink() { - return vm.$el.querySelector('a.js-mr-merged-commit-sha'); - }, - }; - - beforeEach(() => { - const Component = Vue.extend(mergedComponent); - const mr = { - isRemovingSourceBranch: false, - cherryPickInForkPath: false, - canCherryPickInCurrentMR: true, - revertInForkPath: false, - canRevertInCurrentMR: true, - canRemoveSourceBranch: true, - sourceBranchRemoved: true, - metrics: { - mergedBy: { - name: 'Administrator', - username: 'root', - webUrl: 'http://localhost:3000/root', - avatarUrl: - 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - mergedAt: 'Jan 24, 2018 1:02pm GMT+0000', - readableMergedAt: '', - closedBy: {}, - closedAt: 'Jan 24, 2018 1:02pm GMT+0000', - readableClosedAt: '', - }, - updatedAt: 'mergedUpdatedAt', - shortMergeCommitSha: '958c0475', - mergeCommitSha: '958c047516e182dfc52317f721f696e8a1ee85ed', - mergeCommitPath: - 'http://localhost:3000/root/nautilus/commit/f7ce827c314c9340b075657fd61c789fb01cf74d', - sourceBranch: 'bar', - targetBranch, - }; - - const service = { - removeSourceBranch() {}, - }; - - spyOn(eventHub, '$emit'); - - vm = mountComponent(Component, { mr, service }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('shouldShowRemoveSourceBranch', () => { - it('returns true when sourceBranchRemoved is false', () => { - vm.mr.sourceBranchRemoved = false; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(true); - }); - - it('returns false when sourceBranchRemoved is true', () => { - vm.mr.sourceBranchRemoved = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns false when canRemoveSourceBranch is false', () => { - vm.mr.sourceBranchRemoved = false; - vm.mr.canRemoveSourceBranch = false; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns false when is making request', () => { - vm.mr.canRemoveSourceBranch = true; - vm.isMakingRequest = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - - it('returns true when all are true', () => { - vm.mr.isRemovingSourceBranch = true; - vm.mr.canRemoveSourceBranch = true; - vm.isMakingRequest = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - }); - }); - - describe('shouldShowSourceBranchRemoving', () => { - it('should correct value when fields changed', () => { - vm.mr.sourceBranchRemoved = false; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(false); - - vm.mr.sourceBranchRemoved = true; - - expect(vm.shouldShowRemoveSourceBranch).toEqual(false); - - vm.mr.sourceBranchRemoved = false; - vm.isMakingRequest = true; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(true); - - vm.isMakingRequest = false; - vm.mr.isRemovingSourceBranch = true; - - expect(vm.shouldShowSourceBranchRemoving).toEqual(true); - }); - }); - }); - - describe('methods', () => { - describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', done => { - spyOn(vm.service, 'removeSourceBranch').and.returnValue( - new Promise(resolve => { - resolve({ - data: { - message: 'Branch was deleted', - }, - }); - }), - ); - - vm.removeSourceBranch(); - setTimeout(() => { - const args = eventHub.$emit.calls.argsFor(0); - - expect(vm.isMakingRequest).toEqual(true); - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).not.toThrow(); - done(); - }, 333); - }); - }); - }); - - it('has merged by information', () => { - expect(vm.$el.textContent).toContain('Merged by'); - expect(vm.$el.textContent).toContain('Administrator'); - }); - - it('renders branch information', () => { - expect(vm.$el.textContent).toContain('The changes were merged into'); - expect(vm.$el.textContent).toContain(targetBranch); - }); - - it('renders information about branch being deleted', () => { - expect(vm.$el.textContent).toContain('The source branch has been deleted'); - }); - - it('shows revert and cherry-pick buttons', () => { - expect(vm.$el.textContent).toContain('Revert'); - expect(vm.$el.textContent).toContain('Cherry-pick'); - }); - - it('shows button to copy commit SHA to clipboard', () => { - expect(selectors.copyMergeShaButton).toExist(); - expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe( - vm.mr.mergeCommitSha, - ); - }); - - it('hides button to copy commit SHA if SHA does not exist', done => { - vm.mr.mergeCommitSha = null; - - Vue.nextTick(() => { - expect(selectors.copyMergeShaButton).not.toExist(); - expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with'); - done(); - }); - }); - - it('shows merge commit SHA link', () => { - expect(selectors.mergeCommitShaLink).toExist(); - expect(selectors.mergeCommitShaLink.text).toContain(vm.mr.shortMergeCommitSha); - expect(selectors.mergeCommitShaLink.href).toBe(vm.mr.mergeCommitPath); - }); - - it('should not show source branch deleted text', done => { - vm.mr.sourceBranchRemoved = false; - - Vue.nextTick(() => { - expect(vm.$el.innerText).toContain('You can delete the source branch now'); - expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); - done(); - }); - }); - - it('should show source branch deleting text', done => { - vm.mr.isRemovingSourceBranch = true; - vm.mr.sourceBranchRemoved = false; - - Vue.nextTick(() => { - expect(vm.$el.innerText).toContain('The source branch is being deleted'); - expect(vm.$el.innerText).not.toContain('You can delete the source branch now'); - expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); - done(); - }); - }); - - it('should use mergedEvent mergedAt as tooltip title', () => { - expect(vm.$el.querySelector('time').getAttribute('data-original-title')).toBe( - 'Jan 24, 2018 1:02pm GMT+0000', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js deleted file mode 100644 index 06d236064dd..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merging_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue'; - -describe('MRWidgetMerging', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(mergingComponent); - - vm = mountComponent(Component, { - mr: { - targetBranchPath: '/branch-path', - targetBranch: 'branch', - }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders information about merge request being merged', () => { - expect( - vm.$el - .querySelector('.media-body') - .textContent.trim() - .replace(/\s\s+/g, ' ') - .replace(/[\r\n]+/g, ' '), - ).toContain('This merge request is in the process of being merged'); - }); - - it('renders branch information', () => { - expect( - vm.$el - .querySelector('.mr-info-list') - .textContent.trim() - .replace(/\s\s+/g, ' ') - .replace(/[\r\n]+/g, ' '), - ).toEqual('The changes will be merged into branch'); - - expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('/branch-path'); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js deleted file mode 100644 index 47b989e2022..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue'; - -describe('MRWidgetMissingBranch', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(missingBranchComponent); - vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('missingBranchName', () => { - it('should return proper branch name', () => { - expect(vm.missingBranchName).toEqual('source'); - - vm.mr.sourceBranchRemoved = false; - - expect(vm.missingBranchName).toEqual('target'); - }); - }); - }); - - describe('template', () => { - it('should have correct elements', () => { - const el = vm.$el; - const content = el.textContent.replace(/\n(\s)+/g, ' ').trim(); - - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(content.replace(/\s\s+/g, ' ')).toContain('source branch does not exist.'); - expect(content).toContain('Please restore it or use a different source branch'); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js deleted file mode 100644 index b1cb91663c9..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_not_allowed_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue'; - -describe('MRWidgetNotAllowed', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(notAllowedComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders success icon', () => { - expect(vm.$el.querySelector('.ci-status-icon-success')).not.toBe(null); - }); - - it('renders informative text', () => { - expect(vm.$el.innerText).toContain('Ready to be merged automatically.'); - expect(vm.$el.innerText).toContain( - 'Ask someone with write access to this repository to merge this request', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js deleted file mode 100644 index bd0bd36ebc2..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import Vue from 'vue'; -import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue'; - -describe('NothingToMerge', () => { - describe('template', () => { - const Component = Vue.extend(NothingToMerge); - const newBlobPath = '/foo'; - const vm = new Component({ - el: document.createElement('div'), - propsData: { - mr: { newBlobPath }, - }, - }); - - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('a').href).toContain(newBlobPath); - expect(vm.$el.innerText).toContain( - "Currently there are no changes in this merge request's source branch", - ); - - expect(vm.$el.innerText.replace(/\s\s+/g, ' ')).toContain( - 'Please push new commits or use a different branch.', - ); - }); - - it('should not show new blob link if there is no link available', () => { - vm.mr.newBlobPath = null; - Vue.nextTick(() => { - expect(vm.$el.querySelector('a')).toEqual(null); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js deleted file mode 100644 index 0bca86b12b2..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_blocked_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { removeBreakLine } from 'spec/helpers/text_helper'; -import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue'; - -describe('MRWidgetPipelineBlocked', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(pipelineBlockedComponent); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders warning icon', () => { - expect(vm.$el.querySelector('.ci-status-icon-warning')).not.toBe(null); - }); - - it('renders information text', () => { - expect(removeBreakLine(vm.$el.textContent).trim()).toContain( - 'Pipeline blocked. The pipeline for this merge request requires a manual action to proceed', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js deleted file mode 100644 index 85f65d024a8..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_pipeline_failed_spec.js +++ /dev/null @@ -1,19 +0,0 @@ -import Vue from 'vue'; -import { removeBreakLine } from 'spec/helpers/text_helper'; -import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue'; - -describe('PipelineFailed', () => { - describe('template', () => { - const Component = Vue.extend(PipelineFailed); - const vm = new Component({ - el: document.createElement('div'), - }); - it('should have correct elements', () => { - expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(removeBreakLine(vm.$el.innerText).trim()).toContain( - 'The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure', - ); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js deleted file mode 100644 index 9ba429c3d20..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ /dev/null @@ -1,983 +0,0 @@ -import Vue from 'vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; -import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; -import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; -import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; -import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import { MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; - -const commitMessage = 'This is the commit message'; -const squashCommitMessage = 'This is the squash commit message'; -const commitMessageWithDescription = 'This is the commit message description'; -const createTestMr = customConfig => { - const mr = { - isPipelineActive: false, - pipeline: null, - isPipelineFailed: false, - isPipelinePassing: false, - isMergeAllowed: true, - isApproved: true, - onlyAllowMergeIfPipelineSucceeds: false, - ffOnlyEnabled: false, - hasCI: false, - ciStatus: null, - sha: '12345678', - squash: false, - commitMessage, - squashCommitMessage, - commitMessageWithDescription, - shouldRemoveSourceBranch: true, - canRemoveSourceBranch: false, - targetBranch: 'master', - preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, - availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], - }; - - Object.assign(mr, customConfig.mr); - - return mr; -}; - -const createTestService = () => ({ - merge() {}, - poll() {}, -}); - -const createComponent = (customConfig = {}) => { - const Component = Vue.extend(ReadyToMerge); - - return new Component({ - el: document.createElement('div'), - propsData: { - mr: createTestMr(customConfig), - service: createTestService(), - }, - }); -}; - -describe('ReadyToMerge', () => { - let vm; - let updateMrCountSpy; - - beforeEach(() => { - vm = createComponent(); - updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts'); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('props', () => { - it('should have props', () => { - const { mr, service } = ReadyToMerge.props; - - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - - expect(service.type instanceof Object).toBeTruthy(); - expect(service.required).toBeTruthy(); - }); - }); - - describe('data', () => { - it('should have default data', () => { - expect(vm.mergeWhenBuildSucceeds).toBeFalsy(); - expect(vm.useCommitMessageWithDescription).toBeFalsy(); - expect(vm.showCommitMessageEditor).toBeFalsy(); - expect(vm.isMakingRequest).toBeFalsy(); - expect(vm.isMergingImmediately).toBeFalsy(); - expect(vm.commitMessage).toBe(vm.mr.commitMessage); - expect(vm.successSvg).toBeDefined(); - expect(vm.warningSvg).toBeDefined(); - }); - }); - - describe('computed', () => { - describe('isAutoMergeAvailable', () => { - it('should return true when at least one merge strategy is available', () => { - vm.mr.availableAutoMergeStrategies = [MWPS_MERGE_STRATEGY]; - - expect(vm.isAutoMergeAvailable).toBe(true); - }); - - it('should return false when no merge strategies are available', () => { - vm.mr.availableAutoMergeStrategies = []; - - expect(vm.isAutoMergeAvailable).toBe(false); - }); - }); - - describe('status', () => { - it('defaults to success', () => { - Vue.set(vm.mr, 'pipeline', true); - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.status).toEqual('success'); - }); - - it('returns failed when MR has CI but also has an unknown status', () => { - Vue.set(vm.mr, 'hasCI', true); - - expect(vm.status).toEqual('failed'); - }); - - it('returns default when MR has no pipeline', () => { - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.status).toEqual('success'); - }); - - it('returns pending when pipeline is active', () => { - Vue.set(vm.mr, 'pipeline', {}); - Vue.set(vm.mr, 'isPipelineActive', true); - - expect(vm.status).toEqual('pending'); - }); - - it('returns failed when pipeline is failed', () => { - Vue.set(vm.mr, 'pipeline', {}); - Vue.set(vm.mr, 'isPipelineFailed', true); - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.status).toEqual('failed'); - }); - }); - - describe('mergeButtonVariant', () => { - it('defaults to success class', () => { - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.mergeButtonVariant).toEqual('success'); - }); - - it('returns success class for success status', () => { - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - Vue.set(vm.mr, 'pipeline', true); - - expect(vm.mergeButtonVariant).toEqual('success'); - }); - - it('returns info class for pending status', () => { - Vue.set(vm.mr, 'availableAutoMergeStrategies', [MTWPS_MERGE_STRATEGY]); - - expect(vm.mergeButtonVariant).toEqual('info'); - }); - - it('returns danger class for failed status', () => { - vm.mr.hasCI = true; - - expect(vm.mergeButtonVariant).toEqual('danger'); - }); - }); - - describe('status icon', () => { - it('defaults to tick icon', () => { - expect(vm.iconClass).toEqual('success'); - }); - - it('shows tick for success status', () => { - vm.mr.pipeline = true; - - expect(vm.iconClass).toEqual('success'); - }); - - it('shows tick for pending status', () => { - vm.mr.pipeline = {}; - vm.mr.isPipelineActive = true; - - expect(vm.iconClass).toEqual('success'); - }); - - it('shows warning icon for failed status', () => { - vm.mr.hasCI = true; - - expect(vm.iconClass).toEqual('warning'); - }); - - it('shows warning icon for merge not allowed', () => { - vm.mr.hasCI = true; - - expect(vm.iconClass).toEqual('warning'); - }); - }); - - describe('mergeButtonText', () => { - it('should return "Merge" when no auto merge strategies are available', () => { - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.mergeButtonText).toEqual('Merge'); - }); - - it('should return "Merge in progress"', () => { - Vue.set(vm, 'isMergingImmediately', true); - - expect(vm.mergeButtonText).toEqual('Merge in progress'); - }); - - it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => { - Vue.set(vm, 'isMergingImmediately', false); - Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY); - - expect(vm.mergeButtonText).toEqual('Merge when pipeline succeeds'); - }); - }); - - describe('autoMergeText', () => { - it('should return Merge when pipeline succeeds', () => { - Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY); - - expect(vm.autoMergeText).toEqual('Merge when pipeline succeeds'); - }); - }); - - describe('shouldShowMergeImmediatelyDropdown', () => { - it('should return false if no pipeline is active', () => { - Vue.set(vm.mr, 'isPipelineActive', false); - Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false); - - expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); - - it('should return false if "Pipelines must succeed" is enabled for the current project', () => { - Vue.set(vm.mr, 'isPipelineActive', true); - Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true); - - expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false); - }); - - it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => { - Vue.set(vm.mr, 'isPipelineActive', true); - Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false); - - expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true); - }); - }); - - describe('isMergeButtonDisabled', () => { - it('should return false with initial data', () => { - Vue.set(vm.mr, 'isMergeAllowed', true); - - expect(vm.isMergeButtonDisabled).toBe(false); - }); - - it('should return true when there is no commit message', () => { - Vue.set(vm.mr, 'isMergeAllowed', true); - Vue.set(vm, 'commitMessage', ''); - - expect(vm.isMergeButtonDisabled).toBe(true); - }); - - it('should return true if merge is not allowed', () => { - Vue.set(vm.mr, 'isMergeAllowed', false); - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true); - - expect(vm.isMergeButtonDisabled).toBe(true); - }); - - it('should return true when the vm instance is making request', () => { - Vue.set(vm.mr, 'isMergeAllowed', true); - Vue.set(vm, 'isMakingRequest', true); - - expect(vm.isMergeButtonDisabled).toBe(true); - }); - }); - - describe('isMergeImmediatelyDangerous', () => { - it('should always return false in CE', () => { - expect(vm.isMergeImmediatelyDangerous).toBe(false); - }); - }); - }); - - describe('methods', () => { - describe('shouldShowMergeControls', () => { - it('should return false when an external pipeline is running and required to succeed', () => { - Vue.set(vm.mr, 'isMergeAllowed', false); - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.shouldShowMergeControls).toBe(false); - }); - - it('should return true when the build succeeded or build not required to succeed', () => { - Vue.set(vm.mr, 'isMergeAllowed', true); - Vue.set(vm.mr, 'availableAutoMergeStrategies', []); - - expect(vm.shouldShowMergeControls).toBe(true); - }); - - it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => { - Vue.set(vm.mr, 'isMergeAllowed', false); - Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]); - - expect(vm.shouldShowMergeControls).toBe(true); - }); - - it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => { - Vue.set(vm.mr, 'isMergeAllowed', true); - Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]); - - expect(vm.shouldShowMergeControls).toBe(true); - }); - }); - - describe('updateMergeCommitMessage', () => { - it('should revert flag and change commitMessage', () => { - expect(vm.commitMessage).toEqual(commitMessage); - vm.updateMergeCommitMessage(true); - - expect(vm.commitMessage).toEqual(commitMessageWithDescription); - vm.updateMergeCommitMessage(false); - - expect(vm.commitMessage).toEqual(commitMessage); - }); - }); - - describe('handleMergeButtonClick', () => { - const returnPromise = status => - new Promise(resolve => { - resolve({ - data: { - status, - }, - }); - }); - - it('should handle merge when pipeline succeeds', done => { - spyOn(eventHub, '$emit'); - spyOn(vm.service, 'merge').and.returnValue(returnPromise('merge_when_pipeline_succeeds')); - vm.removeSourceBranch = false; - vm.handleMergeButtonClick(true); - - setTimeout(() => { - expect(vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - - const params = vm.service.merge.calls.argsFor(0)[0]; - - expect(params).toEqual( - jasmine.objectContaining({ - sha: vm.mr.sha, - commit_message: vm.mr.commitMessage, - should_remove_source_branch: false, - auto_merge_strategy: 'merge_when_pipeline_succeeds', - }), - ); - done(); - }, 333); - }); - - it('should handle merge failed', done => { - spyOn(eventHub, '$emit'); - spyOn(vm.service, 'merge').and.returnValue(returnPromise('failed')); - vm.handleMergeButtonClick(false, true); - - setTimeout(() => { - expect(vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); - - const params = vm.service.merge.calls.argsFor(0)[0]; - - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - done(); - }, 333); - }); - - it('should handle merge action accepted case', done => { - spyOn(vm.service, 'merge').and.returnValue(returnPromise('success')); - spyOn(vm, 'initiateMergePolling'); - vm.handleMergeButtonClick(); - - setTimeout(() => { - expect(vm.isMakingRequest).toBeTruthy(); - expect(vm.initiateMergePolling).toHaveBeenCalled(); - - const params = vm.service.merge.calls.argsFor(0)[0]; - - expect(params.should_remove_source_branch).toBeTruthy(); - expect(params.auto_merge_strategy).toBeUndefined(); - done(); - }, 333); - }); - }); - - describe('initiateMergePolling', () => { - beforeEach(() => { - jasmine.clock().install(); - }); - - afterEach(() => { - jasmine.clock().uninstall(); - }); - - it('should call simplePoll', () => { - const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll'); - vm.initiateMergePolling(); - - expect(simplePoll).toHaveBeenCalledWith(jasmine.any(Function), { timeout: 0 }); - }); - - it('should call handleMergePolling', () => { - spyOn(vm, 'handleMergePolling'); - - vm.initiateMergePolling(); - - jasmine.clock().tick(2000); - - expect(vm.handleMergePolling).toHaveBeenCalled(); - }); - }); - - describe('handleMergePolling', () => { - const returnPromise = state => - new Promise(resolve => { - resolve({ - data: { - state, - source_branch_exists: true, - }, - }); - }); - - beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html'); - }); - - it('should call start and stop polling when MR merged', done => { - spyOn(eventHub, '$emit'); - spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); - spyOn(vm, 'initiateRemoveSourceBranchPolling'); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - vm.handleMergePolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - setTimeout(() => { - expect(vm.service.poll).toHaveBeenCalled(); - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent'); - expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled(); - expect(updateMrCountSpy).toHaveBeenCalled(); - expect(cpc).toBeFalsy(); - expect(spc).toBeTruthy(); - - done(); - }, 333); - }); - - it('updates status box', done => { - spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); - spyOn(vm, 'initiateRemoveSourceBranchPolling'); - - vm.handleMergePolling(() => {}, () => {}); - - setTimeout(() => { - const statusBox = document.querySelector('.status-box'); - - expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy(); - expect(statusBox.textContent).toContain('Merged'); - - done(); - }); - }); - - it('hides close button', done => { - spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); - spyOn(vm, 'initiateRemoveSourceBranchPolling'); - - vm.handleMergePolling(() => {}, () => {}); - - setTimeout(() => { - expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy(); - - done(); - }); - }); - - it('updates merge request count badge', done => { - spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); - spyOn(vm, 'initiateRemoveSourceBranchPolling'); - - vm.handleMergePolling(() => {}, () => {}); - - setTimeout(() => { - expect(document.querySelector('.js-merge-counter').textContent).toBe('0'); - - done(); - }); - }); - - it('should continue polling until MR is merged', done => { - spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state')); - spyOn(vm, 'initiateRemoveSourceBranchPolling'); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - vm.handleMergePolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - setTimeout(() => { - expect(cpc).toBeTruthy(); - expect(spc).toBeFalsy(); - - done(); - }, 333); - }); - }); - - describe('initiateRemoveSourceBranchPolling', () => { - it('should emit event and call simplePoll', () => { - spyOn(eventHub, '$emit'); - const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll'); - - vm.initiateRemoveSourceBranchPolling(); - - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); - expect(simplePoll).toHaveBeenCalled(); - }); - }); - - describe('handleRemoveBranchPolling', () => { - const returnPromise = state => - new Promise(resolve => { - resolve({ - data: { - source_branch_exists: state, - }, - }); - }); - - it('should call start and stop polling when MR merged', done => { - spyOn(eventHub, '$emit'); - spyOn(vm.service, 'poll').and.returnValue(returnPromise(false)); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - setTimeout(() => { - expect(vm.service.poll).toHaveBeenCalled(); - - const args = eventHub.$emit.calls.argsFor(0); - - expect(args[0]).toEqual('MRWidgetUpdateRequested'); - expect(args[1]).toBeDefined(); - args[1](); - - expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); - - expect(cpc).toBeFalsy(); - expect(spc).toBeTruthy(); - - done(); - }, 333); - }); - - it('should continue polling until MR is merged', done => { - spyOn(vm.service, 'poll').and.returnValue(returnPromise(true)); - - let cpc = false; // continuePollingCalled - let spc = false; // stopPollingCalled - - vm.handleRemoveBranchPolling( - () => { - cpc = true; - }, - () => { - spc = true; - }, - ); - setTimeout(() => { - expect(cpc).toBeTruthy(); - expect(spc).toBeFalsy(); - - done(); - }, 333); - }); - }); - }); - - describe('Remove source branch checkbox', () => { - describe('when user can merge but cannot delete branch', () => { - it('should be disabled in the rendered output', () => { - const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); - - expect(checkboxElement).toBeNull(); - }); - }); - - describe('when user can merge and can delete branch', () => { - beforeEach(() => { - vm = createComponent({ - mr: { canRemoveSourceBranch: true }, - }); - }); - - it('isRemoveSourceBranchButtonDisabled should be false', () => { - expect(vm.isRemoveSourceBranchButtonDisabled).toBe(false); - }); - - it('removed source branch should be enabled in rendered output', () => { - const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); - - expect(checkboxElement).not.toBeNull(); - }); - }); - }); - - describe('render children components', () => { - let wrapper; - const localVue = createLocalVue(); - - const createLocalComponent = (customConfig = {}) => { - wrapper = shallowMount(localVue.extend(ReadyToMerge), { - localVue, - propsData: { - mr: createTestMr(customConfig), - service: createTestService(), - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - const findCheckboxElement = () => wrapper.find(SquashBeforeMerge); - const findCommitsHeaderElement = () => wrapper.find(CommitsHeader); - const findCommitEditElements = () => wrapper.findAll(CommitEdit); - const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown); - const findFirstCommitEditLabel = () => - findCommitEditElements() - .at(0) - .props('label'); - - describe('squash checkbox', () => { - it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { - createLocalComponent({ - mr: { commitsCount: 2, enableSquashBeforeMerge: true }, - }); - - expect(findCheckboxElement().exists()).toBeTruthy(); - }); - - it('should not be rendered when squash before merge is disabled', () => { - createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } }); - - expect(findCheckboxElement().exists()).toBeFalsy(); - }); - - it('should not be rendered when there is only 1 commit', () => { - createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } }); - - expect(findCheckboxElement().exists()).toBeFalsy(); - }); - }); - - describe('commits count collapsible header', () => { - it('should be rendered when fast-forward is disabled', () => { - createLocalComponent(); - - expect(findCommitsHeaderElement().exists()).toBeTruthy(); - }); - - describe('when fast-forward is enabled', () => { - it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - enableSquashBeforeMerge: true, - squash: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeTruthy(); - }); - - it('should not be rendered if squash before merge is disabled', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - enableSquashBeforeMerge: false, - squash: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - - it('should not be rendered if squash is disabled', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: false, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - - it('should not be rendered if commits count is 1', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: true, - commitsCount: 1, - }, - }); - - expect(findCommitsHeaderElement().exists()).toBeFalsy(); - }); - }); - }); - - describe('commits edit components', () => { - describe('when fast-forward merge is enabled', () => { - it('should not be rendered if squash is disabled', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: false, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should not be rendered if squash before merge is disabled', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: false, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should not be rendered if there is only one commit', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: true, - commitsCount: 1, - }, - }); - - expect(findCommitEditElements().length).toBe(0); - }); - - it('should have one edit component if squash is enabled and there is more than 1 commit', () => { - createLocalComponent({ - mr: { - ffOnlyEnabled: true, - squash: true, - enableSquashBeforeMerge: true, - commitsCount: 2, - }, - }); - - expect(findCommitEditElements().length).toBe(1); - expect(findFirstCommitEditLabel()).toBe('Squash commit message'); - }); - }); - - it('should have one edit component when squash is disabled', () => { - createLocalComponent(); - - expect(findCommitEditElements().length).toBe(1); - }); - - it('should have two edit components when squash is enabled and there is more than 1 commit', () => { - createLocalComponent({ - mr: { - commitsCount: 2, - squash: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findCommitEditElements().length).toBe(2); - }); - - it('should have one edit components when squash is enabled and there is 1 commit only', () => { - createLocalComponent({ - mr: { - commitsCount: 1, - squash: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findCommitEditElements().length).toBe(1); - }); - - it('should have correct edit merge commit label', () => { - createLocalComponent(); - - expect(findFirstCommitEditLabel()).toBe('Merge commit message'); - }); - - it('should have correct edit squash commit label', () => { - createLocalComponent({ - mr: { - commitsCount: 2, - squash: true, - enableSquashBeforeMerge: true, - }, - }); - - expect(findFirstCommitEditLabel()).toBe('Squash commit message'); - }); - }); - - describe('commits dropdown', () => { - it('should not be rendered if squash is disabled', () => { - createLocalComponent(); - - expect(findCommitDropdownElement().exists()).toBeFalsy(); - }); - - it('should be rendered if squash is enabled and there is more than 1 commit', () => { - createLocalComponent({ - mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 }, - }); - - expect(findCommitDropdownElement().exists()).toBeTruthy(); - }); - }); - }); - - describe('Merge controls', () => { - describe('when allowed to merge', () => { - beforeEach(() => { - vm = createComponent({ - mr: { isMergeAllowed: true, canRemoveSourceBranch: true }, - }); - }); - - it('shows remove source branch checkbox', () => { - expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).not.toBeNull(); - }); - - it('shows modify commit message button', () => { - expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); - }); - - it('does not show message about needing to resolve items', () => { - expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeNull(); - }); - }); - - describe('when not allowed to merge', () => { - beforeEach(() => { - vm = createComponent({ - mr: { isMergeAllowed: false }, - }); - }); - - it('does not show remove source branch checkbox', () => { - expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeNull(); - }); - - it('shows message to resolve all items before being allowed to merge', () => { - expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeDefined(); - }); - }); - }); - - describe('Merge request project settings', () => { - describe('when the merge commit merge method is enabled', () => { - beforeEach(() => { - vm = createComponent({ - mr: { ffOnlyEnabled: false }, - }); - }); - - it('should not show fast forward message', () => { - expect(vm.$el.querySelector('.mr-fast-forward-message')).toBeNull(); - }); - - it('should show "Modify commit message" button', () => { - expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); - }); - }); - - describe('when the fast-forward merge method is enabled', () => { - beforeEach(() => { - vm = createComponent({ - mr: { ffOnlyEnabled: true }, - }); - }); - - it('should show fast forward message', () => { - expect(vm.$el.querySelector('.mr-fast-forward-message')).toBeDefined(); - }); - - it('should not show "Modify commit message" button', () => { - expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeNull(); - }); - }); - }); - - describe('with a mismatched SHA', () => { - const findMismatchShaBlock = () => vm.$el.querySelector('.js-sha-mismatch'); - - beforeEach(() => { - vm = createComponent({ - mr: { - isSHAMismatch: true, - mergeRequestDiffsPath: '/merge_requests/1/diffs', - }, - }); - }); - - it('displays a warning message', () => { - expect(findMismatchShaBlock()).toExist(); - }); - - it('warns the user to refresh to review', () => { - expect(findMismatchShaBlock().textContent.trim()).toBe( - 'New changes were added. Reload the page to review them', - ); - }); - - it('displays link to the diffs tab', () => { - expect(findMismatchShaBlock().querySelector('a').href).toContain(vm.mr.mergeRequestDiffsPath); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js deleted file mode 100644 index 11eb0fef9b2..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_sha_mismatch_spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { removeBreakLine } from 'spec/helpers/text_helper'; -import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue'; - -describe('ShaMismatch', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(ShaMismatch); - vm = mountComponent(Component); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render information message', () => { - expect(vm.$el.querySelector('button').disabled).toEqual(true); - - expect(removeBreakLine(vm.$el.textContent).trim()).toContain( - 'The source branch HEAD has recently changed. Please reload the page and review the changes before merging', - ); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js deleted file mode 100644 index b70d580ed04..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ /dev/null @@ -1,99 +0,0 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; - -const localVue = createLocalVue(); - -describe('Squash before merge component', () => { - let wrapper; - - const createComponent = props => { - wrapper = shallowMount(localVue.extend(SquashBeforeMerge), { - localVue, - propsData: { - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('checkbox', () => { - const findCheckbox = () => wrapper.find('.js-squash-checkbox'); - - it('is unchecked if passed value prop is false', () => { - createComponent({ - value: false, - }); - - expect(findCheckbox().element.checked).toBeFalsy(); - }); - - it('is checked if passed value prop is true', () => { - createComponent({ - value: true, - }); - - expect(findCheckbox().element.checked).toBeTruthy(); - }); - - it('changes value on click', done => { - createComponent({ - value: false, - }); - - findCheckbox().element.checked = true; - - findCheckbox().trigger('change'); - - wrapper.vm.$nextTick(() => { - expect(findCheckbox().element.checked).toBeTruthy(); - done(); - }); - }); - - it('is disabled if isDisabled prop is true', () => { - createComponent({ - value: false, - isDisabled: true, - }); - - expect(findCheckbox().attributes('disabled')).toBeTruthy(); - }); - }); - - describe('about link', () => { - it('is not rendered if no help path is passed', () => { - createComponent({ - value: false, - }); - - const aboutLink = wrapper.find('a'); - - expect(aboutLink.exists()).toBeFalsy(); - }); - - it('is rendered if help path is passed', () => { - createComponent({ - value: false, - helpPath: 'test-path', - }); - - const aboutLink = wrapper.find('a'); - - expect(aboutLink.exists()).toBeTruthy(); - }); - - it('should have a correct help path if passed', () => { - createComponent({ - value: false, - helpPath: 'test-path', - }); - - const aboutLink = wrapper.find('a'); - - expect(aboutLink.attributes('href')).toEqual('test-path'); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js deleted file mode 100644 index e8367caa438..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue'; - -describe('UnresolvedDiscussions', () => { - const Component = Vue.extend(UnresolvedDiscussions); - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('with threads path', () => { - beforeEach(() => { - vm = mountComponent(Component, { - mr: { - createIssueToResolveDiscussionsPath: gl.TEST_HOST, - }, - }); - }); - - it('should have correct elements', () => { - expect(vm.$el.innerText).toContain( - 'There are unresolved threads. Please resolve these threads', - ); - - expect(vm.$el.innerText).toContain('Create an issue to resolve them later'); - expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(gl.TEST_HOST); - }); - }); - - describe('without threads path', () => { - beforeEach(() => { - vm = mountComponent(Component, { mr: {} }); - }); - - it('should not show create issue link if user cannot create issue', () => { - expect(vm.$el.innerText).toContain( - 'There are unresolved threads. Please resolve these threads', - ); - - expect(vm.$el.querySelector('.js-create-issue')).toEqual(null); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js deleted file mode 100644 index 9153231b974..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ /dev/null @@ -1,99 +0,0 @@ -import Vue from 'vue'; -import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; - -const createComponent = () => { - const Component = Vue.extend(WorkInProgress); - const mr = { - title: 'The best MR ever', - removeWIPPath: '/path/to/remove/wip', - }; - const service = { - removeWIP() {}, - }; - return new Component({ - el: document.createElement('div'), - propsData: { mr, service }, - }); -}; - -describe('Wip', () => { - describe('props', () => { - it('should have props', () => { - const { mr, service } = WorkInProgress.props; - - expect(mr.type instanceof Object).toBeTruthy(); - expect(mr.required).toBeTruthy(); - - expect(service.type instanceof Object).toBeTruthy(); - expect(service.required).toBeTruthy(); - }); - }); - - describe('data', () => { - it('should have default data', () => { - const vm = createComponent(); - - expect(vm.isMakingRequest).toBeFalsy(); - }); - }); - - describe('methods', () => { - const mrObj = { - is_new_mr_data: true, - }; - - describe('handleRemoveWIP', () => { - it('should make a request to service and handle response', done => { - const vm = createComponent(); - - const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true); - spyOn(eventHub, '$emit'); - spyOn(vm.service, 'removeWIP').and.returnValue( - new Promise(resolve => { - resolve({ - data: mrObj, - }); - }), - ); - - vm.handleRemoveWIP(); - setTimeout(() => { - expect(vm.isMakingRequest).toBeTruthy(); - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice'); - done(); - }, 333); - }); - }); - }); - - describe('template', () => { - let vm; - let el; - - beforeEach(() => { - vm = createComponent(); - el = vm.$el; - }); - - it('should have correct elements', () => { - expect(el.classList.contains('mr-widget-body')).toBeTruthy(); - expect(el.innerText).toContain('This is a Work in Progress'); - expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy(); - expect(el.querySelector('button').innerText).toContain('Merge'); - expect(el.querySelector('.js-remove-wip').innerText.replace(/\s\s+/g, ' ')).toContain( - 'Resolve WIP status', - ); - }); - - it('should not show removeWIP button is user cannot update MR', done => { - vm.mr.removeWIPPath = ''; - - Vue.nextTick(() => { - expect(el.querySelector('.js-remove-wip')).toEqual(null); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js deleted file mode 100644 index 7783fcb6f93..00000000000 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from '../../frontend/vue_mr_widget/mock_data'; -export * from '../../frontend/vue_mr_widget/mock_data'; diff --git a/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js deleted file mode 100644 index e031583b43a..00000000000 --- a/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js +++ /dev/null @@ -1,261 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; - -const modalComponent = Vue.extend(DeprecatedModal2); - -describe('DeprecatedModal2', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('props', () => { - describe('with id', () => { - const props = { - id: 'my-modal', - }; - - beforeEach(() => { - vm = mountComponent(modalComponent, props); - }); - - it('assigns the id to the modal', () => { - expect(vm.$el.id).toBe(props.id); - }); - }); - - describe('without id', () => { - beforeEach(() => { - vm = mountComponent(modalComponent, {}); - }); - - it('does not add an id attribute to the modal', () => { - expect(vm.$el.hasAttribute('id')).toBe(false); - }); - }); - - describe('with headerTitleText', () => { - const props = { - headerTitleText: 'my title text', - }; - - beforeEach(() => { - vm = mountComponent(modalComponent, props); - }); - - it('sets the modal title', () => { - const modalTitle = vm.$el.querySelector('.modal-title'); - - expect(modalTitle.innerHTML.trim()).toBe(props.headerTitleText); - }); - }); - - describe('with footerPrimaryButtonVariant', () => { - const props = { - footerPrimaryButtonVariant: 'danger', - }; - - beforeEach(() => { - vm = mountComponent(modalComponent, props); - }); - - it('sets the primary button class', () => { - const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type'); - - expect(primaryButton).toHaveClass(`btn-${props.footerPrimaryButtonVariant}`); - }); - }); - - describe('with footerPrimaryButtonText', () => { - const props = { - footerPrimaryButtonText: 'my button text', - }; - - beforeEach(() => { - vm = mountComponent(modalComponent, props); - }); - - it('sets the primary button text', () => { - const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type'); - - expect(primaryButton.innerHTML.trim()).toBe(props.footerPrimaryButtonText); - }); - }); - }); - - it('works with data-toggle="modal"', done => { - setFixtures(` - <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button> - <div id="modal-container"></div> - `); - - const modalContainer = document.getElementById('modal-container'); - const modalButton = document.getElementById('modal-button'); - vm = mountComponent( - modalComponent, - { - id: 'my-modal', - }, - modalContainer, - ); - $(vm.$el).on('shown.bs.modal', () => done()); - - modalButton.click(); - }); - - describe('methods', () => { - const dummyEvent = 'not really an event'; - - beforeEach(() => { - vm = mountComponent(modalComponent, {}); - spyOn(vm, '$emit'); - }); - - describe('emitCancel', () => { - it('emits a cancel event', () => { - vm.emitCancel(dummyEvent); - - expect(vm.$emit).toHaveBeenCalledWith('cancel', dummyEvent); - }); - }); - - describe('emitSubmit', () => { - it('emits a submit event', () => { - vm.emitSubmit(dummyEvent); - - expect(vm.$emit).toHaveBeenCalledWith('submit', dummyEvent); - }); - }); - - describe('opened', () => { - it('emits a open event', () => { - vm.opened(); - - expect(vm.$emit).toHaveBeenCalledWith('open'); - }); - }); - - describe('closed', () => { - it('emits a closed event', () => { - vm.closed(); - - expect(vm.$emit).toHaveBeenCalledWith('closed'); - }); - }); - }); - - describe('slots', () => { - const slotContent = 'this should go into the slot'; - const modalWithSlot = slotName => { - let template; - if (slotName) { - template = ` - <deprecated-modal-2> - <template slot="${slotName}">${slotContent}</template> - </deprecated-modal-2> - `; - } else { - template = `<deprecated-modal-2>${slotContent}</deprecated-modal-2>`; - } - - return Vue.extend({ - components: { - DeprecatedModal2, - }, - template, - }); - }; - - describe('default slot', () => { - beforeEach(() => { - vm = mountComponent(modalWithSlot()); - }); - - it('sets the modal body', () => { - const modalBody = vm.$el.querySelector('.modal-body'); - - expect(modalBody.innerHTML).toBe(slotContent); - }); - }); - - describe('header slot', () => { - beforeEach(() => { - vm = mountComponent(modalWithSlot('header')); - }); - - it('sets the modal header', () => { - const modalHeader = vm.$el.querySelector('.modal-header'); - - expect(modalHeader.innerHTML).toBe(slotContent); - }); - }); - - describe('title slot', () => { - beforeEach(() => { - vm = mountComponent(modalWithSlot('title')); - }); - - it('sets the modal title', () => { - const modalTitle = vm.$el.querySelector('.modal-title'); - - expect(modalTitle.innerHTML).toBe(slotContent); - }); - }); - - describe('footer slot', () => { - beforeEach(() => { - vm = mountComponent(modalWithSlot('footer')); - }); - - it('sets the modal footer', () => { - const modalFooter = vm.$el.querySelector('.modal-footer'); - - expect(modalFooter.innerHTML).toBe(slotContent); - }); - }); - }); - - describe('handling sizes', () => { - it('should render modal-sm', () => { - vm = mountComponent(modalComponent, { - modalSize: 'sm', - }); - - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(true); - }); - - it('should render modal-lg', () => { - vm = mountComponent(modalComponent, { - modalSize: 'lg', - }); - - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(true); - }); - - it('should render modal-xl', () => { - vm = mountComponent(modalComponent, { - modalSize: 'xl', - }); - - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-xl')).toEqual(true); - }); - - it('should not add modal size classes when md size is passed', () => { - vm = mountComponent(modalComponent, { - modalSize: 'md', - }); - - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-md')).toEqual(false); - }); - - it('should not add modal size classes by default', () => { - vm = mountComponent(modalComponent, {}); - - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(false); - expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(false); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/deprecated_modal_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_spec.js deleted file mode 100644 index d6c10e32794..00000000000 --- a/spec/javascripts/vue_shared/components/deprecated_modal_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; - -const modalComponent = Vue.extend(DeprecatedModal); - -describe('DeprecatedModal', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('props', () => { - describe('without primaryButtonLabel', () => { - beforeEach(() => { - vm = mountComponent(modalComponent, { - primaryButtonLabel: null, - }); - }); - - it('does not render a primary button', () => { - expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); - }); - }); - - describe('with id', () => { - describe('does not render a primary button', () => { - beforeEach(() => { - vm = mountComponent(modalComponent, { - id: 'my-modal', - }); - }); - - it('assigns the id to the modal', () => { - expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull(); - }); - - it('does not show the modal immediately', () => { - expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show'); - }); - - it('does not show a backdrop', () => { - expect(vm.$el.querySelector('modal-backdrop')).toBeNull(); - }); - }); - }); - - it('works with data-toggle="modal"', done => { - setFixtures(` - <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button> - <div id="modal-container"></div> - `); - - const modalContainer = document.getElementById('modal-container'); - const modalButton = document.getElementById('modal-button'); - vm = mountComponent( - modalComponent, - { - id: 'my-modal', - }, - modalContainer, - ); - const modalElement = vm.$el.querySelector('#my-modal'); - $(modalElement).on('shown.bs.modal', () => done()); - - modalButton.click(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/file_finder/index_spec.js b/spec/javascripts/vue_shared/components/file_finder/index_spec.js deleted file mode 100644 index 7ded228d3ea..00000000000 --- a/spec/javascripts/vue_shared/components/file_finder/index_spec.js +++ /dev/null @@ -1,368 +0,0 @@ -import Vue from 'vue'; -import Mousetrap from 'mousetrap'; -import { file } from 'spec/ide/helpers'; -import timeoutPromise from 'spec/helpers/set_timeout_promise_helper'; -import FindFileComponent from '~/vue_shared/components/file_finder/index.vue'; -import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; - -describe('File finder item spec', () => { - const Component = Vue.extend(FindFileComponent); - let vm; - - function createComponent(props) { - vm = new Component({ - propsData: { - files: [], - visible: true, - loading: false, - ...props, - }, - }); - - vm.$mount('#app'); - } - - beforeEach(() => { - setFixtures('<div id="app"></div>'); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('with entries', () => { - beforeEach(done => { - createComponent({ - files: [ - { - ...file('index.js'), - path: 'index.js', - type: 'blob', - url: '/index.jsurl', - }, - { - ...file('component.js'), - path: 'component.js', - type: 'blob', - }, - ], - }); - - setTimeout(done); - }); - - it('renders list of blobs', () => { - expect(vm.$el.textContent).toContain('index.js'); - expect(vm.$el.textContent).toContain('component.js'); - expect(vm.$el.textContent).not.toContain('folder'); - }); - - it('filters entries', done => { - vm.searchText = 'index'; - - setTimeout(() => { - expect(vm.$el.textContent).toContain('index.js'); - expect(vm.$el.textContent).not.toContain('component.js'); - - done(); - }); - }); - - it('shows clear button when searchText is not empty', done => { - vm.searchText = 'index'; - - setTimeout(() => { - expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value'); - expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden'); - - done(); - }); - }); - - it('clear button resets searchText', done => { - vm.searchText = 'index'; - - timeoutPromise() - .then(() => { - vm.$el.querySelector('.dropdown-input-clear').click(); - }) - .then(timeoutPromise) - .then(() => { - expect(vm.searchText).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - - it('clear button focues search input', done => { - spyOn(vm.$refs.searchInput, 'focus'); - vm.searchText = 'index'; - - timeoutPromise() - .then(() => { - vm.$el.querySelector('.dropdown-input-clear').click(); - }) - .then(timeoutPromise) - .then(() => { - expect(vm.$refs.searchInput.focus).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - describe('listShowCount', () => { - it('returns 1 when no filtered entries exist', done => { - vm.searchText = 'testing 123'; - - setTimeout(() => { - expect(vm.listShowCount).toBe(1); - - done(); - }); - }); - - it('returns entries length when not filtered', () => { - expect(vm.listShowCount).toBe(2); - }); - }); - - describe('listHeight', () => { - it('returns 55 when entries exist', () => { - expect(vm.listHeight).toBe(55); - }); - - it('returns 33 when entries dont exist', done => { - vm.searchText = 'testing 123'; - - setTimeout(() => { - expect(vm.listHeight).toBe(33); - - done(); - }); - }); - }); - - describe('filteredBlobsLength', () => { - it('returns length of filtered blobs', done => { - vm.searchText = 'index'; - - setTimeout(() => { - expect(vm.filteredBlobsLength).toBe(1); - - done(); - }); - }); - }); - - describe('watches', () => { - describe('searchText', () => { - it('resets focusedIndex when updated', done => { - vm.focusedIndex = 1; - vm.searchText = 'test'; - - setTimeout(() => { - expect(vm.focusedIndex).toBe(0); - - done(); - }); - }); - }); - - describe('visible', () => { - it('returns searchText when false', done => { - vm.searchText = 'test'; - vm.visible = true; - - timeoutPromise() - .then(() => { - vm.visible = false; - }) - .then(timeoutPromise) - .then(() => { - expect(vm.searchText).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('openFile', () => { - beforeEach(() => { - spyOn(vm, '$emit'); - }); - - it('closes file finder', () => { - vm.openFile(vm.files[0]); - - expect(vm.$emit).toHaveBeenCalledWith('toggle', false); - }); - - it('pushes to router', () => { - vm.openFile(vm.files[0]); - - expect(vm.$emit).toHaveBeenCalledWith('click', vm.files[0]); - }); - }); - - describe('onKeyup', () => { - it('opens file on enter key', done => { - const event = new CustomEvent('keyup'); - event.keyCode = ENTER_KEY_CODE; - - spyOn(vm, 'openFile'); - - vm.$refs.searchInput.dispatchEvent(event); - - setTimeout(() => { - expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]); - - done(); - }); - }); - - it('closes file finder on esc key', done => { - const event = new CustomEvent('keyup'); - event.keyCode = ESC_KEY_CODE; - - spyOn(vm, '$emit'); - - vm.$refs.searchInput.dispatchEvent(event); - - setTimeout(() => { - expect(vm.$emit).toHaveBeenCalledWith('toggle', false); - - done(); - }); - }); - }); - - describe('onKeyDown', () => { - let el; - - beforeEach(() => { - el = vm.$refs.searchInput; - }); - - describe('up key', () => { - const event = new CustomEvent('keydown'); - event.keyCode = UP_KEY_CODE; - - it('resets to last index when at top', () => { - el.dispatchEvent(event); - - expect(vm.focusedIndex).toBe(1); - }); - - it('minus 1 from focusedIndex', () => { - vm.focusedIndex = 1; - - el.dispatchEvent(event); - - expect(vm.focusedIndex).toBe(0); - }); - }); - - describe('down key', () => { - const event = new CustomEvent('keydown'); - event.keyCode = DOWN_KEY_CODE; - - it('resets to first index when at bottom', () => { - vm.focusedIndex = 1; - el.dispatchEvent(event); - - expect(vm.focusedIndex).toBe(0); - }); - - it('adds 1 to focusedIndex', () => { - el.dispatchEvent(event); - - expect(vm.focusedIndex).toBe(1); - }); - }); - }); - }); - - describe('without entries', () => { - it('renders loading text when loading', () => { - createComponent({ - loading: true, - }); - - expect(vm.$el.textContent).toContain('Loading...'); - }); - - it('renders no files text', () => { - createComponent(); - - expect(vm.$el.textContent).toContain('No files found.'); - }); - }); - - describe('keyboard shortcuts', () => { - beforeEach(done => { - createComponent(); - - spyOn(vm, 'toggle'); - - vm.$nextTick(done); - }); - - it('calls toggle on `t` key press', done => { - Mousetrap.trigger('t'); - - vm.$nextTick() - .then(() => { - expect(vm.toggle).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('calls toggle on `command+p` key press', done => { - Mousetrap.trigger('command+p'); - - vm.$nextTick() - .then(() => { - expect(vm.toggle).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('calls toggle on `ctrl+p` key press', done => { - Mousetrap.trigger('ctrl+p'); - - vm.$nextTick() - .then(() => { - expect(vm.toggle).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('always allows `command+p` to trigger toggle', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'command+p'), - ).toBe(false); - }); - - it('always allows `ctrl+p` to trigger toggle', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'ctrl+p'), - ).toBe(false); - }); - - it('onlys handles `t` when focused in input-field', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'), - ).toBe(true); - }); - - it('stops callback in monaco editor', () => { - setFixtures('<div class="inputarea"></div>'); - - expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js deleted file mode 100644 index 5a3e483fb03..00000000000 --- a/spec/javascripts/vue_shared/components/icon_spec.js +++ /dev/null @@ -1,73 +0,0 @@ -import Vue from 'vue'; -import { mount } from '@vue/test-utils'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import Icon from '~/vue_shared/components/icon.vue'; - -describe('Sprite Icon Component', function() { - describe('Initialization', function() { - let icon; - - beforeEach(function() { - const IconComponent = Vue.extend(Icon); - - icon = mountComponent(IconComponent, { - name: 'commit', - size: 32, - }); - }); - - afterEach(() => { - icon.$destroy(); - }); - - it('should return a defined Vue component', function() { - expect(icon).toBeDefined(); - }); - - it('should have <svg> as a child element', function() { - expect(icon.$el.tagName).toBe('svg'); - }); - - it('should have <use> as a child element with the correct href', function() { - expect(icon.$el.firstChild.tagName).toBe('use'); - expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#commit`); - }); - - it('should properly compute iconSizeClass', function() { - expect(icon.iconSizeClass).toBe('s32'); - }); - - it('forbids invalid size prop', () => { - expect(icon.$options.props.size.validator(NaN)).toBeFalsy(); - expect(icon.$options.props.size.validator(0)).toBeFalsy(); - expect(icon.$options.props.size.validator(9001)).toBeFalsy(); - }); - - it('should properly render img css', function() { - const { classList } = icon.$el; - const containsSizeClass = classList.contains('s32'); - - expect(containsSizeClass).toBe(true); - }); - - it('`name` validator should return false for non existing icons', () => { - expect(Icon.props.name.validator('non_existing_icon_sprite')).toBe(false); - }); - - it('`name` validator should return false for existing icons', () => { - expect(Icon.props.name.validator('commit')).toBe(true); - }); - }); - - it('should call registered listeners when they are triggered', () => { - const clickHandler = jasmine.createSpy('clickHandler'); - const wrapper = mount(Icon, { - propsData: { name: 'commit' }, - listeners: { click: clickHandler }, - }); - - wrapper.find('svg').trigger('click'); - - expect(clickHandler).toHaveBeenCalled(); - }); -}); diff --git a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js b/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js deleted file mode 100644 index 3c42f0c2aa9..00000000000 --- a/spec/javascripts/vue_shared/components/issue/related_issuable_mock_data.js +++ /dev/null @@ -1 +0,0 @@ -export * from '../../../../frontend/vue_shared/components/issue/related_issuable_mock_data'; diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js deleted file mode 100644 index d65ee8eeb2d..00000000000 --- a/spec/javascripts/vue_shared/components/panel_resizer_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import panelResizer from '~/vue_shared/components/panel_resizer.vue'; - -describe('Panel Resizer component', () => { - let vm; - let PanelResizer; - - const triggerEvent = (eventName, el = vm.$el, clientX = 0) => { - const event = document.createEvent('MouseEvents'); - event.initMouseEvent( - eventName, - true, - true, - window, - 1, - clientX, - 0, - clientX, - 0, - false, - false, - false, - false, - 0, - null, - ); - - el.dispatchEvent(event); - }; - - beforeEach(() => { - PanelResizer = Vue.extend(panelResizer); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render a div element with the correct classes and styles', () => { - vm = mountComponent(PanelResizer, { - startSize: 100, - side: 'left', - }); - - expect(vm.$el.tagName).toEqual('DIV'); - expect(vm.$el.getAttribute('class')).toBe( - 'position-absolute position-top-0 position-bottom-0 drag-handle position-left-0', - ); - - expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;'); - }); - - it('should render a div element with the correct classes for a right side panel', () => { - vm = mountComponent(PanelResizer, { - startSize: 100, - side: 'right', - }); - - expect(vm.$el.tagName).toEqual('DIV'); - expect(vm.$el.getAttribute('class')).toBe( - 'position-absolute position-top-0 position-bottom-0 drag-handle position-right-0', - ); - }); - - it('drag the resizer', () => { - vm = mountComponent(PanelResizer, { - startSize: 100, - side: 'left', - }); - - spyOn(vm, '$emit'); - triggerEvent('mousedown', vm.$el); - triggerEvent('mousemove', document); - triggerEvent('mouseup', document); - - expect(vm.$emit.calls.allArgs()).toEqual([ - ['resize-start', 100], - ['update:size', 100], - ['resize-end', 100], - ]); - - expect(vm.size).toBe(100); - }); -}); diff --git a/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js b/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js deleted file mode 100644 index 47ebdc505c9..00000000000 --- a/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js +++ /dev/null @@ -1,83 +0,0 @@ -import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue'; - -describe('Toggle Button', () => { - let vm; - - const createComponent = ({ length, remain }) => { - const smartListProperties = { - rtag: 'section', - wtag: 'ul', - wclass: 'test-class', - // Size in pixels does not matter for our tests here - size: 35, - length, - remain, - }; - - const Component = Vue.extend({ - components: { - SmartVirtualScrollList, - }, - smartListProperties, - items: Array(length).fill(1), - template: ` - <smart-virtual-scroll-list v-bind="$options.smartListProperties"> - <li v-for="(val, key) in $options.items" :key="key">{{ key + 1 }}</li> - </smart-virtual-scroll-list>`, - }); - - return mountComponent(Component); - }; - - afterEach(() => { - vm.$destroy(); - }); - - describe('if the list is shorter than the maximum shown elements', () => { - const listLength = 10; - - beforeEach(() => { - vm = createComponent({ length: listLength, remain: 20 }); - }); - - it('renders without the vue-virtual-scroll-list component', () => { - expect(vm.$el.classList).not.toContain('js-virtual-list'); - expect(vm.$el.classList).toContain('js-plain-element'); - }); - - it('renders list with provided tags and classes for the wrapper elements', () => { - expect(vm.$el.tagName).toEqual('SECTION'); - expect(vm.$el.firstChild.tagName).toEqual('UL'); - expect(vm.$el.firstChild.classList).toContain('test-class'); - }); - - it('renders all children list elements', () => { - expect(vm.$el.querySelectorAll('li').length).toEqual(listLength); - }); - }); - - describe('if the list is longer than the maximum shown elements', () => { - const maxItemsShown = 20; - - beforeEach(() => { - vm = createComponent({ length: 1000, remain: maxItemsShown }); - }); - - it('uses the vue-virtual-scroll-list component', () => { - expect(vm.$el.classList).toContain('js-virtual-list'); - expect(vm.$el.classList).not.toContain('js-plain-element'); - }); - - it('renders list with provided tags and classes for the wrapper elements', () => { - expect(vm.$el.tagName).toEqual('SECTION'); - expect(vm.$el.firstChild.tagName).toEqual('UL'); - expect(vm.$el.firstChild.classList).toContain('test-class'); - }); - - it('renders at max twice the maximum shown elements', () => { - expect(vm.$el.querySelectorAll('li').length).toBeLessThanOrEqual(2 * maxItemsShown); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js deleted file mode 100644 index f1ca5f61496..00000000000 --- a/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; - -/** - * We're testing this directive's hooks as pure functions - * since behaviour of this directive is highly-dependent - * on underlying DOM methods. - */ -describe('AutofocusOnShow directive', () => { - describe('with input invisible on component render', () => { - let el; - - beforeAll(() => { - setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>'); - el = document.querySelector('#inputel'); - }); - - it('should bind IntersectionObserver on input element', () => { - spyOn(el, 'focus'); - - autofocusonshow.inserted(el); - - expect(el.visibilityObserver).toBeDefined(); - expect(el.focus).not.toHaveBeenCalled(); - }); - - it('should stop IntersectionObserver on input element on unbind hook', () => { - el.visibilityObserver = { - disconnect: () => {}, - }; - spyOn(el.visibilityObserver, 'disconnect'); - - autofocusonshow.unbind(el); - - expect(el.visibilityObserver).toBeDefined(); - expect(el.visibilityObserver.disconnect).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/directives/tooltip_spec.js b/spec/javascripts/vue_shared/directives/tooltip_spec.js deleted file mode 100644 index 1d516a280b0..00000000000 --- a/spec/javascripts/vue_shared/directives/tooltip_spec.js +++ /dev/null @@ -1,89 +0,0 @@ -import $ from 'jquery'; -import Vue from 'vue'; -import tooltip from '~/vue_shared/directives/tooltip'; - -describe('Tooltip directive', () => { - let vm; - - afterEach(() => { - if (vm) { - vm.$destroy(); - } - }); - - describe('with a single tooltip', () => { - beforeEach(() => { - setFixtures('<div id="dummy-element"></div>'); - vm = new Vue({ - el: '#dummy-element', - directives: { - tooltip, - }, - data() { - return { - tooltip: 'some text', - }; - }, - template: '<div v-tooltip :title="tooltip"></div>', - }); - }); - - it('should have tooltip plugin applied', () => { - expect($(vm.$el).data('bs.tooltip')).toBeDefined(); - }); - - it('displays the title as tooltip', () => { - $(vm.$el).tooltip('show'); - const tooltipElement = document.querySelector('.tooltip-inner'); - - expect(tooltipElement.innerText).toContain('some text'); - }); - - it('updates a visible tooltip', done => { - $(vm.$el).tooltip('show'); - const tooltipElement = document.querySelector('.tooltip-inner'); - - vm.tooltip = 'other text'; - - Vue.nextTick() - .then(() => { - expect(tooltipElement).toContainText('other text'); - done(); - }) - .catch(done.fail); - }); - }); - - describe('with multiple tooltips', () => { - beforeEach(() => { - const SomeComponent = Vue.extend({ - directives: { - tooltip, - }, - template: ` - <div> - <div - v-tooltip - class="js-look-for-tooltip" - title="foo"> - </div> - <div - v-tooltip - title="bar"> - </div> - </div> - `, - }); - - vm = new SomeComponent().$mount(); - }); - - it('should have tooltip plugin applied to all instances', () => { - expect( - $(vm.$el) - .find('.js-look-for-tooltip') - .data('bs.tooltip'), - ).toBeDefined(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/translate_spec.js b/spec/javascripts/vue_shared/translate_spec.js deleted file mode 100644 index adca7cd64a1..00000000000 --- a/spec/javascripts/vue_shared/translate_spec.js +++ /dev/null @@ -1,251 +0,0 @@ -import Vue from 'vue'; -import Jed from 'jed'; - -import { trimText } from 'spec/helpers/text_helper'; -import locale from '~/locale'; -import Translate from '~/vue_shared/translate'; - -describe('Vue translate filter', () => { - let el; - - const createTranslationMock = (key, ...translations) => { - const fakeLocale = new Jed({ - domain: 'app', - locale_data: { - app: { - '': { - domain: 'app', - lang: 'vo', - plural_forms: 'nplurals=2; plural=(n != 1);', - }, - [key]: translations, - }, - }, - }); - - // eslint-disable-next-line no-underscore-dangle - locale.__Rewire__('locale', fakeLocale); - }; - - afterEach(() => { - // eslint-disable-next-line no-underscore-dangle - locale.__ResetDependency__('locale'); - }); - - beforeEach(() => { - Vue.use(Translate); - - el = document.createElement('div'); - - document.body.appendChild(el); - }); - - it('translate singular text (`__`)', done => { - const key = 'singular'; - const translation = 'singular_translated'; - createTranslationMock(key, translation); - - const vm = new Vue({ - el, - template: ` - <span> - {{ __('${key}') }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(translation); - - done(); - }); - }); - - it('translate plural text (`n__`) without any substituting text', done => { - const key = 'plural'; - const translationPlural = 'plural_multiple translation'; - createTranslationMock(key, 'plural_singular translation', translationPlural); - - const vm = new Vue({ - el, - template: ` - <span> - {{ n__('${key}', 'plurals', 2) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(translationPlural); - - done(); - }); - }); - - describe('translate plural text (`n__`) with substituting %d', () => { - const key = '%d day'; - - beforeEach(() => { - createTranslationMock(key, '%d singular translated', '%d plural translated'); - }); - - it('and n === 1', done => { - const vm = new Vue({ - el, - template: ` - <span> - {{ n__('${key}', '%d days', 1) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe('1 singular translated'); - - done(); - }); - }); - - it('and n > 1', done => { - const vm = new Vue({ - el, - template: ` - <span> - {{ n__('${key}', '%d days', 2) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe('2 plural translated'); - - done(); - }); - }); - }); - - describe('translates text with context `s__`', () => { - const key = 'Context|Foobar'; - const translation = 'Context|Foobar translated'; - const expectation = 'Foobar translated'; - - beforeEach(() => { - createTranslationMock(key, translation); - }); - - it('and using two parameters', done => { - const vm = new Vue({ - el, - template: ` - <span> - {{ s__('Context', 'Foobar') }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(expectation); - - done(); - }); - }); - - it('and using the pipe syntax', done => { - const vm = new Vue({ - el, - template: ` - <span> - {{ s__('${key}') }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(expectation); - - done(); - }); - }); - }); - - it('translate multi line text', done => { - const translation = 'multiline string translated'; - createTranslationMock('multiline string', translation); - - const vm = new Vue({ - el, - template: ` - <span> - {{ __(\` - multiline - string - \`) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(translation); - - done(); - }); - }); - - it('translate pluralized multi line text', done => { - const translation = 'multiline string plural'; - - createTranslationMock('multiline string', 'multiline string singular', translation); - - const vm = new Vue({ - el, - template: ` - <span> - {{ n__( - \` - multiline - string - \`, - \` - multiline - strings - \`, - 2 - ) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(translation); - - done(); - }); - }); - - it('translate pluralized multi line text with context', done => { - const translation = 'multiline string with context'; - - createTranslationMock('Context| multiline string', translation); - - const vm = new Vue({ - el, - template: ` - <span> - {{ s__( - \` - Context| - multiline - string - \` - ) }} - </span> - `, - }).$mount(); - - Vue.nextTick(() => { - expect(trimText(vm.$el.textContent)).toBe(translation); - - done(); - }); - }); -}); diff --git a/spec/javascripts/vuex_shared/modules/modal/actions_spec.js b/spec/javascripts/vuex_shared/modules/modal/actions_spec.js deleted file mode 100644 index 2c4cb845424..00000000000 --- a/spec/javascripts/vuex_shared/modules/modal/actions_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import testAction from 'spec/helpers/vuex_action_helper'; -import * as types from '~/vuex_shared/modules/modal/mutation_types'; -import * as actions from '~/vuex_shared/modules/modal/actions'; - -describe('Vuex ModalModule actions', () => { - describe('open', () => { - it('works', done => { - const data = { id: 7 }; - - testAction(actions.open, data, {}, [{ type: types.OPEN, payload: data }], [], done); - }); - }); - - describe('close', () => { - it('works', done => { - testAction(actions.close, null, {}, [{ type: types.CLOSE }], [], done); - }); - }); - - describe('show', () => { - it('works', done => { - testAction(actions.show, null, {}, [{ type: types.SHOW }], [], done); - }); - }); - - describe('hide', () => { - it('works', done => { - testAction(actions.hide, null, {}, [{ type: types.HIDE }], [], done); - }); - }); -}); diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js deleted file mode 100644 index 5dee11b3810..00000000000 --- a/spec/javascripts/zen_mode_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import $ from 'jquery'; -import Dropzone from 'dropzone'; -import Mousetrap from 'mousetrap'; -import ZenMode from '~/zen_mode'; -import initNotes from '~/init_notes'; - -describe('ZenMode', () => { - let zen; - let dropzoneForElementSpy; - const fixtureName = 'snippets/show.html'; - - preloadFixtures(fixtureName); - - function enterZen() { - $('.notes-form .js-zen-enter').click(); - } - - function exitZen() { - $('.notes-form .js-zen-leave').click(); - } - - function escapeKeydown() { - $('.notes-form textarea').trigger( - $.Event('keydown', { - keyCode: 27, - }), - ); - } - - beforeEach(() => { - loadFixtures(fixtureName); - initNotes(); - - dropzoneForElementSpy = spyOn(Dropzone, 'forElement').and.callFake(() => ({ - enable: () => true, - })); - zen = new ZenMode(); - - // Set this manually because we can't actually scroll the window - zen.scroll_position = 456; - }); - - describe('enabling dropzone', () => { - beforeEach(() => { - enterZen(); - }); - - it('should not call dropzone if element is not dropzone valid', () => { - $('.div-dropzone').addClass('js-invalid-dropzone'); - exitZen(); - - expect(dropzoneForElementSpy.calls.count()).toEqual(0); - }); - - it('should call dropzone if element is dropzone valid', () => { - $('.div-dropzone').removeClass('js-invalid-dropzone'); - exitZen(); - - expect(dropzoneForElementSpy.calls.count()).toEqual(2); - }); - }); - - describe('on enter', () => { - it('pauses Mousetrap', () => { - const mouseTrapPauseSpy = spyOn(Mousetrap, 'pause'); - enterZen(); - - expect(mouseTrapPauseSpy).toHaveBeenCalled(); - }); - - it('removes textarea styling', () => { - $('.notes-form textarea').attr('style', 'height: 400px'); - enterZen(); - - expect($('.notes-form textarea')).not.toHaveAttr('style'); - }); - }); - - describe('in use', () => { - beforeEach(enterZen); - - it('exits on Escape', () => { - escapeKeydown(); - - expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen'); - }); - }); - - describe('on exit', () => { - beforeEach(enterZen); - - it('unpauses Mousetrap', () => { - const mouseTrapUnpauseSpy = spyOn(Mousetrap, 'unpause'); - exitZen(); - - expect(mouseTrapUnpauseSpy).toHaveBeenCalled(); - }); - - it('restores the scroll position', () => { - spyOn(zen, 'scrollTo'); - exitZen(); - - expect(zen.scrollTo).toHaveBeenCalled(); - }); - }); -}); |