summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/awards_handler_spec.js25
-rw-r--r--spec/javascripts/blob/3d_viewer/mesh_object_spec.js42
-rw-r--r--spec/javascripts/blob/pdf/index_spec.js80
-rw-r--r--spec/javascripts/blob/pdf/test.pdfbin0 -> 11956 bytes
-rw-r--r--spec/javascripts/blob/sketch/index_spec.js118
-rw-r--r--spec/javascripts/boards/board_list_spec.js201
-rw-r--r--spec/javascripts/build_spec.js98
-rw-r--r--spec/javascripts/comment_type_toggle_spec.js157
-rw-r--r--spec/javascripts/droplab/constants_spec.js29
-rw-r--r--spec/javascripts/droplab/drop_down_spec.js593
-rw-r--r--spec/javascripts/droplab/hook_spec.js82
-rw-r--r--spec/javascripts/droplab/plugins/input_setter_spec.js212
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js15
-rw-r--r--spec/javascripts/environments/environment_spec.js104
-rw-r--r--spec/javascripts/environments/environments_store_spec.js110
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js5
-rw-r--r--spec/javascripts/environments/mock_data.js16
-rw-r--r--spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js166
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js8
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js6
-rw-r--r--spec/javascripts/filtered_search/services/recent_searches_service_spec.js56
-rw-r--r--spec/javascripts/filtered_search/stores/recent_searches_store_spec.js59
-rw-r--r--spec/javascripts/fixtures/environments/metrics.html.haml64
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb7
-rw-r--r--spec/javascripts/fixtures/pdf_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/sketch_viewer.html.haml2
-rw-r--r--spec/javascripts/issue_show/issue_title_spec.js22
-rw-r--r--spec/javascripts/issue_spec.js194
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js51
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js41
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js93
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js148
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js45
-rw-r--r--spec/javascripts/merge_request_widget_spec.js7
-rw-r--r--spec/javascripts/merged_buttons_spec.js44
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js33
-rw-r--r--spec/javascripts/test_bundle.js1
-rw-r--r--spec/javascripts/u2f/register_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/async_button_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/empty_state_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/error_state_spec.js2
-rw-r--r--spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js15
42 files changed, 2663 insertions, 295 deletions
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index ea7753c7a1d..d6d50304086 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -3,6 +3,8 @@
import Cookies from 'js-cookie';
import AwardsHandler from '~/awards_handler';
+require('~/lib/utils/common_utils');
+
(function() {
var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
@@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler';
loadFixtures('issues/issue_with_comment.html.raw');
awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
- return function(url, emoji, cb) {
+ return function(button, url, emoji, cb) {
return cb();
};
})(this));
@@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler';
return expect($emojiButton.next('.js-counter').text()).toBe('4');
});
});
+ describe('::userAuthored', function() {
+ it('should update tooltip to user authored title', function() {
+ var $thumbsUpEmoji, $votesBlock;
+ $votesBlock = $('.js-awards-block').eq(0);
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
+ $thumbsUpEmoji.attr('data-title', 'sam');
+ awardsHandler.userAuthored($thumbsUpEmoji);
+ return expect($thumbsUpEmoji.data("original-title")).toBe("You cannot vote on your own issue, MR and note");
+ });
+ it('should restore tooltip back to initial vote list', function() {
+ var $thumbsUpEmoji, $votesBlock;
+ jasmine.clock().install();
+ $votesBlock = $('.js-awards-block').eq(0);
+ $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
+ $thumbsUpEmoji.attr('data-title', 'sam');
+ awardsHandler.userAuthored($thumbsUpEmoji);
+ jasmine.clock().tick(2801);
+ jasmine.clock().uninstall();
+ return expect($thumbsUpEmoji.data("original-title")).toBe("sam");
+ });
+ });
describe('::getAwardUrl', function() {
return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
diff --git a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js
new file mode 100644
index 00000000000..d1ebae33dab
--- /dev/null
+++ b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js
@@ -0,0 +1,42 @@
+import {
+ BoxGeometry,
+} from 'three/build/three.module';
+import MeshObject from '~/blob/3d_viewer/mesh_object';
+
+describe('Mesh object', () => {
+ it('defaults to non-wireframe material', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+
+ expect(object.material.wireframe).toBeFalsy();
+ });
+
+ it('changes to wirefame material', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+
+ object.changeMaterial('wireframe');
+
+ expect(object.material.wireframe).toBeTruthy();
+ });
+
+ it('scales object down', () => {
+ const object = new MeshObject(
+ new BoxGeometry(10, 10, 10),
+ );
+ const radius = object.geometry.boundingSphere.radius;
+
+ expect(radius).not.toBeGreaterThan(4);
+ });
+
+ it('does not scale object down', () => {
+ const object = new MeshObject(
+ new BoxGeometry(1, 1, 1),
+ );
+ const radius = object.geometry.boundingSphere.radius;
+
+ expect(radius).toBeLessThan(1);
+ });
+});
diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js
new file mode 100644
index 00000000000..d3a4d04345b
--- /dev/null
+++ b/spec/javascripts/blob/pdf/index_spec.js
@@ -0,0 +1,80 @@
+import renderPDF from '~/blob/pdf';
+import testPDF from './test.pdf';
+
+describe('PDF renderer', () => {
+ let viewer;
+ let app;
+
+ const checkLoaded = (done) => {
+ if (app.loading) {
+ setTimeout(() => {
+ checkLoaded(done);
+ }, 100);
+ } else {
+ done();
+ }
+ };
+
+ preloadFixtures('static/pdf_viewer.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/pdf_viewer.html.raw');
+ viewer = document.getElementById('js-pdf-viewer');
+ viewer.dataset.endpoint = testPDF;
+ });
+
+ it('shows loading icon', () => {
+ renderPDF();
+
+ expect(
+ document.querySelector('.loading'),
+ ).not.toBeNull();
+ });
+
+ describe('successful response', () => {
+ beforeEach((done) => {
+ app = renderPDF();
+
+ checkLoaded(done);
+ });
+
+ it('does not show loading icon', () => {
+ expect(
+ document.querySelector('.loading'),
+ ).toBeNull();
+ });
+
+ it('renders the PDF', () => {
+ expect(
+ document.querySelector('.pdf-viewer'),
+ ).not.toBeNull();
+ });
+
+ it('renders the PDF page', () => {
+ expect(
+ document.querySelector('.pdf-page'),
+ ).not.toBeNull();
+ });
+ });
+
+ describe('error getting file', () => {
+ beforeEach((done) => {
+ viewer.dataset.endpoint = 'invalid/endpoint';
+ app = renderPDF();
+
+ checkLoaded(done);
+ });
+
+ it('does not show loading icon', () => {
+ expect(
+ document.querySelector('.loading'),
+ ).toBeNull();
+ });
+
+ it('shows error message', () => {
+ expect(
+ document.querySelector('.md').textContent.trim(),
+ ).toBe('An error occured whilst loading the file. Please try again later.');
+ });
+ });
+});
diff --git a/spec/javascripts/blob/pdf/test.pdf b/spec/javascripts/blob/pdf/test.pdf
new file mode 100644
index 00000000000..eb3d147fde3
--- /dev/null
+++ b/spec/javascripts/blob/pdf/test.pdf
Binary files differ
diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js
new file mode 100644
index 00000000000..0e4431548c4
--- /dev/null
+++ b/spec/javascripts/blob/sketch/index_spec.js
@@ -0,0 +1,118 @@
+/* eslint-disable no-new */
+import JSZip from 'jszip';
+import SketchLoader from '~/blob/sketch';
+
+describe('Sketch viewer', () => {
+ const generateZipFileArrayBuffer = (zipFile, resolve, done) => {
+ zipFile
+ .generateAsync({ type: 'arrayBuffer' })
+ .then((content) => {
+ resolve(content);
+
+ setTimeout(() => {
+ done();
+ }, 100);
+ });
+ };
+
+ preloadFixtures('static/sketch_viewer.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/sketch_viewer.html.raw');
+ });
+
+ describe('with error message', () => {
+ beforeEach((done) => {
+ spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+
+ setTimeout(() => {
+ done();
+ });
+ }));
+
+ new SketchLoader(document.getElementById('js-sketch-viewer'));
+ });
+
+ it('renders error message', () => {
+ expect(
+ document.querySelector('#js-sketch-viewer p'),
+ ).not.toBeNull();
+
+ expect(
+ document.querySelector('#js-sketch-viewer p').textContent.trim(),
+ ).toContain('Cannot show preview.');
+ });
+
+ it('removes render the loading icon', () => {
+ expect(
+ document.querySelector('.js-loading-icon'),
+ ).toBeNull();
+ });
+ });
+
+ describe('success', () => {
+ beforeEach((done) => {
+ spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve) => {
+ const zipFile = new JSZip();
+ zipFile.folder('previews')
+ .file('preview.png', 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAA1JREFUeNoBAgD9/wAAAAIAAVMrnDAAAAAASUVORK5CYII=', {
+ base64: true,
+ });
+
+ generateZipFileArrayBuffer(zipFile, resolve, done);
+ }));
+
+ new SketchLoader(document.getElementById('js-sketch-viewer'));
+ });
+
+ it('does not render error message', () => {
+ expect(
+ document.querySelector('#js-sketch-viewer p'),
+ ).toBeNull();
+ });
+
+ it('removes render the loading icon', () => {
+ expect(
+ document.querySelector('.js-loading-icon'),
+ ).toBeNull();
+ });
+
+ it('renders preview img', () => {
+ const img = document.querySelector('#js-sketch-viewer img');
+
+ expect(img).not.toBeNull();
+ expect(img.classList.contains('img-responsive')).toBeTruthy();
+ });
+
+ it('renders link to image', () => {
+ const img = document.querySelector('#js-sketch-viewer img');
+ const link = document.querySelector('#js-sketch-viewer a');
+
+ expect(link.href).toBe(img.src);
+ expect(link.target).toBe('_blank');
+ });
+ });
+
+ describe('incorrect file', () => {
+ beforeEach((done) => {
+ spyOn(SketchLoader.prototype, 'getZipFile').and.callFake(() => new Promise((resolve) => {
+ const zipFile = new JSZip();
+
+ generateZipFileArrayBuffer(zipFile, resolve, done);
+ }));
+
+ new SketchLoader(document.getElementById('js-sketch-viewer'));
+ });
+
+ it('renders error message', () => {
+ expect(
+ document.querySelector('#js-sketch-viewer p'),
+ ).not.toBeNull();
+
+ expect(
+ document.querySelector('#js-sketch-viewer p').textContent.trim(),
+ ).toContain('Cannot show preview.');
+ });
+ });
+});
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
new file mode 100644
index 00000000000..3f598887603
--- /dev/null
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -0,0 +1,201 @@
+/* global BoardService */
+/* global boardsMockInterceptor */
+/* global List */
+/* global listObj */
+/* global ListIssue */
+import Vue from 'vue';
+import _ from 'underscore';
+import Sortable from 'vendor/Sortable';
+import BoardList from '~/boards/components/board_list';
+import eventHub from '~/boards/eventhub';
+import '~/boards/mixins/sortable_default_options';
+import '~/boards/models/issue';
+import '~/boards/models/list';
+import '~/boards/stores/boards_store';
+import './mock_data';
+
+window.Sortable = Sortable;
+
+describe('Board list component', () => {
+ let component;
+
+ beforeEach((done) => {
+ const el = document.createElement('div');
+
+ document.body.appendChild(el);
+ Vue.http.interceptors.push(boardsMockInterceptor);
+ gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.issueBoards.BoardsStore.create();
+ gl.IssueBoardsApp = new Vue();
+
+ const BoardListComp = Vue.extend(BoardList);
+ const list = new List(listObj);
+ const issue = new ListIssue({
+ title: 'Testing',
+ iid: 1,
+ confidential: false,
+ labels: [],
+ });
+ list.issuesSize = 1;
+ list.issues.push(issue);
+
+ component = new BoardListComp({
+ el,
+ propsData: {
+ disabled: false,
+ list,
+ issues: list.issues,
+ loading: false,
+ issueLinkBase: '/issues',
+ rootPath: '/',
+ },
+ }).$mount();
+
+ Vue.nextTick(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+ });
+
+ it('renders component', () => {
+ expect(
+ component.$el.classList.contains('board-list-component'),
+ ).toBe(true);
+ });
+
+ it('renders loading icon', (done) => {
+ component.loading = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-loading'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders issues', () => {
+ expect(
+ component.$el.querySelectorAll('.card').length,
+ ).toBe(1);
+ });
+
+ it('sets data attribute with issue id', () => {
+ expect(
+ component.$el.querySelector('.card').getAttribute('data-issue-id'),
+ ).toBe('1');
+ });
+
+ it('shows new issue form', (done) => {
+ component.toggleForm();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-new-issue-form'),
+ ).not.toBeNull();
+
+ expect(
+ component.$el.querySelector('.is-smaller'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('shows new issue form after eventhub event', (done) => {
+ eventHub.$emit(`hide-issue-form-${component.list.id}`);
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-new-issue-form'),
+ ).not.toBeNull();
+
+ expect(
+ component.$el.querySelector('.is-smaller'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+
+ it('does not show new issue form for closed list', (done) => {
+ component.list.type = 'closed';
+ component.toggleForm();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-new-issue-form'),
+ ).toBeNull();
+
+ done();
+ });
+ });
+
+ it('shows count list item', (done) => {
+ component.showCount = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count'),
+ ).not.toBeNull();
+
+ expect(
+ component.$el.querySelector('.board-list-count').textContent.trim(),
+ ).toBe('Showing all issues');
+
+ done();
+ });
+ });
+
+ it('shows how many more issues to load', (done) => {
+ component.showCount = true;
+ component.list.issuesSize = 20;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count').textContent.trim(),
+ ).toBe('Showing 1 of 20 issues');
+
+ done();
+ });
+ });
+
+ it('loads more issues after scrolling', (done) => {
+ spyOn(component.list, 'nextPage');
+ component.$refs.list.style.height = '100px';
+ component.$refs.list.style.overflow = 'scroll';
+
+ for (let i = 0; i < 19; i += 1) {
+ const issue = component.list.issues[0];
+ issue.id += 1;
+ component.list.issues.push(issue);
+ }
+
+ Vue.nextTick(() => {
+ component.$refs.list.scrollTop = 20000;
+
+ setTimeout(() => {
+ expect(component.list.nextPage).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
+
+ it('shows loading more spinner', (done) => {
+ component.showCount = true;
+ component.list.loadingMore = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count .fa-spinner'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js
index 549c7af8ea8..7174bf1e041 100644
--- a/spec/javascripts/build_spec.js
+++ b/spec/javascripts/build_spec.js
@@ -64,54 +64,33 @@ describe('Build', () => {
});
});
- 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 () {
+ spyOn(gl.utils, 'visitUrl');
+
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=`,
- );
- expect(dataType).toBe('json');
- expect(success).toEqual(jasmine.any(Function));
+ expect($.ajax.calls.count()).toBe(1);
+
+ // We have to do it this way to prevent Webpack to fail to compile
+ // when destructuring assignments and reusing
+ // the same variables names inside the same scope
+ let args = $.ajax.calls.argsFor(0)[0];
- success.call(context, {
+ expect(args.url).toBe(`${BUILD_URL}/trace.json`);
+ expect(args.dataType).toBe('json');
+ expect(args.success).toEqual(jasmine.any(Function));
+
+ args.success.call($, {
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
append: true,
+ complete: false,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
@@ -120,16 +99,19 @@ describe('Build', () => {
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, {
+ args = $.ajax.calls.argsFor(2)[0];
+ expect(args.url).toBe(`${BUILD_URL}/trace.json`);
+ expect(args.dataType).toBe('json');
+ expect(args.data.state).toBe('newstate');
+ expect(args.success).toEqual(jasmine.any(Function));
+
+ args.success.call($, {
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
append: true,
+ complete: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
@@ -137,19 +119,22 @@ describe('Build', () => {
});
it('replaces the entire build trace', () => {
+ spyOn(gl.utils, 'visitUrl');
+
jasmine.clock().tick(4001);
- let [{ success, context }] = $.ajax.calls.argsFor(1);
- success.call(context, {
+ let args = $.ajax.calls.argsFor(0)[0];
+ args.success.call($, {
html: '<span>Update</span>',
status: 'running',
- append: true,
+ append: false,
+ complete: false,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
jasmine.clock().tick(4001);
- [{ success, context }] = $.ajax.calls.argsFor(2);
- success.call(context, {
+ args = $.ajax.calls.argsFor(2)[0];
+ args.success.call($, {
html: '<span>Different</span>',
status: 'running',
append: false,
@@ -159,15 +144,34 @@ describe('Build', () => {
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
+ it('shows information about truncated log', () => {
+ jasmine.clock().tick(4001);
+ const [{ success }] = $.ajax.calls.argsFor(0);
+
+ success.call($, {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ truncated: true,
+ size: '50',
+ });
+
+ expect(
+ $('#build-trace .js-truncated-info').text().trim(),
+ ).toContain('Showing last 50 KiB of log');
+ expect($('#build-trace .js-truncated-info-size').text()).toMatch('50');
+ });
+
it('reloads the page when the build is done', () => {
spyOn(gl.utils, 'visitUrl');
jasmine.clock().tick(4001);
- const [{ success, context }] = $.ajax.calls.argsFor(1);
- success.call(context, {
+ const [{ success }] = $.ajax.calls.argsFor(0);
+ success.call($, {
html: '<span>Final</span>',
status: 'passed',
append: true,
+ complete: true,
});
expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL);
diff --git a/spec/javascripts/comment_type_toggle_spec.js b/spec/javascripts/comment_type_toggle_spec.js
new file mode 100644
index 00000000000..dfd0810d52e
--- /dev/null
+++ b/spec/javascripts/comment_type_toggle_spec.js
@@ -0,0 +1,157 @@
+import CommentTypeToggle from '~/comment_type_toggle';
+import * as dropLabSrc from '~/droplab/drop_lab';
+import InputSetter from '~/droplab/plugins/input_setter';
+
+describe('CommentTypeToggle', function () {
+ describe('class constructor', function () {
+ beforeEach(function () {
+ this.dropdownTrigger = {};
+ this.dropdownList = {};
+ this.noteTypeInput = {};
+ this.submitButton = {};
+ this.closeButton = {};
+
+ this.commentTypeToggle = new CommentTypeToggle({
+ dropdownTrigger: this.dropdownTrigger,
+ dropdownList: this.dropdownList,
+ noteTypeInput: this.noteTypeInput,
+ submitButton: this.submitButton,
+ closeButton: this.closeButton,
+ });
+ });
+
+ it('should set .dropdownTrigger', function () {
+ expect(this.commentTypeToggle.dropdownTrigger).toBe(this.dropdownTrigger);
+ });
+
+ it('should set .dropdownList', function () {
+ expect(this.commentTypeToggle.dropdownList).toBe(this.dropdownList);
+ });
+
+ it('should set .noteTypeInput', function () {
+ expect(this.commentTypeToggle.noteTypeInput).toBe(this.noteTypeInput);
+ });
+
+ it('should set .submitButton', function () {
+ expect(this.commentTypeToggle.submitButton).toBe(this.submitButton);
+ });
+
+ it('should set .closeButton', function () {
+ expect(this.commentTypeToggle.closeButton).toBe(this.closeButton);
+ });
+
+ it('should set .reopenButton', function () {
+ expect(this.commentTypeToggle.reopenButton).toBe(this.reopenButton);
+ });
+ });
+
+ describe('initDroplab', function () {
+ beforeEach(function () {
+ this.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ closeButton: {},
+ setConfig: () => {},
+ };
+ this.config = {};
+
+ this.droplab = jasmine.createSpyObj('droplab', ['init']);
+
+ spyOn(dropLabSrc, 'default').and.returnValue(this.droplab);
+ spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config);
+
+ CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle);
+ });
+
+ it('should instantiate a DropLab instance', function () {
+ expect(dropLabSrc.default).toHaveBeenCalled();
+ });
+
+ it('should set .droplab', function () {
+ expect(this.commentTypeToggle.droplab).toBe(this.droplab);
+ });
+
+ it('should call .setConfig', function () {
+ expect(this.commentTypeToggle.setConfig).toHaveBeenCalled();
+ });
+
+ it('should call DropLab.prototype.init', function () {
+ expect(this.droplab.init).toHaveBeenCalledWith(
+ this.commentTypeToggle.dropdownTrigger,
+ this.commentTypeToggle.dropdownList,
+ [InputSetter],
+ this.config,
+ );
+ });
+ });
+
+ describe('setConfig', function () {
+ describe('if no .closeButton is provided', function () {
+ beforeEach(function () {
+ this.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ reopenButton: {},
+ };
+
+ this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
+ });
+
+ it('should not add .closeButton related InputSetter config', function () {
+ expect(this.setConfig).toEqual({
+ InputSetter: [{
+ input: this.commentTypeToggle.noteTypeInput,
+ valueAttribute: 'data-value',
+ }, {
+ input: this.commentTypeToggle.submitButton,
+ valueAttribute: 'data-submit-text',
+ }, {
+ input: this.commentTypeToggle.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ }, {
+ input: this.commentTypeToggle.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ inputAttribute: 'data-alternative-text',
+ }],
+ });
+ });
+ });
+
+ describe('if no .reopenButton is provided', function () {
+ beforeEach(function () {
+ this.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ closeButton: {},
+ };
+
+ this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
+ });
+
+ it('should not add .reopenButton related InputSetter config', function () {
+ expect(this.setConfig).toEqual({
+ InputSetter: [{
+ input: this.commentTypeToggle.noteTypeInput,
+ valueAttribute: 'data-value',
+ }, {
+ input: this.commentTypeToggle.submitButton,
+ valueAttribute: 'data-submit-text',
+ }, {
+ input: this.commentTypeToggle.closeButton,
+ valueAttribute: 'data-close-text',
+ }, {
+ input: this.commentTypeToggle.closeButton,
+ valueAttribute: 'data-close-text',
+ inputAttribute: 'data-alternative-text',
+ }],
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/droplab/constants_spec.js b/spec/javascripts/droplab/constants_spec.js
new file mode 100644
index 00000000000..35239e4fb8e
--- /dev/null
+++ b/spec/javascripts/droplab/constants_spec.js
@@ -0,0 +1,29 @@
+/* eslint-disable */
+
+import * as constants from '~/droplab/constants';
+
+describe('constants', function () {
+ describe('DATA_TRIGGER', function () {
+ it('should be `data-dropdown-trigger`', function() {
+ expect(constants.DATA_TRIGGER).toBe('data-dropdown-trigger');
+ });
+ });
+
+ describe('DATA_DROPDOWN', function () {
+ it('should be `data-dropdown`', function() {
+ expect(constants.DATA_DROPDOWN).toBe('data-dropdown');
+ });
+ });
+
+ describe('SELECTED_CLASS', function () {
+ it('should be `droplab-item-selected`', function() {
+ expect(constants.SELECTED_CLASS).toBe('droplab-item-selected');
+ });
+ });
+
+ describe('ACTIVE_CLASS', function () {
+ it('should be `droplab-item-active`', function() {
+ expect(constants.ACTIVE_CLASS).toBe('droplab-item-active');
+ });
+ });
+});
diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js
new file mode 100644
index 00000000000..802e2435672
--- /dev/null
+++ b/spec/javascripts/droplab/drop_down_spec.js
@@ -0,0 +1,593 @@
+/* eslint-disable */
+
+import DropDown from '~/droplab/drop_down';
+import utils from '~/droplab/utils';
+import { SELECTED_CLASS } from '~/droplab/constants';
+
+describe('DropDown', function () {
+ describe('class constructor', function () {
+ beforeEach(function () {
+ spyOn(DropDown.prototype, 'getItems');
+ spyOn(DropDown.prototype, 'initTemplateString');
+ spyOn(DropDown.prototype, 'addEvents');
+
+ this.list = { innerHTML: 'innerHTML' };
+ this.dropdown = new DropDown(this.list);
+ });
+
+ it('sets the .hidden property to true', function () {
+ expect(this.dropdown.hidden).toBe(true);
+ })
+
+ it('sets the .list property', function () {
+ expect(this.dropdown.list).toBe(this.list);
+ });
+
+ it('calls .getItems', function () {
+ expect(DropDown.prototype.getItems).toHaveBeenCalled();
+ });
+
+ it('calls .initTemplateString', function () {
+ expect(DropDown.prototype.initTemplateString).toHaveBeenCalled();
+ });
+
+ it('calls .addEvents', function () {
+ expect(DropDown.prototype.addEvents).toHaveBeenCalled();
+ });
+
+ it('sets the .initialState property to the .list.innerHTML', function () {
+ expect(this.dropdown.initialState).toBe(this.list.innerHTML);
+ });
+
+ describe('if the list argument is a string', function () {
+ beforeEach(function () {
+ this.element = {};
+ this.selector = '.selector';
+
+ spyOn(Document.prototype, 'querySelector').and.returnValue(this.element);
+
+ this.dropdown = new DropDown(this.selector);
+ });
+
+ it('calls .querySelector with the selector string', function () {
+ expect(Document.prototype.querySelector).toHaveBeenCalledWith(this.selector);
+ });
+
+ it('sets the .list property element', function () {
+ expect(this.dropdown.list).toBe(this.element);
+ });
+ });
+ });
+
+ describe('getItems', function () {
+ beforeEach(function () {
+ this.list = { querySelectorAll: () => {} };
+ this.dropdown = { list: this.list };
+ this.nodeList = [];
+
+ spyOn(this.list, 'querySelectorAll').and.returnValue(this.nodeList);
+
+ this.getItems = DropDown.prototype.getItems.call(this.dropdown);
+ });
+
+ it('calls .querySelectorAll with a list item query', function () {
+ expect(this.list.querySelectorAll).toHaveBeenCalledWith('li');
+ });
+
+ it('sets the .items property to the returned list items', function () {
+ expect(this.dropdown.items).toEqual(jasmine.any(Array));
+ });
+
+ it('returns the .items', function () {
+ expect(this.getItems).toEqual(jasmine.any(Array));
+ });
+ });
+
+ describe('initTemplateString', function () {
+ beforeEach(function () {
+ this.items = [{ outerHTML: '<a></a>' }, { outerHTML: '<img>' }];
+ this.dropdown = { items: this.items };
+
+ DropDown.prototype.initTemplateString.call(this.dropdown);
+ });
+
+ it('should set .templateString to the last items .outerHTML', function () {
+ expect(this.dropdown.templateString).toBe(this.items[1].outerHTML);
+ });
+
+ it('should not set .templateString to a non-last items .outerHTML', function () {
+ expect(this.dropdown.templateString).not.toBe(this.items[0].outerHTML);
+ });
+
+ describe('if .items is not set', function () {
+ beforeEach(function () {
+ this.dropdown = { getItems: () => {} };
+
+ spyOn(this.dropdown, 'getItems').and.returnValue([]);
+
+ DropDown.prototype.initTemplateString.call(this.dropdown);
+ });
+
+ it('should call .getItems', function () {
+ expect(this.dropdown.getItems).toHaveBeenCalled();
+ });
+ });
+
+ describe('if items array is empty', function () {
+ beforeEach(function () {
+ this.dropdown = { items: [] };
+
+ DropDown.prototype.initTemplateString.call(this.dropdown);
+ });
+
+ it('should set .templateString to an empty string', function () {
+ expect(this.dropdown.templateString).toBe('');
+ });
+ });
+ });
+
+ describe('clickEvent', function () {
+ beforeEach(function () {
+ this.list = { dispatchEvent: () => {} };
+ this.dropdown = { hide: () => {}, list: this.list, addSelectedClass: () => {} };
+ this.event = { preventDefault: () => {}, target: {} };
+ this.customEvent = {};
+ this.closestElement = {};
+
+ spyOn(this.dropdown, 'hide');
+ spyOn(this.dropdown, 'addSelectedClass');
+ spyOn(this.list, 'dispatchEvent');
+ spyOn(this.event, 'preventDefault');
+ spyOn(window, 'CustomEvent').and.returnValue(this.customEvent);
+ spyOn(utils, 'closest').and.returnValues(this.closestElement, undefined);
+
+ DropDown.prototype.clickEvent.call(this.dropdown, this.event);
+ });
+
+ it('should call utils.closest', function () {
+ expect(utils.closest).toHaveBeenCalledWith(this.event.target, 'LI');
+ });
+
+ it('should call addSelectedClass', function () {
+ expect(this.dropdown.addSelectedClass).toHaveBeenCalledWith(this.closestElement);
+ })
+
+ it('should call .preventDefault', function () {
+ expect(this.event.preventDefault).toHaveBeenCalled();
+ });
+
+ it('should call .hide', function () {
+ expect(this.dropdown.hide).toHaveBeenCalled();
+ });
+
+ it('should construct CustomEvent', function () {
+ expect(window.CustomEvent).toHaveBeenCalledWith('click.dl', jasmine.any(Object));
+ });
+
+ it('should call .dispatchEvent with the customEvent', function () {
+ expect(this.list.dispatchEvent).toHaveBeenCalledWith(this.customEvent);
+ });
+
+ describe('if the target is a UL element', function () {
+ beforeEach(function () {
+ this.event = { preventDefault: () => {}, target: { tagName: 'UL' } };
+
+ spyOn(this.event, 'preventDefault');
+ utils.closest.calls.reset();
+
+ DropDown.prototype.clickEvent.call(this.dropdown, this.event);
+ });
+
+ it('should return immediately', function () {
+ expect(utils.closest).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('if no selected element exists', function () {
+ beforeEach(function () {
+ this.event.preventDefault.calls.reset();
+ this.clickEvent = DropDown.prototype.clickEvent.call(this.dropdown, this.event);
+ });
+
+ it('should return undefined', function () {
+ expect(this.clickEvent).toBe(undefined);
+ });
+
+ it('should return before .preventDefault is called', function () {
+ expect(this.event.preventDefault).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('addSelectedClass', function () {
+ beforeEach(function () {
+ this.items = Array(4).forEach((item, i) => {
+ this.items[i] = { classList: { add: () => {} } };
+ spyOn(this.items[i].classList, 'add');
+ });
+ this.selected = { classList: { add: () => {} } };
+ this.dropdown = { removeSelectedClasses: () => {} };
+
+ spyOn(this.dropdown, 'removeSelectedClasses');
+ spyOn(this.selected.classList, 'add');
+
+ DropDown.prototype.addSelectedClass.call(this.dropdown, this.selected);
+ });
+
+ it('should call .removeSelectedClasses', function () {
+ expect(this.dropdown.removeSelectedClasses).toHaveBeenCalled();
+ });
+
+ it('should call .classList.add', function () {
+ expect(this.selected.classList.add).toHaveBeenCalledWith(SELECTED_CLASS);
+ });
+ });
+
+ describe('removeSelectedClasses', function () {
+ beforeEach(function () {
+ this.items = Array(4);
+ this.items.forEach((item, i) => {
+ this.items[i] = { classList: { add: () => {} } };
+ spyOn(this.items[i].classList, 'add');
+ });
+ this.dropdown = { items: this.items };
+
+ DropDown.prototype.removeSelectedClasses.call(this.dropdown);
+ });
+
+ it('should call .classList.remove for all items', function () {
+ this.items.forEach((item, i) => {
+ expect(this.items[i].classList.add).toHaveBeenCalledWith(SELECTED_CLASS);
+ });
+ });
+
+ describe('if .items is not set', function () {
+ beforeEach(function () {
+ this.dropdown = { getItems: () => {} };
+
+ spyOn(this.dropdown, 'getItems').and.returnValue([]);
+
+ DropDown.prototype.removeSelectedClasses.call(this.dropdown);
+ });
+
+ it('should call .getItems', function () {
+ expect(this.dropdown.getItems).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('addEvents', function () {
+ beforeEach(function () {
+ this.list = { addEventListener: () => {} };
+ this.dropdown = { list: this.list, clickEvent: () => {}, eventWrapper: {} };
+
+ spyOn(this.list, 'addEventListener');
+
+ DropDown.prototype.addEvents.call(this.dropdown);
+ });
+
+ it('should call .addEventListener', function () {
+ expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function));
+ });
+ });
+
+ describe('toggle', function () {
+ beforeEach(function () {
+ this.dropdown = { hidden: true, show: () => {}, hide: () => {} };
+
+ spyOn(this.dropdown, 'show');
+ spyOn(this.dropdown, 'hide');
+
+ DropDown.prototype.toggle.call(this.dropdown);
+ });
+
+ it('should call .show if hidden is true', function () {
+ expect(this.dropdown.show).toHaveBeenCalled();
+ });
+
+ describe('if hidden is false', function () {
+ beforeEach(function () {
+ this.dropdown = { hidden: false, show: () => {}, hide: () => {} };
+
+ spyOn(this.dropdown, 'show');
+ spyOn(this.dropdown, 'hide');
+
+ DropDown.prototype.toggle.call(this.dropdown);
+ });
+
+ it('should call .show if hidden is true', function () {
+ expect(this.dropdown.hide).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('setData', function () {
+ beforeEach(function () {
+ this.dropdown = { render: () => {} };
+ this.data = ['data'];
+
+ spyOn(this.dropdown, 'render');
+
+ DropDown.prototype.setData.call(this.dropdown, this.data);
+ });
+
+ it('should set .data', function () {
+ expect(this.dropdown.data).toBe(this.data);
+ });
+
+ it('should call .render with the .data', function () {
+ expect(this.dropdown.render).toHaveBeenCalledWith(this.data);
+ });
+ });
+
+ describe('addData', function () {
+ beforeEach(function () {
+ this.dropdown = { render: () => {}, data: ['data1'] };
+ this.data = ['data2'];
+
+ spyOn(this.dropdown, 'render');
+ spyOn(Array.prototype, 'concat').and.callThrough();
+
+ DropDown.prototype.addData.call(this.dropdown, this.data);
+ });
+
+ it('should call .concat with data', function () {
+ expect(Array.prototype.concat).toHaveBeenCalledWith(this.data);
+ });
+
+ it('should set .data with concatination', function () {
+ expect(this.dropdown.data).toEqual(['data1', 'data2']);
+ });
+
+ it('should call .render with the .data', function () {
+ expect(this.dropdown.render).toHaveBeenCalledWith(['data1', 'data2']);
+ });
+
+ describe('if .data is undefined', function () {
+ beforeEach(function () {
+ this.dropdown = { render: () => {}, data: undefined };
+ this.data = ['data2'];
+
+ spyOn(this.dropdown, 'render');
+
+ DropDown.prototype.addData.call(this.dropdown, this.data);
+ });
+
+ it('should set .data with concatination', function () {
+ expect(this.dropdown.data).toEqual(['data2']);
+ });
+ });
+ });
+
+ describe('render', function () {
+ beforeEach(function () {
+ this.list = { querySelector: () => {} };
+ this.dropdown = { renderChildren: () => {}, list: this.list };
+ this.renderableList = {};
+ this.data = [0, 1];
+
+ spyOn(this.dropdown, 'renderChildren').and.callFake(data => data);
+ spyOn(this.list, 'querySelector').and.returnValue(this.renderableList);
+ spyOn(this.data, 'map').and.callThrough();
+
+ DropDown.prototype.render.call(this.dropdown, this.data);
+ });
+
+ it('should call .map', function () {
+ expect(this.data.map).toHaveBeenCalledWith(jasmine.any(Function));
+ });
+
+ it('should call .renderChildren for each data item', function() {
+ expect(this.dropdown.renderChildren.calls.count()).toBe(this.data.length);
+ });
+
+ it('sets the renderableList .innerHTML', function () {
+ expect(this.renderableList.innerHTML).toBe('01');
+ });
+
+ describe('if no data argument is passed' , function () {
+ beforeEach(function () {
+ this.data.map.calls.reset();
+ this.dropdown.renderChildren.calls.reset();
+
+ DropDown.prototype.render.call(this.dropdown, undefined);
+ });
+
+ it('should not call .map', function () {
+ expect(this.data.map).not.toHaveBeenCalled();
+ });
+
+ it('should not call .renderChildren', function () {
+ expect(this.dropdown.renderChildren).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('if no dynamic list is present', function () {
+ beforeEach(function () {
+ this.list = { querySelector: () => {} };
+ this.dropdown = { renderChildren: () => {}, list: this.list };
+ this.data = [0, 1];
+
+ spyOn(this.dropdown, 'renderChildren').and.callFake(data => data);
+ spyOn(this.list, 'querySelector');
+ spyOn(this.data, 'map').and.callThrough();
+
+ DropDown.prototype.render.call(this.dropdown, this.data);
+ });
+
+ it('sets the .list .innerHTML', function () {
+ expect(this.list.innerHTML).toBe('01');
+ });
+ });
+ });
+
+ describe('renderChildren', function () {
+ beforeEach(function () {
+ this.templateString = 'templateString';
+ this.dropdown = { setImagesSrc: () => {}, templateString: this.templateString };
+ this.data = { droplab_hidden: true };
+ this.html = 'html';
+ this.template = { firstChild: { outerHTML: 'outerHTML', style: {} } };
+
+ spyOn(utils, 't').and.returnValue(this.html);
+ spyOn(document, 'createElement').and.returnValue(this.template);
+ spyOn(this.dropdown, 'setImagesSrc');
+
+ this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data);
+ });
+
+ it('should call utils.t with .templateString and data', function () {
+ expect(utils.t).toHaveBeenCalledWith(this.templateString, this.data);
+ });
+
+ it('should call document.createElement', function () {
+ expect(document.createElement).toHaveBeenCalledWith('div');
+ });
+
+ it('should set the templates .innerHTML to the HTML', function () {
+ expect(this.template.innerHTML).toBe(this.html);
+ });
+
+ it('should call .setImagesSrc with the template', function () {
+ expect(this.dropdown.setImagesSrc).toHaveBeenCalledWith(this.template);
+ });
+
+ it('should set the template display to none', function () {
+ expect(this.template.firstChild.style.display).toBe('none');
+ });
+
+ it('should return the templates .firstChild.outerHTML', function () {
+ expect(this.renderChildren).toBe(this.template.firstChild.outerHTML);
+ });
+
+ describe('if droplab_hidden is false', function () {
+ beforeEach(function () {
+ this.data = { droplab_hidden: false };
+ this.renderChildren = DropDown.prototype.renderChildren.call(this.dropdown, this.data);
+ });
+
+ it('should set the template display to block', function () {
+ expect(this.template.firstChild.style.display).toBe('block');
+ });
+ });
+ });
+
+ describe('setImagesSrc', function () {
+ beforeEach(function () {
+ this.dropdown = {};
+ this.template = { querySelectorAll: () => {} };
+
+ spyOn(this.template, 'querySelectorAll').and.returnValue([]);
+
+ DropDown.prototype.setImagesSrc.call(this.dropdown, this.template);
+ });
+
+ it('should call .querySelectorAll', function () {
+ expect(this.template.querySelectorAll).toHaveBeenCalledWith('img[data-src]');
+ });
+ });
+
+ describe('show', function () {
+ beforeEach(function () {
+ this.list = { style: {} };
+ this.dropdown = { list: this.list, hidden: true };
+
+ DropDown.prototype.show.call(this.dropdown);
+ });
+
+ it('it should set .list display to block', function () {
+ expect(this.list.style.display).toBe('block');
+ });
+
+ it('it should set .hidden to false', function () {
+ expect(this.dropdown.hidden).toBe(false);
+ });
+
+ describe('if .hidden is false', function () {
+ beforeEach(function () {
+ this.list = { style: {} };
+ this.dropdown = { list: this.list, hidden: false };
+
+ this.show = DropDown.prototype.show.call(this.dropdown);
+ });
+
+ it('should return undefined', function () {
+ expect(this.show).toEqual(undefined);
+ });
+
+ it('should not set .list display to block', function () {
+ expect(this.list.style.display).not.toEqual('block');
+ });
+ });
+ });
+
+ describe('hide', function () {
+ beforeEach(function () {
+ this.list = { style: {} };
+ this.dropdown = { list: this.list };
+
+ DropDown.prototype.hide.call(this.dropdown);
+ });
+
+ it('it should set .list display to none', function () {
+ expect(this.list.style.display).toBe('none');
+ });
+
+ it('it should set .hidden to true', function () {
+ expect(this.dropdown.hidden).toBe(true);
+ });
+ });
+
+ describe('toggle', function () {
+ beforeEach(function () {
+ this.hidden = true
+ this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
+
+ spyOn(this.dropdown, 'show');
+ spyOn(this.dropdown, 'hide');
+
+ DropDown.prototype.toggle.call(this.dropdown);
+ });
+
+ it('should call .show', function () {
+ expect(this.dropdown.show).toHaveBeenCalled();
+ });
+
+ describe('if .hidden is false', function () {
+ beforeEach(function () {
+ this.hidden = false
+ this.dropdown = { hidden: this.hidden, show: () => {}, hide: () => {} };
+
+ spyOn(this.dropdown, 'show');
+ spyOn(this.dropdown, 'hide');
+
+ DropDown.prototype.toggle.call(this.dropdown);
+ });
+
+ it('should call .hide', function () {
+ expect(this.dropdown.hide).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('destroy', function () {
+ beforeEach(function () {
+ this.list = { removeEventListener: () => {} };
+ this.eventWrapper = { clickEvent: 'clickEvent' };
+ this.dropdown = { list: this.list, hide: () => {}, eventWrapper: this.eventWrapper };
+
+ spyOn(this.list, 'removeEventListener');
+ spyOn(this.dropdown, 'hide');
+
+ DropDown.prototype.destroy.call(this.dropdown);
+ });
+
+ it('it should call .hide', function () {
+ expect(this.dropdown.hide).toHaveBeenCalled();
+ });
+
+ it('it should call .removeEventListener', function () {
+ expect(this.list.removeEventListener).toHaveBeenCalledWith('click', this.eventWrapper.clickEvent);
+ });
+ });
+});
diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js
new file mode 100644
index 00000000000..8ebdcdd1404
--- /dev/null
+++ b/spec/javascripts/droplab/hook_spec.js
@@ -0,0 +1,82 @@
+/* eslint-disable */
+
+import Hook from '~/droplab/hook';
+import * as dropdownSrc from '~/droplab/drop_down';
+
+describe('Hook', function () {
+ describe('class constructor', function () {
+ beforeEach(function () {
+ this.trigger = { id: 'id' };
+ this.list = {};
+ this.plugins = {};
+ this.config = {};
+ this.dropdown = {};
+
+ spyOn(dropdownSrc, 'default').and.returnValue(this.dropdown);
+
+ this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
+ });
+
+ it('should set .trigger', function () {
+ expect(this.hook.trigger).toBe(this.trigger);
+ });
+
+ it('should set .list', function () {
+ expect(this.hook.list).toBe(this.dropdown);
+ });
+
+ it('should call DropDown constructor', function () {
+ expect(dropdownSrc.default).toHaveBeenCalledWith(this.list);
+ });
+
+ it('should set .type', function () {
+ expect(this.hook.type).toBe('Hook');
+ });
+
+ it('should set .event', function () {
+ expect(this.hook.event).toBe('click');
+ });
+
+ it('should set .plugins', function () {
+ expect(this.hook.plugins).toBe(this.plugins);
+ });
+
+ it('should set .config', function () {
+ expect(this.hook.config).toBe(this.config);
+ });
+
+ it('should set .id', function () {
+ expect(this.hook.id).toBe(this.trigger.id);
+ });
+
+ describe('if config argument is undefined', function () {
+ beforeEach(function () {
+ this.config = undefined;
+
+ this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
+ });
+
+ it('should set .config to an empty object', function () {
+ expect(this.hook.config).toEqual({});
+ });
+ });
+
+ describe('if plugins argument is undefined', function () {
+ beforeEach(function () {
+ this.plugins = undefined;
+
+ this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
+ });
+
+ it('should set .plugins to an empty array', function () {
+ expect(this.hook.plugins).toEqual([]);
+ });
+ });
+ });
+
+ describe('addEvents', function () {
+ it('should exist', function () {
+ expect(Hook.prototype.hasOwnProperty('addEvents')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/droplab/plugins/input_setter_spec.js b/spec/javascripts/droplab/plugins/input_setter_spec.js
new file mode 100644
index 00000000000..bd625f4ae80
--- /dev/null
+++ b/spec/javascripts/droplab/plugins/input_setter_spec.js
@@ -0,0 +1,212 @@
+/* eslint-disable */
+
+import InputSetter from '~/droplab/plugins/input_setter';
+
+describe('InputSetter', function () {
+ describe('init', function () {
+ beforeEach(function () {
+ this.config = { InputSetter: {} };
+ this.hook = { config: this.config };
+ this.inputSetter = jasmine.createSpyObj('inputSetter', ['addEvents']);
+
+ InputSetter.init.call(this.inputSetter, this.hook);
+ });
+
+ it('should set .hook', function () {
+ expect(this.inputSetter.hook).toBe(this.hook);
+ });
+
+ it('should set .config', function () {
+ expect(this.inputSetter.config).toBe(this.config.InputSetter);
+ });
+
+ it('should set .eventWrapper', function () {
+ expect(this.inputSetter.eventWrapper).toEqual({});
+ });
+
+ it('should call .addEvents', function () {
+ expect(this.inputSetter.addEvents).toHaveBeenCalled();
+ });
+
+ describe('if config.InputSetter is not set', function () {
+ beforeEach(function () {
+ this.config = { InputSetter: undefined };
+ this.hook = { config: this.config };
+
+ InputSetter.init.call(this.inputSetter, this.hook);
+ });
+
+ it('should set .config to an empty object', function () {
+ expect(this.inputSetter.config).toEqual({});
+ });
+
+ it('should set hook.config to an empty object', function () {
+ expect(this.hook.config.InputSetter).toEqual({});
+ });
+ })
+ });
+
+ describe('addEvents', function () {
+ beforeEach(function () {
+ this.hook = { list: { list: jasmine.createSpyObj('list', ['addEventListener']) } };
+ this.inputSetter = { eventWrapper: {}, hook: this.hook, setInputs: () => {} };
+
+ InputSetter.addEvents.call(this.inputSetter);
+ });
+
+ it('should set .eventWrapper.setInputs', function () {
+ expect(this.inputSetter.eventWrapper.setInputs).toEqual(jasmine.any(Function));
+ });
+
+ it('should call .addEventListener', function () {
+ expect(this.hook.list.list.addEventListener)
+ .toHaveBeenCalledWith('click.dl', this.inputSetter.eventWrapper.setInputs);
+ });
+ });
+
+ describe('removeEvents', function () {
+ beforeEach(function () {
+ this.hook = { list: { list: jasmine.createSpyObj('list', ['removeEventListener']) } };
+ this.eventWrapper = jasmine.createSpyObj('eventWrapper', ['setInputs']);
+ this.inputSetter = { eventWrapper: this.eventWrapper, hook: this.hook };
+
+ InputSetter.removeEvents.call(this.inputSetter);
+ });
+
+ it('should call .removeEventListener', function () {
+ expect(this.hook.list.list.removeEventListener)
+ .toHaveBeenCalledWith('click.dl', this.eventWrapper.setInputs);
+ });
+ });
+
+ describe('setInputs', function () {
+ beforeEach(function () {
+ this.event = { detail: { selected: {} } };
+ this.config = [0, 1];
+ this.inputSetter = { config: this.config, setInput: () => {} };
+
+ spyOn(this.inputSetter, 'setInput');
+
+ InputSetter.setInputs.call(this.inputSetter, this.event);
+ });
+
+ it('should call .setInput for each config element', function () {
+ const allArgs = this.inputSetter.setInput.calls.allArgs();
+
+ expect(allArgs.length).toEqual(2);
+
+ allArgs.forEach((args, i) => {
+ expect(args[0]).toBe(this.config[i]);
+ expect(args[1]).toBe(this.event.detail.selected);
+ });
+ });
+
+ describe('if config isnt an array', function () {
+ beforeEach(function () {
+ this.inputSetter = { config: {}, setInput: () => {} };
+
+ InputSetter.setInputs.call(this.inputSetter, this.event);
+ });
+
+ it('should set .config to an array with .config as the first element', function () {
+ expect(this.inputSetter.config).toEqual([{}]);
+ });
+ });
+ });
+
+ describe('setInput', function () {
+ beforeEach(function () {
+ this.selectedItem = { getAttribute: () => {} };
+ this.input = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} };
+ this.config = { valueAttribute: {}, input: this.input };
+ this.inputSetter = { hook: { trigger: {} } };
+ this.newValue = 'newValue';
+
+ spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue);
+ spyOn(this.input, 'hasAttribute').and.returnValue(false);
+
+ InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
+ });
+
+ it('should call .getAttribute', function () {
+ expect(this.selectedItem.getAttribute).toHaveBeenCalledWith(this.config.valueAttribute);
+ });
+
+ it('should call .hasAttribute', function () {
+ expect(this.input.hasAttribute).toHaveBeenCalledWith(undefined);
+ });
+
+ it('should set the value of the input', function () {
+ expect(this.input.value).toBe(this.newValue);
+ });
+
+ describe('if no config.input is provided', function () {
+ beforeEach(function () {
+ this.config = { valueAttribute: {} };
+ this.trigger = { value: 'oldValue', tagName: 'INPUT', hasAttribute: () => {} };
+ this.inputSetter = { hook: { trigger: this.trigger } };
+
+ InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
+ });
+
+ it('should set the value of the hook.trigger', function () {
+ expect(this.trigger.value).toBe(this.newValue);
+ });
+ });
+
+ describe('if the input tag is not INPUT', function () {
+ beforeEach(function () {
+ this.input = { textContent: 'oldValue', tagName: 'SPAN', hasAttribute: () => {} };
+ this.config = { valueAttribute: {}, input: this.input };
+
+ InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
+ });
+
+ it('should set the textContent of the input', function () {
+ expect(this.input.textContent).toBe(this.newValue);
+ });
+ });
+
+ describe('if there is an inputAttribute', function () {
+ beforeEach(function () {
+ this.selectedItem = { getAttribute: () => {} };
+ this.input = { id: 'oldValue', hasAttribute: () => {}, setAttribute: () => {} };
+ this.inputSetter = { hook: { trigger: {} } };
+ this.newValue = 'newValue';
+ this.inputAttribute = 'id';
+ this.config = {
+ valueAttribute: {},
+ input: this.input,
+ inputAttribute: this.inputAttribute,
+ };
+
+ spyOn(this.selectedItem, 'getAttribute').and.returnValue(this.newValue);
+ spyOn(this.input, 'hasAttribute').and.returnValue(true);
+ spyOn(this.input, 'setAttribute');
+
+ InputSetter.setInput.call(this.inputSetter, this.config, this.selectedItem);
+ });
+
+ it('should call setAttribute', function () {
+ expect(this.input.setAttribute).toHaveBeenCalledWith(this.inputAttribute, this.newValue);
+ });
+
+ it('should not set the value or textContent of the input', function () {
+ expect(this.input.value).not.toBe('newValue');
+ expect(this.input.textContent).not.toBe('newValue');
+ });
+ });
+ });
+
+ describe('destroy', function () {
+ beforeEach(function () {
+ this.inputSetter = jasmine.createSpyObj('inputSetter', ['removeEvents']);
+
+ InputSetter.destroy.call(this.inputSetter);
+ });
+
+ it('should call .removeEvents', function () {
+ expect(this.inputSetter.removeEvents).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js
index 13840b42bd6..6348d97b0a5 100644
--- a/spec/javascripts/environments/environment_actions_spec.js
+++ b/spec/javascripts/environments/environment_actions_spec.js
@@ -19,6 +19,11 @@ describe('Actions Component', () => {
name: 'foo',
play_path: '#',
},
+ {
+ name: 'foo bar',
+ play_path: 'url',
+ playable: false,
+ },
];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
@@ -49,4 +54,14 @@ describe('Actions Component', () => {
expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path);
});
+
+ it('should render a disabled action when it\'s not playable', () => {
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
+ ).toEqual('disabled');
+
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
+ ).toEqual(true);
+ });
});
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 9bcf617fcd8..9762688af1a 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/flash';
import EnvironmentsComponent from '~/environments/components/environment';
-import { environment } from './mock_data';
+import { environment, folder } from './mock_data';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
@@ -83,9 +83,10 @@ describe('Environment', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
+ expect(component.$el.querySelectorAll('table')).toBeDefined();
expect(
- component.$el.querySelectorAll('table tbody tr').length,
- ).toEqual(1);
+ component.$el.querySelector('.environment-name').textContent.trim(),
+ ).toEqual(environment.name);
done();
}, 0);
});
@@ -179,4 +180,101 @@ describe('Environment', () => {
}, 0);
});
});
+
+ describe('expandable folders', () => {
+ const environmentsResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: [folder],
+ stopped_count: 0,
+ available_count: 1,
+ }), {
+ status: 200,
+ headers: {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ },
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsResponseInterceptor);
+ component = new EnvironmentsComponent({
+ el: document.querySelector('#environments-list-view'),
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsResponseInterceptor,
+ );
+ });
+
+ it('should open a closed folder', (done) => {
+ setTimeout(() => {
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
+ ).toContain('display: none');
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
+ ).not.toContain('display: none');
+ done();
+ });
+ });
+ });
+
+ it('should close an opened folder', (done) => {
+ setTimeout(() => {
+ // open folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ // close folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
+ ).toContain('display: none');
+ expect(
+ component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
+ ).not.toContain('display: none');
+ done();
+ });
+ });
+ });
+ });
+
+ it('should show children environments and a button to show all environments', (done) => {
+ setTimeout(() => {
+ // open folder
+ component.$el.querySelector('.folder-name').click();
+
+ Vue.nextTick(() => {
+ const folderInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ environments: [environment],
+ }), { status: 200 }));
+ };
+
+ Vue.http.interceptors.push(folderInterceptor);
+
+ // wait for next async request
+ setTimeout(() => {
+ expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1);
+ expect(component.$el.querySelector('td.text-center > a.btn').textContent).toContain('Show all');
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor);
+ done();
+ });
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 115d84b50f5..f617c4bdffe 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -1,38 +1,106 @@
import Store from '~/environments/stores/environments_store';
import { environmentsList, serverData } from './mock_data';
-(() => {
- describe('Store', () => {
- let store;
+describe('Store', () => {
+ let store;
- beforeEach(() => {
- store = new Store();
- });
+ beforeEach(() => {
+ store = new Store();
+ });
- it('should start with a blank state', () => {
- expect(store.state.environments.length).toEqual(0);
- expect(store.state.stoppedCounter).toEqual(0);
- expect(store.state.availableCounter).toEqual(0);
- expect(store.state.paginationInformation).toEqual({});
- });
+ it('should start with a blank state', () => {
+ expect(store.state.environments.length).toEqual(0);
+ expect(store.state.stoppedCounter).toEqual(0);
+ expect(store.state.availableCounter).toEqual(0);
+ expect(store.state.paginationInformation).toEqual({});
+ });
+ it('should store environments', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments.length).toEqual(serverData.length);
+ expect(store.state.environments[0]).toEqual(environmentsList[0]);
+ });
+
+ it('should store available count', () => {
+ store.storeAvailableCount(2);
+ expect(store.state.availableCounter).toEqual(2);
+ });
+
+ it('should store stopped count', () => {
+ store.storeStoppedCount(2);
+ expect(store.state.stoppedCounter).toEqual(2);
+ });
+
+ describe('store environments', () => {
it('should store environments', () => {
store.storeEnvironments(serverData);
expect(store.state.environments.length).toEqual(serverData.length);
- expect(store.state.environments[0]).toEqual(environmentsList[0]);
});
- it('should store available count', () => {
- store.storeAvailableCount(2);
- expect(store.state.availableCounter).toEqual(2);
+ it('should add folder keys when environment is a folder', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ };
+
+ store.storeEnvironments([environment]);
+ expect(store.state.environments[0].isFolder).toEqual(true);
+ expect(store.state.environments[0].folderName).toEqual('bar');
+ });
+
+ it('should extract content of `latest` key when provided', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ latest: {
+ last_deployment: {},
+ isStoppable: true,
+ },
+ };
+
+ store.storeEnvironments([environment]);
+ expect(store.state.environments[0].last_deployment).toEqual({});
+ expect(store.state.environments[0].isStoppable).toEqual(true);
});
- it('should store stopped count', () => {
- store.storeStoppedCount(2);
- expect(store.state.stoppedCounter).toEqual(2);
+ it('should store latest.name when the environment is not a folder', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
});
- it('should store pagination information', () => {
+ it('should store root level name when environment is a folder', () => {
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
+ });
+ });
+
+ describe('toggleFolder', () => {
+ it('should toggle folder', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(false);
+ });
+ });
+
+ describe('setfolderContent', () => {
+ it('should store folder content', () => {
+ store.storeEnvironments(serverData);
+
+ store.setfolderContent(store.state.environments[1], serverData);
+
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ expect(store.state.environments[1].children[0].isChildren).toEqual(true);
+ });
+ });
+
+ describe('store pagination', () => {
+ it('should store normalized and integer pagination information', () => {
const pagination = {
'X-nExt-pAge': '2',
'X-page': '1',
@@ -55,4 +123,4 @@ import { environmentsList, serverData } from './mock_data';
expect(store.state.paginationInformation).toEqual(expectedResult);
});
});
-})();
+});
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 43a217a67f5..72f3db29a66 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -47,9 +47,10 @@ describe('Environments Folder View', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
+ expect(component.$el.querySelectorAll('table')).toBeDefined();
expect(
- component.$el.querySelectorAll('table tbody tr').length,
- ).toEqual(2);
+ component.$el.querySelector('.environment-name').textContent.trim(),
+ ).toEqual(environmentsList[0].name);
done();
}, 0);
});
diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js
index 30861481cc5..15e11aa686b 100644
--- a/spec/javascripts/environments/mock_data.js
+++ b/spec/javascripts/environments/mock_data.js
@@ -84,3 +84,19 @@ export const environment = {
updated_at: '2017-01-31T10:53:46.894Z',
},
};
+
+export const folder = {
+ folderName: 'build',
+ size: 5,
+ id: 12,
+ name: 'build/update-README',
+ state: 'available',
+ external_url: null,
+ environment_type: 'build',
+ last_deployment: null,
+ 'stop_action?': false,
+ environment_path: '/root/review-app/environments/12',
+ stop_path: '/root/review-app/environments/12/stop',
+ created_at: '2017-02-01T19:42:18.400Z',
+ updated_at: '2017-02-01T19:42:18.400Z',
+};
diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
new file mode 100644
index 00000000000..2722882375f
--- /dev/null
+++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
@@ -0,0 +1,166 @@
+import Vue from 'vue';
+import eventHub from '~/filtered_search/event_hub';
+import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content';
+
+const createComponent = (propsData) => {
+ const Component = Vue.extend(RecentSearchesDropdownContent);
+
+ return new Component({
+ el: document.createElement('div'),
+ propsData,
+ });
+};
+
+// Remove all the newlines and whitespace from the formatted markup
+const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim();
+
+describe('RecentSearchesDropdownContent', () => {
+ const propsDataWithoutItems = {
+ items: [],
+ };
+ const propsDataWithItems = {
+ items: [
+ 'foo',
+ 'author:@root label:~foo bar',
+ ],
+ };
+
+ let vm;
+ afterEach(() => {
+ if (vm) {
+ vm.$destroy();
+ }
+ });
+
+ describe('with no items', () => {
+ let el;
+
+ beforeEach(() => {
+ vm = createComponent(propsDataWithoutItems);
+ el = vm.$el;
+ });
+
+ it('should render empty state', () => {
+ expect(el.querySelector('.dropdown-info-note')).toBeDefined();
+
+ const items = el.querySelectorAll('.filtered-search-history-dropdown-item');
+ expect(items.length).toEqual(propsDataWithoutItems.items.length);
+ });
+ });
+
+ describe('with items', () => {
+ let el;
+
+ beforeEach(() => {
+ vm = createComponent(propsDataWithItems);
+ el = vm.$el;
+ });
+
+ it('should render clear recent searches button', () => {
+ expect(el.querySelector('.filtered-search-history-clear-button')).toBeDefined();
+ });
+
+ it('should render recent search items', () => {
+ const items = el.querySelectorAll('.filtered-search-history-dropdown-item');
+ expect(items.length).toEqual(propsDataWithItems.items.length);
+
+ expect(trimMarkupWhitespace(items[0].querySelector('.filtered-search-history-dropdown-search-token').textContent)).toEqual('foo');
+
+ const item1Tokens = items[1].querySelectorAll('.filtered-search-history-dropdown-token');
+ expect(item1Tokens.length).toEqual(2);
+ expect(item1Tokens[0].querySelector('.name').textContent).toEqual('author:');
+ expect(item1Tokens[0].querySelector('.value').textContent).toEqual('@root');
+ expect(item1Tokens[1].querySelector('.name').textContent).toEqual('label:');
+ expect(item1Tokens[1].querySelector('.value').textContent).toEqual('~foo');
+ expect(trimMarkupWhitespace(items[1].querySelector('.filtered-search-history-dropdown-search-token').textContent)).toEqual('bar');
+ });
+ });
+
+ describe('computed', () => {
+ describe('processedItems', () => {
+ it('with items', () => {
+ vm = createComponent(propsDataWithItems);
+ const processedItems = vm.processedItems;
+
+ expect(processedItems.length).toEqual(2);
+
+ expect(processedItems[0].text).toEqual(propsDataWithItems.items[0]);
+ expect(processedItems[0].tokens).toEqual([]);
+ expect(processedItems[0].searchToken).toEqual('foo');
+
+ expect(processedItems[1].text).toEqual(propsDataWithItems.items[1]);
+ expect(processedItems[1].tokens.length).toEqual(2);
+ expect(processedItems[1].tokens[0].prefix).toEqual('author:');
+ expect(processedItems[1].tokens[0].suffix).toEqual('@root');
+ expect(processedItems[1].tokens[1].prefix).toEqual('label:');
+ expect(processedItems[1].tokens[1].suffix).toEqual('~foo');
+ expect(processedItems[1].searchToken).toEqual('bar');
+ });
+
+ it('with no items', () => {
+ vm = createComponent(propsDataWithoutItems);
+ const processedItems = vm.processedItems;
+
+ expect(processedItems.length).toEqual(0);
+ });
+ });
+
+ describe('hasItems', () => {
+ it('with items', () => {
+ vm = createComponent(propsDataWithItems);
+ const hasItems = vm.hasItems;
+ expect(hasItems).toEqual(true);
+ });
+
+ it('with no items', () => {
+ vm = createComponent(propsDataWithoutItems);
+ const hasItems = vm.hasItems;
+ expect(hasItems).toEqual(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('onItemActivated', () => {
+ let onRecentSearchesItemSelectedSpy;
+
+ beforeEach(() => {
+ onRecentSearchesItemSelectedSpy = jasmine.createSpy('spy');
+ eventHub.$on('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy);
+
+ vm = createComponent(propsDataWithItems);
+ });
+
+ afterEach(() => {
+ eventHub.$off('recentSearchesItemSelected', onRecentSearchesItemSelectedSpy);
+ });
+
+ it('emits event', () => {
+ expect(onRecentSearchesItemSelectedSpy).not.toHaveBeenCalled();
+ vm.onItemActivated('something');
+ expect(onRecentSearchesItemSelectedSpy).toHaveBeenCalledWith('something');
+ });
+ });
+
+ describe('onRequestClearRecentSearches', () => {
+ let onRequestClearRecentSearchesSpy;
+
+ beforeEach(() => {
+ onRequestClearRecentSearchesSpy = jasmine.createSpy('spy');
+ eventHub.$on('requestClearRecentSearches', onRequestClearRecentSearchesSpy);
+
+ vm = createComponent(propsDataWithItems);
+ });
+
+ afterEach(() => {
+ eventHub.$off('requestClearRecentSearches', onRequestClearRecentSearchesSpy);
+ });
+
+ it('emits event', () => {
+ expect(onRequestClearRecentSearchesSpy).not.toHaveBeenCalled();
+ vm.onRequestClearRecentSearches({ stopPropagation: () => {} });
+ expect(onRequestClearRecentSearchesSpy).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index c16f77c53a2..2b1fe5e3eef 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -33,7 +33,7 @@ require('~/filtered_search/dropdown_user');
});
});
- describe('config droplabAjaxFilter\'s endpoint', () => {
+ describe('config AjaxFilter\'s endpoint', () => {
beforeEach(() => {
spyOn(gl.DropdownUser.prototype, 'bindEvents').and.callFake(() => {});
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
@@ -45,13 +45,13 @@ require('~/filtered_search/dropdown_user');
};
const dropdown = new gl.DropdownUser();
- expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
it('should return endpoint when relative_url_root is undefined', () => {
const dropdown = new gl.DropdownUser();
- expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/autocomplete/users.json');
});
it('should return endpoint with relative url when available', () => {
@@ -60,7 +60,7 @@ require('~/filtered_search/dropdown_user');
};
const dropdown = new gl.DropdownUser();
- expect(dropdown.config.droplabAjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
+ expect(dropdown.config.AjaxFilter.endpoint).toBe('/gitlab_directory/autocomplete/users.json');
});
afterEach(() => {
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 5f7c05e9014..97af681429b 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -29,7 +29,7 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
beforeEach(() => {
setFixtures(`
- <div class="filtered-search-input-container">
+ <div class="filtered-search-box">
<form>
<ul class="tokens-container list-unstyled">
${FilteredSearchSpecHelper.createInputHTML(placeholder)}
@@ -264,12 +264,12 @@ const FilteredSearchSpecHelper = require('../helpers/filtered_search_spec_helper
describe('toggleInputContainerFocus', () => {
it('toggles on focus', () => {
input.focus();
- expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(true);
+ expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(true);
});
it('toggles on blur', () => {
input.blur();
- expect(document.querySelector('.filtered-search-input-container').classList.contains('focus')).toEqual(false);
+ expect(document.querySelector('.filtered-search-box').classList.contains('focus')).toEqual(false);
});
});
});
diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
new file mode 100644
index 00000000000..2a58fb3a7df
--- /dev/null
+++ b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
@@ -0,0 +1,56 @@
+import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
+
+describe('RecentSearchesService', () => {
+ let service;
+
+ beforeEach(() => {
+ service = new RecentSearchesService();
+ window.localStorage.removeItem(service.localStorageKey);
+ });
+
+ describe('fetch', () => {
+ it('should default to empty array', (done) => {
+ const fetchItemsPromise = service.fetch();
+
+ fetchItemsPromise
+ .then((items) => {
+ expect(items).toEqual([]);
+ done();
+ })
+ .catch((err) => {
+ done.fail('Shouldn\'t reject with empty localStorage key', err);
+ });
+ });
+
+ it('should reject when unable to parse', (done) => {
+ window.localStorage.setItem(service.localStorageKey, 'fail');
+ const fetchItemsPromise = service.fetch();
+
+ fetchItemsPromise
+ .catch(() => {
+ done();
+ });
+ });
+
+ it('should return items from localStorage', (done) => {
+ window.localStorage.setItem(service.localStorageKey, '["foo", "bar"]');
+ const fetchItemsPromise = service.fetch();
+
+ fetchItemsPromise
+ .then((items) => {
+ expect(items).toEqual(['foo', 'bar']);
+ done();
+ });
+ });
+ });
+
+ describe('setRecentSearches', () => {
+ it('should save things in localStorage', () => {
+ const items = ['foo', 'bar'];
+ service.save(items);
+ const newLocalStorageValue =
+ window.localStorage.getItem(service.localStorageKey);
+ expect(JSON.parse(newLocalStorageValue)).toEqual(items);
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js b/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js
new file mode 100644
index 00000000000..1eebc6f2367
--- /dev/null
+++ b/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js
@@ -0,0 +1,59 @@
+import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
+
+describe('RecentSearchesStore', () => {
+ let store;
+
+ beforeEach(() => {
+ store = new RecentSearchesStore();
+ });
+
+ describe('addRecentSearch', () => {
+ it('should add to the front of the list', () => {
+ store.addRecentSearch('foo');
+ store.addRecentSearch('bar');
+
+ expect(store.state.recentSearches).toEqual(['bar', 'foo']);
+ });
+
+ it('should deduplicate', () => {
+ store.addRecentSearch('foo');
+ store.addRecentSearch('bar');
+ store.addRecentSearch('foo');
+
+ expect(store.state.recentSearches).toEqual(['foo', 'bar']);
+ });
+
+ it('only keeps track of 5 items', () => {
+ store.addRecentSearch('1');
+ store.addRecentSearch('2');
+ store.addRecentSearch('3');
+ store.addRecentSearch('4');
+ store.addRecentSearch('5');
+ store.addRecentSearch('6');
+ store.addRecentSearch('7');
+
+ expect(store.state.recentSearches).toEqual(['7', '6', '5', '4', '3']);
+ });
+ });
+
+ describe('setRecentSearches', () => {
+ it('should override list', () => {
+ store.setRecentSearches([
+ 'foo',
+ 'bar',
+ ]);
+ store.setRecentSearches([
+ 'baz',
+ 'qux',
+ ]);
+
+ expect(store.state.recentSearches).toEqual(['baz', 'qux']);
+ });
+
+ it('only keeps track of 5 items', () => {
+ store.setRecentSearches(['1', '2', '3', '4', '5', '6', '7']);
+
+ expect(store.state.recentSearches).toEqual(['1', '2', '3', '4', '5']);
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
index 483063fb889..e2dd9519898 100644
--- a/spec/javascripts/fixtures/environments/metrics.html.haml
+++ b/spec/javascripts/fixtures/environments/metrics.html.haml
@@ -1,12 +1,62 @@
-%div
+.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' }
.top-area
.row
.col-sm-6
%h3.page-title
Metrics for environment
- .row
- .col-sm-12
- %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
- .row
- .col-sm-12
- %svg.prometheus-graph{ 'graph-type' => 'memory_values' } \ No newline at end of file
+ .prometheus-state
+ .js-getting-started.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Get started with performance monitoring
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ Configure Prometheus
+ .js-loading.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Waiting for performance data
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ View documentation
+ .js-unable-to-connect.hidden
+ .row
+ .col-md-4.col-md-offset-4.state-svg
+ %svg
+ .row
+ .col-md-6.col-md-offset-3
+ %h4.text-center.state-title
+ Unable to connect to Prometheus server
+ .row
+ .col-md-6.col-md-offset-3
+ .description-text.text-center.state-description
+ Ensure connectivity is available from the GitLab server to the Prometheus server
+ .row.state-button-section
+ .col-md-4.col-md-offset-4.text-center.state-button
+ %a.btn.btn-success
+ View documentation
+ .prometheus-graphs
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index fddeaaf504d..47d904b865b 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
let(:pipeline) do
create(
:ci_pipeline,
@@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merge_request)
end
+ it 'merge_requests/merged_merge_request.html.raw' do |example|
+ allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true)
+ render_merge_request(example.description, merged_merge_request)
+ end
+
private
def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/fixtures/pdf_viewer.html.haml b/spec/javascripts/fixtures/pdf_viewer.html.haml
new file mode 100644
index 00000000000..2e57beae54b
--- /dev/null
+++ b/spec/javascripts/fixtures/pdf_viewer.html.haml
@@ -0,0 +1 @@
+.file-content#js-pdf-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/sketch_viewer.html.haml b/spec/javascripts/fixtures/sketch_viewer.html.haml
new file mode 100644
index 00000000000..f01bd00925a
--- /dev/null
+++ b/spec/javascripts/fixtures/sketch_viewer.html.haml
@@ -0,0 +1,2 @@
+.file-content#js-sketch-viewer{ data: { endpoint: '/test_sketch_file.sketch' } }
+ .js-loading-icon
diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js
new file mode 100644
index 00000000000..806d728a874
--- /dev/null
+++ b/spec/javascripts/issue_show/issue_title_spec.js
@@ -0,0 +1,22 @@
+import Vue from 'vue';
+import issueTitle from '~/issue_show/issue_title';
+
+describe('Issue Title', () => {
+ let IssueTitleComponent;
+
+ beforeEach(() => {
+ IssueTitleComponent = Vue.extend(issueTitle);
+ });
+
+ it('should render a title', () => {
+ const component = new IssueTitleComponent({
+ propsData: {
+ initialTitle: 'wow',
+ endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title',
+ },
+ }).$mount();
+
+ expect(component.$el.classList).toContain('title');
+ expect(component.$el.innerHTML).toContain('wow');
+ });
+});
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index aabc8bea12f..9a2570ef7e9 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,18 +1,17 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
+/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue';
require('~/lib/utils/text_utility');
describe('Issue', function() {
- var INVALID_URL = 'http://goesnowhere.nothing/whereami';
- var $boxClosed, $boxOpen, $btnClose, $btnReopen;
+ let $boxClosed, $boxOpen, $btnClose, $btnReopen;
preloadFixtures('issues/closed-issue.html.raw');
preloadFixtures('issues/issue-with-task-list.html.raw');
preloadFixtures('issues/open-issue.html.raw');
function expectErrorMessage() {
- var $flashMessage = $('div.flash-alert');
+ const $flashMessage = $('div.flash-alert');
expect($flashMessage).toExist();
expect($flashMessage).toBeVisible();
expect($flashMessage).toHaveText('Unable to update this issue at this time.');
@@ -26,10 +25,28 @@ describe('Issue', function() {
expectVisibility($btnReopen, !isIssueOpen);
}
- function expectPendingRequest(req, $triggeredButton) {
- expect(req.type).toBe('PUT');
- expect(req.url).toBe($triggeredButton.attr('href'));
- expect($triggeredButton).toHaveProp('disabled', true);
+ function expectNewBranchButtonState(isPending, canCreate) {
+ if (Issue.$btnNewBranch.length === 0) {
+ return;
+ }
+
+ const $available = Issue.$btnNewBranch.find('.available');
+ expect($available).toHaveText('New branch');
+
+ if (!isPending && canCreate) {
+ expect($available).toBeVisible();
+ } else {
+ expect($available).toBeHidden();
+ }
+
+ const $unavailable = Issue.$btnNewBranch.find('.unavailable');
+ expect($unavailable).toHaveText('New branch unavailable');
+
+ if (!isPending && !canCreate) {
+ expect($unavailable).toBeVisible();
+ } else {
+ expect($unavailable).toBeHidden();
+ }
}
function expectVisibility($element, shouldBeVisible) {
@@ -81,100 +98,107 @@ describe('Issue', function() {
});
});
- describe('close issue', function() {
- beforeEach(function() {
- loadFixtures('issues/open-issue.html.raw');
- findElements();
- this.issue = new Issue();
-
- expectIssueState(true);
- });
+ [true, false].forEach((isIssueInitiallyOpen) => {
+ describe(`with ${isIssueInitiallyOpen ? 'open' : 'closed'} issue`, function() {
+ const action = isIssueInitiallyOpen ? 'close' : 'reopen';
+
+ function ajaxSpy(req) {
+ if (req.url === this.$triggeredButton.attr('href')) {
+ expect(req.type).toBe('PUT');
+ expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expectNewBranchButtonState(true, false);
+ return this.issueStateDeferred;
+ } else if (req.url === Issue.$btnNewBranch.data('path')) {
+ expect(req.type).toBe('get');
+ expectNewBranchButtonState(true, false);
+ return this.canCreateBranchDeferred;
+ }
+
+ expect(req.url).toBe('unexpected');
+ return null;
+ }
+
+ beforeEach(function() {
+ if (isIssueInitiallyOpen) {
+ loadFixtures('issues/open-issue.html.raw');
+ } else {
+ loadFixtures('issues/closed-issue.html.raw');
+ }
+
+ findElements();
+ this.issue = new Issue();
+ expectIssueState(isIssueInitiallyOpen);
+ this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
+
+ this.$projectIssuesCounter = $('.issue_counter');
+ this.$projectIssuesCounter.text('1,001');
+
+ this.issueStateDeferred = new jQuery.Deferred();
+ this.canCreateBranchDeferred = new jQuery.Deferred();
+
+ spyOn(jQuery, 'ajax').and.callFake(ajaxSpy.bind(this));
+ });
- it('closes an issue', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
+ it(`${action}s the issue`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.resolve({
id: 34
});
- });
-
- $btnClose.trigger('click');
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: !isIssueInitiallyOpen
+ });
- expectIssueState(false);
- expect($btnClose).toHaveProp('disabled', false);
- expect($('.issue_counter')).toHaveText(0);
- });
+ expectIssueState(!isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
+ expectNewBranchButtonState(false, !isIssueInitiallyOpen);
+ });
- it('fails to close an issue with success:false', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
+ it(`fails to ${action} the issue if saved:false`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.resolve({
saved: false
});
- });
-
- $btnClose.attr('href', INVALID_URL);
- $btnClose.trigger('click');
-
- expectIssueState(true);
- expect($btnClose).toHaveProp('disabled', false);
- expectErrorMessage();
- expect($('.issue_counter')).toHaveText(1);
- });
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: isIssueInitiallyOpen
+ });
- it('fails to closes an issue with HTTP error', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.error();
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
});
- $btnClose.attr('href', INVALID_URL);
- $btnClose.trigger('click');
-
- expectIssueState(true);
- expect($btnClose).toHaveProp('disabled', true);
- expectErrorMessage();
- expect($('.issue_counter')).toHaveText(1);
- });
-
- it('updates counter', () => {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnClose);
- req.success({
- id: 34
+ it(`fails to ${action} the issue if HTTP error occurs`, function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ this.canCreateBranchDeferred.resolve({
+ can_create_branch: isIssueInitiallyOpen
});
- });
- expect($('.issue_counter')).toHaveText(1);
- $('.issue_counter').text('1,001');
- expect($('.issue_counter').text()).toEqual('1,001');
- $btnClose.trigger('click');
- expect($('.issue_counter').text()).toEqual('1,000');
- });
- });
+ expectIssueState(isIssueInitiallyOpen);
+ expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expectErrorMessage();
+ expect(this.$projectIssuesCounter.text()).toBe('1,001');
+ expectNewBranchButtonState(false, isIssueInitiallyOpen);
+ });
- describe('reopen issue', function() {
- beforeEach(function() {
- loadFixtures('issues/closed-issue.html.raw');
- findElements();
- this.issue = new Issue();
+ it('disables the new branch button if Ajax call fails', function() {
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ this.canCreateBranchDeferred.reject();
- expectIssueState(false);
- });
-
- it('reopens an issue', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
- expectPendingRequest(req, $btnReopen);
- req.success({
- id: 34
- });
+ expectNewBranchButtonState(false, false);
});
- $btnReopen.trigger('click');
+ it('does not trigger Ajax call if new branch button is missing', function() {
+ Issue.$btnNewBranch = $();
+ this.canCreateBranchDeferred = null;
- expectIssueState(true);
- expect($btnReopen).toHaveProp('disabled', false);
- expect($('.issue_counter')).toHaveText(1);
+ this.$triggeredButton.trigger('click');
+ this.issueStateDeferred.reject();
+ });
});
});
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 5a93d479c1f..03f3c206f44 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -310,5 +310,56 @@ require('~/lib/utils/common_utils');
});
}, 10000);
});
+
+ describe('gl.utils.setFavicon', () => {
+ it('should set page favicon to provided favicon', () => {
+ const faviconName = 'custom_favicon';
+ const fakeLink = {
+ setAttribute() {},
+ };
+
+ spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
+ spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
+ expect(attr).toEqual('href');
+ expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true);
+ });
+ gl.utils.setFavicon(faviconName);
+ });
+ });
+
+ describe('gl.utils.resetFavicon', () => {
+ it('should reset page favicon to tanuki', () => {
+ const fakeLink = {
+ setAttribute() {},
+ };
+
+ spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
+ spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
+ expect(attr).toEqual('href');
+ expect(val).toMatch(/favicon/);
+ });
+ gl.utils.resetFavicon();
+ });
+ });
+
+ describe('gl.utils.setCiStatusFavicon', () => {
+ it('should set page favicon to CI status favicon based on provided status', () => {
+ const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
+ const FAVICON_PATH = 'ci_favicons/';
+ const FAVICON = 'icon_status_success';
+ const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
+ const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ icon: FAVICON });
+ expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON);
+ options.success();
+ expect(spyResetFavicon).toHaveBeenCalled();
+ options.error();
+ expect(spyResetFavicon).toHaveBeenCalled();
+ });
+
+ gl.utils.setCiStatusFavicon(BUILD_URL);
+ });
+ });
});
})();
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
new file mode 100644
index 00000000000..5fde8be9123
--- /dev/null
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -0,0 +1,41 @@
+import { formatRelevantDigits } from '~/lib/utils/number_utils';
+
+describe('Number Utils', () => {
+ describe('formatRelevantDigits', () => {
+ it('returns an empty string when the number is NaN', () => {
+ expect(formatRelevantDigits('fail')).toBe('');
+ });
+
+ it('returns 4 decimals when there is 4 plus digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('1000.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+ expect(rightFromDecimal.length).toBe(4);
+ expect(leftFromDecimal.length).toBe(4);
+ });
+
+ it('returns 3 decimals when there is 1 digit to the left', () => {
+ const formattedNumber = formatRelevantDigits('0.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+ expect(rightFromDecimal.length).toBe(3);
+ expect(leftFromDecimal.length).toBe(1);
+ });
+
+ it('returns 2 decimals when there is 2 digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('10.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+ expect(rightFromDecimal.length).toBe(2);
+ expect(leftFromDecimal.length).toBe(2);
+ });
+
+ it('returns 1 decimal when there is 3 digits to the left', () => {
+ const formattedNumber = formatRelevantDigits('100.1234567');
+ const rightFromDecimal = formattedNumber.split('.')[1];
+ const leftFromDecimal = formattedNumber.split('.')[0];
+ expect(rightFromDecimal.length).toBe(1);
+ expect(leftFromDecimal.length).toBe(3);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index e3429c2a1cb..918b6d32c43 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -4,6 +4,20 @@ import Poll from '~/lib/utils/poll';
Vue.use(VueResource);
+const waitForAllCallsToFinish = (service, waitForCount, successCallback) => {
+ const timer = () => {
+ setTimeout(() => {
+ if (service.fetch.calls.count() === waitForCount) {
+ successCallback();
+ } else {
+ timer();
+ }
+ }, 5);
+ };
+
+ timer();
+};
+
class ServiceMock {
constructor(endpoint) {
this.service = Vue.resource(endpoint);
@@ -16,6 +30,7 @@ class ServiceMock {
describe('Poll', () => {
let callbacks;
+ let service;
beforeEach(() => {
callbacks = {
@@ -23,8 +38,11 @@ describe('Poll', () => {
error: () => {},
};
+ service = new ServiceMock('endpoint');
+
spyOn(callbacks, 'success');
spyOn(callbacks, 'error');
+ spyOn(service, 'fetch').and.callThrough();
});
it('calls the success callback when no header for interval is provided', (done) => {
@@ -35,19 +53,20 @@ describe('Poll', () => {
Vue.http.interceptors.push(successInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
+
done();
}, 0);
-
- Vue.http.interceptors = _.without(Vue.http.interceptors, successInterceptor);
});
it('calls the error callback whe the http request returns an error', (done) => {
@@ -58,19 +77,19 @@ describe('Poll', () => {
Vue.http.interceptors.push(errorInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
}).makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
expect(callbacks.error).toHaveBeenCalled();
- done();
- }, 0);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
- Vue.http.interceptors = _.without(Vue.http.interceptors, errorInterceptor);
+ done();
+ });
});
it('should call the success callback when the interval header is -1', (done) => {
@@ -81,7 +100,7 @@ describe('Poll', () => {
Vue.http.interceptors.push(intervalInterceptor);
new Poll({
- resource: new ServiceMock('endpoint'),
+ resource: service,
method: 'fetch',
successCallback: callbacks.success,
errorCallback: callbacks.error,
@@ -90,10 +109,11 @@ describe('Poll', () => {
setTimeout(() => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
+
+ Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
+
done();
}, 0);
-
- Vue.http.interceptors = _.without(Vue.http.interceptors, intervalInterceptor);
});
it('starts polling when http status is 200 and interval header is provided', (done) => {
@@ -103,26 +123,28 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
- spyOn(service, 'fetch').and.callThrough();
-
- new Poll({
+ const Polling = new Poll({
resource: service,
method: 'fetch',
data: { page: 1 },
successCallback: callbacks.success,
errorCallback: callbacks.error,
- }).makeRequest();
+ });
+
+ Polling.makeRequest();
+
+ waitForAllCallsToFinish(service, 2, () => {
+ Polling.stop();
- setTimeout(() => {
expect(service.fetch.calls.count()).toEqual(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
- done();
- }, 5);
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
describe('stop', () => {
@@ -133,9 +155,6 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
- spyOn(service, 'fetch').and.callThrough();
-
const Polling = new Poll({
resource: service,
method: 'fetch',
@@ -150,14 +169,15 @@ describe('Poll', () => {
Polling.makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 1, () => {
expect(service.fetch.calls.count()).toEqual(1);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- done();
- }, 100);
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
});
@@ -169,10 +189,6 @@ describe('Poll', () => {
Vue.http.interceptors.push(pollInterceptor);
- const service = new ServiceMock('endpoint');
-
- spyOn(service, 'fetch').and.callThrough();
-
const Polling = new Poll({
resource: service,
method: 'fetch',
@@ -187,17 +203,22 @@ describe('Poll', () => {
});
spyOn(Polling, 'stop').and.callThrough();
+ spyOn(Polling, 'restart').and.callThrough();
Polling.makeRequest();
- setTimeout(() => {
+ waitForAllCallsToFinish(service, 2, () => {
+ Polling.stop();
+
expect(service.fetch.calls.count()).toEqual(2);
expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
expect(Polling.stop).toHaveBeenCalled();
- done();
- }, 10);
+ expect(Polling.restart).toHaveBeenCalled();
- Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pollInterceptor);
+
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 4200e943121..daef9b93fa5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -1,110 +1,108 @@
require('~/lib/utils/text_utility');
-(() => {
- describe('text_utility', () => {
- describe('gl.text.getTextWidth', () => {
- it('returns zero width when no text is passed', () => {
- expect(gl.text.getTextWidth('')).toBe(0);
- });
+describe('text_utility', () => {
+ describe('gl.text.getTextWidth', () => {
+ it('returns zero width when no text is passed', () => {
+ expect(gl.text.getTextWidth('')).toBe(0);
+ });
- it('returns zero width when no text is passed and font is passed', () => {
- expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
- });
+ it('returns zero width when no text is passed and font is passed', () => {
+ expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
+ });
- it('returns width when text is passed', () => {
- expect(gl.text.getTextWidth('foo') > 0).toBe(true);
- });
+ it('returns width when text is passed', () => {
+ expect(gl.text.getTextWidth('foo') > 0).toBe(true);
+ });
- it('returns bigger width when font is larger', () => {
- const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
- const regular = gl.text.getTextWidth('foo', '10px sans-serif');
- expect(largeFont > regular).toBe(true);
- });
+ it('returns bigger width when font is larger', () => {
+ const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
+ const regular = gl.text.getTextWidth('foo', '10px sans-serif');
+ expect(largeFont > regular).toBe(true);
});
+ });
- describe('gl.text.pluralize', () => {
- it('returns pluralized', () => {
- expect(gl.text.pluralize('test', 2)).toBe('tests');
- });
+ describe('gl.text.pluralize', () => {
+ it('returns pluralized', () => {
+ expect(gl.text.pluralize('test', 2)).toBe('tests');
+ });
- it('returns pluralized when count is 0', () => {
- expect(gl.text.pluralize('test', 0)).toBe('tests');
- });
+ it('returns pluralized when count is 0', () => {
+ expect(gl.text.pluralize('test', 0)).toBe('tests');
+ });
- it('does not return pluralized', () => {
- expect(gl.text.pluralize('test', 1)).toBe('test');
- });
+ it('does not return pluralized', () => {
+ expect(gl.text.pluralize('test', 1)).toBe('test');
});
+ });
- describe('gl.text.highCountTrim', () => {
- it('returns 99+ for count >= 100', () => {
- expect(gl.text.highCountTrim(105)).toBe('99+');
- expect(gl.text.highCountTrim(100)).toBe('99+');
- });
+ describe('gl.text.highCountTrim', () => {
+ it('returns 99+ for count >= 100', () => {
+ expect(gl.text.highCountTrim(105)).toBe('99+');
+ expect(gl.text.highCountTrim(100)).toBe('99+');
+ });
- it('returns exact number for count < 100', () => {
- expect(gl.text.highCountTrim(45)).toBe(45);
- });
+ it('returns exact number for count < 100', () => {
+ expect(gl.text.highCountTrim(45)).toBe(45);
});
+ });
- describe('gl.text.insertText', () => {
- let textArea;
+ describe('gl.text.insertText', () => {
+ let textArea;
- beforeAll(() => {
- textArea = document.createElement('textarea');
- document.querySelector('body').appendChild(textArea);
- });
+ beforeAll(() => {
+ textArea = document.createElement('textarea');
+ document.querySelector('body').appendChild(textArea);
+ });
- afterAll(() => {
- textArea.parentNode.removeChild(textArea);
- });
+ afterAll(() => {
+ textArea.parentNode.removeChild(textArea);
+ });
- describe('without selection', () => {
- it('inserts the tag on an empty line', () => {
- const initialValue = '';
+ describe('without selection', () => {
+ it('inserts the tag on an empty line', () => {
+ const initialValue = '';
- textArea.value = initialValue;
- textArea.selectionStart = 0;
- textArea.selectionEnd = 0;
+ textArea.value = initialValue;
+ textArea.selectionStart = 0;
+ textArea.selectionEnd = 0;
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
- it('inserts the tag on a new line if the current one is not empty', () => {
- const initialValue = 'some text';
+ it('inserts the tag on a new line if the current one is not empty', () => {
+ const initialValue = 'some text';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}\n* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}\n* `);
+ });
- it('inserts the tag on the same line if the current line only contains spaces', () => {
- const initialValue = ' ';
+ it('inserts the tag on the same line if the current line only contains spaces', () => {
+ const initialValue = ' ';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
- it('inserts the tag on the same line if the current line only contains tabs', () => {
- const initialValue = '\t\t\t';
+ it('inserts the tag on the same line if the current line only contains tabs', () => {
+ const initialValue = '\t\t\t';
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
+ gl.text.insertText(textArea, textArea.value, '*', null, '', false);
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
});
});
-})();
+});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 7b9632be84e..e437333d522 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,8 +1,12 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
require('~/merge_request_tabs');
+require('~/commit/pipelines/pipelines_bundle.js');
require('~/breakpoints');
require('~/lib/utils/common_utils');
+require('~/diff');
+require('~/single_file_diff');
+require('~/files_comment_button');
require('vendor/jquery.scrollTo');
(function () {
@@ -39,7 +43,8 @@ require('vendor/jquery.scrollTo');
});
afterEach(function () {
- this.class.destroy();
+ this.class.unbindEvents();
+ this.class.destroyPipelinesView();
});
describe('#activateTab', function () {
@@ -65,6 +70,7 @@ require('vendor/jquery.scrollTo');
expect($('#diffs')).toHaveClass('active');
});
});
+
describe('#opensInNewTab', function () {
var tabUrl;
var windowTarget = '_blank';
@@ -116,6 +122,7 @@ require('vendor/jquery.scrollTo');
stopImmediatePropagation: function () {}
});
});
+
it('opens page tab in a new browser tab with Cmd+Click - Mac', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
@@ -129,6 +136,7 @@ require('vendor/jquery.scrollTo');
stopImmediatePropagation: function () {}
});
});
+
it('opens page tab in a new browser tab with Middle-click - Mac/PC', function () {
spyOn(window, 'open').and.callFake(function (url, name) {
expect(url).toEqual(tabUrl);
@@ -149,6 +157,7 @@ require('vendor/jquery.scrollTo');
spyOn($, 'ajax').and.callFake(function () {});
this.subject = this.class.setCurrentAction;
});
+
it('changes from commits', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1/commits'
@@ -156,13 +165,16 @@ require('vendor/jquery.scrollTo');
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
});
+
it('changes from diffs', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1/diffs'
});
+
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
});
+
it('changes from diffs.html', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1/diffs.html'
@@ -170,6 +182,7 @@ require('vendor/jquery.scrollTo');
expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1');
expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
});
+
it('changes from notes', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1'
@@ -177,6 +190,7 @@ require('vendor/jquery.scrollTo');
expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
});
+
it('includes search parameters and hash string', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1/diffs',
@@ -185,6 +199,7 @@ require('vendor/jquery.scrollTo');
});
expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
});
+
it('replaces the current history state', function () {
var newState;
setLocation({
@@ -197,6 +212,7 @@ require('vendor/jquery.scrollTo');
}, document.title, newState);
}
});
+
it('treats "show" like "notes"', function () {
setLocation({
pathname: '/foo/bar/merge_requests/1/commits'
@@ -207,12 +223,16 @@ require('vendor/jquery.scrollTo');
describe('#tabShown', () => {
beforeEach(function () {
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ html: '' });
+ });
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
});
describe('with "Side-by-side"/parallel diff view', () => {
beforeEach(function () {
this.class.diffViewType = () => 'parallel';
+ gl.Diff.prototype.diffViewType = () => 'parallel';
});
it('maintains `container-limited` for pipelines tab', function (done) {
@@ -224,7 +244,6 @@ require('vendor/jquery.scrollTo');
});
});
};
-
asyncClick('.merge-request-tabs .pipelines-tab a')
.then(() => asyncClick('.merge-request-tabs .diffs-tab a'))
.then(() => asyncClick('.merge-request-tabs .pipelines-tab a'))
@@ -237,6 +256,28 @@ require('vendor/jquery.scrollTo');
done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`);
});
});
+
+ it('maintains `container-limited` when switching from "Changes" tab before it loads', function (done) {
+ const asyncClick = function (selector) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ document.querySelector(selector).click();
+ resolve();
+ });
+ });
+ };
+
+ asyncClick('.merge-request-tabs .diffs-tab a')
+ .then(() => asyncClick('.merge-request-tabs .notes-tab a'))
+ .then(() => {
+ const hasContainerLimitedClass = document.querySelector('.content-wrapper .container-fluid').classList.contains('container-limited');
+ expect(hasContainerLimitedClass).toBe(true);
+ })
+ .then(done)
+ .catch((err) => {
+ done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`);
+ });
+ });
});
});
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index d5193b41c33..88dae8c3e06 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility');
it('should call showCIStatus even if a notification should not be displayed', function() {
var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
});
it('should call showCIStatus when a notification should be displayed', function() {
var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(true);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
});
it('should call showCICoverage when the coverage rate is set', function() {
var spy;
spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage);
});
@@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility');
var spy;
this.ciStatusData.coverage = null;
spy = spyOn(this["class"], 'showCICoverage').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
return expect(spy).not.toHaveBeenCalled();
});
it('should not display a notification on the first check after the widget has been created', function() {
var spy;
spy = spyOn(window, 'notify');
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"] = new window.gl.MergeRequestWidget(this.opts);
this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled();
@@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility');
it('should update the pipeline URL when the pipeline changes', function() {
var spy;
spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
this.ciStatusData.pipeline += 1;
this["class"].getCIStatus(false);
@@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility');
it('should update the commit URL when the sha changes', function() {
var spy;
spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
+ spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false);
this.ciStatusData.sha = "9b50b99a";
this["class"].getCIStatus(false);
diff --git a/spec/javascripts/merged_buttons_spec.js b/spec/javascripts/merged_buttons_spec.js
new file mode 100644
index 00000000000..b5c5e60dd97
--- /dev/null
+++ b/spec/javascripts/merged_buttons_spec.js
@@ -0,0 +1,44 @@
+/* global MergedButtons */
+
+import '~/merged_buttons';
+
+describe('MergedButtons', () => {
+ const fixturesPath = 'merge_requests/merged_merge_request.html.raw';
+ preloadFixtures(fixturesPath);
+
+ beforeEach(() => {
+ loadFixtures(fixturesPath);
+ this.mergedButtons = new MergedButtons();
+ this.$removeBranchWidget = $('.remove_source_branch_widget:not(.failed)');
+ this.$removeBranchProgress = $('.remove_source_branch_in_progress');
+ this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
+ this.$removeBranchButton = $('.remove_source_branch');
+ });
+
+ describe('removeSourceBranch', () => {
+ it('shows loader', () => {
+ $('.remove_source_branch').trigger('click');
+ expect(this.$removeBranchProgress).toBeVisible();
+ expect(this.$removeBranchWidget).not.toBeVisible();
+ });
+ });
+
+ describe('removeBranchSuccess', () => {
+ it('refreshes page when branch removed', () => {
+ spyOn(gl.utils, 'refreshCurrentPage').and.stub();
+ const response = { status: 200 };
+ this.$removeBranchButton.trigger('ajax:success', response, 'xhr');
+ expect(gl.utils.refreshCurrentPage).toHaveBeenCalled();
+ });
+ });
+
+ describe('removeBranchError', () => {
+ it('shows error message', () => {
+ const response = { status: 500 };
+ this.$removeBranchButton.trigger('ajax:error', response, 'xhr');
+ expect(this.$removeBranchFailed).toBeVisible();
+ expect(this.$removeBranchProgress).not.toBeVisible();
+ expect(this.$removeBranchWidget).not.toBeVisible();
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
index a3c1c5e1b7c..4b904fc2960 100644
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -1,5 +1,4 @@
import 'jquery';
-import '~/lib/utils/common_utils';
import PrometheusGraph from '~/monitoring/prometheus_graph';
import { prometheusMockData } from './prometheus_mock_data';
@@ -12,6 +11,7 @@ describe('PrometheusGraph', () => {
beforeEach(() => {
loadFixtures(fixtureName);
+ $('.prometheus-container').data('has-metrics', 'true');
this.prometheusGraph = new PrometheusGraph();
const self = this;
const fakeInit = (metricsResponse) => {
@@ -37,9 +37,11 @@ describe('PrometheusGraph', () => {
it('transforms the data', () => {
this.prometheusGraph.init(prometheusMockData.metrics);
- expect(this.prometheusGraph.data).toBeDefined();
- expect(this.prometheusGraph.data.cpu_values.length).toBe(121);
- expect(this.prometheusGraph.data.memory_values.length).toBe(121);
+ Object.keys(this.prometheusGraph.graphSpecificProperties, (key) => {
+ const graphProps = this.prometheusGraph.graphSpecificProperties[key];
+ expect(graphProps.data).toBeDefined();
+ expect(graphProps.data.length).toBe(121);
+ });
});
it('creates two graphs', () => {
@@ -68,8 +70,29 @@ describe('PrometheusGraph', () => {
expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
- expect($axisLabelContainer.find('rect').length).toBe(2);
+ expect($axisLabelContainer.find('rect').length).toBe(3);
expect($axisLabelContainer.find('text').length).toBe(4);
});
});
});
+
+describe('PrometheusGraphs UX states', () => {
+ const fixtureName = 'static/environments/metrics.html.raw';
+ preloadFixtures(fixtureName);
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ this.prometheusGraph = new PrometheusGraph();
+ });
+
+ it('shows a specified state', () => {
+ this.prometheusGraph.state = '.js-getting-started';
+ this.prometheusGraph.updateState();
+ const $state = $('.js-getting-started');
+ expect($state).toBeDefined();
+ expect($('.state-title', $state)).toBeDefined();
+ expect($('.state-svg', $state)).toBeDefined();
+ expect($('.state-description', $state)).toBeDefined();
+ expect($('.state-button', $state)).toBeDefined();
+ });
+});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index b30c5da8822..07dc51a7815 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') {
'./snippet/snippet_bundle.js',
'./terminal/terminal_bundle.js',
'./users/users_bundle.js',
+ './issue_show/index.js',
];
describe('Uncovered files', function () {
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 0f390c8b980..3960759f7cb 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -22,7 +22,7 @@ require('./mock_u2f_device');
it('allows registering a U2F device', function() {
var deviceResponse, inProgressMessage, registeredMessage, setupButton;
setupButton = this.container.find("#js-setup-u2f-device");
- expect(setupButton.text()).toBe('Setup New U2F Device');
+ expect(setupButton.text()).toBe('Setup new U2F device');
setupButton.trigger('click');
inProgressMessage = this.container.children("p");
expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
diff --git a/spec/javascripts/vue_pipelines_index/async_button_spec.js b/spec/javascripts/vue_pipelines_index/async_button_spec.js
index bc8e504c413..6e910d2dc71 100644
--- a/spec/javascripts/vue_pipelines_index/async_button_spec.js
+++ b/spec/javascripts/vue_pipelines_index/async_button_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import asyncButtonComp from '~/vue_pipelines_index/components/async_button';
+import asyncButtonComp from '~/vue_pipelines_index/components/async_button.vue';
describe('Pipelines Async Button', () => {
let component;
diff --git a/spec/javascripts/vue_pipelines_index/empty_state_spec.js b/spec/javascripts/vue_pipelines_index/empty_state_spec.js
index 733337168dc..2b10d54babe 100644
--- a/spec/javascripts/vue_pipelines_index/empty_state_spec.js
+++ b/spec/javascripts/vue_pipelines_index/empty_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import emptyStateComp from '~/vue_pipelines_index/components/empty_state';
+import emptyStateComp from '~/vue_pipelines_index/components/empty_state.vue';
describe('Pipelines Empty State', () => {
let component;
diff --git a/spec/javascripts/vue_pipelines_index/error_state_spec.js b/spec/javascripts/vue_pipelines_index/error_state_spec.js
index 524e018b1fa..7999c15c18d 100644
--- a/spec/javascripts/vue_pipelines_index/error_state_spec.js
+++ b/spec/javascripts/vue_pipelines_index/error_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import errorStateComp from '~/vue_pipelines_index/components/error_state';
+import errorStateComp from '~/vue_pipelines_index/components/error_state.vue';
describe('Pipelines Error State', () => {
let component;
diff --git a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
index dba998c7688..0910df61915 100644
--- a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
+++ b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js
@@ -15,6 +15,11 @@ describe('Pipelines Actions dropdown', () => {
name: 'stop_review',
path: '/root/review-app/builds/1893/play',
},
+ {
+ name: 'foo',
+ path: '#',
+ playable: false,
+ },
];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
@@ -59,4 +64,14 @@ describe('Pipelines Actions dropdown', () => {
expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
});
+
+ it('should render a disabled action when it\'s not playable', () => {
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
+ ).toEqual('disabled');
+
+ expect(
+ component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
+ ).toEqual(true);
+ });
});