summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-08 09:08:23 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-08 09:08:23 +0000
commitb001207ce2033589373cd7558ca69c4e5732ce6b (patch)
tree11f0b257a3a8e4644afd8995ac628a869d6d3811 /spec
parent449287e15a3633f7a17533c2da0273e31ddf1c1f (diff)
downloadgitlab-ce-b001207ce2033589373cd7558ca69c4e5732ce6b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/board.json3
-rw-r--r--spec/frontend/awards_handler_spec.js (renamed from spec/javascripts/awards_handler_spec.js)111
-rw-r--r--spec/frontend/collapsed_sidebar_todo_spec.js (renamed from spec/javascripts/collapsed_sidebar_todo_spec.js)21
-rw-r--r--spec/frontend/comment_type_toggle_spec.js169
-rw-r--r--spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js)36
-rw-r--r--spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js)32
-rw-r--r--spec/frontend/gl_form_spec.js (renamed from spec/javascripts/gl_form_spec.js)63
-rw-r--r--spec/frontend/global_search_input_spec.js (renamed from spec/javascripts/global_search_input_spec.js)48
-rw-r--r--spec/frontend/labels_issue_sidebar_spec.js (renamed from spec/javascripts/labels_issue_sidebar_spec.js)10
-rw-r--r--spec/frontend/line_highlighter_spec.js268
-rw-r--r--spec/frontend/merge_request_tabs_spec.js (renamed from spec/javascripts/merge_request_tabs_spec.js)161
-rw-r--r--spec/frontend/right_sidebar_spec.js (renamed from spec/javascripts/right_sidebar_spec.js)22
-rw-r--r--spec/frontend/shortcuts_spec.js46
-rw-r--r--spec/frontend/user_popovers_spec.js (renamed from spec/javascripts/user_popovers_spec.js)10
-rw-r--r--spec/frontend/zen_mode_spec.js (renamed from spec/javascripts/zen_mode_spec.js)18
-rw-r--r--spec/javascripts/comment_type_toggle_spec.js168
-rw-r--r--spec/javascripts/line_highlighter_spec.js261
-rw-r--r--spec/javascripts/shortcuts_spec.js46
-rw-r--r--spec/lib/gitlab/routing_spec.rb21
-rw-r--r--spec/models/project_spec.rb26
20 files changed, 815 insertions, 725 deletions
diff --git a/spec/fixtures/api/schemas/public_api/v4/board.json b/spec/fixtures/api/schemas/public_api/v4/board.json
index e4933ee0b93..89a21c29969 100644
--- a/spec/fixtures/api/schemas/public_api/v4/board.json
+++ b/spec/fixtures/api/schemas/public_api/v4/board.json
@@ -78,7 +78,8 @@
},
"position": { "type": ["integer", "null"] },
"max_issue_count": { "type": "integer" },
- "max_issue_weight": { "type": "integer" }
+ "max_issue_weight": { "type": "integer" },
+ "limit_metric": { "type": ["string", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 02200f77ad7..754f0702b84 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Cookies from 'js-cookie';
import loadAwardsHandler from '~/awards_handler';
import '~/lib/utils/common_utils';
+import waitForPromises from './helpers/wait_for_promises';
window.gl = window.gl || {};
window.gon = window.gon || {};
@@ -10,28 +11,32 @@ let openAndWaitForEmojiMenu;
let awardsHandler = null;
const urlRoot = gon.relative_url_root;
-const lazyAssert = function(done, assertFn) {
- setTimeout(function() {
- assertFn();
- done();
- // Maybe jasmine.clock here?
- }, 333);
+const lazyAssert = (done, assertFn) => {
+ jest.runOnlyPendingTimers();
+ waitForPromises()
+ .then(() => {
+ assertFn();
+ done();
+ })
+ .catch(e => {
+ throw e;
+ });
};
-describe('AwardsHandler', function() {
+describe('AwardsHandler', () => {
preloadFixtures('snippets/show.html');
- beforeEach(function(done) {
+ beforeEach(done => {
loadFixtures('snippets/show.html');
loadAwardsHandler(true)
.then(obj => {
awardsHandler = obj;
- spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+ jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
done();
})
- .catch(fail);
+ .catch(done.fail);
let isEmojiMenuBuilt = false;
- openAndWaitForEmojiMenu = function() {
+ openAndWaitForEmojiMenu = () => {
return new Promise(resolve => {
if (isEmojiMenuBuilt) {
resolve();
@@ -49,7 +54,7 @@ describe('AwardsHandler', function() {
};
});
- afterEach(function() {
+ afterEach(() => {
// restore original url root value
gon.relative_url_root = urlRoot;
@@ -59,12 +64,12 @@ describe('AwardsHandler', function() {
awardsHandler.destroy();
});
- describe('::showEmojiMenu', function() {
- it('should show emoji menu when Add emoji button clicked', function(done) {
+ describe('::showEmojiMenu', () => {
+ it('should show emoji menu when Add emoji button clicked', done => {
$('.js-add-award')
.eq(0)
.click();
- lazyAssert(done, function() {
+ lazyAssert(done, () => {
const $emojiMenu = $('.emoji-menu');
expect($emojiMenu.length).toBe(1);
@@ -74,20 +79,20 @@ describe('AwardsHandler', function() {
});
});
- it('should also show emoji menu for the smiley icon in notes', function(done) {
+ it('should also show emoji menu for the smiley icon in notes', done => {
$('.js-add-award.note-action-button').click();
- lazyAssert(done, function() {
+ lazyAssert(done, () => {
const $emojiMenu = $('.emoji-menu');
expect($emojiMenu.length).toBe(1);
});
});
- it('should remove emoji menu when body is clicked', function(done) {
+ it('should remove emoji menu when body is clicked', done => {
$('.js-add-award')
.eq(0)
.click();
- lazyAssert(done, function() {
+ lazyAssert(done, () => {
const $emojiMenu = $('.emoji-menu');
$('body').click();
@@ -97,11 +102,11 @@ describe('AwardsHandler', function() {
});
});
- it('should not remove emoji menu when search is clicked', function(done) {
+ it('should not remove emoji menu when search is clicked', done => {
$('.js-add-award')
.eq(0)
.click();
- lazyAssert(done, function() {
+ lazyAssert(done, () => {
const $emojiMenu = $('.emoji-menu');
$('.emoji-search').click();
@@ -112,8 +117,8 @@ describe('AwardsHandler', function() {
});
});
- describe('::addAwardToEmojiBar', function() {
- it('should add emoji to votes block', function() {
+ describe('::addAwardToEmojiBar', () => {
+ it('should add emoji to votes block', () => {
const $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
const $emojiButton = $votesBlock.find('[data-name=heart]');
@@ -123,7 +128,7 @@ describe('AwardsHandler', function() {
expect($votesBlock.hasClass('hidden')).toBe(false);
});
- it('should remove the emoji when we click again', function() {
+ it('should remove the emoji when we click again', () => {
const $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
@@ -132,7 +137,7 @@ describe('AwardsHandler', function() {
expect($emojiButton.length).toBe(0);
});
- it('should decrement the emoji counter', function() {
+ it('should decrement the emoji counter', () => {
const $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAwardToEmojiBar($votesBlock, 'heart', false);
const $emojiButton = $votesBlock.find('[data-name=heart]');
@@ -144,8 +149,8 @@ describe('AwardsHandler', function() {
});
});
- describe('::userAuthored', function() {
- it('should update tooltip to user authored title', function() {
+ describe('::userAuthored', () => {
+ it('should update tooltip to user authored title', () => {
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
@@ -156,27 +161,25 @@ describe('AwardsHandler', function() {
);
});
- it('should restore tooltip back to initial vote list', function() {
- jasmine.clock().install();
+ it('should restore tooltip back to initial vote list', () => {
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.userAuthored($thumbsUpEmoji);
- jasmine.clock().tick(2801);
- jasmine.clock().uninstall();
+ jest.advanceTimersByTime(2801);
expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
});
});
- describe('::getAwardUrl', function() {
- it('returns the url for request', function() {
+ describe('::getAwardUrl', () => {
+ it('returns the url for request', () => {
expect(awardsHandler.getAwardUrl()).toBe('http://test.host/snippets/1/toggle_award_emoji');
});
});
- describe('::addAward and ::checkMutuality', function() {
- it('should handle :+1: and :-1: mutuality', function() {
+ describe('::addAward and ::checkMutuality', () => {
+ it('should handle :+1: and :-1: mutuality', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
@@ -194,8 +197,8 @@ describe('AwardsHandler', function() {
});
});
- describe('::removeEmoji', function() {
- it('should remove emoji', function() {
+ describe('::removeEmoji', () => {
+ it('should remove emoji', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
awardsHandler.addAward($votesBlock, awardUrl, 'fire', false);
@@ -207,8 +210,8 @@ describe('AwardsHandler', function() {
});
});
- describe('::addYouToUserList', function() {
- it('should prepend "You" to the award tooltip', function() {
+ describe('::addYouToUserList', () => {
+ it('should prepend "You" to the award tooltip', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
@@ -219,7 +222,7 @@ describe('AwardsHandler', function() {
expect($thumbsUpEmoji.data('originalTitle')).toBe('You, sam, jerry, max, and andy');
});
- it('handles the special case where "You" is not cleanly comma separated', function() {
+ it('handles the special case where "You" is not cleanly comma separated', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
@@ -231,8 +234,8 @@ describe('AwardsHandler', function() {
});
});
- describe('::removeYouToUserList', function() {
- it('removes "You" from the front of the tooltip', function() {
+ describe('::removeYouToUserList', () => {
+ it('removes "You" from the front of the tooltip', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
@@ -244,7 +247,7 @@ describe('AwardsHandler', function() {
expect($thumbsUpEmoji.data('originalTitle')).toBe('sam, jerry, max, and andy');
});
- it('handles the special case where "You" is not cleanly comma separated', function() {
+ it('handles the special case where "You" is not cleanly comma separated', () => {
const awardUrl = awardsHandler.getAwardUrl();
const $votesBlock = $('.js-awards-block').eq(0);
const $thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
@@ -258,7 +261,7 @@ describe('AwardsHandler', function() {
});
describe('::searchEmojis', () => {
- it('should filter the emoji', function(done) {
+ it('should filter the emoji', done => {
openAndWaitForEmojiMenu()
.then(() => {
expect($('[data-name=angel]').is(':visible')).toBe(true);
@@ -276,7 +279,7 @@ describe('AwardsHandler', function() {
});
});
- it('should clear the search when searching for nothing', function(done) {
+ it('should clear the search when searching for nothing', done => {
openAndWaitForEmojiMenu()
.then(() => {
awardsHandler.searchEmojis('ali');
@@ -298,9 +301,9 @@ describe('AwardsHandler', function() {
});
});
- describe('emoji menu', function() {
+ describe('emoji menu', () => {
const emojiSelector = '[data-name="sunglasses"]';
- const openEmojiMenuAndAddEmoji = function() {
+ const openEmojiMenuAndAddEmoji = () => {
return openAndWaitForEmojiMenu().then(() => {
const $menu = $('.emoji-menu');
const $block = $('.js-awards-block');
@@ -315,7 +318,7 @@ describe('AwardsHandler', function() {
});
};
- it('should add selected emoji to awards block', function(done) {
+ it('should add selected emoji to awards block', done => {
openEmojiMenuAndAddEmoji()
.then(done)
.catch(err => {
@@ -323,7 +326,7 @@ describe('AwardsHandler', function() {
});
});
- it('should remove already selected emoji', function(done) {
+ it('should remove already selected emoji', done => {
openEmojiMenuAndAddEmoji()
.then(() => {
$('.js-add-award')
@@ -344,13 +347,13 @@ describe('AwardsHandler', function() {
});
});
- describe('frequently used emojis', function() {
+ describe('frequently used emojis', () => {
beforeEach(() => {
// Clear it out
Cookies.set('frequently_used_emojis', '');
});
- it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', function(done) {
+ it('shouldn\'t have any "Frequently used" heading if no frequently used emojis', done => {
return openAndWaitForEmojiMenu()
.then(() => {
const emojiMenu = document.querySelector('.emoji-menu');
@@ -364,7 +367,7 @@ describe('AwardsHandler', function() {
});
});
- it('should have any frequently used section when there are frequently used emojis', function(done) {
+ it('should have any frequently used section when there are frequently used emojis', done => {
awardsHandler.addEmojiToFrequentlyUsedList('8ball');
return openAndWaitForEmojiMenu()
@@ -383,7 +386,7 @@ describe('AwardsHandler', function() {
});
});
- it('should disregard invalid frequently used emoji that are being attempted to be added', function() {
+ it('should disregard invalid frequently used emoji that are being attempted to be added', () => {
awardsHandler.addEmojiToFrequentlyUsedList('8ball');
awardsHandler.addEmojiToFrequentlyUsedList('invalid_emoji');
awardsHandler.addEmojiToFrequentlyUsedList('grinning');
@@ -391,7 +394,7 @@ describe('AwardsHandler', function() {
expect(awardsHandler.getFrequentlyUsedEmojis()).toEqual(['8ball', 'grinning']);
});
- it('should disregard invalid frequently used emoji already set in cookie', function() {
+ it('should disregard invalid frequently used emoji already set in cookie', () => {
Cookies.set('frequently_used_emojis', '8ball,invalid_emoji,grinning');
expect(awardsHandler.getFrequentlyUsedEmojis()).toEqual(['8ball', 'grinning']);
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/frontend/collapsed_sidebar_todo_spec.js
index f2eb08fa198..0ea797ce4b3 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/frontend/collapsed_sidebar_todo_spec.js
@@ -3,7 +3,8 @@ import { clone } from 'lodash';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
-import timeoutPromise from './helpers/set_timeout_promise_helper';
+import waitForPromises from './helpers/wait_for_promises';
+import { TEST_HOST } from 'spec/test_constants';
describe('Issuable right sidebar collapsed todo toggle', () => {
const fixtureName = 'issues/open-issue.html';
@@ -23,7 +24,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
mock = new MockAdapter(axios);
- mock.onPost(`${gl.TEST_HOST}/frontend-fixtures/issues-project/todos`).reply(() => {
+ mock.onPost(`${TEST_HOST}/frontend-fixtures/issues-project/todos`).reply(() => {
const response = clone(todoData);
return [200, response];
@@ -64,7 +65,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('toggle todo state', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- setTimeout(() => {
+ setImmediate(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
).not.toBeNull();
@@ -82,7 +83,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('toggle todo state of expanded todo toggle', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- setTimeout(() => {
+ setImmediate(() => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
).toBe('Mark as done');
@@ -94,7 +95,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('toggles todo button tooltip', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- setTimeout(() => {
+ setImmediate(() => {
expect(
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
@@ -108,7 +109,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('marks todo as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- timeoutPromise()
+ waitForPromises()
.then(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
@@ -116,7 +117,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
- .then(timeoutPromise)
+ .then(waitForPromises)
.then(() => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon .todo-undone'),
@@ -133,7 +134,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('updates aria-label to Mark as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- setTimeout(() => {
+ setImmediate(() => {
expect(
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
@@ -147,7 +148,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('updates aria-label to add todo', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
- timeoutPromise()
+ waitForPromises()
.then(() => {
expect(
document
@@ -157,7 +158,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
- .then(timeoutPromise)
+ .then(waitForPromises)
.then(() => {
expect(
document
diff --git a/spec/frontend/comment_type_toggle_spec.js b/spec/frontend/comment_type_toggle_spec.js
new file mode 100644
index 00000000000..06dbfac1803
--- /dev/null
+++ b/spec/frontend/comment_type_toggle_spec.js
@@ -0,0 +1,169 @@
+import CommentTypeToggle from '~/comment_type_toggle';
+import DropLab from '~/droplab/drop_lab';
+import InputSetter from '~/droplab/plugins/input_setter';
+
+describe('CommentTypeToggle', () => {
+ const testContext = {};
+
+ describe('class constructor', () => {
+ beforeEach(() => {
+ testContext.dropdownTrigger = {};
+ testContext.dropdownList = {};
+ testContext.noteTypeInput = {};
+ testContext.submitButton = {};
+ testContext.closeButton = {};
+
+ testContext.commentTypeToggle = new CommentTypeToggle({
+ dropdownTrigger: testContext.dropdownTrigger,
+ dropdownList: testContext.dropdownList,
+ noteTypeInput: testContext.noteTypeInput,
+ submitButton: testContext.submitButton,
+ closeButton: testContext.closeButton,
+ });
+ });
+
+ it('should set .dropdownTrigger', () => {
+ expect(testContext.commentTypeToggle.dropdownTrigger).toBe(testContext.dropdownTrigger);
+ });
+
+ it('should set .dropdownList', () => {
+ expect(testContext.commentTypeToggle.dropdownList).toBe(testContext.dropdownList);
+ });
+
+ it('should set .noteTypeInput', () => {
+ expect(testContext.commentTypeToggle.noteTypeInput).toBe(testContext.noteTypeInput);
+ });
+
+ it('should set .submitButton', () => {
+ expect(testContext.commentTypeToggle.submitButton).toBe(testContext.submitButton);
+ });
+
+ it('should set .closeButton', () => {
+ expect(testContext.commentTypeToggle.closeButton).toBe(testContext.closeButton);
+ });
+
+ it('should set .reopenButton', () => {
+ expect(testContext.commentTypeToggle.reopenButton).toBe(testContext.reopenButton);
+ });
+ });
+
+ describe('initDroplab', () => {
+ beforeEach(() => {
+ testContext.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ closeButton: {},
+ setConfig: () => {},
+ };
+ testContext.config = {};
+
+ jest.spyOn(DropLab.prototype, 'init').mockImplementation();
+ jest.spyOn(DropLab.prototype, 'constructor').mockImplementation();
+
+ jest.spyOn(testContext.commentTypeToggle, 'setConfig').mockReturnValue(testContext.config);
+
+ CommentTypeToggle.prototype.initDroplab.call(testContext.commentTypeToggle);
+ });
+
+ it('should instantiate a DropLab instance and set .droplab', () => {
+ expect(testContext.commentTypeToggle.droplab instanceof DropLab).toBe(true);
+ });
+
+ it('should call .setConfig', () => {
+ expect(testContext.commentTypeToggle.setConfig).toHaveBeenCalled();
+ });
+
+ it('should call DropLab.prototype.init', () => {
+ expect(DropLab.prototype.init).toHaveBeenCalledWith(
+ testContext.commentTypeToggle.dropdownTrigger,
+ testContext.commentTypeToggle.dropdownList,
+ [InputSetter],
+ testContext.config,
+ );
+ });
+ });
+
+ describe('setConfig', () => {
+ describe('if no .closeButton is provided', () => {
+ beforeEach(() => {
+ testContext.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ reopenButton: {},
+ };
+
+ testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
+ testContext.commentTypeToggle,
+ );
+ });
+
+ it('should not add .closeButton related InputSetter config', () => {
+ expect(testContext.setConfig).toEqual({
+ InputSetter: [
+ {
+ input: testContext.commentTypeToggle.noteTypeInput,
+ valueAttribute: 'data-value',
+ },
+ {
+ input: testContext.commentTypeToggle.submitButton,
+ valueAttribute: 'data-submit-text',
+ },
+ {
+ input: testContext.commentTypeToggle.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ },
+ {
+ input: testContext.commentTypeToggle.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ inputAttribute: 'data-alternative-text',
+ },
+ ],
+ });
+ });
+ });
+
+ describe('if no .reopenButton is provided', () => {
+ beforeEach(() => {
+ testContext.commentTypeToggle = {
+ dropdownTrigger: {},
+ dropdownList: {},
+ noteTypeInput: {},
+ submitButton: {},
+ closeButton: {},
+ };
+
+ testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
+ testContext.commentTypeToggle,
+ );
+ });
+
+ it('should not add .reopenButton related InputSetter config', () => {
+ expect(testContext.setConfig).toEqual({
+ InputSetter: [
+ {
+ input: testContext.commentTypeToggle.noteTypeInput,
+ valueAttribute: 'data-value',
+ },
+ {
+ input: testContext.commentTypeToggle.submitButton,
+ valueAttribute: 'data-submit-text',
+ },
+ {
+ input: testContext.commentTypeToggle.closeButton,
+ valueAttribute: 'data-close-text',
+ },
+ {
+ input: testContext.commentTypeToggle.closeButton,
+ valueAttribute: 'data-close-text',
+ inputAttribute: 'data-alternative-text',
+ },
+ ],
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js
index 853f6b3b7b8..e9ee69ca163 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,9 +1,13 @@
-import $ from 'jquery';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
describe('Filtered Search Dropdown Manager', () => {
+ let mock;
+
beforeEach(() => {
- spyOn($, 'ajax');
+ mock = new MockAdapter(axios);
+ mock.onGet().reply(200);
});
describe('addWordToInput', () => {
@@ -32,7 +36,7 @@ describe('Filtered Search Dropdown Manager', () => {
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('milestone');
+ expect(token.querySelector('.name').textContent).toBe('milestone');
expect(getInputValue()).toBe('');
});
@@ -42,7 +46,7 @@ describe('Filtered Search Dropdown Manager', () => {
let token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('label');
+ expect(token.querySelector('.name').textContent).toBe('label');
expect(getInputValue()).toBe('');
FilteredSearchDropdownManager.addWordToInput({ tokenName: 'label', tokenOperator: '=' });
@@ -50,8 +54,8 @@ describe('Filtered Search Dropdown Manager', () => {
token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('label');
- expect(token.querySelector('.operator').innerText).toBe('=');
+ expect(token.querySelector('.name').textContent).toBe('label');
+ expect(token.querySelector('.operator').textContent).toBe('=');
expect(getInputValue()).toBe('');
FilteredSearchDropdownManager.addWordToInput({
@@ -64,9 +68,9 @@ describe('Filtered Search Dropdown Manager', () => {
token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('label');
- expect(token.querySelector('.operator').innerText).toBe('=');
- expect(token.querySelector('.value').innerText).toBe('none');
+ expect(token.querySelector('.name').textContent).toBe('label');
+ expect(token.querySelector('.operator').textContent).toBe('=');
+ expect(token.querySelector('.value').textContent).toBe('none');
expect(getInputValue()).toBe('');
});
});
@@ -79,7 +83,7 @@ describe('Filtered Search Dropdown Manager', () => {
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('author');
+ expect(token.querySelector('.name').textContent).toBe('author');
expect(getInputValue()).toBe('');
});
@@ -97,9 +101,9 @@ describe('Filtered Search Dropdown Manager', () => {
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('author');
- expect(token.querySelector('.operator').innerText).toBe('=');
- expect(token.querySelector('.value').innerText).toBe('@root');
+ expect(token.querySelector('.name').textContent).toBe('author');
+ expect(token.querySelector('.operator').textContent).toBe('=');
+ expect(token.querySelector('.value').textContent).toBe('@root');
expect(getInputValue()).toBe('');
});
@@ -116,9 +120,9 @@ describe('Filtered Search Dropdown Manager', () => {
const token = document.querySelector('.tokens-container .js-visual-token');
expect(token.classList.contains('filtered-search-token')).toEqual(true);
- expect(token.querySelector('.name').innerText).toBe('label');
- expect(token.querySelector('.operator').innerText).toBe('=');
- expect(token.querySelector('.value').innerText).toBe('~\'"test me"\'');
+ expect(token.querySelector('.name').textContent).toBe('label');
+ expect(token.querySelector('.operator').textContent).toBe('=');
+ expect(token.querySelector('.value').textContent).toBe('~\'"test me"\'');
expect(getInputValue()).toBe('');
});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
index fda078bd41c..e59ee925cc7 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,7 +1,10 @@
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Filtered Search Visual Tokens', () => {
+ let mock;
const subject = FilteredSearchVisualTokens;
const findElements = tokenElement => {
@@ -17,6 +20,9 @@ describe('Filtered Search Visual Tokens', () => {
let bugLabelToken;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet().reply(200);
+
setFixtures(`
<ul class="tokens-container">
${FilteredSearchSpecHelper.createInputHTML()}
@@ -248,15 +254,15 @@ describe('Filtered Search Visual Tokens', () => {
});
it('contains name div', () => {
- expect(tokenElement.querySelector('.name')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.name')).toEqual(expect.anything());
});
it('contains value container div', () => {
- expect(tokenElement.querySelector('.value-container')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.value-container')).toEqual(expect.anything());
});
it('contains value div', () => {
- expect(tokenElement.querySelector('.value-container .value')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.value-container .value')).toEqual(expect.anything());
});
it('contains selectable class', () => {
@@ -270,12 +276,12 @@ describe('Filtered Search Visual Tokens', () => {
describe('remove token', () => {
it('contains remove-token button', () => {
expect(tokenElement.querySelector('.value-container .remove-token')).toEqual(
- jasmine.anything(),
+ expect.anything(),
);
});
it('contains fa-close icon', () => {
- expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(jasmine.anything());
+ expect(tokenElement.querySelector('.remove-token .fa-close')).toEqual(expect.anything());
});
});
});
@@ -453,7 +459,7 @@ describe('Filtered Search Visual Tokens', () => {
valueContainer.dataset.originalValue = originalValue;
const avatar = document.createElement('img');
const valueElement = valueContainer.querySelector('.value');
- valueElement.insertAdjacentElement('afterbegin', avatar);
+ valueElement.appendChild(avatar);
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(
authorToken.outerHTML,
);
@@ -573,7 +579,7 @@ describe('Filtered Search Visual Tokens', () => {
it("tokenize's existing input", () => {
input.value = 'some text';
- spyOn(subject, 'tokenizeInput').and.callThrough();
+ jest.spyOn(subject, 'tokenizeInput');
subject.editToken(token);
@@ -635,8 +641,8 @@ describe('Filtered Search Visual Tokens', () => {
FilteredSearchSpecHelper.createFilterVisualTokenHTML('label', '=', 'none'),
);
- spyOn(subject, 'tokenizeInput').and.callFake(() => {});
- spyOn(subject, 'getLastVisualTokenBeforeInput').and.callThrough();
+ jest.spyOn(subject, 'tokenizeInput').mockImplementation(() => {});
+ jest.spyOn(subject, 'getLastVisualTokenBeforeInput');
subject.moveInputToTheRight();
@@ -711,12 +717,16 @@ describe('Filtered Search Visual Tokens', () => {
it('renders a author token value element', () => {
const { tokenNameElement, tokenValueElement } = findElements(authorToken);
- const tokenName = tokenNameElement.innerText;
+ const tokenName = tokenNameElement.textContent;
const tokenValue = 'new value';
subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
- expect(tokenValueElement.innerText).toBe(tokenValue);
+ jest.runOnlyPendingTimers();
+
+ setImmediate(() => {
+ expect(tokenValueElement.textContent).toBe(tokenValue);
+ });
});
});
});
diff --git a/spec/javascripts/gl_form_spec.js b/spec/frontend/gl_form_spec.js
index 69b3dae743a..150d8a053d5 100644
--- a/spec/javascripts/gl_form_spec.js
+++ b/spec/frontend/gl_form_spec.js
@@ -5,51 +5,56 @@ import '~/lib/utils/text_utility';
import '~/lib/utils/common_utils';
describe('GLForm', () => {
- describe('when instantiated', function() {
+ const testContext = {};
+
+ describe('when instantiated', () => {
beforeEach(done => {
- this.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
- this.textarea = this.form.find('textarea');
- spyOn($.prototype, 'off').and.returnValue(this.textarea);
- spyOn($.prototype, 'on').and.returnValue(this.textarea);
- spyOn($.prototype, 'css');
-
- this.glForm = new GLForm(this.form, false);
- setTimeout(() => {
- $.prototype.off.calls.reset();
- $.prototype.on.calls.reset();
- $.prototype.css.calls.reset();
+ testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
+ testContext.textarea = testContext.form.find('textarea');
+ jest.spyOn($.prototype, 'off').mockReturnValue(testContext.textarea);
+ jest.spyOn($.prototype, 'on').mockReturnValue(testContext.textarea);
+ jest.spyOn($.prototype, 'css').mockImplementation(() => {});
+
+ testContext.glForm = new GLForm(testContext.form, false);
+
+ setImmediate(() => {
+ $.prototype.off.mockClear();
+ $.prototype.on.mockClear();
+ $.prototype.css.mockClear();
done();
});
});
describe('setupAutosize', () => {
beforeEach(done => {
- this.glForm.setupAutosize();
- setTimeout(() => {
+ testContext.glForm.setupAutosize();
+
+ setImmediate(() => {
done();
});
});
it('should register an autosize event handler on the textarea', () => {
expect($.prototype.off).toHaveBeenCalledWith('autosize:resized');
- expect($.prototype.on).toHaveBeenCalledWith('autosize:resized', jasmine.any(Function));
+ expect($.prototype.on).toHaveBeenCalledWith('autosize:resized', expect.any(Function));
});
it('should register a mouseup event handler on the textarea', () => {
expect($.prototype.off).toHaveBeenCalledWith('mouseup.autosize');
- expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', jasmine.any(Function));
+ expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', expect.any(Function));
});
it('should set the resize css property to vertical', () => {
+ jest.runOnlyPendingTimers();
expect($.prototype.css).toHaveBeenCalledWith('resize', 'vertical');
});
});
describe('setHeightData', () => {
beforeEach(() => {
- spyOn($.prototype, 'data');
- spyOn($.prototype, 'outerHeight').and.returnValue(200);
- this.glForm.setHeightData();
+ jest.spyOn($.prototype, 'data').mockImplementation(() => {});
+ jest.spyOn($.prototype, 'outerHeight').mockReturnValue(200);
+ testContext.glForm.setHeightData();
});
it('should set the height data attribute', () => {
@@ -64,12 +69,12 @@ describe('GLForm', () => {
describe('destroyAutosize', () => {
describe('when called', () => {
beforeEach(() => {
- spyOn($.prototype, 'data');
- spyOn($.prototype, 'outerHeight').and.returnValue(200);
- spyOn(window, 'outerHeight').and.returnValue(400);
- spyOn(autosize, 'destroy');
+ jest.spyOn($.prototype, 'data').mockImplementation(() => {});
+ jest.spyOn($.prototype, 'outerHeight').mockReturnValue(200);
+ window.outerHeight = () => 400;
+ jest.spyOn(autosize, 'destroy').mockImplementation(() => {});
- this.glForm.destroyAutosize();
+ testContext.glForm.destroyAutosize();
});
it('should call outerHeight', () => {
@@ -81,7 +86,7 @@ describe('GLForm', () => {
});
it('should call autosize destroy', () => {
- expect(autosize.destroy).toHaveBeenCalledWith(this.textarea);
+ expect(autosize.destroy).toHaveBeenCalledWith(testContext.textarea);
});
it('should set the data-height attribute', () => {
@@ -98,11 +103,11 @@ describe('GLForm', () => {
});
it('should return undefined if the data-height equals the outerHeight', () => {
- spyOn($.prototype, 'outerHeight').and.returnValue(200);
- spyOn($.prototype, 'data').and.returnValue(200);
- spyOn(autosize, 'destroy');
+ jest.spyOn($.prototype, 'outerHeight').mockReturnValue(200);
+ jest.spyOn($.prototype, 'data').mockReturnValue(200);
+ jest.spyOn(autosize, 'destroy').mockImplementation(() => {});
- expect(this.glForm.destroyAutosize()).toBeUndefined();
+ expect(testContext.glForm.destroyAutosize()).toBeUndefined();
expect(autosize.destroy).not.toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/global_search_input_spec.js b/spec/frontend/global_search_input_spec.js
index 00ae8a8f2ea..8c00ea5f193 100644
--- a/spec/javascripts/global_search_input_spec.js
+++ b/spec/frontend/global_search_input_spec.js
@@ -28,7 +28,7 @@ describe('Global search input dropdown', () => {
const groupName = 'Gitlab Org';
- const removeBodyAttributes = function() {
+ const removeBodyAttributes = () => {
const $body = $('body');
$body.removeAttr('data-page');
@@ -38,7 +38,7 @@ describe('Global search input dropdown', () => {
// Add required attributes to body before starting the test.
// section would be dashboard|group|project
- const addBodyAttributes = function(section) {
+ const addBodyAttributes = section => {
if (section == null) {
section = 'dashboard';
}
@@ -57,12 +57,12 @@ describe('Global search input dropdown', () => {
}
};
- const disableProjectIssues = function() {
+ const disableProjectIssues = () => {
document.querySelector('.js-search-project-options').setAttribute('data-issues-disabled', true);
};
// Mock `gl` object in window for dashboard specific page. App code will need it.
- const mockDashboardOptions = function() {
+ const mockDashboardOptions = () => {
window.gl || (window.gl = {});
return (window.gl.dashboardOptions = {
issuesPath: dashboardIssuesPath,
@@ -71,7 +71,7 @@ describe('Global search input dropdown', () => {
};
// Mock `gl` object in window for project specific page. App code will need it.
- const mockProjectOptions = function() {
+ const mockProjectOptions = () => {
window.gl || (window.gl = {});
return (window.gl.projectOptions = {
'gitlab-ce': {
@@ -82,7 +82,7 @@ describe('Global search input dropdown', () => {
});
};
- const mockGroupOptions = function() {
+ const mockGroupOptions = () => {
window.gl || (window.gl = {});
return (window.gl.groupOptions = {
'gitlab-org': {
@@ -93,7 +93,7 @@ describe('Global search input dropdown', () => {
});
};
- const assertLinks = function(list, issuesPath, mrsPath) {
+ const assertLinks = (list, issuesPath, mrsPath) => {
if (issuesPath) {
const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`;
const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`;
@@ -113,7 +113,7 @@ describe('Global search input dropdown', () => {
};
preloadFixtures('static/global_search_input.html');
- beforeEach(function() {
+ beforeEach(() => {
loadFixtures('static/global_search_input.html');
window.gon = {};
@@ -123,13 +123,13 @@ describe('Global search input dropdown', () => {
return (widget = initGlobalSearchInput());
});
- afterEach(function() {
+ afterEach(() => {
// Undo what we did to the shared <body>
removeBodyAttributes();
window.gon = {};
});
- it('should show Dashboard specific dropdown menu', function() {
+ it('should show Dashboard specific dropdown menu', () => {
addBodyAttributes();
mockDashboardOptions();
widget.searchInput.triggerHandler('focus');
@@ -137,7 +137,7 @@ describe('Global search input dropdown', () => {
return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
});
- it('should show Group specific dropdown menu', function() {
+ it('should show Group specific dropdown menu', () => {
addBodyAttributes('group');
mockGroupOptions();
widget.searchInput.triggerHandler('focus');
@@ -145,7 +145,7 @@ describe('Global search input dropdown', () => {
return assertLinks(list, groupIssuesPath, groupMRsPath);
});
- it('should show Project specific dropdown menu', function() {
+ it('should show Project specific dropdown menu', () => {
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.triggerHandler('focus');
@@ -153,7 +153,7 @@ describe('Global search input dropdown', () => {
return assertLinks(list, projectIssuesPath, projectMRsPath);
});
- it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
+ it('should show only Project mergeRequest dropdown menu items when project issues are disabled', () => {
addBodyAttributes('project');
disableProjectIssues();
mockProjectOptions();
@@ -162,7 +162,7 @@ describe('Global search input dropdown', () => {
assertLinks(list, null, projectMRsPath);
});
- it('should not show category related menu if there is text in the input', function() {
+ it('should not show category related menu if there is text in the input', () => {
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.val('help');
@@ -173,12 +173,12 @@ describe('Global search input dropdown', () => {
expect(list.find(link).length).toBe(0);
});
- it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
+ it('should not submit the search form when selecting an autocomplete row with the keyboard', () => {
const ENTER = 13;
const DOWN = 40;
addBodyAttributes();
mockDashboardOptions(true);
- const submitSpy = spyOnEvent('form', 'submit');
+ const submitSpy = jest.spyOn(document.querySelector('form'), 'submit');
widget.searchInput.triggerHandler('focus');
widget.wrap.trigger($.Event('keydown', { which: DOWN }));
const enterKeyEvent = $.Event('keydown', { which: ENTER });
@@ -186,16 +186,16 @@ describe('Global search input dropdown', () => {
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
- expect(submitSpy).not.toHaveBeenTriggered();
+ expect(submitSpy).not.toHaveBeenCalled();
});
- describe('disableDropdown', function() {
- beforeEach(function() {
+ describe('disableDropdown', () => {
+ beforeEach(() => {
widget.enableDropdown();
});
- it('should close the Dropdown', function() {
- const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
+ it('should close the Dropdown', () => {
+ const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
widget.dropdown.addClass('show');
widget.disableDropdown();
@@ -204,9 +204,9 @@ describe('Global search input dropdown', () => {
});
});
- describe('enableDropdown', function() {
- it('should open the Dropdown', function() {
- const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
+ describe('enableDropdown', () => {
+ it('should open the Dropdown', () => {
+ const toggleSpy = jest.spyOn(widget.dropdownToggle, 'dropdown');
widget.enableDropdown();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/frontend/labels_issue_sidebar_spec.js
index 94e833ec83b..fafefca94df 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/frontend/labels_issue_sidebar_spec.js
@@ -21,7 +21,9 @@ function testLabelClicks(labelOrder, done) {
.get(0)
.click();
- setTimeout(() => {
+ jest.runOnlyPendingTimers();
+
+ setImmediate(() => {
const labelsInDropdown = $('.dropdown-content a');
expect(labelsInDropdown.length).toBe(10);
@@ -38,11 +40,11 @@ function testLabelClicks(labelOrder, done) {
.get(0)
.click();
- setTimeout(() => {
+ setImmediate(() => {
expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder);
done();
- }, 0);
- }, 0);
+ });
+ });
}
describe('Issue dropdown sidebar', () => {
diff --git a/spec/frontend/line_highlighter_spec.js b/spec/frontend/line_highlighter_spec.js
new file mode 100644
index 00000000000..0da1ea1df2d
--- /dev/null
+++ b/spec/frontend/line_highlighter_spec.js
@@ -0,0 +1,268 @@
+/* eslint-disable no-return-assign, no-new, no-underscore-dangle */
+
+import $ from 'jquery';
+import LineHighlighter from '~/line_highlighter';
+
+describe('LineHighlighter', () => {
+ const testContext = {};
+
+ preloadFixtures('static/line_highlighter.html');
+ const clickLine = (number, eventData = {}) => {
+ if ($.isEmptyObject(eventData)) {
+ return $(`#L${number}`).click();
+ }
+ const e = $.Event('click', eventData);
+ return $(`#L${number}`).trigger(e);
+ };
+ beforeEach(() => {
+ loadFixtures('static/line_highlighter.html');
+ testContext.class = new LineHighlighter();
+ testContext.css = testContext.class.highlightLineClass;
+ return (testContext.spies = {
+ __setLocationHash__: jest
+ .spyOn(testContext.class, '__setLocationHash__')
+ .mockImplementation(() => {}),
+ });
+ });
+
+ describe('behavior', () => {
+ it('highlights one line given in the URL hash', () => {
+ new LineHighlighter({ hash: '#L13' });
+
+ expect($('#LC13')).toHaveClass(testContext.css);
+ });
+
+ it('highlights one line given in the URL hash with given CSS class name', () => {
+ const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' });
+
+ expect(hiliter.highlightLineClass).toBe('hilite');
+ expect($('#LC13')).toHaveClass('hilite');
+ expect($('#LC13')).not.toHaveClass('hll');
+ });
+
+ it('highlights a range of lines given in the URL hash', () => {
+ new LineHighlighter({ hash: '#L5-25' });
+
+ expect($(`.${testContext.css}`).length).toBe(21);
+ for (let line = 5; line <= 25; line += 1) {
+ expect($(`#LC${line}`)).toHaveClass(testContext.css);
+ }
+ });
+
+ it('scrolls to the first highlighted line on initial load', () => {
+ const spy = jest.spyOn($, 'scrollTo');
+ new LineHighlighter({ hash: '#L5-25' });
+
+ expect(spy).toHaveBeenCalledWith('#L5', expect.anything());
+ });
+
+ it('discards click events', () => {
+ const clickSpy = jest.fn();
+
+ $('a[data-line-number]').click(clickSpy);
+
+ clickLine(13);
+
+ expect(clickSpy.mock.calls[0][0].isDefaultPrevented()).toEqual(true);
+ });
+
+ it('handles garbage input from the hash', () => {
+ const func = () => {
+ return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' });
+ };
+
+ expect(func).not.toThrow();
+ });
+
+ it('handles hashchange event', () => {
+ const highlighter = new LineHighlighter();
+
+ jest.spyOn(highlighter, 'highlightHash').mockImplementation(() => {});
+
+ window.dispatchEvent(new Event('hashchange'), 'L15');
+
+ expect(highlighter.highlightHash).toHaveBeenCalled();
+ });
+ });
+
+ describe('clickHandler', () => {
+ it('handles clicking on a child icon element', () => {
+ const spy = jest.spyOn(testContext.class, 'setHash');
+ $('#L13 i')
+ .mousedown()
+ .click();
+
+ expect(spy).toHaveBeenCalledWith(13);
+ expect($('#LC13')).toHaveClass(testContext.css);
+ });
+
+ describe('without shiftKey', () => {
+ it('highlights one line when clicked', () => {
+ clickLine(13);
+
+ expect($('#LC13')).toHaveClass(testContext.css);
+ });
+
+ it('unhighlights previously highlighted lines', () => {
+ clickLine(13);
+ clickLine(20);
+
+ expect($('#LC13')).not.toHaveClass(testContext.css);
+ expect($('#LC20')).toHaveClass(testContext.css);
+ });
+
+ it('sets the hash', () => {
+ const spy = jest.spyOn(testContext.class, 'setHash');
+ clickLine(13);
+
+ expect(spy).toHaveBeenCalledWith(13);
+ });
+ });
+
+ describe('with shiftKey', () => {
+ it('sets the hash', () => {
+ const spy = jest.spyOn(testContext.class, 'setHash');
+ clickLine(13);
+ clickLine(20, {
+ shiftKey: true,
+ });
+
+ expect(spy).toHaveBeenCalledWith(13);
+ expect(spy).toHaveBeenCalledWith(13, 20);
+ });
+
+ describe('without existing highlight', () => {
+ it('highlights the clicked line', () => {
+ clickLine(13, {
+ shiftKey: true,
+ });
+
+ expect($('#LC13')).toHaveClass(testContext.css);
+ expect($(`.${testContext.css}`).length).toBe(1);
+ });
+
+ it('sets the hash', () => {
+ const spy = jest.spyOn(testContext.class, 'setHash');
+ clickLine(13, {
+ shiftKey: true,
+ });
+
+ expect(spy).toHaveBeenCalledWith(13);
+ });
+ });
+
+ describe('with existing single-line highlight', () => {
+ it('uses existing line as last line when target is lesser', () => {
+ clickLine(20);
+ clickLine(15, {
+ shiftKey: true,
+ });
+
+ expect($(`.${testContext.css}`).length).toBe(6);
+ for (let line = 15; line <= 20; line += 1) {
+ expect($(`#LC${line}`)).toHaveClass(testContext.css);
+ }
+ });
+
+ it('uses existing line as first line when target is greater', () => {
+ clickLine(5);
+ clickLine(10, {
+ shiftKey: true,
+ });
+
+ expect($(`.${testContext.css}`).length).toBe(6);
+ for (let line = 5; line <= 10; line += 1) {
+ expect($(`#LC${line}`)).toHaveClass(testContext.css);
+ }
+ });
+ });
+
+ describe('with existing multi-line highlight', () => {
+ beforeEach(() => {
+ clickLine(10, {
+ shiftKey: true,
+ });
+ clickLine(13, {
+ shiftKey: true,
+ });
+ });
+
+ it('uses target as first line when it is less than existing first line', () => {
+ clickLine(5, {
+ shiftKey: true,
+ });
+
+ expect($(`.${testContext.css}`).length).toBe(6);
+ for (let line = 5; line <= 10; line += 1) {
+ expect($(`#LC${line}`)).toHaveClass(testContext.css);
+ }
+ });
+
+ it('uses target as last line when it is greater than existing first line', () => {
+ clickLine(15, {
+ shiftKey: true,
+ });
+
+ expect($(`.${testContext.css}`).length).toBe(6);
+ for (let line = 10; line <= 15; line += 1) {
+ expect($(`#LC${line}`)).toHaveClass(testContext.css);
+ }
+ });
+ });
+ });
+ });
+
+ describe('hashToRange', () => {
+ beforeEach(() => {
+ testContext.subject = testContext.class.hashToRange;
+ });
+
+ it('extracts a single line number from the hash', () => {
+ expect(testContext.subject('#L5')).toEqual([5, null]);
+ });
+
+ it('extracts a range of line numbers from the hash', () => {
+ expect(testContext.subject('#L5-15')).toEqual([5, 15]);
+ });
+
+ it('returns [null, null] when the hash is not a line number', () => {
+ expect(testContext.subject('#foo')).toEqual([null, null]);
+ });
+ });
+
+ describe('highlightLine', () => {
+ beforeEach(() => {
+ testContext.subject = testContext.class.highlightLine;
+ });
+
+ it('highlights the specified line', () => {
+ testContext.subject(13);
+
+ expect($('#LC13')).toHaveClass(testContext.css);
+ });
+
+ it('accepts a String-based number', () => {
+ testContext.subject('13');
+
+ expect($('#LC13')).toHaveClass(testContext.css);
+ });
+ });
+
+ describe('setHash', () => {
+ beforeEach(() => {
+ testContext.subject = testContext.class.setHash;
+ });
+
+ it('sets the location hash for a single line', () => {
+ testContext.subject(5);
+
+ expect(testContext.spies.__setLocationHash__).toHaveBeenCalledWith('#L5');
+ });
+
+ it('sets the location hash for a range', () => {
+ testContext.subject(5, 15);
+
+ expect(testContext.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 2b5fde9532d..3d3be647d12 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -5,12 +5,16 @@ import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/lib/utils/common_utils';
import 'vendor/jquery.scrollTo';
-import initMrPage from './helpers/init_vue_mr_page_helper';
+import initMrPage from '../javascripts/helpers/init_vue_mr_page_helper';
-describe('MergeRequestTabs', function() {
- let mrPageMock;
+jest.mock('~/lib/utils/webpack', () => ({
+ resetServiceWorkersPublicPath: jest.fn(),
+}));
+
+describe('MergeRequestTabs', () => {
+ const testContext = {};
const stubLocation = {};
- const setLocation = function(stubs) {
+ const setLocation = stubs => {
const defaults = {
pathname: '',
search: '',
@@ -24,29 +28,25 @@ describe('MergeRequestTabs', function() {
'merge_requests/diff_comment.html',
);
- beforeEach(function() {
- mrPageMock = initMrPage();
- this.class = new MergeRequestTabs({ stubLocation });
+ beforeEach(() => {
+ initMrPage();
+
+ testContext.class = new MergeRequestTabs({ stubLocation });
setLocation();
- this.spies = {
- history: spyOn(window.history, 'pushState').and.callFake(function() {}),
+ testContext.spies = {
+ history: jest.spyOn(window.history, 'pushState').mockImplementation(() => {}),
};
- });
- afterEach(function() {
- this.class.unbindEvents();
- this.class.destroyPipelinesView();
- mrPageMock.restore();
- $('.js-merge-request-test').remove();
+ gl.mrWidget = {};
});
- describe('opensInNewTab', function() {
+ describe('opensInNewTab', () => {
const windowTarget = '_blank';
let clickTabParams;
let tabUrl;
- beforeEach(function() {
+ beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html');
tabUrl = $('.commits-tab a').attr('href');
@@ -68,76 +68,76 @@ describe('MergeRequestTabs', function() {
describe('meta click', () => {
let metakeyEvent;
- beforeEach(function() {
+ beforeEach(() => {
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
});
- it('opens page when commits link is clicked', function() {
- spyOn(window, 'open').and.callFake(function(url, name) {
+ it('opens page when commits link is clicked', () => {
+ jest.spyOn(window, 'open').mockImplementation((url, name) => {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.bindEvents();
+ testContext.class.bindEvents();
$('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
expect(window.open).toHaveBeenCalled();
});
- it('opens page when commits badge is clicked', function() {
- spyOn(window, 'open').and.callFake(function(url, name) {
+ it('opens page when commits badge is clicked', () => {
+ jest.spyOn(window, 'open').mockImplementation((url, name) => {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.bindEvents();
+ testContext.class.bindEvents();
$('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
expect(window.open).toHaveBeenCalled();
});
});
- it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function() {
- spyOn(window, 'open').and.callFake(function(url, name) {
+ it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', () => {
+ jest.spyOn(window, 'open').mockImplementation((url, name) => {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({ ...clickTabParams, metaKey: true });
+ testContext.class.clickTab({ ...clickTabParams, metaKey: true });
expect(window.open).toHaveBeenCalled();
});
- it('opens page tab in a new browser tab with Cmd+Click - Mac', function() {
- spyOn(window, 'open').and.callFake(function(url, name) {
+ it('opens page tab in a new browser tab with Cmd+Click - Mac', () => {
+ jest.spyOn(window, 'open').mockImplementation((url, name) => {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({ ...clickTabParams, ctrlKey: true });
+ testContext.class.clickTab({ ...clickTabParams, ctrlKey: true });
expect(window.open).toHaveBeenCalled();
});
- it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() {
- spyOn(window, 'open').and.callFake(function(url, name) {
+ it('opens page tab in a new browser tab with Middle-click - Mac/PC', () => {
+ jest.spyOn(window, 'open').mockImplementation((url, name) => {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({ ...clickTabParams, which: 2 });
+ testContext.class.clickTab({ ...clickTabParams, which: 2 });
expect(window.open).toHaveBeenCalled();
});
});
- describe('setCurrentAction', function() {
+ describe('setCurrentAction', () => {
let mock;
- beforeEach(function() {
+ beforeEach(() => {
mock = new MockAdapter(axios);
mock.onAny().reply({ data: {} });
- this.subject = this.class.setCurrentAction;
+ testContext.subject = testContext.class.setCurrentAction;
});
afterEach(() => {
@@ -145,53 +145,53 @@ describe('MergeRequestTabs', function() {
window.history.replaceState({}, '', '/');
});
- it('changes from commits', function() {
+ it('changes from commits', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1/commits',
});
- expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1');
- expect(this.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs');
+ expect(testContext.subject('show')).toBe('/foo/bar/-/merge_requests/1');
+ expect(testContext.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs');
});
- it('changes from diffs', function() {
+ it('changes from diffs', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1/diffs',
});
- expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1');
- expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
+ expect(testContext.subject('show')).toBe('/foo/bar/-/merge_requests/1');
+ expect(testContext.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
});
- it('changes from diffs.html', function() {
+ it('changes from diffs.html', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1/diffs.html',
});
- expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1');
- expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
+ expect(testContext.subject('show')).toBe('/foo/bar/-/merge_requests/1');
+ expect(testContext.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
});
- it('changes from notes', function() {
+ it('changes from notes', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1',
});
- expect(this.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs');
- expect(this.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
+ expect(testContext.subject('diffs')).toBe('/foo/bar/-/merge_requests/1/diffs');
+ expect(testContext.subject('commits')).toBe('/foo/bar/-/merge_requests/1/commits');
});
- it('includes search parameters and hash string', function() {
+ it('includes search parameters and hash string', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1/diffs',
search: '?view=parallel',
hash: '#L15-35',
});
- expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1?view=parallel#L15-35');
+ expect(testContext.subject('show')).toBe('/foo/bar/-/merge_requests/1?view=parallel#L15-35');
});
- it('replaces the current history state', function() {
+ it('replaces the current history state', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1',
});
@@ -204,9 +204,9 @@ describe('MergeRequestTabs', function() {
window.location.href,
);
- const newState = this.subject('commits');
+ const newState = testContext.subject('commits');
- expect(this.spies.history).toHaveBeenCalledWith(
+ expect(testContext.spies.history).toHaveBeenCalledWith(
{
url: newState,
action: 'commits',
@@ -216,16 +216,16 @@ describe('MergeRequestTabs', function() {
);
});
- it('treats "show" like "notes"', function() {
+ it('treats "show" like "notes"', () => {
setLocation({
pathname: '/foo/bar/-/merge_requests/1/commits',
});
- expect(this.subject('show')).toBe('/foo/bar/-/merge_requests/1');
+ expect(testContext.subject('show')).toBe('/foo/bar/-/merge_requests/1');
});
});
- describe('expandViewContainer', function() {
+ describe('expandViewContainer', () => {
beforeEach(() => {
$('body').append(
'<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>',
@@ -236,59 +236,58 @@ describe('MergeRequestTabs', function() {
$('.content-wrapper').remove();
});
- it('removes container-limited from containers', function() {
- this.class.expandViewContainer();
+ it('removes container-limited from containers', () => {
+ testContext.class.expandViewContainer();
- expect($('.content-wrapper')).not.toContainElement('.container-limited');
+ expect($('.content-wrapper .container-limited')).toHaveLength(0);
});
- it('does not add container-limited when fluid layout is prefered', function() {
+ it('does not add container-limited when fluid layout is prefered', () => {
$('.content-wrapper .container-fluid').removeClass('container-limited');
- this.class.expandViewContainer(false);
+ testContext.class.expandViewContainer(false);
- expect($('.content-wrapper')).not.toContainElement('.container-limited');
+ expect($('.content-wrapper .container-limited')).toHaveLength(0);
});
- it('does remove container-limited from breadcrumbs', function() {
+ it('does remove container-limited from breadcrumbs', () => {
$('.container-limited').addClass('breadcrumbs');
- this.class.expandViewContainer();
+ testContext.class.expandViewContainer();
- expect($('.content-wrapper')).toContainElement('.container-limited');
+ expect($('.content-wrapper .container-limited')).toHaveLength(1);
});
});
- describe('tabShown', function() {
+ describe('tabShown', () => {
const mainContent = document.createElement('div');
const tabContent = document.createElement('div');
- beforeEach(function() {
- spyOn(mainContent, 'getBoundingClientRect').and.returnValue({ top: 10 });
- spyOn(tabContent, 'getBoundingClientRect').and.returnValue({ top: 100 });
- spyOn(document, 'querySelector').and.callFake(function(selector) {
+ beforeEach(() => {
+ jest.spyOn(mainContent, 'getBoundingClientRect').mockReturnValue({ top: 10 });
+ jest.spyOn(tabContent, 'getBoundingClientRect').mockReturnValue({ top: 100 });
+ jest.spyOn(document, 'querySelector').mockImplementation(selector => {
return selector === '.content-wrapper' ? mainContent : tabContent;
});
- this.class.currentAction = 'commits';
+ testContext.class.currentAction = 'commits';
});
- it('calls window scrollTo with options if document has scrollBehavior', function() {
+ it('calls window scrollTo with options if document has scrollBehavior', () => {
document.documentElement.style.scrollBehavior = '';
- spyOn(window, 'scrollTo');
+ jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
- this.class.tabShown('commits', 'foobar');
+ testContext.class.tabShown('commits', 'foobar');
- expect(window.scrollTo.calls.first().args[0]).toEqual({ top: 39, behavior: 'smooth' });
+ expect(window.scrollTo.mock.calls[0][0]).toEqual({ top: 39, behavior: 'smooth' });
});
- it('calls window scrollTo with two args if document does not have scrollBehavior', function() {
- spyOnProperty(document.documentElement, 'style', 'get').and.returnValue({});
-
- spyOn(window, 'scrollTo');
+ it('calls window scrollTo with two args if document does not have scrollBehavior', () => {
+ jest.spyOn(document.documentElement, 'style', 'get').mockReturnValue({});
+ jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
- this.class.tabShown('commits', 'foobar');
+ testContext.class.tabShown('commits', 'foobar');
- expect(window.scrollTo.calls.first().args).toEqual([0, 39]);
+ expect(window.scrollTo.mock.calls[0]).toEqual([0, 39]);
});
});
});
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/frontend/right_sidebar_spec.js
index 9565e3ce546..d80d80152a5 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/frontend/right_sidebar_spec.js
@@ -10,7 +10,7 @@ let $icon = null;
let $page = null;
let $labelsIcon = null;
-const assertSidebarState = function(state) {
+const assertSidebarState = state => {
const shouldBeExpanded = state === 'expanded';
const shouldBeCollapsed = state === 'collapsed';
expect($aside.hasClass('right-sidebar-expanded')).toBe(shouldBeExpanded);
@@ -21,14 +21,13 @@ const assertSidebarState = function(state) {
expect($icon.hasClass('fa-angle-double-left')).toBe(shouldBeCollapsed);
};
-describe('RightSidebar', function() {
+describe('RightSidebar', () => {
describe('fixture tests', () => {
const fixtureName = 'issues/open-issue.html';
preloadFixtures(fixtureName);
- loadJSONFixtures('todos/todos.json');
let mock;
- beforeEach(function() {
+ beforeEach(() => {
loadFixtures(fixtureName);
mock = new MockAdapter(axios);
new Sidebar(); // eslint-disable-line no-new
@@ -43,7 +42,7 @@ describe('RightSidebar', function() {
mock.restore();
});
- it('should expand/collapse the sidebar when arrow is clicked', function() {
+ it('should expand/collapse the sidebar when arrow is clicked', () => {
assertSidebarState('expanded');
$toggle.click();
assertSidebarState('collapsed');
@@ -51,28 +50,29 @@ describe('RightSidebar', function() {
assertSidebarState('expanded');
});
- it('should float over the page and when sidebar icons clicked', function() {
+ it('should float over the page and when sidebar icons clicked', () => {
$labelsIcon.click();
assertSidebarState('expanded');
});
- it('should collapse when the icon arrow clicked while it is floating on page', function() {
+ it('should collapse when the icon arrow clicked while it is floating on page', () => {
$labelsIcon.click();
assertSidebarState('expanded');
$toggle.click();
assertSidebarState('collapsed');
});
- it('should broadcast todo:toggle event when add todo clicked', function(done) {
+ it('should broadcast todo:toggle event when add todo clicked', done => {
const todos = getJSONFixture('todos/todos.json');
mock.onPost(/(.*)\/todos$/).reply(200, todos);
- const todoToggleSpy = spyOnEvent(document, 'todo:toggle');
+ const todoToggleSpy = jest.fn();
+ $(document).on('todo:toggle', todoToggleSpy);
$('.issuable-sidebar-header .js-issuable-todo').click();
- setTimeout(() => {
- expect(todoToggleSpy.calls.count()).toEqual(1);
+ setImmediate(() => {
+ expect(todoToggleSpy.mock.calls.length).toEqual(1);
done();
});
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
new file mode 100644
index 00000000000..3d16074154c
--- /dev/null
+++ b/spec/frontend/shortcuts_spec.js
@@ -0,0 +1,46 @@
+import $ from 'jquery';
+import Shortcuts from '~/behaviors/shortcuts/shortcuts';
+
+describe('Shortcuts', () => {
+ const fixtureName = 'snippets/show.html';
+ const createEvent = (type, target) =>
+ $.Event(type, {
+ target,
+ });
+
+ preloadFixtures(fixtureName);
+
+ describe('toggleMarkdownPreview', () => {
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+
+ jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus');
+ jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus');
+
+ new Shortcuts(); // eslint-disable-line no-new
+ });
+
+ it('focuses preview button in form', () => {
+ Shortcuts.toggleMarkdownPreview(
+ createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text')),
+ );
+
+ expect(
+ document.querySelector('.js-new-note-form .js-md-preview-button').focus,
+ ).toHaveBeenCalled();
+ });
+
+ it('focues preview button inside edit comment form', () => {
+ document.querySelector('.js-note-edit').click();
+
+ Shortcuts.toggleMarkdownPreview(
+ createEvent('KeyboardEvent', document.querySelector('.edit-note .js-note-text')),
+ );
+
+ expect(
+ document.querySelector('.js-new-note-form .js-md-preview-button').focus,
+ ).not.toHaveBeenCalled();
+ expect(document.querySelector('.edit-note .js-md-preview-button').focus).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/frontend/user_popovers_spec.js
index 6ac22fca2d3..0367b9cc924 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/frontend/user_popovers_spec.js
@@ -26,10 +26,12 @@ describe('User Popovers', () => {
loadFixtures(fixtureTemplate);
const usersCacheSpy = () => Promise.resolve(dummyUser);
- spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId));
+ jest.spyOn(UsersCache, 'retrieveById').mockImplementation(userId => usersCacheSpy(userId));
const userStatusCacheSpy = () => Promise.resolve(dummyUserStatus);
- spyOn(UsersCache, 'retrieveStatusById').and.callFake(userId => userStatusCacheSpy(userId));
+ jest
+ .spyOn(UsersCache, 'retrieveStatusById')
+ .mockImplementation(userId => userStatusCacheSpy(userId));
popovers = initUserPopovers(document.querySelectorAll(selector));
});
@@ -53,6 +55,8 @@ describe('User Popovers', () => {
let userLink;
beforeEach(() => {
+ UsersCache.retrieveById.mockReset();
+
userLink = document.querySelector(selector);
triggerEvent('mouseenter', userLink);
@@ -68,7 +72,7 @@ describe('User Popovers', () => {
const [firstPopover] = popovers;
expect(firstPopover.$props.user).toEqual(
- jasmine.objectContaining({
+ expect.objectContaining({
name,
userId,
username,
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 5dee11b3810..8e0d170289b 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -1,10 +1,13 @@
import $ from 'jquery';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import Dropzone from 'dropzone';
import Mousetrap from 'mousetrap';
import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
describe('ZenMode', () => {
+ let mock;
let zen;
let dropzoneForElementSpy;
const fixtureName = 'snippets/show.html';
@@ -28,10 +31,13 @@ describe('ZenMode', () => {
}
beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet().reply(200);
+
loadFixtures(fixtureName);
initNotes();
- dropzoneForElementSpy = spyOn(Dropzone, 'forElement').and.callFake(() => ({
+ dropzoneForElementSpy = jest.spyOn(Dropzone, 'forElement').mockImplementation(() => ({
enable: () => true,
}));
zen = new ZenMode();
@@ -49,20 +55,20 @@ describe('ZenMode', () => {
$('.div-dropzone').addClass('js-invalid-dropzone');
exitZen();
- expect(dropzoneForElementSpy.calls.count()).toEqual(0);
+ expect(dropzoneForElementSpy.mock.calls.length).toEqual(0);
});
it('should call dropzone if element is dropzone valid', () => {
$('.div-dropzone').removeClass('js-invalid-dropzone');
exitZen();
- expect(dropzoneForElementSpy.calls.count()).toEqual(2);
+ expect(dropzoneForElementSpy.mock.calls.length).toEqual(2);
});
});
describe('on enter', () => {
it('pauses Mousetrap', () => {
- const mouseTrapPauseSpy = spyOn(Mousetrap, 'pause');
+ const mouseTrapPauseSpy = jest.spyOn(Mousetrap, 'pause');
enterZen();
expect(mouseTrapPauseSpy).toHaveBeenCalled();
@@ -90,14 +96,14 @@ describe('ZenMode', () => {
beforeEach(enterZen);
it('unpauses Mousetrap', () => {
- const mouseTrapUnpauseSpy = spyOn(Mousetrap, 'unpause');
+ const mouseTrapUnpauseSpy = jest.spyOn(Mousetrap, 'unpause');
exitZen();
expect(mouseTrapUnpauseSpy).toHaveBeenCalled();
});
it('restores the scroll position', () => {
- spyOn(zen, 'scrollTo');
+ jest.spyOn(zen, 'scrollTo').mockImplementation(() => {});
exitZen();
expect(zen.scrollTo).toHaveBeenCalled();
diff --git a/spec/javascripts/comment_type_toggle_spec.js b/spec/javascripts/comment_type_toggle_spec.js
deleted file mode 100644
index 8b1217a000f..00000000000
--- a/spec/javascripts/comment_type_toggle_spec.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import CommentTypeToggle from '~/comment_type_toggle';
-import InputSetter from '~/droplab/plugins/input_setter';
-
-describe('CommentTypeToggle', function() {
- describe('class constructor', function() {
- beforeEach(function() {
- this.dropdownTrigger = {};
- this.dropdownList = {};
- this.noteTypeInput = {};
- this.submitButton = {};
- this.closeButton = {};
-
- this.commentTypeToggle = new CommentTypeToggle({
- dropdownTrigger: this.dropdownTrigger,
- dropdownList: this.dropdownList,
- noteTypeInput: this.noteTypeInput,
- submitButton: this.submitButton,
- closeButton: this.closeButton,
- });
- });
-
- it('should set .dropdownTrigger', function() {
- expect(this.commentTypeToggle.dropdownTrigger).toBe(this.dropdownTrigger);
- });
-
- it('should set .dropdownList', function() {
- expect(this.commentTypeToggle.dropdownList).toBe(this.dropdownList);
- });
-
- it('should set .noteTypeInput', function() {
- expect(this.commentTypeToggle.noteTypeInput).toBe(this.noteTypeInput);
- });
-
- it('should set .submitButton', function() {
- expect(this.commentTypeToggle.submitButton).toBe(this.submitButton);
- });
-
- it('should set .closeButton', function() {
- expect(this.commentTypeToggle.closeButton).toBe(this.closeButton);
- });
-
- it('should set .reopenButton', function() {
- expect(this.commentTypeToggle.reopenButton).toBe(this.reopenButton);
- });
- });
-
- describe('initDroplab', function() {
- beforeEach(function() {
- this.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- closeButton: {},
- setConfig: () => {},
- };
- this.config = {};
-
- this.droplab = jasmine.createSpyObj('droplab', ['init']);
-
- this.droplabConstructor = spyOnDependency(CommentTypeToggle, 'DropLab').and.returnValue(
- this.droplab,
- );
- spyOn(this.commentTypeToggle, 'setConfig').and.returnValue(this.config);
-
- CommentTypeToggle.prototype.initDroplab.call(this.commentTypeToggle);
- });
-
- it('should instantiate a DropLab instance', function() {
- expect(this.droplabConstructor).toHaveBeenCalled();
- });
-
- it('should set .droplab', function() {
- expect(this.commentTypeToggle.droplab).toBe(this.droplab);
- });
-
- it('should call .setConfig', function() {
- expect(this.commentTypeToggle.setConfig).toHaveBeenCalled();
- });
-
- it('should call DropLab.prototype.init', function() {
- expect(this.droplab.init).toHaveBeenCalledWith(
- this.commentTypeToggle.dropdownTrigger,
- this.commentTypeToggle.dropdownList,
- [InputSetter],
- this.config,
- );
- });
- });
-
- describe('setConfig', function() {
- describe('if no .closeButton is provided', function() {
- beforeEach(function() {
- this.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- reopenButton: {},
- };
-
- this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
- });
-
- it('should not add .closeButton related InputSetter config', function() {
- expect(this.setConfig).toEqual({
- InputSetter: [
- {
- input: this.commentTypeToggle.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: this.commentTypeToggle.submitButton,
- valueAttribute: 'data-submit-text',
- },
- {
- input: this.commentTypeToggle.reopenButton,
- valueAttribute: 'data-reopen-text',
- },
- {
- input: this.commentTypeToggle.reopenButton,
- valueAttribute: 'data-reopen-text',
- inputAttribute: 'data-alternative-text',
- },
- ],
- });
- });
- });
-
- describe('if no .reopenButton is provided', function() {
- beforeEach(function() {
- this.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- closeButton: {},
- };
-
- this.setConfig = CommentTypeToggle.prototype.setConfig.call(this.commentTypeToggle);
- });
-
- it('should not add .reopenButton related InputSetter config', function() {
- expect(this.setConfig).toEqual({
- InputSetter: [
- {
- input: this.commentTypeToggle.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: this.commentTypeToggle.submitButton,
- valueAttribute: 'data-submit-text',
- },
- {
- input: this.commentTypeToggle.closeButton,
- valueAttribute: 'data-close-text',
- },
- {
- input: this.commentTypeToggle.closeButton,
- valueAttribute: 'data-close-text',
- inputAttribute: 'data-alternative-text',
- },
- ],
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
deleted file mode 100644
index bedab0fd003..00000000000
--- a/spec/javascripts/line_highlighter_spec.js
+++ /dev/null
@@ -1,261 +0,0 @@
-/* eslint-disable dot-notation, no-return-assign, no-new, no-underscore-dangle */
-
-import $ from 'jquery';
-import LineHighlighter from '~/line_highlighter';
-
-describe('LineHighlighter', function() {
- preloadFixtures('static/line_highlighter.html');
- const clickLine = function(number, eventData = {}) {
- if ($.isEmptyObject(eventData)) {
- return $(`#L${number}`).click();
- }
- const e = $.Event('click', eventData);
- return $(`#L${number}`).trigger(e);
- };
- beforeEach(function() {
- loadFixtures('static/line_highlighter.html');
- this['class'] = new LineHighlighter();
- this.css = this['class'].highlightLineClass;
- return (this.spies = {
- __setLocationHash__: spyOn(this['class'], '__setLocationHash__').and.callFake(function() {}),
- });
- });
-
- describe('behavior', function() {
- it('highlights one line given in the URL hash', function() {
- new LineHighlighter({ hash: '#L13' });
-
- expect($('#LC13')).toHaveClass(this.css);
- });
-
- it('highlights one line given in the URL hash with given CSS class name', function() {
- const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' });
-
- expect(hiliter.highlightLineClass).toBe('hilite');
- expect($('#LC13')).toHaveClass('hilite');
- expect($('#LC13')).not.toHaveClass('hll');
- });
-
- it('highlights a range of lines given in the URL hash', function() {
- new LineHighlighter({ hash: '#L5-25' });
-
- expect($(`.${this.css}`).length).toBe(21);
- for (let line = 5; line <= 25; line += 1) {
- expect($(`#LC${line}`)).toHaveClass(this.css);
- }
- });
-
- it('scrolls to the first highlighted line on initial load', function() {
- const spy = spyOn($, 'scrollTo');
- new LineHighlighter({ hash: '#L5-25' });
-
- expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything());
- });
-
- it('discards click events', function() {
- const spy = spyOnEvent('a[data-line-number]', 'click');
- clickLine(13);
-
- expect(spy).toHaveBeenPrevented();
- });
-
- it('handles garbage input from the hash', function() {
- const func = function() {
- return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' });
- };
-
- expect(func).not.toThrow();
- });
-
- it('handles hashchange event', () => {
- const highlighter = new LineHighlighter();
-
- spyOn(highlighter, 'highlightHash');
-
- window.dispatchEvent(new Event('hashchange'), 'L15');
-
- expect(highlighter.highlightHash).toHaveBeenCalled();
- });
- });
-
- describe('clickHandler', function() {
- it('handles clicking on a child icon element', function() {
- const spy = spyOn(this['class'], 'setHash').and.callThrough();
- $('#L13 i')
- .mousedown()
- .click();
-
- expect(spy).toHaveBeenCalledWith(13);
- expect($('#LC13')).toHaveClass(this.css);
- });
-
- describe('without shiftKey', function() {
- it('highlights one line when clicked', function() {
- clickLine(13);
-
- expect($('#LC13')).toHaveClass(this.css);
- });
-
- it('unhighlights previously highlighted lines', function() {
- clickLine(13);
- clickLine(20);
-
- expect($('#LC13')).not.toHaveClass(this.css);
- expect($('#LC20')).toHaveClass(this.css);
- });
-
- it('sets the hash', function() {
- const spy = spyOn(this['class'], 'setHash').and.callThrough();
- clickLine(13);
-
- expect(spy).toHaveBeenCalledWith(13);
- });
- });
-
- describe('with shiftKey', function() {
- it('sets the hash', function() {
- const spy = spyOn(this['class'], 'setHash').and.callThrough();
- clickLine(13);
- clickLine(20, {
- shiftKey: true,
- });
-
- expect(spy).toHaveBeenCalledWith(13);
- expect(spy).toHaveBeenCalledWith(13, 20);
- });
-
- describe('without existing highlight', function() {
- it('highlights the clicked line', function() {
- clickLine(13, {
- shiftKey: true,
- });
-
- expect($('#LC13')).toHaveClass(this.css);
- expect($(`.${this.css}`).length).toBe(1);
- });
-
- it('sets the hash', function() {
- const spy = spyOn(this['class'], 'setHash');
- clickLine(13, {
- shiftKey: true,
- });
-
- expect(spy).toHaveBeenCalledWith(13);
- });
- });
-
- describe('with existing single-line highlight', function() {
- it('uses existing line as last line when target is lesser', function() {
- clickLine(20);
- clickLine(15, {
- shiftKey: true,
- });
-
- expect($(`.${this.css}`).length).toBe(6);
- for (let line = 15; line <= 20; line += 1) {
- expect($(`#LC${line}`)).toHaveClass(this.css);
- }
- });
-
- it('uses existing line as first line when target is greater', function() {
- clickLine(5);
- clickLine(10, {
- shiftKey: true,
- });
-
- expect($(`.${this.css}`).length).toBe(6);
- for (let line = 5; line <= 10; line += 1) {
- expect($(`#LC${line}`)).toHaveClass(this.css);
- }
- });
- });
-
- describe('with existing multi-line highlight', function() {
- beforeEach(function() {
- clickLine(10, {
- shiftKey: true,
- });
- clickLine(13, {
- shiftKey: true,
- });
- });
-
- it('uses target as first line when it is less than existing first line', function() {
- clickLine(5, {
- shiftKey: true,
- });
-
- expect($(`.${this.css}`).length).toBe(6);
- for (let line = 5; line <= 10; line += 1) {
- expect($(`#LC${line}`)).toHaveClass(this.css);
- }
- });
-
- it('uses target as last line when it is greater than existing first line', function() {
- clickLine(15, {
- shiftKey: true,
- });
-
- expect($(`.${this.css}`).length).toBe(6);
- for (let line = 10; line <= 15; line += 1) {
- expect($(`#LC${line}`)).toHaveClass(this.css);
- }
- });
- });
- });
- });
-
- describe('hashToRange', function() {
- beforeEach(function() {
- this.subject = this['class'].hashToRange;
- });
-
- it('extracts a single line number from the hash', function() {
- expect(this.subject('#L5')).toEqual([5, null]);
- });
-
- it('extracts a range of line numbers from the hash', function() {
- expect(this.subject('#L5-15')).toEqual([5, 15]);
- });
-
- it('returns [null, null] when the hash is not a line number', function() {
- expect(this.subject('#foo')).toEqual([null, null]);
- });
- });
-
- describe('highlightLine', function() {
- beforeEach(function() {
- this.subject = this['class'].highlightLine;
- });
-
- it('highlights the specified line', function() {
- this.subject(13);
-
- expect($('#LC13')).toHaveClass(this.css);
- });
-
- it('accepts a String-based number', function() {
- this.subject('13');
-
- expect($('#LC13')).toHaveClass(this.css);
- });
- });
-
- describe('setHash', function() {
- beforeEach(function() {
- this.subject = this['class'].setHash;
- });
-
- it('sets the location hash for a single line', function() {
- this.subject(5);
-
- expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5');
- });
-
- it('sets the location hash for a range', function() {
- this.subject(5, 15);
-
- expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
- });
- });
-});
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
deleted file mode 100644
index df7012bb659..00000000000
--- a/spec/javascripts/shortcuts_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import $ from 'jquery';
-import Shortcuts from '~/behaviors/shortcuts/shortcuts';
-
-describe('Shortcuts', () => {
- const fixtureName = 'snippets/show.html';
- const createEvent = (type, target) =>
- $.Event(type, {
- target,
- });
-
- preloadFixtures(fixtureName);
-
- describe('toggleMarkdownPreview', () => {
- beforeEach(() => {
- loadFixtures(fixtureName);
-
- spyOnEvent('.js-new-note-form .js-md-preview-button', 'focus');
- spyOnEvent('.edit-note .js-md-preview-button', 'focus');
-
- new Shortcuts(); // eslint-disable-line no-new
- });
-
- it('focuses preview button in form', () => {
- Shortcuts.toggleMarkdownPreview(
- createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text')),
- );
-
- expect('focus').toHaveBeenTriggeredOn('.js-new-note-form .js-md-preview-button');
- });
-
- it('focues preview button inside edit comment form', done => {
- document.querySelector('.js-note-edit').click();
-
- setTimeout(() => {
- Shortcuts.toggleMarkdownPreview(
- createEvent('KeyboardEvent', document.querySelector('.edit-note .js-note-text')),
- );
-
- expect('focus').not.toHaveBeenTriggeredOn('.js-new-note-form .js-md-preview-button');
- expect('focus').toHaveBeenTriggeredOn('.edit-note .js-md-preview-button');
-
- done();
- });
- });
- });
-});
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
index 965564cb83b..5446d6559fe 100644
--- a/spec/lib/gitlab/routing_spec.rb
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -22,4 +22,25 @@ describe Gitlab::Routing do
expect(subject).to respond_to(:namespace_project_path)
end
end
+
+ describe Gitlab::Routing::LegacyRedirector do
+ subject { described_class.new(:wikis) }
+
+ let(:request) { double(:request, path: path, query_string: '') }
+ let(:path) { '/gitlab-org/gitlab-test/wikis/home' }
+
+ it 'returns "-" scoped url' do
+ expect(subject.call({}, request)).to eq('/gitlab-org/gitlab-test/-/wikis/home')
+ end
+
+ context 'invalid uri characters' do
+ let(:path) { '/gitlab-org/gitlab-test/wikis/home[' }
+
+ it 'raises error' do
+ expect do
+ subject.call({}, request)
+ end.to raise_error(ActionController::RoutingError)
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5f4a3dbd99c..d7c4695203d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4812,6 +4812,32 @@ describe Project do
end
end
+ describe '#execute_services' do
+ let(:service) { create(:slack_service, push_events: true, merge_requests_events: false, active: true) }
+
+ it 'executes services with the specified scope' do
+ data = 'any data'
+
+ expect(SlackService).to receive(:allocate).and_wrap_original do |method|
+ method.call.tap do |instance|
+ expect(instance).to receive(:async_execute).with(data).once
+ end
+ end
+
+ service.project.execute_services(data, :push_hooks)
+ end
+
+ it 'does not execute services that don\'t match the specified scope' do
+ expect(SlackService).not_to receive(:allocate).and_wrap_original do |method|
+ method.call.tap do |instance|
+ expect(instance).not_to receive(:async_execute)
+ end
+ end
+
+ service.project.execute_services(anything, :merge_request_hooks)
+ end
+ end
+
describe '#has_active_hooks?' do
let_it_be(:project) { create(:project) }