diff options
Diffstat (limited to 'spec/javascripts')
80 files changed, 2166 insertions, 450 deletions
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc new file mode 100644 index 00000000000..7792acffac2 --- /dev/null +++ b/spec/javascripts/.eslintrc @@ -0,0 +1,15 @@ +{ + "plugins": ["jasmine"], + "env": { + "jasmine": true + }, + "extends": "plugin:jasmine/recommended", + "rules": { + "prefer-arrow-callback": 0, + "func-names": 0 + }, + "globals": { + "fixture": false, + "spyOnEvent": false + } +} diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6 index a3171353bfb..49e56249565 100644 --- a/spec/javascripts/abuse_reports_spec.js.es6 +++ b/spec/javascripts/abuse_reports_spec.js.es6 @@ -1,42 +1,44 @@ -/* eslint-disable */ +/*= require lib/utils/text_utility */ /*= require abuse_reports */ -/*= require jquery */ - ((global) => { - const FIXTURE = 'abuse_reports.html'; - const MAX_MESSAGE_LENGTH = 500; + describe('Abuse Reports', () => { + const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const MAX_MESSAGE_LENGTH = 500; + + let messages; - function assertMaxLength($message) { - expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); - } + const assertMaxLength = $message => expect($message.text().length).toEqual(MAX_MESSAGE_LENGTH); + const findMessage = searchText => messages.filter( + (index, element) => element.innerText.indexOf(searchText) > -1, + ).first(); - describe('Abuse Reports', function() { fixture.preload(FIXTURE); - beforeEach(function() { + beforeEach(function () { fixture.load(FIXTURE); - new global.AbuseReports(); + this.abuseReports = new global.AbuseReports(); + messages = $('.abuse-reports .message'); }); - it('should truncate long messages', function() { - const $longMessage = $('#long'); + + it('should truncate long messages', () => { + const $longMessage = findMessage('LONG MESSAGE'); expect($longMessage.data('original-message')).toEqual(jasmine.anything()); assertMaxLength($longMessage); }); - it('should not truncate short messages', function() { - const $shortMessage = $('#short'); + it('should not truncate short messages', () => { + const $shortMessage = findMessage('SHORT MESSAGE'); expect($shortMessage.data('original-message')).not.toEqual(jasmine.anything()); }); - it('should allow clicking a truncated message to expand and collapse the full message', function() { - const $longMessage = $('#long'); + it('should allow clicking a truncated message to expand and collapse the full message', () => { + const $longMessage = findMessage('LONG MESSAGE'); $longMessage.click(); expect($longMessage.data('original-message').length).toEqual($longMessage.text().length); $longMessage.click(); assertMaxLength($longMessage); }); }); - })(window.gl); diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6 index 9d855ef1060..192da4ee8d9 100644 --- a/spec/javascripts/activities_spec.js.es6 +++ b/spec/javascripts/activities_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable no-unused-expressions, comma-spacing, prefer-const, no-prototype-builtins, semi, no-new, keyword-spacing, no-plusplus, no-shadow, max-len */ + /*= require js.cookie.js */ /*= require jquery.endless-scroll.js */ /*= require pager */ @@ -35,7 +36,7 @@ describe('Activities', () => { beforeEach(() => { fixture.load(fixtureTemplate); - new Activities(); + new gl.Activities(); }); for(let i = 0; i < filters.length; i++) { diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js deleted file mode 100644 index 16e908f3a81..00000000000 --- a/spec/javascripts/application_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable */ - -/*= require lib/utils/common_utils */ - -(function() { - describe('Application', function() { - return describe('disable buttons', function() { - fixture.preload('application.html'); - beforeEach(function() { - return fixture.load('application.html'); - }); - it('should prevent default action for disabled buttons', function() { - var $button, isClicked; - gl.utils.preventDisabledButtons(); - isClicked = false; - $button = $('#test-button'); - expect($button).toExist(); - $button.click(function() { - return isClicked = true; - }); - $button.trigger('click'); - return expect(isClicked).toBe(false); - }); - - it('should be on the same page if a disabled link clicked', function() { - var locationBeforeLinkClick, $link; - locationBeforeLinkClick = window.location.href; - gl.utils.preventDisabledButtons(); - $link = $('#test-link'); - expect($link).toExist(); - $link.click(); - return expect(window.location.href).toBe(locationBeforeLinkClick); - }); - }); - }); - -}).call(this); diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index 3d705e1cb2e..89201c8cb8b 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, padded-blocks, max-len */ +/* global AwardsHandler */ /*= require awards_handler */ /*= require jquery */ @@ -33,9 +34,9 @@ }; describe('AwardsHandler', function() { - fixture.preload('awards_handler.html'); + fixture.preload('issues/open-issue.html.raw'); beforeEach(function() { - fixture.load('awards_handler.html'); + fixture.load('issues/open-issue.html.raw'); awardsHandler = new AwardsHandler; spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { return function(url, emoji, cb) { @@ -113,7 +114,7 @@ }); describe('::getAwardUrl', function() { return it('should return the url for request', function() { - return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'); + return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); }); }); describe('::addAward and ::checkMutuality', function() { @@ -209,7 +210,7 @@ $('.js-add-award').eq(0).click(); $menu = $('.emoji-menu'); $block = $('.js-awards-block'); - $emoji = $menu.find(".emoji-menu-list-item " + selector); + $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector); expect($emoji.length).toBe(1); expect($block.find(selector).length).toBe(0); $emoji.click(); @@ -224,7 +225,7 @@ openEmojiMenuAndAddEmoji(); $('.js-add-award').eq(0).click(); $block = $('.js-awards-block'); - $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector); + $emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector); $emoji.click(); return expect($block.find(selector).length).toBe(0); }); diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js index 36254a7370e..b4573e53a4e 100644 --- a/spec/javascripts/behaviors/autosize_spec.js +++ b/spec/javascripts/behaviors/autosize_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, padded-blocks, max-len */ /*= require behaviors/autosize */ diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 7370ccb4a08..0f61000bc37 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, padded-blocks, max-len */ /*= require behaviors/quick_submit */ diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index 32469a4fd1f..c3f4c867d6a 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, padded-blocks */ /*= require behaviors/requires_input */ diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 6208c2386b0..b3a1afa28a5 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -1,4 +1,11 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, one-var, no-unused-vars, indent */ +/* global Vue */ +/* global BoardService */ +/* global boardsMockInterceptor */ +/* global Cookies */ +/* global listObj */ +/* global listObjDuplicate */ + //= require jquery //= require jquery_ujs //= require js.cookie @@ -13,8 +20,9 @@ //= require boards/stores/boards_store //= require ./mock_data -(() => { +describe('Store', () => { beforeEach(() => { + Vue.http.interceptors.push(boardsMockInterceptor); gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); @@ -24,145 +32,147 @@ }); }); - describe('Store', () => { - it('starts with a blank state', () => { - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); - describe('lists', () => { - it('creates new list without persisting to DB', () => { - gl.issueBoards.BoardsStore.addList(listObj); + it('starts with a blank state', () => { + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); + }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - }); + describe('lists', () => { + it('creates new list without persisting to DB', () => { + gl.issueBoards.BoardsStore.addList(listObj); - it('finds list by ID', () => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + }); - expect(list.id).toBe(1); - }); + it('finds list by ID', () => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('id', 1); - it('finds list by type', () => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('type', 'label'); + expect(list.id).toBe(1); + }); - expect(list).toBeDefined(); - }); + it('finds list by type', () => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('type', 'label'); - it('finds list limited by type', () => { - gl.issueBoards.BoardsStore.addList({ - id: 1, - position: 0, - title: 'Test', - list_type: 'backlog' - }); - const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog'); + expect(list).toBeDefined(); + }); - expect(list).toBeDefined(); + it('finds list limited by type', () => { + gl.issueBoards.BoardsStore.addList({ + id: 1, + position: 0, + title: 'Test', + list_type: 'backlog' }); + const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog'); - it('gets issue when new list added', (done) => { - gl.issueBoards.BoardsStore.addList(listObj); - const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(list).toBeDefined(); + }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + it('gets issue when new list added', (done) => { + gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('id', 1); - setTimeout(() => { - expect(list.issues.length).toBe(1); - expect(list.issues[0].id).toBe(1); - done(); - }, 0); - }); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - it('persists new list', (done) => { - gl.issueBoards.BoardsStore.new({ - title: 'Test', - type: 'label', - label: { - id: 1, - title: 'Testing', - color: 'red', - description: 'testing;' - } - }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - - setTimeout(() => { - const list = gl.issueBoards.BoardsStore.findList('id', 1); - expect(list).toBeDefined(); - expect(list.id).toBe(1); - expect(list.position).toBe(0); - done(); - }, 0); - }); + setTimeout(() => { + expect(list.issues.length).toBe(1); + expect(list.issues[0].id).toBe(1); + done(); + }, 0); + }); - it('check for blank state adding', () => { - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + it('persists new list', (done) => { + gl.issueBoards.BoardsStore.new({ + title: 'Test', + type: 'label', + label: { + id: 1, + title: 'Testing', + color: 'red', + description: 'testing;' + } }); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - it('check for blank state not adding', () => { - gl.issueBoards.BoardsStore.addList(listObj); - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false); - }); + setTimeout(() => { + const list = gl.issueBoards.BoardsStore.findList('id', 1); + expect(list).toBeDefined(); + expect(list.id).toBe(1); + expect(list.position).toBe(0); + done(); + }, 0); + }); - it('check for blank state adding when backlog & done list exist', () => { - gl.issueBoards.BoardsStore.addList({ - list_type: 'backlog' - }); - gl.issueBoards.BoardsStore.addList({ - list_type: 'done' - }); + it('check for blank state adding', () => { + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + }); + + it('check for blank state not adding', () => { + gl.issueBoards.BoardsStore.addList(listObj); + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false); + }); - expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + it('check for blank state adding when backlog & done list exist', () => { + gl.issueBoards.BoardsStore.addList({ + list_type: 'backlog' + }); + gl.issueBoards.BoardsStore.addList({ + list_type: 'done' }); - it('adds the blank state', () => { - gl.issueBoards.BoardsStore.addBlankState(); + expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true); + }); - const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank'); - expect(list).toBeDefined(); - }); + it('adds the blank state', () => { + gl.issueBoards.BoardsStore.addBlankState(); - it('removes list from state', () => { - gl.issueBoards.BoardsStore.addList(listObj); + const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank'); + expect(list).toBeDefined(); + }); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); + it('removes list from state', () => { + gl.issueBoards.BoardsStore.addList(listObj); - gl.issueBoards.BoardsStore.removeList(1, 'label'); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); - }); + gl.issueBoards.BoardsStore.removeList(1, 'label'); + + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0); + }); - it('moves the position of lists', () => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + it('moves the position of lists', () => { + const listOne = gl.issueBoards.BoardsStore.addList(listObj), + listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); - gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']); + gl.issueBoards.BoardsStore.moveList(listOne, ['2', '1']); - expect(listOne.position).toBe(1); - }); + expect(listOne.position).toBe(1); + }); - it('moves an issue from one list to another', (done) => { - const listOne = gl.issueBoards.BoardsStore.addList(listObj), - listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); + it('moves an issue from one list to another', (done) => { + const listOne = gl.issueBoards.BoardsStore.addList(listObj), + listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate); - expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); + expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2); - setTimeout(() => { - expect(listOne.issues.length).toBe(1); - expect(listTwo.issues.length).toBe(1); + setTimeout(() => { + expect(listOne.issues.length).toBe(1); + expect(listTwo.issues.length).toBe(1); - gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1)); + gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1)); - expect(listOne.issues.length).toBe(0); - expect(listTwo.issues.length).toBe(1); + expect(listOne.issues.length).toBe(0); + expect(listTwo.issues.length).toBe(1); - done(); - }, 0); - }); + done(); + }, 0); }); }); -})(); +}); diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js.es6 index 90cb8926545..c8a61a0a9b5 100644 --- a/spec/javascripts/boards/issue_spec.js.es6 +++ b/spec/javascripts/boards/issue_spec.js.es6 @@ -1,4 +1,7 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle */ +/* global BoardService */ +/* global ListIssue */ + //= require jquery //= require jquery_ujs //= require js.cookie diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index 1a0427fdd90..7d942ec3d65 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -1,4 +1,10 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle */ +/* global Vue */ +/* global boardsMockInterceptor */ +/* global BoardService */ +/* global List */ +/* global listObj */ + //= require jquery //= require jquery_ujs //= require js.cookie @@ -17,12 +23,17 @@ describe('List model', () => { let list; beforeEach(() => { + Vue.http.interceptors.push(boardsMockInterceptor); gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); list = new List(listObj); }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); + it('gets issues when created', (done) => { setTimeout(() => { expect(list.issues.length).toBe(1); diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index 80d05e8a1a3..8d3e2237fda 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, no-unused-vars, quote-props */ + const listObj = { id: 1, position: 0, @@ -48,10 +49,10 @@ const BoardsMockData = { } }; -Vue.http.interceptors.push((request, next) => { +const boardsMockInterceptor = (request, next) => { const body = BoardsMockData[request.method][request.url]; next(request.respondWith(JSON.stringify(body), { status: 200 })); -}); +}; diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 new file mode 100644 index 00000000000..133712debab --- /dev/null +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 @@ -0,0 +1,59 @@ +//= require lib/utils/bootstrap_linked_tabs + +(() => { + describe('Linked Tabs', () => { + fixture.preload('linked_tabs'); + + beforeEach(() => { + fixture.load('linked_tabs'); + }); + + describe('when is initialized', () => { + beforeEach(() => { + spyOn(window.history, 'replaceState').and.callFake(function () {}); + }); + + it('should activate the tab correspondent to the given action', () => { + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'tab1', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + expect(document.querySelector('#tab1').classList).toContain('active'); + }); + + it('should active the default tab action when the action is show', () => { + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'show', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + expect(document.querySelector('#tab1').classList).toContain('active'); + }); + }); + + describe('on click', () => { + it('should change the url according to the clicked tab', () => { + const historySpy = spyOn(history, 'replaceState').and.callFake(() => {}); + + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'show', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a'); + const newState = secondTab.getAttribute('href') + linkedTabs.currentLocation.search + linkedTabs.currentLocation.hash; + + secondTab.click(); + + expect(historySpy).toHaveBeenCalledWith({ + turbolinks: true, + url: newState, + }, document.title, newState); + }); + }); + }); +})(); diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6 new file mode 100644 index 00000000000..3983cad4c13 --- /dev/null +++ b/spec/javascripts/build_spec.js.es6 @@ -0,0 +1,184 @@ +/* eslint-disable no-new */ +/* global Build */ +/* global Turbolinks */ + +//= require lib/utils/datetime_utility +//= require build +//= require breakpoints +//= require jquery.nicescroll +//= require turbolinks + +describe('Build', () => { + const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`; + // see spec/factories/ci/builds.rb + const BUILD_TRACE = 'BUILD TRACE'; + // see lib/ci/ansi2html.rb + const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({ + offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0, + })); + + fixture.preload('builds/build-with-artifacts.html.raw'); + + beforeEach(() => { + fixture.load('builds/build-with-artifacts.html.raw'); + spyOn($, 'ajax'); + }); + + describe('constructor', () => { + beforeEach(() => { + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + describe('setup', () => { + beforeEach(function () { + this.build = new Build(); + }); + + it('copies build options', function () { + expect(this.build.pageUrl).toBe(BUILD_URL); + expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`); + expect(this.build.buildStatus).toBe('success'); + expect(this.build.buildStage).toBe('test'); + expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE); + }); + + it('only shows the jobs matching the current stage', () => { + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); + + it('selects the current stage in the build dropdown menu', () => { + expect($('.stage-selection').text()).toBe('test'); + }); + + it('updates the jobs when the build dropdown changes', () => { + $('.stage-item:contains("build")').click(); + + expect($('.stage-selection').text()).toBe('build'); + expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true); + expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); + expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); + }); + + it('displays the remove date correctly', () => { + const removeDateElement = document.querySelector('.js-artifacts-remove'); + expect(removeDateElement.innerText.trim()).toBe('1 year'); + }); + }); + + describe('initial build trace', () => { + beforeEach(() => { + new Build(); + }); + + it('displays the initial build trace', () => { + expect($.ajax.calls.count()).toBe(1); + const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); + expect(url).toBe(`${BUILD_URL}.json`); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { trace_html: '<span>Example</span>', status: 'running' }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Example/); + }); + + it('removes the spinner', () => { + const [{ success, context }] = $.ajax.calls.argsFor(0); + success.call(context, { trace_html: '<span>Example</span>', status: 'success' }); + + expect($('.js-build-refresh').length).toBe(0); + }); + }); + + describe('running build', () => { + beforeEach(function () { + $('.js-build-options').data('buildStatus', 'running'); + this.build = new Build(); + spyOn(this.build, 'location').and.returnValue(BUILD_URL); + }); + + it('updates the build trace on an interval', function () { + jasmine.clock().tick(4001); + + expect($.ajax.calls.count()).toBe(2); + let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1); + expect(url).toBe( + `${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}`, + ); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: '<span>Update<span>', + status: 'running', + state: 'newstate', + append: true, + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); + expect(this.build.state).toBe('newstate'); + + jasmine.clock().tick(4001); + + expect($.ajax.calls.count()).toBe(3); + [{ url, dataType, success, context }] = $.ajax.calls.argsFor(2); + expect(url).toBe(`${BUILD_URL}/trace.json?state=newstate`); + expect(dataType).toBe('json'); + expect(success).toEqual(jasmine.any(Function)); + + success.call(context, { + html: '<span>More</span>', + status: 'running', + state: 'finalstate', + append: true, + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/); + expect(this.build.state).toBe('finalstate'); + }); + + it('replaces the entire build trace', () => { + jasmine.clock().tick(4001); + let [{ success, context }] = $.ajax.calls.argsFor(1); + success.call(context, { + html: '<span>Update</span>', + status: 'running', + append: true, + }); + + expect($('#build-trace .js-build-output').text()).toMatch(/Update/); + + jasmine.clock().tick(4001); + [{ success, context }] = $.ajax.calls.argsFor(2); + success.call(context, { + html: '<span>Different</span>', + status: 'running', + append: false, + }); + + expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/); + expect($('#build-trace .js-build-output').text()).toMatch(/Different/); + }); + + it('reloads the page when the build is done', () => { + spyOn(Turbolinks, 'visit'); + + jasmine.clock().tick(4001); + const [{ success, context }] = $.ajax.calls.argsFor(1); + success.call(context, { + html: '<span>Final</span>', + status: 'passed', + append: true, + }); + + expect(Turbolinks.visit).toHaveBeenCalledWith(BUILD_URL); + }); + }); + }); +}); diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6 index 93f73fa0e9a..aadf6f518a8 100644 --- a/spec/javascripts/dashboard_spec.js.es6 +++ b/spec/javascripts/dashboard_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable no-new, padded-blocks */ + /*= require sidebar */ /*= require jquery */ /*= require js.cookie */ diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js.es6 index 9fdbab3a9e9..8ece24555c5 100644 --- a/spec/javascripts/datetime_utility_spec.js.es6 +++ b/spec/javascripts/datetime_utility_spec.js.es6 @@ -1,5 +1,5 @@ -/* eslint-disable */ //= require lib/utils/datetime_utility + (() => { describe('Date time utils', () => { describe('get day name', () => { diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js.es6 index 9b2845af608..18805d26ac0 100644 --- a/spec/javascripts/diff_comments_store_spec.js.es6 +++ b/spec/javascripts/diff_comments_store_spec.js.es6 @@ -1,8 +1,11 @@ -/* eslint-disable */ +/* eslint-disable no-extra-semi, jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ +/* global CommentsStore */ + //= require vue //= require diff_notes/models/discussion //= require diff_notes/models/note //= require diff_notes/stores/comments + (() => { function createDiscussion(noteId = 1, resolved = true) { CommentsStore.create('a', noteId, true, resolved, 'test'); diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6 new file mode 100644 index 00000000000..4bae3f30bb5 --- /dev/null +++ b/spec/javascripts/environments/environment_actions_spec.js.es6 @@ -0,0 +1,67 @@ +//= require vue +//= require environments/components/environment_actions + +describe('Actions Component', () => { + fixture.preload('environments/element.html'); + + beforeEach(() => { + fixture.load('environments/element.html'); + }); + + it('should render a dropdown with the provided actions', () => { + const actionsMock = [ + { + name: 'bar', + play_path: 'https://gitlab.com/play', + }, + { + name: 'foo', + play_path: '#', + }, + ]; + + const component = new window.gl.environmentsList.ActionsComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + actions: actionsMock, + playIconSvg: '<svg></svg>', + }, + }); + + expect( + component.$el.querySelectorAll('.dropdown-menu li').length, + ).toEqual(actionsMock.length); + expect( + component.$el.querySelector('.dropdown-menu li a').getAttribute('href'), + ).toEqual(actionsMock[0].play_path); + }); + + it('should render a dropdown with the provided svg', () => { + const actionsMock = [ + { + name: 'bar', + play_path: 'https://gitlab.com/play', + }, + { + name: 'foo', + play_path: '#', + }, + ]; + + const component = new window.gl.environmentsList.ActionsComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + actions: actionsMock, + playIconSvg: '<svg></svg>', + }, + }); + + expect( + component.$el.querySelector('.js-dropdown-play-icon-container').children, + ).toContain('svg'); + + expect( + component.$el.querySelector('.js-action-play-icon-container').children, + ).toContain('svg'); + }); +}); diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js.es6 new file mode 100644 index 00000000000..9f82567c35b --- /dev/null +++ b/spec/javascripts/environments/environment_external_url_spec.js.es6 @@ -0,0 +1,22 @@ +//= require vue +//= require environments/components/environment_external_url + +describe('External URL Component', () => { + fixture.preload('environments/element.html'); + beforeEach(() => { + fixture.load('environments/element.html'); + }); + + it('should link to the provided externalUrl prop', () => { + const externalURL = 'https://gitlab.com'; + const component = new window.gl.environmentsList.ExternalUrlComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + externalUrl: externalURL, + }, + }); + + expect(component.$el.getAttribute('href')).toEqual(externalURL); + expect(component.$el.querySelector('fa-external-link')).toBeDefined(); + }); +}); diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 new file mode 100644 index 00000000000..5d7c6b2411d --- /dev/null +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -0,0 +1,229 @@ +//= require vue +//= require timeago +//= require environments/components/environment_item + +describe('Environment item', () => { + fixture.preload('environments/table.html'); + beforeEach(() => { + fixture.load('environments/table.html'); + }); + + describe('When item is folder', () => { + let mockItem; + let component; + + beforeEach(() => { + mockItem = { + name: 'review', + children: [ + { + name: 'review-app', + id: 1, + state: 'available', + external_url: '', + last_deployment: {}, + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-10T15:55:58.778Z', + }, + { + name: 'production', + id: 2, + state: 'available', + external_url: '', + last_deployment: {}, + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-10T15:55:58.778Z', + }, + ], + }; + + component = new window.gl.environmentsList.EnvironmentItem({ + el: document.querySelector('tr#environment-row'), + propsData: { + model: mockItem, + toggleRow: () => {}, + canCreateDeployment: false, + canReadEnvironment: true, + }, + }); + }); + + it('Should render folder icon and name', () => { + expect(component.$el.querySelector('.folder-name').textContent).toContain(mockItem.name); + expect(component.$el.querySelector('.folder-icon')).toBeDefined(); + }); + + it('Should render the number of children in a badge', () => { + expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(mockItem.children.length); + }); + }); + + describe('when item is not folder', () => { + let environment; + let component; + + beforeEach(() => { + environment = { + id: 31, + name: 'production', + state: 'stopped', + external_url: 'http://external.com', + environment_type: null, + last_deployment: { + id: 66, + iid: 6, + sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + ref: { + name: 'master', + ref_path: 'root/ci-folders/tree/master', + }, + tag: true, + 'last?': true, + user: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit: { + id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + short_id: '500aabcb', + title: 'Update .gitlab-ci.yml', + author_name: 'Administrator', + author_email: 'admin@example.com', + created_at: '2016-11-07T18:28:13.000+00:00', + message: 'Update .gitlab-ci.yml', + author: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + }, + deployable: { + id: 1279, + name: 'deploy', + build_path: '/root/ci-folders/builds/1279', + retry_path: '/root/ci-folders/builds/1279/retry', + created_at: '2016-11-29T18:11:58.430Z', + updated_at: '2016-11-29T18:11:58.430Z', + }, + manual_actions: [ + { + name: 'action', + play_path: '/play', + }, + ], + }, + 'stoppable?': true, + environment_path: 'root/ci-folders/environments/31', + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-10T15:55:58.778Z', + }; + + component = new window.gl.environmentsList.EnvironmentItem({ + el: document.querySelector('tr#environment-row'), + propsData: { + model: environment, + toggleRow: () => {}, + canCreateDeployment: true, + canReadEnvironment: true, + }, + }); + }); + + it('should render environment name', () => { + expect(component.$el.querySelector('.environment-name').textContent).toContain(environment.name); + }); + + describe('With deployment', () => { + it('should render deployment internal id', () => { + expect( + component.$el.querySelector('.deployment-column span').textContent, + ).toContain(environment.last_deployment.iid); + + expect( + component.$el.querySelector('.deployment-column span').textContent, + ).toContain('#'); + }); + + it('should render last deployment date', () => { + const timeagoInstance = new timeago(); // eslint-disable-line + const formatedDate = timeagoInstance.format( + environment.last_deployment.deployable.created_at, + ); + + expect( + component.$el.querySelector('.environment-created-date-timeago').textContent, + ).toContain(formatedDate); + }); + + describe('With user information', () => { + it('should render user avatar with link to profile', () => { + expect( + component.$el.querySelector('.js-deploy-user-container').getAttribute('href'), + ).toEqual(environment.last_deployment.user.web_url); + }); + }); + + describe('With build url', () => { + it('Should link to build url provided', () => { + expect( + component.$el.querySelector('.build-link').getAttribute('href'), + ).toEqual(environment.last_deployment.deployable.build_path); + }); + + it('Should render deployable name and id', () => { + expect( + component.$el.querySelector('.build-link').getAttribute('href'), + ).toEqual(environment.last_deployment.deployable.build_path); + }); + }); + + describe('With commit information', () => { + it('should render commit component', () => { + expect( + component.$el.querySelector('.js-commit-component'), + ).toBeDefined(); + }); + }); + }); + + describe('With manual actions', () => { + it('Should render actions component', () => { + expect( + component.$el.querySelector('.js-manual-actions-container'), + ).toBeDefined(); + }); + }); + + describe('With external URL', () => { + it('should render external url component', () => { + expect( + component.$el.querySelector('.js-external-url-container'), + ).toBeDefined(); + }); + }); + + describe('With stop action', () => { + it('Should render stop action component', () => { + expect( + component.$el.querySelector('.js-stop-component-container'), + ).toBeDefined(); + }); + }); + + describe('With retry action', () => { + it('Should render rollback component', () => { + expect( + component.$el.querySelector('.js-rollback-component-container'), + ).toBeDefined(); + }); + }); + }); +}); diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6 new file mode 100644 index 00000000000..77ba0ab38ec --- /dev/null +++ b/spec/javascripts/environments/environment_rollback_spec.js.es6 @@ -0,0 +1,48 @@ +//= require vue +//= require environments/components/environment_rollback +describe('Rollback Component', () => { + fixture.preload('environments/element.html'); + + const retryURL = 'https://gitlab.com/retry'; + + beforeEach(() => { + fixture.load('environments/element.html'); + }); + + it('Should link to the provided retryUrl', () => { + const component = new window.gl.environmentsList.RollbackComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + retryUrl: retryURL, + isLastDeployment: true, + }, + }); + + expect(component.$el.getAttribute('href')).toEqual(retryURL); + }); + + it('Should render Re-deploy label when isLastDeployment is true', () => { + const component = new window.gl.environmentsList.RollbackComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + retryUrl: retryURL, + isLastDeployment: true, + }, + }); + + expect(component.$el.querySelector('span').textContent).toContain('Re-deploy'); + }); + + + it('Should render Rollback label when isLastDeployment is false', () => { + const component = new window.gl.environmentsList.RollbackComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + retryUrl: retryURL, + isLastDeployment: false, + }, + }); + + expect(component.$el.querySelector('span').textContent).toContain('Rollback'); + }); +}); diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js.es6 new file mode 100644 index 00000000000..84a41b2bf46 --- /dev/null +++ b/spec/javascripts/environments/environment_stop_spec.js.es6 @@ -0,0 +1,28 @@ +//= require vue +//= require environments/components/environment_stop +describe('Stop Component', () => { + fixture.preload('environments/element.html'); + + let stopURL; + let component; + + beforeEach(() => { + fixture.load('environments/element.html'); + + stopURL = '/stop'; + component = new window.gl.environmentsList.StopComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + stopUrl: stopURL, + }, + }); + }); + + it('should link to the provided URL', () => { + expect(component.$el.getAttribute('href')).toEqual(stopURL); + }); + + it('should have a data-confirm attribute', () => { + expect(component.$el.getAttribute('data-confirm')).toEqual('Are you sure you want to stop this environment?'); + }); +}); diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js.es6 new file mode 100644 index 00000000000..17c00acf63e --- /dev/null +++ b/spec/javascripts/environments/environments_store_spec.js.es6 @@ -0,0 +1,71 @@ +/* global environmentsList */ + +//= require vue +//= require environments/stores/environments_store +//= require ./mock_data + +(() => { + describe('Store', () => { + beforeEach(() => { + gl.environmentsList.EnvironmentsStore.create(); + }); + + it('should start with a blank state', () => { + expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(0); + expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(0); + expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(0); + }); + + describe('store environments', () => { + beforeEach(() => { + gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList); + }); + + it('should count stopped environments and save the count in the state', () => { + expect(gl.environmentsList.EnvironmentsStore.state.stoppedCounter).toBe(1); + }); + + it('should count available environments and save the count in the state', () => { + expect(gl.environmentsList.EnvironmentsStore.state.availableCounter).toBe(3); + }); + + it('should store environments with same environment_type as sibilings', () => { + expect(gl.environmentsList.EnvironmentsStore.state.environments.length).toBe(3); + + const parentFolder = gl.environmentsList.EnvironmentsStore.state.environments + .filter(env => env.children && env.children.length > 0); + + expect(parentFolder[0].children.length).toBe(2); + expect(parentFolder[0].children[0].environment_type).toBe('review'); + expect(parentFolder[0].children[1].environment_type).toBe('review'); + expect(parentFolder[0].children[0].name).toBe('test-environment'); + expect(parentFolder[0].children[1].name).toBe('test-environment-1'); + }); + + it('should sort the environments alphabetically', () => { + const { environments } = gl.environmentsList.EnvironmentsStore.state; + + expect(environments[0].name).toBe('production'); + expect(environments[1].name).toBe('review'); + expect(environments[1].children[0].name).toBe('test-environment'); + expect(environments[1].children[1].name).toBe('test-environment-1'); + expect(environments[2].name).toBe('review_app'); + }); + }); + + describe('toggleFolder', () => { + beforeEach(() => { + gl.environmentsList.EnvironmentsStore.storeEnvironments(environmentsList); + }); + + it('should toggle the open property for the given environment', () => { + gl.environmentsList.EnvironmentsStore.toggleFolder('review'); + + const { environments } = gl.environmentsList.EnvironmentsStore.state; + const environment = environments.filter(env => env['vue-isChildren'] === true && env.name === 'review'); + + expect(environment[0].isOpen).toBe(true); + }); + }); + }); +})(); diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 new file mode 100644 index 00000000000..9e16bc3e6a5 --- /dev/null +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -0,0 +1,135 @@ +/* eslint-disable no-unused-vars */ +const environmentsList = [ + { + id: 31, + name: 'production', + state: 'available', + external_url: 'https://www.gitlab.com', + environment_type: null, + last_deployment: { + id: 64, + iid: 5, + sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + ref: { + name: 'master', + ref_url: 'http://localhost:3000/root/ci-folders/tree/master', + }, + tag: false, + 'last?': true, + user: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit: { + id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + short_id: '500aabcb', + title: 'Update .gitlab-ci.yml', + author_name: 'Administrator', + author_email: 'admin@example.com', + created_at: '2016-11-07T18:28:13.000+00:00', + message: 'Update .gitlab-ci.yml', + author: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + }, + deployable: { + id: 1278, + name: 'build', + build_path: '/root/ci-folders/builds/1278', + retry_path: '/root/ci-folders/builds/1278/retry', + }, + manual_actions: [], + }, + 'stoppable?': true, + environment_path: '/root/ci-folders/environments/31', + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-07T11:11:16.525Z', + }, + { + id: 32, + name: 'review_app', + state: 'stopped', + external_url: 'https://www.gitlab.com', + environment_type: null, + last_deployment: { + id: 64, + iid: 5, + sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + ref: { + name: 'master', + ref_url: 'http://localhost:3000/root/ci-folders/tree/master', + }, + tag: false, + 'last?': true, + user: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit: { + id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + short_id: '500aabcb', + title: 'Update .gitlab-ci.yml', + author_name: 'Administrator', + author_email: 'admin@example.com', + created_at: '2016-11-07T18:28:13.000+00:00', + message: 'Update .gitlab-ci.yml', + author: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', + }, + deployable: { + id: 1278, + name: 'build', + build_path: '/root/ci-folders/builds/1278', + retry_path: '/root/ci-folders/builds/1278/retry', + }, + manual_actions: [], + }, + 'stoppable?': false, + environment_path: '/root/ci-folders/environments/31', + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-07T11:11:16.525Z', + }, + { + id: 33, + name: 'test-environment', + state: 'available', + environment_type: 'review', + last_deployment: null, + 'stoppable?': true, + environment_path: '/root/ci-folders/environments/31', + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-07T11:11:16.525Z', + }, + { + id: 34, + name: 'test-environment-1', + state: 'available', + environment_type: 'review', + last_deployment: null, + 'stoppable?': true, + environment_path: '/root/ci-folders/environments/31', + created_at: '2016-11-07T11:11:16.525Z', + updated_at: '2016-11-07T11:11:16.525Z', + }, +]; diff --git a/spec/javascripts/extensions/array_spec.js b/spec/javascripts/extensions/array_spec.js deleted file mode 100644 index f28983d7764..00000000000 --- a/spec/javascripts/extensions/array_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ - -/*= require extensions/array */ - -(function() { - describe('Array extensions', function() { - describe('first', function() { - return it('returns the first item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.first()).toBe(0); - }); - }); - return describe('last', function() { - return it('returns the last item', function() { - var arr; - arr = [0, 1, 2, 3, 4, 5]; - return expect(arr.last()).toBe(5); - }); - }); - }); - -}).call(this); diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js.es6 new file mode 100644 index 00000000000..2ec759c8e80 --- /dev/null +++ b/spec/javascripts/extensions/array_spec.js.es6 @@ -0,0 +1,46 @@ +/* eslint-disable space-before-function-paren, no-var, padded-blocks */ + +/*= require extensions/array */ + +(function() { + describe('Array extensions', function() { + describe('first', function() { + return it('returns the first item', function() { + var arr; + arr = [0, 1, 2, 3, 4, 5]; + return expect(arr.first()).toBe(0); + }); + }); + describe('last', function() { + return it('returns the last item', function() { + var arr; + arr = [0, 1, 2, 3, 4, 5]; + return expect(arr.last()).toBe(5); + }); + }); + + describe('find', function () { + beforeEach(() => { + this.arr = [0, 1, 2, 3, 4, 5]; + }); + + it('returns the item that first passes the predicate function', () => { + expect(this.arr.find(item => item === 2)).toBe(2); + }); + + it('returns undefined if no items pass the predicate function', () => { + expect(this.arr.find(item => item === 6)).not.toBeDefined(); + }); + + it('error when called on undefined or null', () => { + expect(Array.prototype.find.bind(undefined, item => item === 1)).toThrow(); + expect(Array.prototype.find.bind(null, item => item === 1)).toThrow(); + }); + + it('error when predicate is not a function', () => { + expect(Array.prototype.find.bind(this.arr, 1)).toThrow(); + }); + }); + }); + +}).call(this); diff --git a/spec/javascripts/extensions/element_spec.js.es6 b/spec/javascripts/extensions/element_spec.js.es6 new file mode 100644 index 00000000000..c5b86d35204 --- /dev/null +++ b/spec/javascripts/extensions/element_spec.js.es6 @@ -0,0 +1,38 @@ +/*= require extensions/element */ + +(() => { + describe('Element extensions', function () { + beforeEach(() => { + this.element = document.createElement('ul'); + }); + + describe('matches', () => { + it('returns true if element matches the selector', () => { + expect(this.element.matches('ul')).toBeTruthy(); + }); + + it("returns false if element doesn't match the selector", () => { + expect(this.element.matches('.not-an-element')).toBeFalsy(); + }); + }); + + describe('closest', () => { + beforeEach(() => { + this.childElement = document.createElement('li'); + this.element.appendChild(this.childElement); + }); + + it('returns the closest parent that matches the selector', () => { + expect(this.childElement.closest('ul').toString()).toBe(this.element.toString()); + }); + + it('returns itself if it matches the selector', () => { + expect(this.childElement.closest('li').toString()).toBe(this.childElement.toString()); + }); + + it('returns undefined if nothing matches the selector', () => { + expect(this.childElement.closest('.no-an-element')).toBeFalsy(); + }); + }); + }); +})(); diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js index 9c361bb0867..76309930f27 100644 --- a/spec/javascripts/extensions/jquery_spec.js +++ b/spec/javascripts/extensions/jquery_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, padded-blocks */ /*= require extensions/jquery */ diff --git a/spec/javascripts/extensions/object_spec.js.es6 b/spec/javascripts/extensions/object_spec.js.es6 new file mode 100644 index 00000000000..3b71c255b30 --- /dev/null +++ b/spec/javascripts/extensions/object_spec.js.es6 @@ -0,0 +1,25 @@ +/*= require extensions/object */ + +describe('Object extensions', () => { + describe('assign', () => { + it('merges source object into target object', () => { + const targetObj = {}; + const sourceObj = { + foo: 'bar', + }; + Object.assign(targetObj, sourceObj); + expect(targetObj.foo).toBe('bar'); + }); + + it('merges object with the same properties', () => { + const targetObj = { + foo: 'bar', + }; + const sourceObj = { + foo: 'baz', + }; + Object.assign(targetObj, sourceObj); + expect(targetObj.foo).toBe('baz'); + }); + }); +}); diff --git a/spec/javascripts/fixtures/abuse_reports.html.haml b/spec/javascripts/fixtures/abuse_reports.html.haml deleted file mode 100644 index 2ec302abcb7..00000000000 --- a/spec/javascripts/fixtures/abuse_reports.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -.abuse-reports - .message#long - Cat ipsum dolor sit amet, hide head under blanket so no one can see. - Gate keepers of hell eat and than sleep on your face but hunt by meowing - loudly at 5am next to human slave food dispenser cats go for world - domination or chase laser, yet poop on grasses chirp at birds. Cat is love, - cat is life chase after silly colored fish toys around the house climb a - tree, wait for a fireman jump to fireman then scratch his face fall asleep - on the washing machine lies down always hungry so caticus cuteicus. Sit on - human. Spot something, big eyes, big eyes, crouch, shake butt, prepare to - pounce sleep in the bathroom sink hiss at vacuum cleaner hide head under - blanket so no one can see throwup on your pillow. - .message#short - Cat ipsum dolor sit amet, groom yourself 4 hours - checked, have your - beauty sleep 18 hours - checked, be fabulous for the rest of the day - - checked! for shake treat bag. diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb new file mode 100644 index 00000000000..de673f94d72 --- /dev/null +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let!(:abuse_report) { create(:abuse_report) } + let!(:abuse_report_with_short_message) { create(:abuse_report, message: 'SHORT MESSAGE') } + let!(:abuse_report_with_long_message) { create(:abuse_report, message: "LONG MESSAGE\n" * 50) } + + render_views + + before(:all) do + clean_frontend_fixtures('abuse_reports/') + end + + before(:each) do + sign_in(admin) + end + + it 'abuse_reports/abuse_reports_list.html.raw' do |example| + get :index + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/fixtures/application.html.haml b/spec/javascripts/fixtures/application.html.haml deleted file mode 100644 index 3fc6114407d..00000000000 --- a/spec/javascripts/fixtures/application.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%a#test-link.btn.disabled{:href => "/foo"} Test link -%button#test-button.btn.disabled Test Button diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml deleted file mode 100644 index 1ef2e8f8624..00000000000 --- a/spec/javascripts/fixtures/awards_handler.html.haml +++ /dev/null @@ -1,52 +0,0 @@ -.issue-details.issuable-details - .detail-page-description.content-block - %h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem. - .description.js-task-list-container.is-task-list-enabled - .wiki - %p Qui exercitationem magnam optio quae fuga earum odio. - %textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio. - %small.edited-text - .content-block.content-block-small - .awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"} - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"} - %span.award-control-text.js-counter 0 - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"} - %span.award-control-text.js-counter 0 - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add - %section.issuable-discussion - #notes - %ul#notes-list.notes.main-notes-list.timeline - %li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""} - .timeline-entry-inner - .timeline-icon - %a{:href => "/u/agustin"} - %img.avatar.s40{:alt => "", :src => "#"}/ - .timeline-content - .note-header - %a.author_link{:href => "/u/agustin"} - %span.author Brenna Stokes - .inline.note-headline-light - @agustin commented - %a{:href => "#note_348"} - %time 11 days ago - .note-actions - %span.note-role Reporter - %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"} - %i.fa.fa-spinner.fa-spin - %i.fa.fa-smile-o.link-highlight - .js-task-list-container.note-body.is-task-list-enabled - .note-text - %p Suscipit sunt quia quisquam sed eveniet ipsam. - .note-awards - .awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"} - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add diff --git a/spec/javascripts/fixtures/builds.rb b/spec/javascripts/fixtures/builds.rb new file mode 100644 index 00000000000..978e25a1c32 --- /dev/null +++ b/spec/javascripts/fixtures/builds.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + let(:admin) { create(:admin) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) } + let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') } + let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') } + + render_views + + before(:all) do + clean_frontend_fixtures('builds/') + end + + before(:each) do + sign_in(admin) + end + + it 'builds/build-with-artifacts.html.raw' do |example| + get :show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: build_with_artifacts.to_param + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml deleted file mode 100644 index cc1f8f15c21..00000000000 --- a/spec/javascripts/fixtures/comments.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.flash-container.timeline-content -.timeline-icon.hidden-xs.hidden-sm - %a.author_link - %img -.timeline-content.timeline-content-form - %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form - .md-area - .md-header - .md-write-holder - .zen-backdrop.div-dropzone-wrapper - .div-dropzone-wrapper - .div-dropzone.dz-clickable - %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area - .note-form-actions.clearfix - %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' } - %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen - Reopen issue - %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close - Close issue - %a.btn.btn-cancel.js-note-discard - Discard draft
\ No newline at end of file diff --git a/spec/javascripts/fixtures/emoji_menu.js b/spec/javascripts/fixtures/emoji_menu.js index 41cf40c29cf..3d776bb9277 100644 --- a/spec/javascripts/fixtures/emoji_menu.js +++ b/spec/javascripts/fixtures/emoji_menu.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, padded-blocks */ (function() { window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>"; diff --git a/spec/javascripts/fixtures/environments/element.html.haml b/spec/javascripts/fixtures/environments/element.html.haml new file mode 100644 index 00000000000..8d7aeb23356 --- /dev/null +++ b/spec/javascripts/fixtures/environments/element.html.haml @@ -0,0 +1 @@ +.test-dom-element diff --git a/spec/javascripts/fixtures/environments/environments.html.haml b/spec/javascripts/fixtures/environments/environments.html.haml new file mode 100644 index 00000000000..d89bc50c1f0 --- /dev/null +++ b/spec/javascripts/fixtures/environments/environments.html.haml @@ -0,0 +1,9 @@ +%div + #environments-list-view{ data: { environments_data: "https://gitlab.com/foo/environments", + "can-create-deployment" => "true", + "can-read-environment" => "true", + "can-create-environment" => "true", + "project-environments-path" => "https://gitlab.com/foo/environments", + "project-stopped-environments-path" => "https://gitlab.com/foo/environments?scope=stopped", + "new-environment-path" => "https://gitlab.com/foo/environments/new", + "help-page-path" => "https://gitlab.com/help_page"}} diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml new file mode 100644 index 00000000000..1ea1725c561 --- /dev/null +++ b/spec/javascripts/fixtures/environments/table.html.haml @@ -0,0 +1,11 @@ +%table + %thead + %tr + %th Environment + %th Last deployment + %th Build + %th Commit + %th + %th + %tbody + %tr#environment-row diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml index 95e248cadf8..5477c6075f0 100644 --- a/spec/javascripts/fixtures/event_filter.html.haml +++ b/spec/javascripts/fixtures/event_filter.html.haml @@ -12,6 +12,10 @@ %span Merge events %li + %a.event-filter-link{ id: "issue_event_filter", title: "Filter by issue events", href: "/dashboard/activity"} + %span + Issue events + %li %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"} %span Comments diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml deleted file mode 100644 index 42ab4aa68b1..00000000000 --- a/spec/javascripts/fixtures/issuable.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%form.js-main-target-form - %textarea#note_note diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml deleted file mode 100644 index 0aecc7334fd..00000000000 --- a/spec/javascripts/fixtures/issue_note.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%ul - %li.note - .js-task-list-container - .note-text - %ul.task-list - %li.task-list-item - %input.task-list-item-checkbox{type: 'checkbox'} - Task List Item - .note-edit-form - %form - %textarea.js-task-list-field - \- [ ] Task List Item diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index d95eb851421..06f708f9e15 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -4,7 +4,8 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller include JavaScriptFixturesHelpers let(:admin) { create(:admin) } - let(:project) { create(:project_empty_repo) } + let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} + let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') } render_views @@ -25,8 +26,13 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller end it 'issues/issue-with-task-list.html.raw' do |example| + issue = create(:issue, project: project, description: '- [ ] Task List Item') + render_issue(example.description, issue) + end + + it 'issues/issue_with_comment.html.raw' do |example| issue = create(:issue, project: project) - issue.update(description: '- [ ] Task List Item') + create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml new file mode 100644 index 00000000000..93c0cf97ff0 --- /dev/null +++ b/spec/javascripts/fixtures/linked_tabs.html.haml @@ -0,0 +1,13 @@ +%ul.nav.nav-tabs.linked-tabs + %li + %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } + Tab 1 + %li + %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } + Tab 2 + +.tab-content + #tab1.tab-pane + Tab 1 Content + #tab2.tab-pane + Tab 2 Content diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml new file mode 100644 index 00000000000..deca50ceaa7 --- /dev/null +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -0,0 +1,15 @@ +%div.pipeline-visualization.js-pipeline-graph + %ul.stage-column-list + %li.stage-column + .stage-name + %a{:href => "/"} + Test + .builds-container + %ul + %li.build + .curve + .build-content + %a + %svg + .ci-status-text + stop_review diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml deleted file mode 100644 index d48b77cf0ce..00000000000 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%div - %div.page-gutter.page-with-sidebar - - %aside.right-sidebar - %div.block.issuable-sidebar-header - %a.gutter-toggle.pull-right.js-sidebar-toggle - %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} - %span.js-issuable-todo-text - Add Todo - %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden - - %form.issuable-context-form - %div.block.labels - %div.sidebar-collapsed-icon - %i.fa.fa-tags - %span 1 diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml new file mode 100644 index 00000000000..12b8d423cbe --- /dev/null +++ b/spec/javascripts/fixtures/signin_tabs.html.haml @@ -0,0 +1,5 @@ +%ul.nav-tabs + %li + %a.active{ id: 'standard', href: '#standard'} Standard + %li + %a{ id: 'ldap', href: '#ldap'} Ldap diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml deleted file mode 100644 index cb906a7feaa..00000000000 --- a/spec/javascripts/fixtures/zen_mode.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.md-area - .zen-backdrop - %textarea#note_note.js-gfm-input.markdown-area - %a.js-zen-enter(tabindex="-1" href="#") - %i.fa.fa-expand - Edit in fullscreen - %a.js-zen-leave(tabindex="-1" href="#") - %i.fa.fa-compress diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 8ba238018cd..bfaf90e2aee 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, prefer-const, no-param-reassign, no-plusplus, semi, no-unused-expressions, arrow-spacing, max-len */ +/* global Turbolinks */ + /*= require jquery */ /*= require gl_dropdown */ /*= require turbolinks */ diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 0713e30e485..5018e87ad6c 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, arrow-body-style, indent, padded-blocks */ + //= require jquery //= require gl_field_errors diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index 8c66c45ba79..bc5cbeb6a40 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,4 +1,8 @@ -/* eslint-disable */ +/* eslint-disable quotes, indent, semi, object-curly-spacing, jasmine/no-suite-dupes, vars-on-top, no-var, padded-blocks, spaced-comment, max-len */ +/* global d3 */ +/* global ContributorsGraph */ +/* global ContributorsMasterGraph */ + //= require graphs/stat_graph_contributors_graph describe("ContributorsGraph", function () { diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 920e4ee0892..751f3d175e2 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable quotes, padded-blocks, no-var, camelcase, object-curly-spacing, semi, indent, object-property-newline, comma-dangle, comma-spacing, spaced-comment, max-len, key-spacing, vars-on-top, quote-props, no-multi-spaces */ +/* global ContributorsStatGraphUtil */ + //= require graphs/stat_graph_contributors_util describe("ContributorsStatGraphUtil", function () { diff --git a/spec/javascripts/graphs/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js index ae2821ecad9..0da124632ae 100644 --- a/spec/javascripts/graphs/stat_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable quotes, padded-blocks, semi */ +/* global StatGraph */ + //= require graphs/stat_graph describe("StatGraph", function () { diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 9a859655d8b..d2bcbc37b64 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, padded-blocks, no-var */ /*= require header */ /*= require lib/utils/text_utility */ /*= require jquery */ diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 949114185cf..faab5ae00c2 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, indent, no-trailing-spaces, comma-dangle, padded-blocks, max-len */ +/* global Issue */ /*= require lib/utils/text_utility */ /*= require issue */ @@ -70,11 +71,11 @@ $('input[type=checkbox]').attr('checked', true).trigger('change'); expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); }); - + it('submits an ajax request on tasklist:changed', function() { spyOn(jQuery, 'ajax').and.callFake(function(req) { expect(req.type).toBe('PATCH'); - expect(req.url).toBe('https://fixture.invalid/namespace3/project3/issues/1.json'); + expect(req.url).toBe(gl.TEST_HOST + '/frontend-fixtures/issues-project/issues/1.json'); // eslint-disable-line prefer-template expect(req.data.issue.description).not.toBe(null); }); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6 index 49687048eb5..0c48d04776f 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js.es6 +++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6 @@ -1,4 +1,7 @@ -/* eslint-disable */ +/* eslint-disable no-new, no-plusplus, object-curly-spacing, prefer-const, semi */ +/* global IssuableContext */ +/* global LabelsSelect */ + //= require lib/utils/type_utility //= require jquery //= require bootstrap diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6 new file mode 100644 index 00000000000..ef75f600898 --- /dev/null +++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6 @@ -0,0 +1,32 @@ +//= require lib/utils/common_utils + +(() => { + describe('common_utils', () => { + describe('gl.utils.parseUrl', () => { + it('returns an anchor tag with url', () => { + expect(gl.utils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url'); + }); + it('url is escaped', () => { + // IE11 will return a relative pathname while other browsers will return a full pathname. + // parseUrl uses an anchor element for parsing an url. With relative urls, the anchor + // element will create an absolute url relative to the current execution context. + // The JavaScript test suite is executed at '/teaspoon' which will lead to an absolute + // url starting with '/teaspoon'. + expect(gl.utils.parseUrl('" test="asf"').pathname).toEqual('/teaspoon/%22%20test=%22asf%22'); + }); + }); + describe('gl.utils.parseUrlPathname', () => { + beforeEach(() => { + spyOn(gl.utils, 'parseUrl').and.callFake(url => ({ + pathname: url, + })); + }); + it('returns an absolute url when given an absolute url', () => { + expect(gl.utils.parseUrlPathname('/some/absolute/url')).toEqual('/some/absolute/url'); + }); + it('returns an absolute url when given a relative url', () => { + expect(gl.utils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url'); + }); + }); + }); +})(); diff --git a/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 b/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 new file mode 100644 index 00000000000..3645dd70c55 --- /dev/null +++ b/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 @@ -0,0 +1,43 @@ +//= require lib/utils/custom_event_polyfill + +describe('Custom Event Polyfill', () => { + it('should be defined', () => { + expect(window.CustomEvent).toBeDefined(); + }); + + it('should create a `CustomEvent` instance', () => { + const e = new window.CustomEvent('foo'); + + expect(e.type).toEqual('foo'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(false); + expect(e.detail).toBeFalsy(); + }); + + it('should create a `CustomEvent` instance with a `details` object', () => { + const e = new window.CustomEvent('bar', { detail: { foo: 'bar' } }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(false); + expect(e.detail.foo).toEqual('bar'); + }); + + it('should create a `CustomEvent` instance with a `bubbles` boolean', () => { + const e = new window.CustomEvent('bar', { bubbles: true }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(true); + expect(e.cancelable).toBe(false); + expect(e.detail).toBeFalsy(); + }); + + it('should create a `CustomEvent` instance with a `cancelable` boolean', () => { + const e = new window.CustomEvent('bar', { cancelable: true }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(true); + expect(e.detail).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index e0192a2d624..decdf583410 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-plusplus, jasmine/no-spec-dupes, no-underscore-dangle, padded-blocks, max-len */ +/* global LineHighlighter */ /*= require line_highlighter */ diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 83d279ab414..4cf1693af1b 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-return-assign, padded-blocks */ +/* global MergeRequest */ /*= require merge_request */ diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 6a53c6aa6ac..130d391bfab 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -1,108 +1,122 @@ -/* eslint-disable */ +/* eslint-disable no-var, comma-dangle, object-shorthand */ /*= require merge_request_tabs */ //= require breakpoints +//= require lib/utils/common_utils +//= require jquery.scrollTo -(function() { - describe('MergeRequestTabs', function() { - var stubLocation; - stubLocation = function(stubs) { - var defaults; - defaults = { +(function () { + describe('MergeRequestTabs', function () { + var stubLocation = {}; + var setLocation = function (stubs) { + var defaults = { pathname: '', search: '', hash: '' }; - return $.extend(defaults, stubs); + $.extend(stubLocation, defaults, stubs || {}); }; fixture.preload('merge_request_tabs.html'); - beforeEach(function() { - this["class"] = new MergeRequestTabs(); - return this.spies = { - ajax: spyOn($, 'ajax').and.callFake(function() {}), - history: spyOn(history, 'replaceState').and.callFake(function() {}) + + beforeEach(function () { + this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation }); + setLocation(); + + this.spies = { + history: spyOn(window.history, 'replaceState').and.callFake(function () {}) }; }); - describe('#activateTab', function() { - beforeEach(function() { + + describe('#activateTab', function () { + beforeEach(function () { + spyOn($, 'ajax').and.callFake(function () {}); fixture.load('merge_request_tabs.html'); - return this.subject = this["class"].activateTab; + this.subject = this.class.activateTab; }); - it('shows the first tab when action is show', function() { + it('shows the first tab when action is show', function () { this.subject('show'); - return expect($('#notes')).toHaveClass('active'); + expect($('#notes')).toHaveClass('active'); }); - it('shows the notes tab when action is notes', function() { + it('shows the notes tab when action is notes', function () { this.subject('notes'); - return expect($('#notes')).toHaveClass('active'); + expect($('#notes')).toHaveClass('active'); }); - it('shows the commits tab when action is commits', function() { + it('shows the commits tab when action is commits', function () { this.subject('commits'); - return expect($('#commits')).toHaveClass('active'); + expect($('#commits')).toHaveClass('active'); }); - return it('shows the diffs tab when action is diffs', function() { + it('shows the diffs tab when action is diffs', function () { this.subject('diffs'); - return expect($('#diffs')).toHaveClass('active'); + expect($('#diffs')).toHaveClass('active'); }); }); - return describe('#setCurrentAction', function() { - beforeEach(function() { - return this.subject = this["class"].setCurrentAction; + + describe('#setCurrentAction', function () { + beforeEach(function () { + spyOn($, 'ajax').and.callFake(function () {}); + this.subject = this.class.setCurrentAction; }); - it('changes from commits', function() { - this["class"]._location = stubLocation({ + it('changes from commits', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/commits' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); + expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); }); - it('changes from diffs', function() { - this["class"]._location = stubLocation({ + it('changes from diffs', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('changes from diffs.html', function() { - this["class"]._location = stubLocation({ + it('changes from diffs.html', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs.html' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('changes from notes', function() { - this["class"]._location = stubLocation({ + it('changes from notes', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1' }); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('includes search parameters and hash string', function() { - this["class"]._location = stubLocation({ + it('includes search parameters and hash string', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs', search: '?view=parallel', hash: '#L15-35' }); - return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); }); - it('replaces the current history state', function() { - var new_state; - this["class"]._location = stubLocation({ + it('replaces the current history state', function () { + var newState; + setLocation({ pathname: '/foo/bar/merge_requests/1' }); - new_state = this.subject('commits'); - return expect(this.spies.history).toHaveBeenCalledWith({ + newState = this.subject('commits'); + expect(this.spies.history).toHaveBeenCalledWith({ turbolinks: true, - url: new_state - }, document.title, new_state); + url: newState + }, document.title, newState); }); - return it('treats "show" like "notes"', function() { - this["class"]._location = stubLocation({ + it('treats "show" like "notes"', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/commits' }); - return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); + }); + }); + describe('#loadDiff', function () { + it('requires an absolute pathname', function () { + spyOn($, 'ajax').and.callFake(function (options) { + expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json'); + }); + this.class.loadDiff('/foo/bar/merge_requests/1/diffs'); }); }); }); - }).call(this); diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 49dfeab61d8..6f91529db00 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,6 +1,7 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, indent, quote-props, no-var, padded-blocks, max-len */ + /*= require merge_request_widget */ -/*= require jquery.timeago.js */ +/*= require lib/utils/datetime_utility */ (function() { describe('MergeRequestWidget', function() { @@ -35,9 +36,9 @@ external_url_formatted: 'test-url.com' }]; - spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { + spyOn(jQuery, 'getJSON').and.callFake(function(req, cb) { cb(this.ciEnvironmentsStatusData); - }); + }.bind(this)); }); it('should call renderEnvironments when the environments property is set', function() { @@ -54,6 +55,69 @@ }); }); + describe('renderEnvironments', function() { + describe('should render correct timeago', function() { + beforeEach(function() { + this.environments = [{ + id: 'test-environment-id', + url: 'testurl', + deployed_at: new Date().toISOString(), + deployed_at_formatted: true + }]; + }); + + function getTimeagoText(template) { + var el = document.createElement('html'); + el.innerHTML = template; + return el.querySelector('.js-environment-timeago').innerText.trim(); + } + + it('should render less than a minute ago text', function() { + spyOn(this.class.$widgetBody, 'before').and.callFake(function(template) { + expect(getTimeagoText(template)).toBe('less than a minute ago.'); + }); + + this.class.renderEnvironments(this.environments); + }); + + it('should render about an hour ago text', function() { + var oneHourAgo = new Date(); + oneHourAgo.setHours(oneHourAgo.getHours() - 1); + + this.environments[0].deployed_at = oneHourAgo.toISOString(); + spyOn(this.class.$widgetBody, 'before').and.callFake(function(template) { + expect(getTimeagoText(template)).toBe('about an hour ago.'); + }); + + this.class.renderEnvironments(this.environments); + }); + + it('should render about 2 hours ago text', function() { + var twoHoursAgo = new Date(); + twoHoursAgo.setHours(twoHoursAgo.getHours() - 2); + + this.environments[0].deployed_at = twoHoursAgo.toISOString(); + spyOn(this.class.$widgetBody, 'before').and.callFake(function(template) { + expect(getTimeagoText(template)).toBe('about 2 hours ago.'); + }); + + this.class.renderEnvironments(this.environments); + }); + }); + }); + + describe('mergeInProgress', function() { + it('should display error with h4 tag', function() { + spyOn(this.class.$widgetBody, 'html').and.callFake(function(html) { + expect(html).toBe('<h4>Sorry, something went wrong.</h4>'); + }); + spyOn($, 'ajax').and.callFake(function(e) { + e.success({ merge_error: 'Sorry, something went wrong.' }); + }); + this.class.mergeInProgress(null); + }); + }); + return describe('getCIStatus', function() { beforeEach(function() { this.ciStatusData = { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index c092424ec32..a6cb9e47744 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, padded-blocks, max-len */ +/* global NewBranchForm */ /*= require jquery-ui/autocomplete */ /*= require new_branch_form */ diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 2e3a4b66e2d..d3bfa7730fa 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, semi, padded-blocks, max-len */ +/* global Notes */ + /*= require notes */ /*= require autosize */ /*= require gl_form */ @@ -6,17 +8,21 @@ (function() { window.gon || (window.gon = {}); - - window.disableButtonIfEmptyField = function() { - return null; - }; + window.gl = window.gl || {}; + gl.utils = gl.utils || {}; describe('Notes', function() { - describe('task lists', function() { - fixture.preload('issue_note.html'); + var commentsTemplate = 'issues/issue_with_comment.raw'; + fixture.preload(commentsTemplate); + beforeEach(function () { + fixture.load(commentsTemplate); + gl.utils.disableButtonIfEmptyField = _.noop; + window.project_uploads_path = 'http://test.host/uploads'; + }); + + describe('task lists', function() { beforeEach(function() { - fixture.load('issue_note.html'); $('form').on('submit', function(e) { e.preventDefault(); }); @@ -41,12 +47,9 @@ }); describe('comments', function() { - var commentsTemplate = 'comments.html'; var textarea = '.js-note-text'; - fixture.preload(commentsTemplate); beforeEach(function() { - fixture.load(commentsTemplate); this.notes = new Notes(); this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update'); diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js.es6 new file mode 100644 index 00000000000..85c9cf4b4f1 --- /dev/null +++ b/spec/javascripts/pipelines_spec.js.es6 @@ -0,0 +1,25 @@ +//= require pipelines + +(() => { + describe('Pipelines', () => { + fixture.preload('pipeline_graph'); + + beforeEach(() => { + fixture.load('pipeline_graph'); + }); + + it('should be defined', () => { + expect(window.gl.Pipelines).toBeDefined(); + }); + + it('should create a `Pipelines` instance without options', () => { + expect(() => { new window.gl.Pipelines(); }).not.toThrow(); //eslint-disable-line + }); + + it('should create a `Pipelines` instance with options', () => { + const pipelines = new window.gl.Pipelines({ foo: 'bar' }); + + expect(pipelines.pipelineGraph).toBeDefined(); + }); + }); +})(); diff --git a/spec/javascripts/pretty_time_spec.js.es6 b/spec/javascripts/pretty_time_spec.js.es6 new file mode 100644 index 00000000000..2e12d45f7a7 --- /dev/null +++ b/spec/javascripts/pretty_time_spec.js.es6 @@ -0,0 +1,134 @@ +//= require lib/utils/pretty_time + +(() => { + const PrettyTime = gl.PrettyTime; + + describe('PrettyTime methods', function () { + describe('parseSeconds', function () { + it('should correctly parse a negative value', function () { + const parser = PrettyTime.parseSeconds; + + const zeroSeconds = parser(-1000); + + expect(zeroSeconds.minutes).toBe(16); + expect(zeroSeconds.hours).toBe(0); + expect(zeroSeconds.days).toBe(0); + expect(zeroSeconds.weeks).toBe(0); + }); + + it('should correctly parse a zero value', function () { + const parser = PrettyTime.parseSeconds; + + const zeroSeconds = parser(0); + + expect(zeroSeconds.minutes).toBe(0); + expect(zeroSeconds.hours).toBe(0); + expect(zeroSeconds.days).toBe(0); + expect(zeroSeconds.weeks).toBe(0); + }); + + it('should correctly parse a small non-zero second values', function () { + const parser = PrettyTime.parseSeconds; + + const subOneMinute = parser(10); + + expect(subOneMinute.minutes).toBe(0); + expect(subOneMinute.hours).toBe(0); + expect(subOneMinute.days).toBe(0); + expect(subOneMinute.weeks).toBe(0); + + const aboveOneMinute = parser(100); + + expect(aboveOneMinute.minutes).toBe(1); + expect(aboveOneMinute.hours).toBe(0); + expect(aboveOneMinute.days).toBe(0); + expect(aboveOneMinute.weeks).toBe(0); + + const manyMinutes = parser(1000); + + expect(manyMinutes.minutes).toBe(16); + expect(manyMinutes.hours).toBe(0); + expect(manyMinutes.days).toBe(0); + expect(manyMinutes.weeks).toBe(0); + }); + + it('should correctly parse large second values', function () { + const parser = PrettyTime.parseSeconds; + + const aboveOneHour = parser(4800); + + expect(aboveOneHour.minutes).toBe(20); + expect(aboveOneHour.hours).toBe(1); + expect(aboveOneHour.days).toBe(0); + expect(aboveOneHour.weeks).toBe(0); + + const aboveOneDay = parser(110000); + + expect(aboveOneDay.minutes).toBe(33); + expect(aboveOneDay.hours).toBe(6); + expect(aboveOneDay.days).toBe(3); + expect(aboveOneDay.weeks).toBe(0); + + const aboveOneWeek = parser(25000000); + + expect(aboveOneWeek.minutes).toBe(26); + expect(aboveOneWeek.hours).toBe(0); + expect(aboveOneWeek.days).toBe(3); + expect(aboveOneWeek.weeks).toBe(173); + }); + }); + + describe('stringifyTime', function () { + it('should stringify values with all non-zero units', function () { + const timeObject = { + weeks: 1, + days: 4, + hours: 7, + minutes: 20, + }; + + const timeString = PrettyTime.stringifyTime(timeObject); + + expect(timeString).toBe('1w 4d 7h 20m'); + }); + + it('should stringify values with some non-zero units', function () { + const timeObject = { + weeks: 0, + days: 4, + hours: 0, + minutes: 20, + }; + + const timeString = PrettyTime.stringifyTime(timeObject); + + expect(timeString).toBe('4d 20m'); + }); + + it('should stringify values with no non-zero units', function () { + const timeObject = { + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + }; + + const timeString = PrettyTime.stringifyTime(timeObject); + + expect(timeString).toBe('0m'); + }); + }); + + describe('abbreviateTime', function () { + it('should abbreviate stringified times for weeks', function () { + const fullTimeString = '1w 3d 4h 5m'; + expect(PrettyTime.abbreviateTime(fullTimeString)).toBe('1w'); + }); + + it('should abbreviate stringified times for non-weeks', function () { + const fullTimeString = '0w 3d 4h 5m'; + expect(PrettyTime.abbreviateTime(fullTimeString)).toBe('3d'); + }); + }); + }); +})(window.gl || (window.gl = {})); diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 1963857bba3..bb802a4b5e3 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, padded-blocks, max-len */ + +/* global Project */ /*= require bootstrap */ /*= require select2 */ @@ -21,16 +23,18 @@ return this.project = new Project(); }); return describe('project list', function() { + var fakeAjaxResponse = function fakeAjaxResponse(req) { + var d; + expect(req.url).toBe('/api/v3/projects.json?simple=true'); + d = $.Deferred(); + d.resolve(this.projects_data); + return d.promise(); + }; + beforeEach((function(_this) { return function() { _this.projects_data = fixture.load('projects.json')[0]; - return spyOn(jQuery, 'ajax').and.callFake(function(req) { - var d; - expect(req.url).toBe('/api/v3/projects.json?simple=true'); - d = $.Deferred(); - d.resolve(_this.projects_data); - return d.promise(); - }); + return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this)); }; })(this)); it('to show on toggle click', (function(_this) { diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index ef03d1147de..a083dbf033a 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, semi, padded-blocks, max-len */ +/* global Sidebar */ /*= require right_sidebar */ /*= require jquery */ @@ -34,9 +35,10 @@ }; describe('RightSidebar', function() { - fixture.preload('right_sidebar.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('right_sidebar.html'); + fixture.load(fixtureName); this.sidebar = new Sidebar; $aside = $('.right-sidebar'); $page = $('.page-with-sidebar'); @@ -44,15 +46,12 @@ $toggle = $aside.find('.js-sidebar-toggle'); return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); }); - it('should expand the sidebar when arrow is clicked', function() { + it('should expand/collapse the sidebar when arrow is clicked', function() { + assertSidebarState('expanded'); $toggle.click(); - return assertSidebarState('expanded'); - }); - it('should collapse the sidebar when arrow is clicked', function() { + assertSidebarState('collapsed'); $toggle.click(); assertSidebarState('expanded'); - $toggle.click(); - return assertSidebarState('collapsed'); }); it('should float over the page and when sidebar icons clicked', function() { $labelsIcon.click(); diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 29080804960..1b7f642d59e 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, padded-blocks, max-len */ /*= require gl_dropdown */ /*= require search_autocomplete */ diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 1f36a048153..7bc898aed5d 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -1,12 +1,15 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes, padded-blocks */ +/* global ShortcutsIssuable */ /*= require shortcuts_issuable */ (function() { describe('ShortcutsIssuable', function() { - fixture.preload('issuable.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('issuable.html'); + fixture.load(fixtureName); + document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); return this.shortcut = new ShortcutsIssuable(); }); return describe('#replyWithSelectedText', function() { diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js.es6 b/spec/javascripts/signin_tabs_memoizer_spec.js.es6 new file mode 100644 index 00000000000..9a9fb22255b --- /dev/null +++ b/spec/javascripts/signin_tabs_memoizer_spec.js.es6 @@ -0,0 +1,53 @@ +/*= require signin_tabs_memoizer */ + +((global) => { + describe('SigninTabsMemoizer', () => { + const fixtureTemplate = 'signin_tabs.html'; + const tabSelector = 'ul.nav-tabs'; + const currentTabKey = 'current_signin_tab'; + let memo; + + function createMemoizer() { + memo = new global.ActiveTabMemoizer({ + currentTabKey, + tabSelector, + }); + return memo; + } + + fixture.preload(fixtureTemplate); + + beforeEach(() => { + fixture.load(fixtureTemplate); + }); + + it('does nothing if no tab was previously selected', () => { + createMemoizer(); + + expect(document.querySelector('li a.active').getAttribute('id')).toEqual('standard'); + }); + + 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('saves last selected tab on change', () => { + createMemoizer(); + + document.getElementById('standard').click(); + + expect(memo.readData()).toEqual('#standard'); + }); + }); +})(window); diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6 new file mode 100644 index 00000000000..1b7ca97cde4 --- /dev/null +++ b/spec/javascripts/smart_interval_spec.js.es6 @@ -0,0 +1,180 @@ +//= require jquery +//= require smart_interval + +(() => { + const DEFAULT_MAX_INTERVAL = 100; + const DEFAULT_STARTING_INTERVAL = 5; + const DEFAULT_SHORT_TIMEOUT = 75; + const DEFAULT_LONG_TIMEOUT = 1000; + const DEFAULT_INCREMENT_FACTOR = 2; + + function createDefaultSmartInterval(config) { + const defaultParams = { + callback: () => {}, + startingInterval: DEFAULT_STARTING_INTERVAL, + maxInterval: DEFAULT_MAX_INTERVAL, + incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, + lazyStart: false, + immediateExecution: false, + hiddenInterval: null, + }; + + if (config) { + _.extend(defaultParams, config); + } + + return new gl.SmartInterval(defaultParams); + } + + describe('SmartInterval', function () { + describe('Increment Interval', function () { + beforeEach(function () { + this.smartInterval = createDefaultSmartInterval(); + }); + + it('should increment the interval delay', function (done) { + const interval = this.smartInterval; + setTimeout(() => { + const intervalConfig = this.smartInterval.cfg; + const iterationCount = 4; + const maxIntervalAfterIterations = intervalConfig.startingInterval * + (intervalConfig.incrementByFactorOf ** (iterationCount - 1)); // 40 + const currentInterval = interval.getCurrentInterval(); + + // Provide some flexibility for performance of testing environment + expect(currentInterval).toBeGreaterThan(intervalConfig.startingInterval); + expect(currentInterval <= maxIntervalAfterIterations).toBeTruthy(); + + done(); + }, DEFAULT_SHORT_TIMEOUT); // 4 iterations, increment by 2x = (5 + 10 + 20 + 40) + }); + + it('should not increment past maxInterval', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + const currentInterval = interval.getCurrentInterval(); + expect(currentInterval).toBe(interval.cfg.maxInterval); + + done(); + }, DEFAULT_LONG_TIMEOUT); + }); + }); + + describe('Public methods', function () { + beforeEach(function () { + this.smartInterval = createDefaultSmartInterval(); + }); + + it('should cancel an interval', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + interval.cancel(); + + const intervalId = interval.state.intervalId; + const currentInterval = interval.getCurrentInterval(); + const intervalLowerLimit = interval.cfg.startingInterval; + + expect(intervalId).toBeUndefined(); + expect(currentInterval).toBe(intervalLowerLimit); + + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + + it('should resume an interval', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + interval.cancel(); + + interval.resume(); + + const intervalId = interval.state.intervalId; + + expect(intervalId).toBeTruthy(); + + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + }); + + describe('DOM Events', function () { + beforeEach(function () { + // This ensures DOM and DOM events are initialized for these specs. + fixture.set('<div></div>'); + + this.smartInterval = createDefaultSmartInterval(); + }); + + it('should pause when page is not visible', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + expect(interval.state.intervalId).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeUndefined(); + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + + it('should change to the hidden interval when page is not visible', function (done) { + const HIDDEN_INTERVAL = 1500; + const interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL }); + + setTimeout(() => { + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL && + interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL); + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + + it('should resume when page is becomes visible at the previous interval', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + expect(interval.state.intervalId).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeUndefined(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'visible' } }); + + expect(interval.state.intervalId).toBeTruthy(); + + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + + it('should cancel on page unload', function (done) { + const interval = this.smartInterval; + + setTimeout(() => { + $(document).trigger('page:before-unload'); + expect(interval.state.intervalId).toBeUndefined(); + expect(interval.getCurrentInterval()).toBe(interval.cfg.startingInterval); + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + + it('should execute callback before first interval', function () { + const interval = createDefaultSmartInterval({ immediateExecution: true }); + expect(interval.cfg.immediateExecution).toBeFalsy(); + }); + }); + }); +})(window.gl || (window.gl = {})); diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js index 9cb8243ee2c..831dfada952 100644 --- a/spec/javascripts/spec_helper.js +++ b/spec/javascripts/spec_helper.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren */ // PhantomJS (Teaspoons default driver) doesn't have support for // Function.prototype.bind, which has caused confusion. Use this polyfill to // avoid the confusion. @@ -41,3 +41,8 @@ }).call(this); + +// defined in ActionDispatch::TestRequest +// see https://github.com/rails/rails/blob/v4.2.7.1/actionpack/lib/action_dispatch/testing/test_request.rb#L7 +window.gl = window.gl || {}; +gl.TEST_HOST = 'http://test.host'; diff --git a/spec/javascripts/subbable_resource_spec.js.es6 b/spec/javascripts/subbable_resource_spec.js.es6 new file mode 100644 index 00000000000..6a70dd856a7 --- /dev/null +++ b/spec/javascripts/subbable_resource_spec.js.es6 @@ -0,0 +1,66 @@ +/* eslint-disable max-len, arrow-parens, comma-dangle, no-plusplus */ + +//= vue +//= vue-resource +//= require jquery +//= require subbable_resource + +/* +* Test that each rest verb calls the publish and subscribe function and passes the correct value back +* +* +* */ +((global) => { + describe('Subbable Resource', function () { + describe('PubSub', function () { + beforeEach(function () { + this.MockResource = new global.SubbableResource('https://example.com'); + }); + it('should successfully add a single subscriber', function () { + const callback = () => {}; + this.MockResource.subscribe(callback); + + expect(this.MockResource.subscribers.length).toBe(1); + expect(this.MockResource.subscribers[0]).toBe(callback); + }); + + it('should successfully add multiple subscribers', function () { + const callbackOne = () => {}; + const callbackTwo = () => {}; + const callbackThree = () => {}; + + this.MockResource.subscribe(callbackOne); + this.MockResource.subscribe(callbackTwo); + this.MockResource.subscribe(callbackThree); + + expect(this.MockResource.subscribers.length).toBe(3); + }); + + it('should successfully publish an update to a single subscriber', function () { + const state = { myprop: 1 }; + + const callbacks = { + one: (data) => expect(data.myprop).toBe(2), + two: (data) => expect(data.myprop).toBe(2), + three: (data) => expect(data.myprop).toBe(2) + }; + + const spyOne = spyOn(callbacks, 'one'); + const spyTwo = spyOn(callbacks, 'two'); + const spyThree = spyOn(callbacks, 'three'); + + this.MockResource.subscribe(callbacks.one); + this.MockResource.subscribe(callbacks.two); + this.MockResource.subscribe(callbacks.three); + + state.myprop++; + + this.MockResource.publish(state); + + expect(spyOne).toHaveBeenCalled(); + expect(spyTwo).toHaveBeenCalled(); + expect(spyThree).toHaveBeenCalled(); + }); + }); + }); +})(window.gl || (window.gl = {})); diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js index 498f0f06797..ac411f6c306 100644 --- a/spec/javascripts/syntax_highlight_spec.js +++ b/spec/javascripts/syntax_highlight_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes, padded-blocks */ /*= require syntax_highlight */ diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 024a91f0a80..a8874ab12d3 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* global MockU2FDevice */ +/* global U2FAuthenticate */ /*= require u2f/authenticate */ /*= require u2f/util */ diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index ad133682fb1..1459f968c3d 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, padded-blocks, max-len */ (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index abea76f622f..189592ea87a 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* global MockU2FDevice */ +/* global U2FRegister */ /*= require u2f/register */ /*= require u2f/util */ diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6 new file mode 100644 index 00000000000..26dfdb94aae --- /dev/null +++ b/spec/javascripts/vue_common_components/commit_spec.js.es6 @@ -0,0 +1,131 @@ +//= require vue_common_component/commit + +describe('Commit component', () => { + let props; + let component; + + it('should render a code-fork icon if it does not represent a tag', () => { + fixture.set('<div class="test-commit-container"></div>'); + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: 'Commit message', + author: { + avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', + web_url: 'https://gitlab.com/jschatz1', + username: 'jschatz1', + }, + }, + }); + + expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-code-fork'); + }); + + describe('Given all the props', () => { + beforeEach(() => { + fixture.set('<div class="test-commit-container"></div>'); + + props = { + tag: true, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: 'Commit message', + author: { + avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', + web_url: 'https://gitlab.com/jschatz1', + username: 'jschatz1', + }, + commitIconSvg: '<svg></svg>', + }; + + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: props, + }); + }); + + it('should render a tag icon if it represents a tag', () => { + expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag'); + }); + + it('should render a link to the ref url', () => { + expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commitRef.ref_url); + }); + + it('should render the ref name', () => { + expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commitRef.name); + }); + + it('should render the commit short sha with a link to the commit url', () => { + expect(component.$el.querySelector('.commit-id').getAttribute('href')).toEqual(props.commitUrl); + expect(component.$el.querySelector('.commit-id').textContent).toContain(props.shortSha); + }); + + it('should render the given commitIconSvg', () => { + expect(component.$el.querySelector('.js-commit-icon').children).toContain('svg'); + }); + + describe('Given commit title and author props', () => { + it('should render a link to the author profile', () => { + expect( + component.$el.querySelector('.commit-title .avatar-image-container').getAttribute('href'), + ).toEqual(props.author.web_url); + }); + + it('Should render the author avatar with title and alt attributes', () => { + expect( + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('title'), + ).toContain(props.author.username); + expect( + component.$el.querySelector('.commit-title .avatar-image-container img').getAttribute('alt'), + ).toContain(`${props.author.username}'s avatar`); + }); + }); + + it('should render the commit title', () => { + expect( + component.$el.querySelector('a.commit-row-message').getAttribute('href'), + ).toEqual(props.commitUrl); + expect( + component.$el.querySelector('a.commit-row-message').textContent, + ).toContain(props.title); + }); + }); + + describe('When commit title is not provided', () => { + it('should render default message', () => { + fixture.set('<div class="test-commit-container"></div>'); + props = { + tag: false, + commitRef: { + name: 'master', + ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', + }, + commitUrl: 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067', + shortSha: 'b7836edd', + title: null, + author: {}, + }; + + component = new window.gl.CommitComponent({ + el: document.querySelector('.test-commit-container'), + propsData: props, + }); + + expect( + component.$el.querySelector('.commit-title span').textContent, + ).toContain('Cant find HEAD commit for this branch'); + }); + }); +}); diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 65b6e3dce33..d80ce5a7f7e 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -1,4 +1,7 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, padded-blocks, max-len */ +/* global Dropzone */ +/* global Mousetrap */ +/* global ZenMode */ /*= require zen_mode */ @@ -6,9 +9,10 @@ var enterZen, escapeKeydown, exitZen; describe('ZenMode', function() { - fixture.preload('zen_mode.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('zen_mode.html'); + fixture.load(fixtureName); spyOn(Dropzone, 'forElement').and.callFake(function() { return { enable: function() { @@ -60,11 +64,11 @@ }); enterZen = function() { - return $('a.js-zen-enter').click(); + return $('.js-zen-enter').click(); }; exitZen = function() { // Ohmmmmmmm - return $('a.js-zen-leave').click(); + return $('.js-zen-leave').click(); }; escapeKeydown = function() { |