summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
authorFelipe Artur <fcardozo@gitlab.com>2018-06-21 12:22:40 +0000
committerTim Zallmann <tzallmann@gitlab.com>2018-06-21 12:22:40 +0000
commit3e66795ef1ff1228906239763910b051d8afcc37 (patch)
treedf6424d9ec008f5d1da455f8465681b371c4a11e /spec/javascripts
parent14e35ac9b19d358d84e0cfd167f74036937285b6 (diff)
downloadgitlab-ce-3e66795ef1ff1228906239763910b051d8afcc37.tar.gz
Changes tab VUE refactoring
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/awards_handler_spec.js106
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js81
-rw-r--r--spec/javascripts/diffs/components/app_spec.js1
-rw-r--r--spec/javascripts/diffs/components/changed_files_dropdown_spec.js1
-rw-r--r--spec/javascripts/diffs/components/changed_files_spec.js100
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js1
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js1
-rw-r--r--spec/javascripts/diffs/components/diff_content_spec.js1
-rw-r--r--spec/javascripts/diffs/components/diff_discussions_spec.js24
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js433
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js88
-rw-r--r--spec/javascripts/diffs/components/diff_gutter_avatars_spec.js115
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js153
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js68
-rw-r--r--spec/javascripts/diffs/components/edit_button_spec.js1
-rw-r--r--spec/javascripts/diffs/components/hidden_files_warning_spec.js1
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js111
-rw-r--r--spec/javascripts/diffs/components/no_changes_spec.js1
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js224
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js496
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js220
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js210
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js24
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js147
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js179
-rw-r--r--spec/javascripts/fixtures/commit.rb33
-rw-r--r--spec/javascripts/fixtures/snippet.rb1
-rw-r--r--spec/javascripts/helpers/index.js3
-rw-r--r--spec/javascripts/helpers/init_vue_mr_page_helper.js40
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js70
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js16
-rw-r--r--spec/javascripts/matchers.js26
-rw-r--r--spec/javascripts/merge_request_notes_spec.js108
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js565
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js94
-rw-r--r--spec/javascripts/notes/components/diff_file_header_spec.js93
-rw-r--r--spec/javascripts/notes/components/diff_with_note_spec.js20
-rw-r--r--spec/javascripts/notes/components/discussion_counter_spec.js58
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js12
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js68
-rw-r--r--spec/javascripts/notes/components/note_awards_list_spec.js8
-rw-r--r--spec/javascripts/notes/components/note_body_spec.js7
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js28
-rw-r--r--spec/javascripts/notes/components/note_header_spec.js30
-rw-r--r--spec/javascripts/notes/components/note_signed_out_widget_spec.js20
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js83
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js15
-rw-r--r--spec/javascripts/notes/mock_data.js13
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js40
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js20
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js104
-rw-r--r--spec/javascripts/notes_spec.js151
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js27
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js77
-rw-r--r--spec/javascripts/shortcuts_spec.js19
-rw-r--r--spec/javascripts/test_bundle.js3
-rw-r--r--spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js20
-rw-r--r--spec/javascripts/vue_shared/components/notes/system_note_spec.js14
-rw-r--r--spec/javascripts/zen_mode_spec.js59
59 files changed, 3681 insertions, 1051 deletions
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index ff6020c8fdd..ada26b37f4a 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, no-unused-vars, quotes, prefer-template, max-len */
+/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-unused-expressions, no-unused-vars, prefer-template, max-len */
import $ from 'jquery';
import Cookies from 'js-cookie';
@@ -21,20 +21,21 @@ import '~/lib/utils/common_utils';
return setTimeout(function() {
assertFn();
return done();
- // Maybe jasmine.clock here?
+ // Maybe jasmine.clock here?
}, 333);
};
describe('AwardsHandler', function() {
- preloadFixtures('merge_requests/diff_comment.html.raw');
+ preloadFixtures('snippets/show.html.raw');
beforeEach(function(done) {
- loadFixtures('merge_requests/diff_comment.html.raw');
- $('body').attr('data-page', 'projects:merge_requests:show');
- loadAwardsHandler(true).then((obj) => {
- awardsHandler = obj;
- spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
- done();
- }).catch(fail);
+ loadFixtures('snippets/show.html.raw');
+ loadAwardsHandler(true)
+ .then(obj => {
+ awardsHandler = obj;
+ spyOn(awardsHandler, 'postEmoji').and.callFake((button, url, emoji, cb) => cb());
+ done();
+ })
+ .catch(fail);
let isEmojiMenuBuilt = false;
openAndWaitForEmojiMenu = function() {
@@ -42,7 +43,9 @@ import '~/lib/utils/common_utils';
if (isEmojiMenuBuilt) {
resolve();
} else {
- $('.js-add-award').eq(0).click();
+ $('.js-add-award')
+ .eq(0)
+ .click();
const $menu = $('.emoji-menu');
$menu.one('build-emoji-menu-finish', () => {
isEmojiMenuBuilt = true;
@@ -63,7 +66,9 @@ import '~/lib/utils/common_utils';
});
describe('::showEmojiMenu', function() {
it('should show emoji menu when Add emoji button clicked', function(done) {
- $('.js-add-award').eq(0).click();
+ $('.js-add-award')
+ .eq(0)
+ .click();
return lazyAssert(done, function() {
var $emojiMenu;
$emojiMenu = $('.emoji-menu');
@@ -81,7 +86,9 @@ import '~/lib/utils/common_utils';
});
});
it('should remove emoji menu when body is clicked', function(done) {
- $('.js-add-award').eq(0).click();
+ $('.js-add-award')
+ .eq(0)
+ .click();
return lazyAssert(done, function() {
var $emojiMenu;
$emojiMenu = $('.emoji-menu');
@@ -92,7 +99,9 @@ import '~/lib/utils/common_utils';
});
});
it('should not remove emoji menu when search is clicked', function(done) {
- $('.js-add-award').eq(0).click();
+ $('.js-add-award')
+ .eq(0)
+ .click();
return lazyAssert(done, function() {
var $emojiMenu;
$emojiMenu = $('.emoji-menu');
@@ -103,6 +112,7 @@ import '~/lib/utils/common_utils';
});
});
});
+
describe('::addAwardToEmojiBar', function() {
it('should add emoji to votes block', function() {
var $emojiButton, $votesBlock;
@@ -139,7 +149,9 @@ import '~/lib/utils/common_utils';
$thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.userAuthored($thumbsUpEmoji);
- return expect($thumbsUpEmoji.data("originalTitle")).toBe("You cannot vote on your own issue, MR and note");
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe(
+ 'You cannot vote on your own issue, MR and note',
+ );
});
it('should restore tooltip back to initial vote list', function() {
var $thumbsUpEmoji, $votesBlock;
@@ -150,12 +162,14 @@ import '~/lib/utils/common_utils';
awardsHandler.userAuthored($thumbsUpEmoji);
jasmine.clock().tick(2801);
jasmine.clock().uninstall();
- return expect($thumbsUpEmoji.data("originalTitle")).toBe("sam");
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
});
});
describe('::getAwardUrl', function() {
return it('returns the url for request', function() {
- return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/merge-requests-project/merge_requests/1/toggle_award_emoji');
+ return expect(awardsHandler.getAwardUrl()).toBe(
+ 'http://test.host/snippets/1/toggle_award_emoji',
+ );
});
});
describe('::addAward and ::checkMutuality', function() {
@@ -195,7 +209,7 @@ import '~/lib/utils/common_utils';
$thumbsUpEmoji.attr('data-title', 'sam, jerry, max, and andy');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
- return expect($thumbsUpEmoji.data("originalTitle")).toBe('You, sam, jerry, max, and andy');
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe('You, sam, jerry, max, and andy');
});
return it('handles the special case where "You" is not cleanly comma seperated', function() {
var $thumbsUpEmoji, $votesBlock, awardUrl;
@@ -205,7 +219,7 @@ import '~/lib/utils/common_utils';
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
- return expect($thumbsUpEmoji.data("originalTitle")).toBe('You and sam');
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe('You and sam');
});
});
describe('::removeYouToUserList', function() {
@@ -218,7 +232,7 @@ import '~/lib/utils/common_utils';
$thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
- return expect($thumbsUpEmoji.data("originalTitle")).toBe('sam, jerry, max, and andy');
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam, jerry, max, and andy');
});
return it('handles the special case where "You" is not cleanly comma seperated', function() {
var $thumbsUpEmoji, $votesBlock, awardUrl;
@@ -229,7 +243,7 @@ import '~/lib/utils/common_utils';
$thumbsUpEmoji.addClass('active');
awardsHandler.addAward($votesBlock, awardUrl, 'thumbsup', false);
$thumbsUpEmoji.tooltip();
- return expect($thumbsUpEmoji.data("originalTitle")).toBe('sam');
+ return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
});
});
describe('::searchEmojis', () => {
@@ -245,7 +259,7 @@ import '~/lib/utils/common_utils';
expect($('.js-emoji-menu-search').val()).toBe('ali');
})
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
@@ -263,7 +277,7 @@ import '~/lib/utils/common_utils';
expect($('.js-emoji-menu-search').val()).toBe('');
})
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
@@ -272,37 +286,40 @@ import '~/lib/utils/common_utils';
describe('emoji menu', function() {
const emojiSelector = '[data-name="sunglasses"]';
const openEmojiMenuAndAddEmoji = function() {
- return openAndWaitForEmojiMenu()
- .then(() => {
- const $menu = $('.emoji-menu');
- const $block = $('.js-awards-block');
- const $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + emojiSelector);
+ return openAndWaitForEmojiMenu().then(() => {
+ const $menu = $('.emoji-menu');
+ const $block = $('.js-awards-block');
+ const $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + emojiSelector);
- expect($emoji.length).toBe(1);
- expect($block.find(emojiSelector).length).toBe(0);
- $emoji.click();
- expect($menu.hasClass('.is-visible')).toBe(false);
- expect($block.find(emojiSelector).length).toBe(1);
- });
+ expect($emoji.length).toBe(1);
+ expect($block.find(emojiSelector).length).toBe(0);
+ $emoji.click();
+ expect($menu.hasClass('.is-visible')).toBe(false);
+ expect($block.find(emojiSelector).length).toBe(1);
+ });
};
it('should add selected emoji to awards block', function(done) {
return openEmojiMenuAndAddEmoji()
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
it('should remove already selected emoji', function(done) {
return openEmojiMenuAndAddEmoji()
.then(() => {
- $('.js-add-award').eq(0).click();
+ $('.js-add-award')
+ .eq(0)
+ .click();
const $block = $('.js-awards-block');
- const $emoji = $('.emoji-menu').find(`.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`);
+ const $emoji = $('.emoji-menu').find(
+ `.emoji-menu-list:not(.frequent-emojis) ${emojiSelector}`,
+ );
$emoji.click();
expect($block.find(emojiSelector).length).toBe(0);
})
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
@@ -318,12 +335,12 @@ import '~/lib/utils/common_utils';
return openAndWaitForEmojiMenu()
.then(() => {
const emojiMenu = document.querySelector('.emoji-menu');
- Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), (title) => {
+ Array.prototype.forEach.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title => {
expect(title.textContent.trim().toLowerCase()).not.toBe('frequently used');
});
})
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
@@ -334,14 +351,15 @@ import '~/lib/utils/common_utils';
return openAndWaitForEmojiMenu()
.then(() => {
const emojiMenu = document.querySelector('.emoji-menu');
- const hasFrequentlyUsedHeading = Array.prototype.some.call(emojiMenu.querySelectorAll('.emoji-menu-title'), title =>
- title.textContent.trim().toLowerCase() === 'frequently used'
+ const hasFrequentlyUsedHeading = Array.prototype.some.call(
+ emojiMenu.querySelectorAll('.emoji-menu-title'),
+ title => title.textContent.trim().toLowerCase() === 'frequently used',
);
expect(hasFrequentlyUsedHeading).toBe(true);
})
.then(done)
- .catch((err) => {
+ .catch(err => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
@@ -361,4 +379,4 @@ import '~/lib/utils/common_utils';
});
});
});
-}).call(window);
+}.call(window));
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index d03836d10f9..d8aa5c636da 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -4,12 +4,11 @@ import '~/behaviors/quick_submit';
describe('Quick Submit behavior', function () {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
- preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ preloadFixtures('snippets/show.html.raw');
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
- $('body').attr('data-page', 'projects:merge_requests:show');
- $('form').submit((e) => {
+ loadFixtures('snippets/show.html.raw');
+ $('form').submit(e => {
// Prevent a form submit from moving us off the testing page
e.preventDefault();
});
@@ -26,24 +25,30 @@ describe('Quick Submit behavior', function () {
});
it('does not respond to other keyCodes', () => {
- this.textarea.trigger(keydownEvent({
- keyCode: 32,
- }));
+ this.textarea.trigger(
+ keydownEvent({
+ keyCode: 32,
+ }),
+ );
expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to Enter alone', () => {
- this.textarea.trigger(keydownEvent({
- ctrlKey: false,
- metaKey: false,
- }));
+ this.textarea.trigger(
+ keydownEvent({
+ ctrlKey: false,
+ metaKey: false,
+ }),
+ );
expect(this.spies.submit).not.toHaveBeenTriggered();
});
it('does not respond to repeated events', () => {
- this.textarea.trigger(keydownEvent({
- repeat: true,
- }));
+ this.textarea.trigger(
+ keydownEvent({
+ repeat: true,
+ }),
+ );
expect(this.spies.submit).not.toHaveBeenTriggered();
});
@@ -83,15 +88,21 @@ describe('Quick Submit behavior', function () {
});
it('excludes other modifier keys', () => {
- this.textarea.trigger(keydownEvent({
- altKey: true,
- }));
- this.textarea.trigger(keydownEvent({
- ctrlKey: true,
- }));
- this.textarea.trigger(keydownEvent({
- shiftKey: true,
- }));
+ this.textarea.trigger(
+ keydownEvent({
+ altKey: true,
+ }),
+ );
+ this.textarea.trigger(
+ keydownEvent({
+ ctrlKey: true,
+ }),
+ );
+ this.textarea.trigger(
+ keydownEvent({
+ shiftKey: true,
+ }),
+ );
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
});
@@ -102,15 +113,21 @@ describe('Quick Submit behavior', function () {
});
it('excludes other modifier keys', () => {
- this.textarea.trigger(keydownEvent({
- altKey: true,
- }));
- this.textarea.trigger(keydownEvent({
- metaKey: true,
- }));
- this.textarea.trigger(keydownEvent({
- shiftKey: true,
- }));
+ this.textarea.trigger(
+ keydownEvent({
+ altKey: true,
+ }),
+ );
+ this.textarea.trigger(
+ keydownEvent({
+ metaKey: true,
+ }),
+ );
+ this.textarea.trigger(
+ keydownEvent({
+ shiftKey: true,
+ }),
+ );
return expect(this.spies.submit).not.toHaveBeenTriggered();
});
}
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/changed_files_spec.js b/spec/javascripts/diffs/components/changed_files_spec.js
new file mode 100644
index 00000000000..2d57af6137c
--- /dev/null
+++ b/spec/javascripts/diffs/components/changed_files_spec.js
@@ -0,0 +1,100 @@
+import Vue from 'vue';
+import $ from 'jquery';
+import { mountComponentWithStore } from 'spec/helpers';
+import store from '~/diffs/store';
+import ChangedFiles from '~/diffs/components/changed_files.vue';
+
+describe('ChangedFiles', () => {
+ const Component = Vue.extend(ChangedFiles);
+ const createComponent = props => mountComponentWithStore(Component, { props, store });
+ let vm;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="dummy-element"></div>
+ <div class="js-tabs-affix"></div>
+ `);
+ const props = {
+ diffFiles: [
+ {
+ addedLines: 10,
+ removedLines: 20,
+ blob: {
+ path: 'some/code.txt',
+ },
+ filePath: 'some/code.txt',
+ },
+ ],
+ };
+ vm = createComponent(props);
+ });
+
+ describe('with single file added', () => {
+ it('shows files changes', () => {
+ expect(vm.$el).toContainText('1 changed file');
+ });
+
+ it('shows file additions and deletions', () => {
+ expect(vm.$el).toContainText('10 additions');
+ expect(vm.$el).toContainText('20 deletions');
+ });
+ });
+
+ describe('template', () => {
+ describe('diff view mode buttons', () => {
+ let inlineButton;
+ let parallelButton;
+
+ beforeEach(() => {
+ inlineButton = vm.$el.querySelector('.js-inline-diff-button');
+ parallelButton = vm.$el.querySelector('.js-parallel-diff-button');
+ });
+
+ it('should have Inline and Side-by-side buttons', () => {
+ expect(inlineButton).toBeDefined();
+ expect(parallelButton).toBeDefined();
+ });
+
+ it('should add active class to Inline button', done => {
+ vm.$store.state.diffs.diffViewType = 'inline';
+
+ vm.$nextTick(() => {
+ expect(inlineButton.classList.contains('active')).toEqual(true);
+ expect(parallelButton.classList.contains('active')).toEqual(false);
+
+ done();
+ });
+ });
+
+ it('should toggle active state of buttons when diff view type changed', done => {
+ vm.$store.state.diffs.diffViewType = 'parallel';
+
+ vm.$nextTick(() => {
+ expect(inlineButton.classList.contains('active')).toEqual(false);
+ expect(parallelButton.classList.contains('active')).toEqual(true);
+
+ done();
+ });
+ });
+
+ describe('clicking them', () => {
+ it('should toggle the diff view type', done => {
+ $(parallelButton).click();
+
+ vm.$nextTick(() => {
+ expect(inlineButton.classList.contains('active')).toEqual(false);
+ expect(parallelButton.classList.contains('active')).toEqual(true);
+
+ $(inlineButton).click();
+
+ vm.$nextTick(() => {
+ expect(inlineButton.classList.contains('active')).toEqual(true);
+ expect(parallelButton.classList.contains('active')).toEqual(false);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_content_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/diff_discussions_spec.js b/spec/javascripts/diffs/components/diff_discussions_spec.js
new file mode 100644
index 00000000000..270f363825f
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_discussions_spec.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+describe('DiffDiscussions', () => {
+ let component;
+ const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+
+ beforeEach(() => {
+ component = createComponentWithStore(Vue.extend(DiffDiscussions), store, {
+ discussions: getDiscussionsMockData(),
+ }).$mount();
+ });
+
+ describe('template', () => {
+ it('should have notes list', () => {
+ const { $el } = component;
+
+ expect($el.querySelectorAll('.discussion .note.timeline-entry').length).toEqual(5);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
new file mode 100644
index 00000000000..d0f1700bee6
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -0,0 +1,433 @@
+import Vue from 'vue';
+import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const discussionFixture = 'merge_requests/diff_discussion.json';
+
+describe('diff_file_header', () => {
+ let vm;
+ let props;
+ const Component = Vue.extend(DiffFileHeader);
+
+ beforeEach(() => {
+ const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
+ const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file, { deep: true });
+ props = {
+ diffFile,
+ currentUser: {},
+ };
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('icon', () => {
+ beforeEach(() => {
+ props.diffFile.blob.icon = 'dummy icon';
+ });
+
+ it('returns the blob icon for files', () => {
+ props.diffFile.submodule = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.icon).toBe(props.diffFile.blob.icon);
+ });
+
+ it('returns the archive icon for submodules', () => {
+ props.diffFile.submodule = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.icon).toBe('archive');
+ });
+ });
+
+ describe('titleLink', () => {
+ beforeEach(() => {
+ Object.assign(props.diffFile, {
+ fileHash: 'badc0ffee',
+ submoduleLink: 'link://to/submodule',
+ submoduleTreeUrl: 'some://tree/url',
+ });
+ });
+
+ it('returns the fileHash for files', () => {
+ props.diffFile.submodule = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.titleLink).toBe(`#${props.diffFile.fileHash}`);
+ });
+
+ it('returns the submoduleTreeUrl for submodules', () => {
+ props.diffFile.submodule = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.titleLink).toBe(props.diffFile.submoduleTreeUrl);
+ });
+
+ it('returns the submoduleLink for submodules without submoduleTreeUrl', () => {
+ Object.assign(props.diffFile, {
+ submodule: true,
+ submoduleTreeUrl: null,
+ });
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.titleLink).toBe(props.diffFile.submoduleLink);
+ });
+ });
+
+ describe('filePath', () => {
+ beforeEach(() => {
+ Object.assign(props.diffFile, {
+ blob: { id: 'b10b1db10b1d' },
+ filePath: 'path/to/file',
+ });
+ });
+
+ it('returns the filePath for files', () => {
+ props.diffFile.submodule = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.filePath).toBe(props.diffFile.filePath);
+ });
+
+ it('appends the truncated blob id for submodules', () => {
+ props.diffFile.submodule = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.filePath).toBe(
+ `${props.diffFile.filePath} @ ${props.diffFile.blob.id.substr(0, 8)}`,
+ );
+ });
+ });
+
+ describe('titleTag', () => {
+ it('returns a link tag if fileHash is set', () => {
+ props.diffFile.fileHash = 'some hash';
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.titleTag).toBe('a');
+ });
+
+ it('returns a span tag if fileHash is not set', () => {
+ props.diffFile.fileHash = null;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.titleTag).toBe('span');
+ });
+ });
+
+ describe('isUsingLfs', () => {
+ beforeEach(() => {
+ Object.assign(props.diffFile, {
+ storedExternally: true,
+ externalStorage: 'lfs',
+ });
+ });
+
+ it('returns true if file is stored in LFS', () => {
+ vm = mountComponent(Component, props);
+
+ expect(vm.isUsingLfs).toBe(true);
+ });
+
+ it('returns false if file is not stored externally', () => {
+ props.diffFile.storedExternally = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.isUsingLfs).toBe(false);
+ });
+
+ it('returns false if file is not stored in LFS', () => {
+ props.diffFile.externalStorage = 'not lfs';
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.isUsingLfs).toBe(false);
+ });
+ });
+
+ describe('collapseIcon', () => {
+ it('returns chevron-down if the diff is expanded', () => {
+ props.expanded = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.collapseIcon).toBe('chevron-down');
+ });
+
+ it('returns chevron-right if the diff is collapsed', () => {
+ props.expanded = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.collapseIcon).toBe('chevron-right');
+ });
+ });
+
+ describe('isDiscussionsExpanded', () => {
+ beforeEach(() => {
+ Object.assign(props, {
+ discussionsExpanded: true,
+ expanded: true,
+ });
+ });
+
+ it('returns true if diff and discussion are expanded', () => {
+ vm = mountComponent(Component, props);
+
+ expect(vm.isDiscussionsExpanded).toBe(true);
+ });
+
+ it('returns false if discussion is collapsed', () => {
+ props.discussionsExpanded = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.isDiscussionsExpanded).toBe(false);
+ });
+
+ it('returns false if diff is collapsed', () => {
+ props.expanded = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.isDiscussionsExpanded).toBe(false);
+ });
+ });
+
+ describe('viewFileButtonText', () => {
+ it('contains the truncated content SHA', () => {
+ const dummySha = 'deebd00f is no SHA';
+ props.diffFile.contentSha = dummySha;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.viewFileButtonText).not.toContain(dummySha);
+ expect(vm.viewFileButtonText).toContain(dummySha.substr(0, 8));
+ });
+ });
+
+ describe('viewReplacedFileButtonText', () => {
+ it('contains the truncated base SHA', () => {
+ const dummySha = 'deadabba sings no more';
+ props.diffFile.diffRefs.baseSha = dummySha;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.viewReplacedFileButtonText).not.toContain(dummySha);
+ expect(vm.viewReplacedFileButtonText).toContain(dummySha.substr(0, 8));
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handleToggle', () => {
+ beforeEach(() => {
+ spyOn(vm, '$emit').and.stub();
+ });
+
+ it('emits toggleFile if checkTarget is false', () => {
+ vm.handleToggle(null, false);
+
+ expect(vm.$emit).toHaveBeenCalledWith('toggleFile');
+ });
+
+ it('emits toggleFile if checkTarget is true and event target is header', () => {
+ vm.handleToggle({ target: vm.$refs.header }, true);
+
+ expect(vm.$emit).toHaveBeenCalledWith('toggleFile');
+ });
+
+ it('does not emit toggleFile if checkTarget is true and event target is not header', () => {
+ vm.handleToggle({ target: 'not header' }, true);
+
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('template', () => {
+ describe('collapse toggle', () => {
+ const collapseToggle = () => vm.$el.querySelector('.diff-toggle-caret');
+
+ it('is visible if collapsible is true', () => {
+ props.collapsible = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(collapseToggle()).not.toBe(null);
+ });
+
+ it('is hidden if collapsible is false', () => {
+ props.collapsible = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(collapseToggle()).toBe(null);
+ });
+ });
+
+ it('displays an icon in the title', () => {
+ vm = mountComponent(Component, props);
+
+ const icon = vm.$el.querySelector(`i[class="fa fa-fw fa-${vm.icon}"]`);
+ expect(icon).not.toBe(null);
+ });
+
+ describe('file paths', () => {
+ const filePaths = () => vm.$el.querySelectorAll('.file-title-name');
+
+ it('displays the path of a added file', () => {
+ props.diffFile.renamedFile = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(filePaths()).toHaveLength(1);
+ expect(filePaths()[0]).toHaveText(props.diffFile.filePath);
+ });
+
+ it('displays path for deleted file', () => {
+ props.diffFile.renamedFile = false;
+ props.diffFile.deletedFile = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(filePaths()).toHaveLength(1);
+ expect(filePaths()[0]).toHaveText(`${props.diffFile.filePath} deleted`);
+ });
+
+ it('displays old and new path if the file was renamed', () => {
+ props.diffFile.renamedFile = true;
+
+ vm = mountComponent(Component, props);
+
+ expect(filePaths()).toHaveLength(2);
+ expect(filePaths()[0]).toHaveText(props.diffFile.oldPath);
+ expect(filePaths()[1]).toHaveText(props.diffFile.newPath);
+ });
+ });
+
+ it('displays a copy to clipboard button', () => {
+ vm = mountComponent(Component, props);
+
+ const button = vm.$el.querySelector('.btn-clipboard');
+ expect(button).not.toBe(null);
+ expect(button.dataset.clipboardText).toBe(props.diffFile.filePath);
+ });
+
+ describe('file mode', () => {
+ it('it displays old and new file mode if it changed', () => {
+ props.diffFile.modeChanged = true;
+
+ vm = mountComponent(Component, props);
+
+ const { fileMode } = vm.$refs;
+ expect(fileMode).not.toBe(undefined);
+ expect(fileMode).toContainText(props.diffFile.aMode);
+ expect(fileMode).toContainText(props.diffFile.bMode);
+ });
+
+ it('does not display the file mode if it has not changed', () => {
+ props.diffFile.modeChanged = false;
+
+ vm = mountComponent(Component, props);
+
+ const { fileMode } = vm.$refs;
+ expect(fileMode).toBe(undefined);
+ });
+ });
+
+ describe('LFS label', () => {
+ const lfsLabel = () => vm.$el.querySelector('.label-lfs');
+
+ it('displays the LFS label for files stored in LFS', () => {
+ Object.assign(props.diffFile, {
+ storedExternally: true,
+ externalStorage: 'lfs',
+ });
+
+ vm = mountComponent(Component, props);
+
+ expect(lfsLabel()).not.toBe(null);
+ expect(lfsLabel()).toHaveText('LFS');
+ });
+
+ it('does not display the LFS label for files stored in repository', () => {
+ props.diffFile.storedExternally = false;
+
+ vm = mountComponent(Component, props);
+
+ expect(lfsLabel()).toBe(null);
+ });
+ });
+
+ describe('edit button', () => {
+ it('should not render edit button if addMergeRequestButtons is not true', () => {
+ vm = mountComponent(Component, props);
+
+ expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
+ });
+
+ it('should show edit button when file is editable', () => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.editPath = '/';
+ vm = mountComponent(Component, props);
+
+ expect(vm.$el.querySelector('.js-edit-blob')).toContainText('Edit');
+ });
+
+ it('should not show edit button when file is deleted', () => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.deletedFile = true;
+ props.diffFile.editPath = '/';
+ vm = mountComponent(Component, props);
+
+ expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null);
+ });
+ });
+
+ describe('addMergeRequestButtons', () => {
+ beforeEach(() => {
+ props.addMergeRequestButtons = true;
+ props.diffFile.editPath = '';
+ });
+
+ describe('view on environment button', () => {
+ const url = 'some.external.url/';
+ const title = 'url.title';
+
+ it('displays link to external url', () => {
+ props.diffFile.externalUrl = url;
+ props.diffFile.formattedExternalUrl = title;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.$el.querySelector(`a[href="${url}"]`)).not.toBe(null);
+ expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).not.toBe(null);
+ });
+
+ it('hides link if no external url', () => {
+ props.diffFile.externalUrl = '';
+ props.diffFile.formattedExternalUrl = title;
+
+ vm = mountComponent(Component, props);
+
+ expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).toBe(null);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
new file mode 100644
index 00000000000..1c1edfac68c
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -0,0 +1,88 @@
+import Vue from 'vue';
+import DiffFileComponent from '~/diffs/components/diff_file.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('DiffFile', () => {
+ let vm;
+ const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
+ file: getDiffFileMock(),
+ currentUser: {},
+ }).$mount();
+ });
+
+ describe('template', () => {
+ it('should render component with file header, file content components', () => {
+ const el = vm.$el;
+ const { fileHash, filePath } = diffFileMockData;
+
+ expect(el.id).toEqual(fileHash);
+ expect(el.classList.contains('diff-file')).toEqual(true);
+ expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
+ expect(el.querySelector('.js-file-title')).toBeDefined();
+ expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true);
+ expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
+ expect(el.querySelectorAll('.line_content').length > 5).toEqual(true);
+ });
+
+ describe('collapsed', () => {
+ it('should not have file content', done => {
+ expect(vm.$el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
+ expect(vm.file.collapsed).toEqual(false);
+ vm.file.collapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.diff-content.hidden').length).toEqual(1);
+
+ done();
+ });
+ });
+
+ it('should have collapsed text and link', done => {
+ vm.file.collapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.innerText).toContain('This diff is collapsed');
+ expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1);
+
+ done();
+ });
+ });
+
+ it('should have loading icon while loading a collapsed diffs', done => {
+ vm.file.collapsed = true;
+ vm.isLoadingCollapsedDiff = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.diff-content.loading').length).toEqual(1);
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe('too large diff', () => {
+ it('should have too large warning and blob link', done => {
+ const BLOB_LINK = '/file/view/path';
+ vm.file.tooLarge = true;
+ vm.file.viewPath = BLOB_LINK;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.innerText).toContain(
+ 'This source diff could not be displayed because it is too large',
+ );
+ expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined();
+ expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK) > -1).toEqual(
+ true,
+ );
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
new file mode 100644
index 00000000000..0085a16815a
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
@@ -0,0 +1,115 @@
+import Vue from 'vue';
+import DiffGutterAvatarsComponent from '~/diffs/components/diff_gutter_avatars.vue';
+import { COUNT_OF_AVATARS_IN_GUTTER } from '~/diffs/constants';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+describe('DiffGutterAvatars', () => {
+ let component;
+ const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+
+ beforeEach(() => {
+ component = createComponentWithStore(Vue.extend(DiffGutterAvatarsComponent), store, {
+ discussions: getDiscussionsMockData(),
+ }).$mount();
+ });
+
+ describe('computed', () => {
+ describe('discussionsExpanded', () => {
+ it('should return true when all discussions are expanded', () => {
+ expect(component.discussionsExpanded).toEqual(true);
+ });
+
+ it('should return false when all discussions are not expanded', () => {
+ component.discussions[0].expanded = false;
+ expect(component.discussionsExpanded).toEqual(false);
+ });
+ });
+
+ describe('allDiscussions', () => {
+ it('should return an array of notes', () => {
+ expect(component.allDiscussions).toEqual([...component.discussions[0].notes]);
+ });
+ });
+
+ describe('notesInGutter', () => {
+ it('should return a subset of discussions to show in gutter', () => {
+ expect(component.notesInGutter.length).toEqual(COUNT_OF_AVATARS_IN_GUTTER);
+ expect(component.notesInGutter[0]).toEqual({
+ note: component.discussions[0].notes[0].note,
+ author: component.discussions[0].notes[0].author,
+ });
+ });
+ });
+
+ describe('moreCount', () => {
+ it('should return count of remaining discussions from gutter', () => {
+ expect(component.moreCount).toEqual(2);
+ });
+ });
+
+ describe('moreText', () => {
+ it('should return proper text if moreCount > 0', () => {
+ expect(component.moreText).toEqual('2 more comments');
+ });
+
+ it('should return empty string if there is no discussion', () => {
+ component.discussions = [];
+ expect(component.moreText).toEqual('');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('getTooltipText', () => {
+ it('should return original comment if it is shorter than max length', () => {
+ const note = component.discussions[0].notes[0];
+
+ expect(component.getTooltipText(note)).toEqual('Administrator: comment 1');
+ });
+
+ it('should return truncated version of comment', () => {
+ const note = component.discussions[0].notes[1];
+
+ expect(component.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
+ });
+ });
+
+ describe('toggleDiscussions', () => {
+ it('should toggle all discussions', () => {
+ expect(component.discussions[0].expanded).toEqual(true);
+
+ component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
+ component.discussions = component.$store.state.notes.discussions;
+ component.toggleDiscussions();
+
+ expect(component.discussions[0].expanded).toEqual(false);
+ component.$store.dispatch('setInitialNotes', []);
+ });
+ });
+ });
+
+ describe('template', () => {
+ const buttonSelector = '.js-diff-comment-button';
+ const svgSelector = `${buttonSelector} svg`;
+ const avatarSelector = '.js-diff-comment-avatar';
+ const plusCountSelector = '.js-diff-comment-plus';
+
+ it('should have button to collapse discussions when the discussions expanded', () => {
+ expect(component.$el.querySelector(buttonSelector)).toBeDefined();
+ expect(component.$el.querySelector(svgSelector)).toBeDefined();
+ });
+
+ it('should have user avatars when discussions collapsed', () => {
+ component.discussions[0].expanded = false;
+
+ Vue.nextTick(() => {
+ expect(component.$el.querySelector(buttonSelector)).toBeNull();
+ expect(component.$el.querySelectorAll(avatarSelector).length).toEqual(4);
+ expect(component.$el.querySelector(plusCountSelector)).toBeDefined();
+ expect(component.$el.querySelector(plusCountSelector).textContent).toEqual('+2');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
new file mode 100644
index 00000000000..312a684f4d2
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -0,0 +1,153 @@
+import Vue from 'vue';
+import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import {
+ MATCH_LINE_TYPE,
+ CONTEXT_LINE_TYPE,
+ OLD_NO_NEW_LINE_TYPE,
+ NEW_NO_NEW_LINE_TYPE,
+} from '~/diffs/constants';
+import discussionsMockData from '../mock_data/diff_discussions';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('DiffLineGutterContent', () => {
+ const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+ const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(DiffLineGutterContent);
+ const props = Object.assign({}, options);
+ props.fileHash = getDiffFileMock().fileHash;
+ props.contextLinesPath = '/context/lines/path';
+
+ return createComponentWithStore(cmp, store, props).$mount();
+ };
+ const setDiscussions = component => {
+ component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
+ };
+
+ const resetDiscussions = component => {
+ component.$store.dispatch('setInitialNotes', []);
+ };
+
+ describe('computed', () => {
+ describe('isMatchLine', () => {
+ it('should return true for match line type', () => {
+ const component = createComponent({ lineType: MATCH_LINE_TYPE });
+ expect(component.isMatchLine).toEqual(true);
+ });
+
+ it('should return false for non-match line type', () => {
+ const component = createComponent({ lineType: CONTEXT_LINE_TYPE });
+ expect(component.isMatchLine).toEqual(false);
+ });
+ });
+
+ describe('isContextLine', () => {
+ it('should return true for context line type', () => {
+ const component = createComponent({ lineType: CONTEXT_LINE_TYPE });
+ expect(component.isContextLine).toEqual(true);
+ });
+
+ it('should return false for non-context line type', () => {
+ const component = createComponent({ lineType: MATCH_LINE_TYPE });
+ expect(component.isContextLine).toEqual(false);
+ });
+ });
+
+ describe('isMetaLine', () => {
+ it('should return true for meta line type', () => {
+ const component = createComponent({ lineType: NEW_NO_NEW_LINE_TYPE });
+ expect(component.isMetaLine).toEqual(true);
+
+ const component2 = createComponent({ lineType: OLD_NO_NEW_LINE_TYPE });
+ expect(component2.isMetaLine).toEqual(true);
+ });
+
+ it('should return false for non-meta line type', () => {
+ const component = createComponent({ lineType: MATCH_LINE_TYPE });
+ expect(component.isMetaLine).toEqual(false);
+ });
+ });
+
+ describe('lineHref', () => {
+ it('should prepend # to lineCode', () => {
+ const lineCode = 'LC_42';
+ const component = createComponent({ lineCode });
+ expect(component.lineHref).toEqual(`#${lineCode}`);
+ });
+
+ it('should return # if there is no lineCode', () => {
+ const component = createComponent({ lineCode: null });
+ expect(component.lineHref).toEqual('#');
+ });
+ });
+
+ describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => {
+ it('should return empty array when there is no discussion', () => {
+ const component = createComponent({ lineCode: 'LC_42' });
+ expect(component.discussions).toEqual([]);
+ expect(component.hasDiscussions).toEqual(false);
+ expect(component.shouldShowAvatarsOnGutter).toEqual(false);
+ });
+
+ it('should return discussions for the given lineCode', () => {
+ const lineCode = getDiffFileMock().highlightedDiffLines[1].lineCode;
+ const component = createComponent({ lineCode, showCommentButton: true });
+
+ setDiscussions(component);
+
+ expect(component.discussions).toEqual(getDiscussionsMockData());
+ expect(component.hasDiscussions).toEqual(true);
+ expect(component.shouldShowAvatarsOnGutter).toEqual(true);
+
+ resetDiscussions(component);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render three dots for context lines', () => {
+ const component = createComponent({
+ lineType: MATCH_LINE_TYPE,
+ });
+
+ expect(component.$el.querySelector('span').classList.contains('context-cell')).toEqual(true);
+ expect(component.$el.innerText).toEqual('...');
+ });
+
+ it('should render comment button', () => {
+ const component = createComponent({
+ showCommentButton: true,
+ });
+ Object.defineProperty(component, 'isLoggedIn', {
+ get() {
+ return true;
+ },
+ });
+
+ expect(component.$el.querySelector('.js-add-diff-note-button')).toBeDefined();
+ });
+
+ it('should render line link', () => {
+ const lineNumber = 42;
+ const lineCode = `LC_${lineNumber}`;
+ const component = createComponent({ lineNumber, lineCode });
+ const link = component.$el.querySelector('a');
+
+ expect(link.href.indexOf(`#${lineCode}`) > -1).toEqual(true);
+ expect(link.dataset.linenumber).toEqual(lineNumber.toString());
+ });
+
+ it('should render user avatars', () => {
+ const component = createComponent({
+ showCommentButton: true,
+ lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode,
+ });
+
+ setDiscussions(component);
+ expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined();
+ resetDiscussions(component);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
new file mode 100644
index 00000000000..724d1948214
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
+import store from '~/mr_notes/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('DiffLineNoteForm', () => {
+ let component;
+ let diffFile;
+ let diffLines;
+ const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+
+ beforeEach(() => {
+ diffFile = getDiffFileMock();
+ diffLines = diffFile.highlightedDiffLines;
+
+ component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, {
+ diffFile,
+ diffLines,
+ line: diffLines[0],
+ noteTargetLine: diffLines[0],
+ }).$mount();
+ });
+
+ describe('methods', () => {
+ describe('handleCancelCommentForm', () => {
+ it('should call cancelCommentForm with lineCode', () => {
+ spyOn(component, 'cancelCommentForm');
+ component.handleCancelCommentForm();
+
+ expect(component.cancelCommentForm).toHaveBeenCalledWith({
+ lineCode: diffLines[0].lineCode,
+ });
+ });
+ });
+
+ describe('saveNoteForm', () => {
+ it('should call saveNote action with proper params', done => {
+ let isPromiseCalled = false;
+ const formDataSpy = spyOnDependency(DiffLineNoteForm, 'getNoteFormData').and.returnValue({
+ postData: 1,
+ });
+ const saveNoteSpy = spyOn(component, 'saveNote').and.returnValue(
+ new Promise(() => {
+ isPromiseCalled = true;
+ done();
+ }),
+ );
+
+ component.handleSaveNote('note body');
+
+ expect(formDataSpy).toHaveBeenCalled();
+ expect(saveNoteSpy).toHaveBeenCalled();
+ expect(isPromiseCalled).toEqual(true);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should have note form', () => {
+ const { $el } = component;
+
+ expect($el.querySelector('.js-vue-textarea')).toBeDefined();
+ expect($el.querySelector('.js-vue-issue-save')).toBeDefined();
+ expect($el.querySelector('.js-vue-markdown-field')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/edit_button_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/hidden_files_warning_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
new file mode 100644
index 00000000000..0d5a3576204
--- /dev/null
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -0,0 +1,111 @@
+import Vue from 'vue';
+import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
+import store from '~/mr_notes/stores';
+import * as constants from '~/diffs/constants';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+describe('InlineDiffView', () => {
+ let component;
+ const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+ const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+
+ beforeEach(() => {
+ const diffFile = getDiffFileMock();
+
+ component = createComponentWithStore(Vue.extend(InlineDiffView), store, {
+ diffFile,
+ diffLines: diffFile.highlightedDiffLines,
+ }).$mount();
+ });
+
+ describe('methods', () => {
+ describe('handleMouse', () => {
+ it('should set hoveredLineCode', () => {
+ expect(component.hoveredLineCode).toEqual(null);
+
+ component.handleMouse('lineCode1', true);
+ expect(component.hoveredLineCode).toEqual('lineCode1');
+
+ component.handleMouse('lineCode1', false);
+ expect(component.hoveredLineCode).toEqual(null);
+ });
+ });
+
+ describe('getLineClass', () => {
+ it('should return line class object', () => {
+ const { LINE_HOVER_CLASS_NAME, LINE_UNFOLD_CLASS_NAME } = constants;
+ const { MATCH_LINE_TYPE, NEW_LINE_TYPE } = constants;
+
+ expect(component.getLineClass(component.diffLines[0])).toEqual({
+ [NEW_LINE_TYPE]: NEW_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: false,
+ [LINE_HOVER_CLASS_NAME]: false,
+ });
+
+ component.handleMouse(component.diffLines[0].lineCode, true);
+ Object.defineProperty(component, 'isLoggedIn', {
+ get() {
+ return true;
+ },
+ });
+
+ expect(component.getLineClass(component.diffLines[0])).toEqual({
+ [NEW_LINE_TYPE]: NEW_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: false,
+ [LINE_HOVER_CLASS_NAME]: true,
+ });
+
+ expect(component.getLineClass(component.diffLines[5])).toEqual({
+ [MATCH_LINE_TYPE]: MATCH_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: true,
+ [LINE_HOVER_CLASS_NAME]: false,
+ });
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should have rendered diff lines', () => {
+ const el = component.$el;
+
+ expect(el.querySelectorAll('tr.line_holder').length).toEqual(6);
+ expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2);
+ expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1);
+ expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true);
+ });
+
+ it('should render discussions', done => {
+ const el = component.$el;
+ component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
+
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
+ expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5);
+ expect(el.innerText.indexOf('comment 5') > -1).toEqual(true);
+ component.$store.dispatch('setInitialNotes', []);
+
+ done();
+ });
+ });
+
+ it('should render new discussion forms', done => {
+ const el = component.$el;
+ const lines = getDiffFileMock().highlightedDiffLines;
+
+ component.handleShowCommentForm({ lineCode: lines[0].lineCode });
+ component.handleShowCommentForm({ lineCode: lines[1].lineCode });
+
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.js-vue-markdown-field').length).toEqual(2);
+ expect(el.querySelectorAll('tr')[1].classList.contains('notes_holder')).toEqual(true);
+ expect(el.querySelectorAll('tr')[3].classList.contains('notes_holder')).toEqual(true);
+
+ store.state.diffs.diffLineCommentForms = {};
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js
new file mode 100644
index 00000000000..7237274eb43
--- /dev/null
+++ b/spec/javascripts/diffs/components/no_changes_spec.js
@@ -0,0 +1 @@
+// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
new file mode 100644
index 00000000000..cab533217c0
--- /dev/null
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -0,0 +1,224 @@
+import Vue from 'vue';
+import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
+import store from '~/mr_notes/stores';
+import * as constants from '~/diffs/constants';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+describe('ParallelDiffView', () => {
+ let component;
+ const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+ const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+
+ beforeEach(() => {
+ const diffFile = getDiffFileMock();
+
+ component = createComponentWithStore(Vue.extend(ParallelDiffView), store, {
+ diffFile,
+ diffLines: diffFile.parallelDiffLines,
+ }).$mount();
+ });
+
+ describe('computed', () => {
+ describe('parallelDiffLines', () => {
+ it('should normalize lines for empty cells', () => {
+ expect(component.parallelDiffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE);
+ expect(component.parallelDiffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('hasDiscussion', () => {
+ it('it should return true if there is a discussion either for left or right section', () => {
+ Object.defineProperty(component, 'discussionsByLineCode', {
+ get() {
+ return { line_42: true };
+ },
+ });
+
+ expect(component.hasDiscussion({ left: {}, right: {} })).toEqual(undefined);
+ expect(component.hasDiscussion({ left: { lineCode: 'line_42' }, right: {} })).toEqual(true);
+ expect(component.hasDiscussion({ left: {}, right: { lineCode: 'line_42' } })).toEqual(true);
+ });
+ });
+
+ describe('getClassName', () => {
+ it('should return line class object', () => {
+ const { LINE_HOVER_CLASS_NAME, LINE_UNFOLD_CLASS_NAME } = constants;
+ const { MATCH_LINE_TYPE, NEW_LINE_TYPE, LINE_POSITION_RIGHT } = constants;
+
+ expect(component.getClassName(component.diffLines[1], LINE_POSITION_RIGHT)).toEqual({
+ [NEW_LINE_TYPE]: NEW_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: false,
+ [LINE_HOVER_CLASS_NAME]: false,
+ });
+
+ const eventMock = {
+ target: component.$refs.rightLines[1],
+ };
+
+ component.handleMouse(eventMock, component.diffLines[1], true);
+ Object.defineProperty(component, 'isLoggedIn', {
+ get() {
+ return true;
+ },
+ });
+
+ expect(component.getClassName(component.diffLines[1], LINE_POSITION_RIGHT)).toEqual({
+ [NEW_LINE_TYPE]: NEW_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: false,
+ [LINE_HOVER_CLASS_NAME]: true,
+ });
+
+ expect(component.getClassName(component.diffLines[5], LINE_POSITION_RIGHT)).toEqual({
+ [MATCH_LINE_TYPE]: MATCH_LINE_TYPE,
+ [LINE_UNFOLD_CLASS_NAME]: true,
+ [LINE_HOVER_CLASS_NAME]: false,
+ });
+ });
+ });
+
+ describe('handleMouse', () => {
+ it('should set hovered line code and line section to null when isHover is false', () => {
+ const rightLineEventMock = { target: component.$refs.rightLines[1] };
+ expect(component.hoveredLineCode).toEqual(null);
+ expect(component.hoveredSection).toEqual(null);
+
+ component.handleMouse(rightLineEventMock, null, false);
+ expect(component.hoveredLineCode).toEqual(null);
+ expect(component.hoveredSection).toEqual(null);
+ });
+
+ it('should set hovered line code and line section for right section', () => {
+ const rightLineEventMock = { target: component.$refs.rightLines[1] };
+ component.handleMouse(rightLineEventMock, component.diffLines[1], true);
+ expect(component.hoveredLineCode).toEqual(component.diffLines[1].right.lineCode);
+ expect(component.hoveredSection).toEqual(constants.LINE_POSITION_RIGHT);
+ });
+
+ it('should set hovered line code and line section for left section', () => {
+ const leftLineEventMock = { target: component.$refs.leftLines[2] };
+ component.handleMouse(leftLineEventMock, component.diffLines[2], true);
+ expect(component.hoveredLineCode).toEqual(component.diffLines[2].left.lineCode);
+ expect(component.hoveredSection).toEqual(constants.LINE_POSITION_LEFT);
+ });
+ });
+
+ describe('shouldRenderDiscussions', () => {
+ it('should return true if there is a discussion on left side and it is expanded', () => {
+ const line = { left: { lineCode: 'lineCode1' } };
+ spyOn(component, 'isDiscussionExpanded').and.returnValue(true);
+ Object.defineProperty(component, 'discussionsByLineCode', {
+ get() {
+ return {
+ [line.left.lineCode]: true,
+ };
+ },
+ });
+
+ expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_LEFT)).toEqual(true);
+ expect(component.isDiscussionExpanded).toHaveBeenCalledWith(line.left.lineCode);
+ });
+
+ it('should return false if there is a discussion on left side but it is collapsed', () => {
+ const line = { left: { lineCode: 'lineCode1' } };
+ spyOn(component, 'isDiscussionExpanded').and.returnValue(false);
+ Object.defineProperty(component, 'discussionsByLineCode', {
+ get() {
+ return {
+ [line.left.lineCode]: true,
+ };
+ },
+ });
+
+ expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_LEFT)).toEqual(
+ false,
+ );
+ });
+
+ it('should return false for discussions on the right side if there is no line type', () => {
+ const CUSTOM_RIGHT_LINE_TYPE = 'CUSTOM_RIGHT_LINE_TYPE';
+ const line = { right: { lineCode: 'lineCode1', type: CUSTOM_RIGHT_LINE_TYPE } };
+ spyOn(component, 'isDiscussionExpanded').and.returnValue(true);
+ Object.defineProperty(component, 'discussionsByLineCode', {
+ get() {
+ return {
+ [line.right.lineCode]: true,
+ };
+ },
+ });
+
+ expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_RIGHT)).toEqual(
+ CUSTOM_RIGHT_LINE_TYPE,
+ );
+ });
+ });
+
+ describe('hasAnyExpandedDiscussion', () => {
+ const LINE_CODE_LEFT = 'LINE_CODE_LEFT';
+ const LINE_CODE_RIGHT = 'LINE_CODE_RIGHT';
+
+ it('should return true if there is a discussion either on the left or the right side', () => {
+ const mockLineOne = {
+ right: { lineCode: LINE_CODE_RIGHT },
+ left: {},
+ };
+ const mockLineTwo = {
+ left: { lineCode: LINE_CODE_LEFT },
+ right: {},
+ };
+
+ spyOn(component, 'isDiscussionExpanded').and.callFake(lc => lc === LINE_CODE_RIGHT);
+ expect(component.hasAnyExpandedDiscussion(mockLineOne)).toEqual(true);
+ expect(component.hasAnyExpandedDiscussion(mockLineTwo)).toEqual(false);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should have rendered diff lines', () => {
+ const el = component.$el;
+
+ expect(el.querySelectorAll('tr.line_holder.parallel').length).toEqual(6);
+ expect(el.querySelectorAll('td.empty-cell').length).toEqual(4);
+ expect(el.querySelectorAll('td.line_content.parallel.right-side').length).toEqual(6);
+ expect(el.querySelectorAll('td.line_content.parallel.left-side').length).toEqual(6);
+ expect(el.querySelectorAll('td.match').length).toEqual(4);
+ expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true);
+ });
+
+ it('should render discussions', done => {
+ const el = component.$el;
+ component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
+
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
+ expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5);
+ expect(el.innerText.indexOf('comment 5') > -1).toEqual(true);
+ component.$store.dispatch('setInitialNotes', []);
+
+ done();
+ });
+ });
+
+ it('should render new discussion forms', done => {
+ const el = component.$el;
+ const lines = getDiffFileMock().parallelDiffLines;
+
+ component.handleShowCommentForm({ lineCode: lines[0].lineCode });
+ component.handleShowCommentForm({ lineCode: lines[1].lineCode });
+
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.js-vue-markdown-field').length).toEqual(2);
+ expect(el.querySelectorAll('tr')[1].classList.contains('notes_holder')).toEqual(true);
+ expect(el.querySelectorAll('tr')[3].classList.contains('notes_holder')).toEqual(true);
+
+ store.state.diffs.diffLineCommentForms = {};
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
new file mode 100644
index 00000000000..41d0dfd8939
--- /dev/null
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -0,0 +1,496 @@
+export default {
+ id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ position: {
+ formatter: {
+ old_line: null,
+ new_line: 2,
+ old_path: 'CHANGELOG',
+ new_path: 'CHANGELOG',
+ base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ },
+ },
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ expanded: true,
+ notes: [
+ {
+ id: 1749,
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-03T21:06:21.521Z',
+ updated_at: '2018-04-08T08:50:41.762Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 1',
+ note_html: '<p dir="auto">comment 1</p>',
+ last_edited_at: '2018-04-08T08:50:41.762Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1749/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1749&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1749',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1749',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: 1753,
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Fatih Acet',
+ username: 'fatihacet',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/fatihacevt',
+ },
+ created_at: '2018-04-08T08:49:35.804Z',
+ updated_at: '2018-04-08T08:50:45.915Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 2 is really long one',
+ note_html: '<p dir="auto">comment 2 is really long one</p>',
+ last_edited_at: '2018-04-08T08:50:45.915Z',
+ last_edited_by: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1753/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1753&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1753',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1753',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: 1754,
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:48.294Z',
+ updated_at: '2018-04-08T08:50:48.294Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 3',
+ note_html: '<p dir="auto">comment 3</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1754/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1754&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1754',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1754',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: 1755,
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:50.911Z',
+ updated_at: '2018-04-08T08:50:50.911Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 4',
+ note_html: '<p dir="auto">comment 4</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1755/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1755&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1755',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1755',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ {
+ id: 1756,
+ type: 'DiffNote',
+ attachment: null,
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ path: '/root',
+ },
+ created_at: '2018-04-08T08:50:53.895Z',
+ updated_at: '2018-04-08T08:50:53.895Z',
+ system: false,
+ noteable_id: 51,
+ noteable_type: 'MergeRequest',
+ noteable_iid: 20,
+ human_access: 'Owner',
+ note: 'comment 5',
+ note_html: '<p dir="auto">comment 5</p>',
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
+ resolved: false,
+ resolvable: true,
+ resolved_by: null,
+ discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8',
+ emoji_awardable: true,
+ award_emoji: [],
+ toggle_award_path: '/gitlab-org/gitlab-test/notes/1756/toggle_award_emoji',
+ report_abuse_path:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1756&user_id=1',
+ path: '/gitlab-org/gitlab-test/notes/1756',
+ noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1756',
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ },
+ ],
+ individual_note: false,
+ resolvable: true,
+ resolved: false,
+ resolve_path:
+ '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve',
+ resolve_with_issue_path:
+ '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20',
+ diff_file: {
+ submodule: false,
+ submodule_link: null,
+ blob: {
+ id: '9e10516ca50788acf18c518a231914a21e5f16f7',
+ path: 'CHANGELOG',
+ name: 'CHANGELOG',
+ mode: '100644',
+ readable_text: true,
+ icon: 'file-text-o',
+ },
+ blob_path: 'CHANGELOG',
+ blob_name: 'CHANGELOG',
+ blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>',
+ file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
+ file_path: 'CHANGELOG',
+ new_file: false,
+ deleted_file: false,
+ renamed_file: false,
+ old_path: 'CHANGELOG',
+ new_path: 'CHANGELOG',
+ mode_changed: false,
+ a_mode: '100644',
+ b_mode: '100644',
+ text: true,
+ added_lines: 2,
+ removed_lines: 0,
+ diff_refs: {
+ base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ },
+ content_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ stored_externally: null,
+ external_storage: null,
+ old_path_html: ['CHANGELOG', 'CHANGELOG'],
+ new_path_html: 'CHANGELOG',
+ context_lines_path:
+ '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
+ highlighted_diff_lines: [
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ old_line: null,
+ new_line: 2,
+ text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ ],
+ parallel_diff_lines: [
+ {
+ left: null,
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: null,
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ old_line: null,
+ new_line: 2,
+ text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ old_line: 1,
+ new_line: 3,
+ text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ old_line: 2,
+ new_line: 4,
+ text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ right: {
+ line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ old_line: 3,
+ new_line: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ meta_data: null,
+ },
+ },
+ {
+ left: {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ right: {
+ line_code: null,
+ type: 'match',
+ old_line: null,
+ new_line: null,
+ text: '',
+ rich_text: '',
+ meta_data: {
+ old_pos: 3,
+ new_pos: 5,
+ },
+ },
+ },
+ ],
+ },
+ diff_discussion: true,
+ truncated_diff_lines:
+ '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
+ image_diff_html:
+ '<div class="image js-replaced-image" data="">\n<div class="two-up view">\n<div class="wrap">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n<div class="wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n</div>\n<div class="swipe view hide">\n<div class="swipe-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="swipe-wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n</div>\n<span class="swipe-bar">\n<span class="top-handle"></span>\n<span class="bottom-handle"></span>\n</span>\n</div>\n</div>\n<div class="onion-skin view hide">\n<div class="onion-skin-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<div class="controls">\n<div class="transparent"></div>\n<div class="drag-track">\n<div class="dragger" style="left: 0px;"></div>\n</div>\n<div class="opaque"></div>\n</div>\n</div>\n</div>\n</div>\n<div class="view-modes hide">\n<ul class="view-modes-menu">\n<li class="two-up" data-mode="two-up">2-up</li>\n<li class="swipe" data-mode="swipe">Swipe</li>\n<li class="onion-skin" data-mode="onion-skin">Onion skin</li>\n</ul>\n</div>\n',
+};
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
new file mode 100644
index 00000000000..d3bf9525924
--- /dev/null
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -0,0 +1,220 @@
+export default {
+ submodule: false,
+ submoduleLink: null,
+ blob: {
+ id: '9e10516ca50788acf18c518a231914a21e5f16f7',
+ path: 'CHANGELOG',
+ name: 'CHANGELOG',
+ mode: '100644',
+ readableText: true,
+ icon: 'file-text-o',
+ },
+ blobPath: 'CHANGELOG',
+ blobName: 'CHANGELOG',
+ blobIcon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>',
+ fileHash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
+ filePath: 'CHANGELOG',
+ newFile: false,
+ deletedFile: false,
+ renamedFile: false,
+ oldPath: 'CHANGELOG',
+ newPath: 'CHANGELOG',
+ modeChanged: false,
+ aMode: '100644',
+ bMode: '100644',
+ text: true,
+ addedLines: 2,
+ removedLines: 0,
+ diffRefs: {
+ baseSha: 'e63f41fe459e62e1228fcef60d7189127aeba95a',
+ startSha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962',
+ headSha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ },
+ contentSha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13',
+ storedExternally: null,
+ externalStorage: null,
+ oldPathHtml: ['CHANGELOG', 'CHANGELOG'],
+ newPathHtml: 'CHANGELOG',
+ editPath: '/gitlab-org/gitlab-test/edit/spooky-stuff/CHANGELOG',
+ viewPath: '/gitlab-org/gitlab-test/blob/spooky-stuff/CHANGELOG',
+ replacedViewPath: null,
+ collapsed: false,
+ tooLarge: false,
+ contextLinesPath:
+ '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff',
+ highlightedDiffLines: [
+ {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ oldLine: null,
+ newLine: 1,
+ text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ metaData: null,
+ },
+ {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ oldLine: null,
+ newLine: 2,
+ text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
+ richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
+ metaData: null,
+ },
+ {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ oldLine: 1,
+ newLine: 3,
+ text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ metaData: null,
+ },
+ {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ oldLine: 2,
+ newLine: 4,
+ text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
+ richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
+ metaData: null,
+ },
+ {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ oldLine: 3,
+ newLine: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ metaData: null,
+ },
+ {
+ lineCode: null,
+ type: 'match',
+ oldLine: null,
+ newLine: null,
+ text: '',
+ richText: '',
+ metaData: {
+ oldPos: 3,
+ newPos: 5,
+ },
+ },
+ ],
+ parallelDiffLines: [
+ {
+ left: {
+ type: 'empty-cell',
+ },
+ right: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1',
+ type: 'new',
+ oldLine: null,
+ newLine: 1,
+ text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ metaData: null,
+ },
+ },
+ {
+ left: {
+ type: 'empty-cell',
+ },
+ right: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2',
+ type: 'new',
+ oldLine: null,
+ newLine: 2,
+ text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
+ richText: '<span id="LC2" class="line" lang="plaintext"></span>\n',
+ metaData: null,
+ },
+ },
+ {
+ left: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ oldLine: 1,
+ newLine: 3,
+ text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ metaData: null,
+ },
+ right: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ type: null,
+ oldLine: 1,
+ newLine: 3,
+ text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
+ metaData: null,
+ },
+ },
+ {
+ left: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ oldLine: 2,
+ newLine: 4,
+ text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
+ richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ metaData: null,
+ },
+ right: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4',
+ type: null,
+ oldLine: 2,
+ newLine: 4,
+ text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
+ richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
+ metaData: null,
+ },
+ },
+ {
+ left: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ oldLine: 3,
+ newLine: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ metaData: null,
+ },
+ right: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5',
+ type: null,
+ oldLine: 3,
+ newLine: 5,
+ text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
+ metaData: null,
+ },
+ },
+ {
+ left: {
+ lineCode: null,
+ type: 'match',
+ oldLine: null,
+ newLine: null,
+ text: '',
+ richText: '',
+ metaData: {
+ oldPos: 3,
+ newPos: 5,
+ },
+ },
+ right: {
+ lineCode: null,
+ type: 'match',
+ oldLine: null,
+ newLine: null,
+ text: '',
+ richText: '',
+ metaData: {
+ oldPos: 3,
+ newPos: 5,
+ },
+ },
+ },
+ ],
+};
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
new file mode 100644
index 00000000000..e61780c9928
--- /dev/null
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -0,0 +1,210 @@
+import MockAdapter from 'axios-mock-adapter';
+import Cookies from 'js-cookie';
+import {
+ DIFF_VIEW_COOKIE_NAME,
+ INLINE_DIFF_VIEW_TYPE,
+ PARALLEL_DIFF_VIEW_TYPE,
+} from '~/diffs/constants';
+import store from '~/diffs/store';
+import * as actions from '~/diffs/store/actions';
+import * as types from '~/diffs/store/mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import testAction from '../../helpers/vuex_action_helper';
+
+describe('DiffsStoreActions', () => {
+ describe('setEndpoint', () => {
+ it('should set given endpoint', done => {
+ const endpoint = '/diffs/set/endpoint';
+
+ testAction(
+ actions.setEndpoint,
+ endpoint,
+ { endpoint: '' },
+ [{ type: types.SET_ENDPOINT, payload: endpoint }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setLoadingState', () => {
+ it('should set loading state', done => {
+ expect(store.state.diffs.isLoading).toEqual(true);
+ const loadingState = false;
+
+ testAction(
+ actions.setLoadingState,
+ loadingState,
+ {},
+ [{ type: types.SET_LOADING, payload: loadingState }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchDiffFiles', () => {
+ it('should fetch diff files', done => {
+ const endpoint = '/fetch/diff/files';
+ const mock = new MockAdapter(axios);
+ const res = { diff_files: 1, merge_request_diffs: [] };
+ mock.onGet(endpoint).reply(200, res);
+
+ testAction(
+ actions.fetchDiffFiles,
+ {},
+ { endpoint },
+ [
+ { type: types.SET_LOADING, payload: true },
+ { type: types.SET_LOADING, payload: false },
+ { type: types.SET_MERGE_REQUEST_DIFFS, payload: res.merge_request_diffs },
+ { type: types.SET_DIFF_DATA, payload: res },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('setInlineDiffViewType', () => {
+ it('should set diff view type to inline and also set the cookie properly', done => {
+ testAction(
+ actions.setInlineDiffViewType,
+ null,
+ {},
+ [{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }],
+ [],
+ () => {
+ setTimeout(() => {
+ expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE);
+ done();
+ }, 0);
+ },
+ );
+ });
+ });
+
+ describe('setParallelDiffViewType', () => {
+ it('should set diff view type to parallel and also set the cookie properly', done => {
+ testAction(
+ actions.setParallelDiffViewType,
+ null,
+ {},
+ [{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }],
+ [],
+ () => {
+ setTimeout(() => {
+ expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE);
+ done();
+ }, 0);
+ },
+ );
+ });
+ });
+
+ describe('showCommentForm', () => {
+ it('should call mutation to show comment form', done => {
+ const payload = { lineCode: 'lineCode' };
+
+ testAction(
+ actions.showCommentForm,
+ payload,
+ {},
+ [{ type: types.ADD_COMMENT_FORM_LINE, payload }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('cancelCommentForm', () => {
+ it('should call mutation to cancel comment form', done => {
+ const payload = { lineCode: 'lineCode' };
+
+ testAction(
+ actions.cancelCommentForm,
+ payload,
+ {},
+ [{ type: types.REMOVE_COMMENT_FORM_LINE, payload }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('loadMoreLines', () => {
+ it('should call mutation to show comment form', done => {
+ const endpoint = '/diffs/load/more/lines';
+ const params = { since: 6, to: 26 };
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const fileHash = 'ff9200';
+ const options = { endpoint, params, lineNumbers, fileHash };
+ const mock = new MockAdapter(axios);
+ const contextLines = { contextLines: [{ lineCode: 6 }] };
+ mock.onGet(endpoint).reply(200, contextLines);
+
+ testAction(
+ actions.loadMoreLines,
+ options,
+ {},
+ [
+ {
+ type: types.ADD_CONTEXT_LINES,
+ payload: { lineNumbers, contextLines, params, fileHash },
+ },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('loadCollapsedDiff', () => {
+ it('should fetch data and call mutation with response and the give parameter', done => {
+ const file = { hash: 123, loadCollapsedDiffUrl: '/load/collapsed/diff/url' };
+ const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
+ const mock = new MockAdapter(axios);
+ mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
+
+ testAction(
+ actions.loadCollapsedDiff,
+ file,
+ {},
+ [
+ {
+ type: types.ADD_COLLAPSED_DIFFS,
+ payload: { file, data },
+ },
+ ],
+ [],
+ () => {
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('expandAllFiles', () => {
+ it('should change the collapsed prop from the diffFiles', done => {
+ testAction(
+ actions.expandAllFiles,
+ null,
+ {},
+ [
+ {
+ type: types.EXPAND_ALL_FILES,
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
new file mode 100644
index 00000000000..7945ddea911
--- /dev/null
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -0,0 +1,24 @@
+import getters from '~/diffs/store/getters';
+import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+
+describe('DiffsStoreGetters', () => {
+ describe('isParallelView', () => {
+ it('should return true if view set to parallel view', () => {
+ expect(getters.isParallelView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeTruthy();
+ });
+
+ it('should return false if view not to parallel view', () => {
+ expect(getters.isParallelView({ diffViewType: 'foo' })).toBeFalsy();
+ });
+ });
+
+ describe('isInlineView', () => {
+ it('should return true if view set to inline view', () => {
+ expect(getters.isInlineView({ diffViewType: INLINE_DIFF_VIEW_TYPE })).toBeTruthy();
+ });
+
+ it('should return false if view not to inline view', () => {
+ expect(getters.isInlineView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
new file mode 100644
index 00000000000..5f1a6e9def7
--- /dev/null
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -0,0 +1,147 @@
+import mutations from '~/diffs/store/mutations';
+import * as types from '~/diffs/store/mutation_types';
+import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+
+describe('DiffsStoreMutations', () => {
+ describe('SET_ENDPOINT', () => {
+ it('should set endpoint', () => {
+ const state = {};
+ const endpoint = '/diffs/endpoint';
+
+ mutations[types.SET_ENDPOINT](state, endpoint);
+ expect(state.endpoint).toEqual(endpoint);
+ });
+ });
+
+ describe('SET_LOADING', () => {
+ it('should set loading state', () => {
+ const state = {};
+
+ mutations[types.SET_LOADING](state, false);
+ expect(state.isLoading).toEqual(false);
+ });
+ });
+
+ describe('SET_DIFF_FILES', () => {
+ it('should set diff files to state', () => {
+ const filePath = '/first-diff-file-path';
+ const state = {};
+ const diffFiles = {
+ a_mode: 1,
+ highlighted_diff_lines: [{ file_path: filePath }],
+ };
+
+ mutations[types.SET_DIFF_FILES](state, diffFiles);
+ expect(state.diffFiles.aMode).toEqual(1);
+ expect(state.diffFiles.highlightedDiffLines[0].filePath).toEqual(filePath);
+ });
+ });
+
+ describe('SET_DIFF_VIEW_TYPE', () => {
+ it('should set diff view type properly', () => {
+ const state = {};
+
+ mutations[types.SET_DIFF_VIEW_TYPE](state, INLINE_DIFF_VIEW_TYPE);
+ expect(state.diffViewType).toEqual(INLINE_DIFF_VIEW_TYPE);
+ });
+ });
+
+ describe('ADD_COMMENT_FORM_LINE', () => {
+ it('should set a truthy reference for the given line code in diffLineCommentForms', () => {
+ const state = { diffLineCommentForms: {} };
+ const lineCode = 'FDE';
+
+ mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
+ expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
+ });
+ });
+
+ describe('REMOVE_COMMENT_FORM_LINE', () => {
+ it('should remove given reference from diffLineCommentForms', () => {
+ const state = { diffLineCommentForms: {} };
+ const lineCode = 'FDE';
+
+ mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode });
+ expect(state.diffLineCommentForms[lineCode]).toBeTruthy();
+
+ mutations[types.REMOVE_COMMENT_FORM_LINE](state, { lineCode });
+ expect(state.diffLineCommentForms[lineCode]).toBeUndefined();
+ });
+ });
+
+ describe('EXPAND_ALL_FILES', () => {
+ it('should change the collapsed prop from diffFiles', () => {
+ const diffFile = {
+ collapsed: true,
+ };
+ const state = { expandAllFiles: true, diffFiles: [diffFile] };
+
+ mutations[types.EXPAND_ALL_FILES](state);
+ expect(state.diffFiles[0].collapsed).toEqual(false);
+ });
+ });
+
+ describe('ADD_CONTEXT_LINES', () => {
+ it('should call utils.addContextLines with proper params', () => {
+ const options = {
+ lineNumbers: { oldLineNumber: 1, newLineNumber: 2 },
+ contextLines: [{ oldLine: 1 }],
+ fileHash: 'ff9200',
+ params: {
+ bottom: true,
+ },
+ };
+ const diffFile = {
+ fileHash: options.fileHash,
+ highlightedDiffLines: [],
+ parallelDiffLines: [],
+ };
+ const state = { diffFiles: [diffFile] };
+ const lines = [{ oldLine: 1 }];
+
+ const findDiffFileSpy = spyOnDependency(mutations, 'findDiffFile').and.returnValue(diffFile);
+ const removeMatchLineSpy = spyOnDependency(mutations, 'removeMatchLine');
+ const lineRefSpy = spyOnDependency(mutations, 'addLineReferences').and.returnValue(lines);
+ const addContextLinesSpy = spyOnDependency(mutations, 'addContextLines');
+
+ mutations[types.ADD_CONTEXT_LINES](state, options);
+
+ expect(findDiffFileSpy).toHaveBeenCalledWith(state.diffFiles, options.fileHash);
+ expect(removeMatchLineSpy).toHaveBeenCalledWith(
+ diffFile,
+ options.lineNumbers,
+ options.params.bottom,
+ );
+ expect(lineRefSpy).toHaveBeenCalledWith(
+ options.contextLines,
+ options.lineNumbers,
+ options.params.bottom,
+ );
+ expect(addContextLinesSpy).toHaveBeenCalledWith({
+ inlineLines: diffFile.highlightedDiffLines,
+ parallelLines: diffFile.parallelDiffLines,
+ contextLines: options.contextLines,
+ bottom: options.params.bottom,
+ lineNumbers: options.lineNumbers,
+ });
+ });
+ });
+
+ describe('ADD_COLLAPSED_DIFFS', () => {
+ it('should update the state with the given data for the given file hash', () => {
+ const spy = spyOnDependency(mutations, 'convertObjectPropsToCamelCase').and.callThrough();
+
+ const fileHash = 123;
+ const state = { diffFiles: [{}, { fileHash, existingField: 0 }] };
+ const file = { fileHash };
+ const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] };
+
+ mutations[types.ADD_COLLAPSED_DIFFS](state, { file, data });
+ expect(spy).toHaveBeenCalledWith(data, { deep: true });
+
+ expect(state.diffFiles[1].fileHash).toEqual(fileHash);
+ expect(state.diffFiles[1].existingField).toEqual(1);
+ expect(state.diffFiles[1].extraField).toEqual(1);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
new file mode 100644
index 00000000000..5a024a0f2ad
--- /dev/null
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -0,0 +1,179 @@
+import * as utils from '~/diffs/store/utils';
+import {
+ LINE_POSITION_LEFT,
+ LINE_POSITION_RIGHT,
+ TEXT_DIFF_POSITION_TYPE,
+ DIFF_NOTE_TYPE,
+ NEW_LINE_TYPE,
+ OLD_LINE_TYPE,
+ MATCH_LINE_TYPE,
+ PARALLEL_DIFF_VIEW_TYPE,
+} from '~/diffs/constants';
+import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants';
+import diffFileMockData from '../mock_data/diff_file';
+import { noteableDataMock } from '../../notes/mock_data';
+
+const getDiffFileMock = () => Object.assign({}, diffFileMockData);
+
+describe('DiffsStoreUtils', () => {
+ describe('findDiffFile', () => {
+ const files = [{ fileHash: 1, name: 'one' }];
+
+ it('should return correct file', () => {
+ expect(utils.findDiffFile(files, 1).name).toEqual('one');
+ expect(utils.findDiffFile(files, 2)).toBeUndefined();
+ });
+ });
+
+ describe('getReversePosition', () => {
+ it('should return correct line position name', () => {
+ expect(utils.getReversePosition(LINE_POSITION_RIGHT)).toEqual(LINE_POSITION_LEFT);
+ expect(utils.getReversePosition(LINE_POSITION_LEFT)).toEqual(LINE_POSITION_RIGHT);
+ });
+ });
+
+ describe('findIndexInInlineLines and findIndexInParallelLines', () => {
+ const expectSet = (method, lines, invalidLines) => {
+ expect(method(lines, { oldLineNumber: 3, newLineNumber: 5 })).toEqual(4);
+ expect(method(invalidLines || lines, { oldLineNumber: 32, newLineNumber: 53 })).toEqual(-1);
+ };
+
+ describe('findIndexInInlineLines', () => {
+ it('should return correct index for given line numbers', () => {
+ expectSet(utils.findIndexInInlineLines, getDiffFileMock().highlightedDiffLines);
+ });
+ });
+
+ describe('findIndexInParallelLines', () => {
+ it('should return correct index for given line numbers', () => {
+ expectSet(utils.findIndexInParallelLines, getDiffFileMock().parallelDiffLines, {});
+ });
+ });
+ });
+
+ describe('removeMatchLine', () => {
+ it('should remove match line properly by regarding the bottom parameter', () => {
+ const diffFile = getDiffFileMock();
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const inlineIndex = utils.findIndexInInlineLines(diffFile.highlightedDiffLines, lineNumbers);
+ const parallelIndex = utils.findIndexInParallelLines(diffFile.parallelDiffLines, lineNumbers);
+ const atInlineIndex = diffFile.highlightedDiffLines[inlineIndex];
+ const atParallelIndex = diffFile.parallelDiffLines[parallelIndex];
+
+ utils.removeMatchLine(diffFile, lineNumbers, false);
+ expect(diffFile.highlightedDiffLines[inlineIndex]).not.toEqual(atInlineIndex);
+ expect(diffFile.parallelDiffLines[parallelIndex]).not.toEqual(atParallelIndex);
+
+ utils.removeMatchLine(diffFile, lineNumbers, true);
+ expect(diffFile.highlightedDiffLines[inlineIndex + 1]).not.toEqual(atInlineIndex);
+ expect(diffFile.parallelDiffLines[parallelIndex + 1]).not.toEqual(atParallelIndex);
+ });
+ });
+
+ describe('addContextLines', () => {
+ it('should add context lines properly with bottom parameter', () => {
+ const diffFile = getDiffFileMock();
+ const inlineLines = diffFile.highlightedDiffLines;
+ const parallelLines = diffFile.parallelDiffLines;
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
+ const contextLines = [{ lineNumber: 42 }];
+ const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true };
+ const inlineIndex = utils.findIndexInInlineLines(diffFile.highlightedDiffLines, lineNumbers);
+ const parallelIndex = utils.findIndexInParallelLines(diffFile.parallelDiffLines, lineNumbers);
+ const normalizedParallelLine = {
+ left: options.contextLines[0],
+ right: options.contextLines[0],
+ };
+
+ utils.addContextLines(options);
+ expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine);
+
+ delete options.bottom;
+ utils.addContextLines(options);
+ expect(inlineLines[inlineIndex]).toEqual(contextLines[0]);
+ expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine);
+ });
+ });
+
+ describe('getNoteFormData', () => {
+ it('should properly create note form data', () => {
+ const diffFile = getDiffFileMock();
+ noteableDataMock.targetType = MERGE_REQUEST_NOTEABLE_TYPE;
+
+ const options = {
+ note: 'Hello world!',
+ noteableData: noteableDataMock,
+ noteableType: MERGE_REQUEST_NOTEABLE_TYPE,
+ diffFile,
+ noteTargetLine: {
+ lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3',
+ metaData: null,
+ newLine: 3,
+ oldLine: 1,
+ },
+ diffViewType: PARALLEL_DIFF_VIEW_TYPE,
+ linePosition: LINE_POSITION_LEFT,
+ };
+
+ const position = JSON.stringify({
+ base_sha: diffFile.diffRefs.baseSha,
+ start_sha: diffFile.diffRefs.startSha,
+ head_sha: diffFile.diffRefs.headSha,
+ old_path: diffFile.oldPath,
+ new_path: diffFile.newPath,
+ position_type: TEXT_DIFF_POSITION_TYPE,
+ old_line: options.noteTargetLine.oldLine,
+ new_line: options.noteTargetLine.newLine,
+ });
+
+ const postData = {
+ view: options.diffViewType,
+ line_type: options.linePosition === LINE_POSITION_RIGHT ? NEW_LINE_TYPE : OLD_LINE_TYPE,
+ merge_request_diff_head_sha: diffFile.diffRefs.headSha,
+ in_reply_to_discussion_id: '',
+ note_project_id: '',
+ target_type: options.noteableType,
+ target_id: options.noteableData.id,
+ note: {
+ noteable_type: options.noteableType,
+ noteable_id: options.noteableData.id,
+ commit_id: '',
+ type: DIFF_NOTE_TYPE,
+ line_code: options.noteTargetLine.lineCode,
+ note: options.note,
+ position,
+ },
+ };
+
+ expect(utils.getNoteFormData(options)).toEqual({
+ endpoint: options.noteableData.create_note_path,
+ data: postData,
+ });
+ });
+ });
+
+ describe('addLineReferences', () => {
+ const lineNumbers = { oldLineNumber: 3, newLineNumber: 4 };
+
+ it('should add correct line references when bottom set to true', () => {
+ const lines = [{ type: null }, { type: MATCH_LINE_TYPE }];
+ const linesWithReferences = utils.addLineReferences(lines, lineNumbers, true);
+
+ expect(linesWithReferences[0].oldLine).toEqual(lineNumbers.oldLineNumber + 1);
+ expect(linesWithReferences[0].newLine).toEqual(lineNumbers.newLineNumber + 1);
+ expect(linesWithReferences[1].metaData.oldPos).toEqual(4);
+ expect(linesWithReferences[1].metaData.newPos).toEqual(5);
+ });
+
+ it('should add correct line references when bottom falsy', () => {
+ const lines = [{ type: null }, { type: MATCH_LINE_TYPE }, { type: null }];
+ const linesWithReferences = utils.addLineReferences(lines, lineNumbers);
+
+ expect(linesWithReferences[0].oldLine).toEqual(0);
+ expect(linesWithReferences[0].newLine).toEqual(1);
+ expect(linesWithReferences[1].metaData.oldPos).toEqual(2);
+ expect(linesWithReferences[1].metaData.newPos).toEqual(3);
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
new file mode 100644
index 00000000000..351db6ba184
--- /dev/null
+++ b/spec/javascripts/fixtures/commit.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('commit/')
+ end
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'commit/show.html.raw' do |example|
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ }
+
+ get :show, params
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
index fa97f352e31..38fc963caf7 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -7,6 +7,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
+ let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') }
render_views
diff --git a/spec/javascripts/helpers/index.js b/spec/javascripts/helpers/index.js
new file mode 100644
index 00000000000..d2c5caf0bdb
--- /dev/null
+++ b/spec/javascripts/helpers/index.js
@@ -0,0 +1,3 @@
+import mountComponent, { mountComponentWithStore } from './vue_mount_component_helper';
+
+export { mountComponent, mountComponentWithStore };
diff --git a/spec/javascripts/helpers/init_vue_mr_page_helper.js b/spec/javascripts/helpers/init_vue_mr_page_helper.js
new file mode 100644
index 00000000000..921d42a0871
--- /dev/null
+++ b/spec/javascripts/helpers/init_vue_mr_page_helper.js
@@ -0,0 +1,40 @@
+import initMRPage from '~/mr_notes/index';
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import { userDataMock, notesDataMock, noteableDataMock } from '../notes/mock_data';
+import diffFileMockData from '../diffs/mock_data/diff_file';
+
+export default function initVueMRPage() {
+ const diffsAppEndpoint = '/diffs/app/endpoint';
+ const mrEl = document.createElement('div');
+ mrEl.className = 'merge-request fixture-mr';
+ mrEl.setAttribute('data-mr-action', 'diffs');
+ document.body.appendChild(mrEl);
+
+ const mrDiscussionsEl = document.createElement('div');
+ mrDiscussionsEl.id = 'js-vue-mr-discussions';
+ mrDiscussionsEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
+ mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock));
+ mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock));
+ mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request');
+ document.body.appendChild(mrDiscussionsEl);
+
+ const discussionCounterEl = document.createElement('div');
+ discussionCounterEl.id = 'js-vue-discussion-counter';
+ document.body.appendChild(discussionCounterEl);
+
+ const diffsAppEl = document.createElement('div');
+ diffsAppEl.id = 'js-diffs-app';
+ diffsAppEl.setAttribute('data-endpoint', diffsAppEndpoint);
+ diffsAppEl.setAttribute('data-current-user-data', JSON.stringify(userDataMock));
+ document.body.appendChild(diffsAppEl);
+
+ const mock = new MockAdapter(axios);
+ mock.onGet(diffsAppEndpoint).reply(200, {
+ branch_name: 'foo',
+ diff_files: [diffFileMockData],
+ });
+
+ initMRPage();
+ return mock;
+}
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index a9ec7f42a9d..41ff59949e5 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -556,5 +556,75 @@ describe('common_utils', () => {
expect(Object.keys(commonUtils.convertObjectPropsToCamelCase()).length).toBe(0);
expect(Object.keys(commonUtils.convertObjectPropsToCamelCase({})).length).toBe(0);
});
+
+ it('does not deep-convert by default', () => {
+ const obj = {
+ snake_key: {
+ child_snake_key: 'value',
+ },
+ };
+
+ expect(
+ commonUtils.convertObjectPropsToCamelCase(obj),
+ ).toEqual({
+ snakeKey: {
+ child_snake_key: 'value',
+ },
+ });
+ });
+
+ describe('deep: true', () => {
+ it('converts object with child objects', () => {
+ const obj = {
+ snake_key: {
+ child_snake_key: 'value',
+ },
+ };
+
+ expect(
+ commonUtils.convertObjectPropsToCamelCase(obj, { deep: true }),
+ ).toEqual({
+ snakeKey: {
+ childSnakeKey: 'value',
+ },
+ });
+ });
+
+ it('converts array with child objects', () => {
+ const arr = [
+ {
+ child_snake_key: 'value',
+ },
+ ];
+
+ expect(
+ commonUtils.convertObjectPropsToCamelCase(arr, { deep: true }),
+ ).toEqual([
+ {
+ childSnakeKey: 'value',
+ },
+ ]);
+ });
+
+ it('converts array with child arrays', () => {
+ const arr = [
+ [
+ {
+ child_snake_key: 'value',
+ },
+ ],
+ ];
+
+ expect(
+ commonUtils.convertObjectPropsToCamelCase(arr, { deep: true }),
+ ).toEqual([
+ [
+ {
+ childSnakeKey: 'value',
+ },
+ ],
+ ]);
+ });
+ });
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index eab5c24406a..33987574f00 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -96,4 +96,20 @@ describe('text_utility', () => {
expect(textUtils.convertToSentenceCase('Hello World')).toBe('Hello world');
});
});
+
+ describe('truncateSha', () => {
+ it('shortens SHAs to 8 characters', () => {
+ expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
+ });
+
+ it('leaves short SHAs as is', () => {
+ expect(textUtils.truncateSha('shortsha')).toBe('shortsha');
+ });
+ });
+
+ describe('splitCamelCase', () => {
+ it('separates a PascalCase word to two', () => {
+ expect(textUtils.splitCamelCase('HelloWorld')).toBe('Hello World');
+ });
+ });
});
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 7cc5e753c22..0d465510fd3 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -1,4 +1,16 @@
export default {
+ toContainText: () => ({
+ compare(vm, text) {
+ if (!(vm.$el instanceof HTMLElement)) {
+ throw new Error('vm.$el is not a DOM element!');
+ }
+
+ const result = {
+ pass: vm.$el.innerText.includes(text),
+ };
+ return result;
+ },
+ }),
toHaveSpriteIcon: () => ({
compare(element, iconName) {
if (!iconName) {
@@ -10,7 +22,9 @@ export default {
}
const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
- const matchingIcon = iconReferences.find(reference => reference.getAttribute('xlink:href').endsWith(`#${iconName}`));
+ const matchingIcon = iconReferences.find(reference =>
+ reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
+ );
const result = {
pass: !!matchingIcon,
};
@@ -20,7 +34,7 @@ export default {
} else {
result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
- const existingIcons = iconReferences.map((reference) => {
+ const existingIcons = iconReferences.map(reference => {
const iconUrl = reference.getAttribute('xlink:href');
return `"${iconUrl.replace(/^.+#/, '')}"`;
});
@@ -32,4 +46,12 @@ export default {
return result;
},
}),
+ toRender: () => ({
+ compare(vm) {
+ const result = {
+ pass: vm.$el.nodeType !== Node.COMMENT_NODE,
+ };
+ return result;
+ },
+ }),
};
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
deleted file mode 100644
index dc9dc4d4249..00000000000
--- a/spec/javascripts/merge_request_notes_spec.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import $ from 'jquery';
-import _ from 'underscore';
-import 'autosize';
-import '~/gl_form';
-import '~/lib/utils/text_utility';
-import '~/behaviors/markdown/render_gfm';
-import Notes from '~/notes';
-
-const upArrowKeyCode = 38;
-
-describe('Merge request notes', () => {
- window.gon = window.gon || {};
- window.gl = window.gl || {};
- gl.utils = gl.utils || {};
-
- const discussionTabFixture = 'merge_requests/diff_comment.html.raw';
- const changesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
- preloadFixtures(discussionTabFixture, changesTabJsonFixture);
-
- describe('Discussion tab with diff comments', () => {
- beforeEach(() => {
- loadFixtures(discussionTabFixture);
- gl.utils.disableButtonIfEmptyField = _.noop;
- window.project_uploads_path = 'http://test.host/uploads';
- $('body').attr('data-page', 'projects:merge_requests:show');
- window.gon.current_user_id = $('.note:last').data('authorId');
-
- return new Notes('', []);
- });
-
- afterEach(() => {
- // Undo what we did to the shared <body>
- $('body').removeAttr('data-page');
- });
-
- describe('up arrow', () => {
- it('edits last comment when triggered in main form', () => {
- const upArrowEvent = $.Event('keydown');
- upArrowEvent.which = upArrowKeyCode;
-
- spyOnEvent('.note:last .js-note-edit', 'click');
-
- $('.js-note-text').trigger(upArrowEvent);
-
- expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
- });
-
- it('edits last comment in discussion when triggered in discussion form', (done) => {
- const upArrowEvent = $.Event('keydown');
- upArrowEvent.which = upArrowKeyCode;
-
- spyOnEvent('.note-discussion .js-note-edit', 'click');
-
- $('.js-discussion-reply-button').click();
-
- setTimeout(() => {
- expect(
- $('.note-discussion .js-note-text'),
- ).toExist();
-
- $('.note-discussion .js-note-text').trigger(upArrowEvent);
-
- expect('click').toHaveBeenTriggeredOn('.note-discussion .js-note-edit');
-
- done();
- });
- });
- });
- });
-
- describe('Changes tab with diff comments', () => {
- beforeEach(() => {
- const diffsResponse = getJSONFixture(changesTabJsonFixture);
- const noteFormHtml = `<form class="js-new-note-form">
- <textarea class="js-note-text"></textarea>
- </form>`;
- setFixtures(diffsResponse.html + noteFormHtml);
- $('body').attr('data-page', 'projects:merge_requests:show');
- window.gon.current_user_id = $('.note:last').data('authorId');
-
- return new Notes('', []);
- });
-
- afterEach(() => {
- // Undo what we did to the shared <body>
- $('body').removeAttr('data-page');
- });
-
- describe('up arrow', () => {
- it('edits last comment in discussion when triggered in discussion form', (done) => {
- const upArrowEvent = $.Event('keydown');
- upArrowEvent.which = upArrowKeyCode;
-
- spyOnEvent('.note:last .js-note-edit', 'click');
-
- $('.js-discussion-reply-button').trigger('click');
-
- setTimeout(() => {
- $('.js-note-text').trigger(upArrowEvent);
-
- expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
-
- done();
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 3dbd9756cd2..08928e13985 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,5 +1,4 @@
-/* eslint-disable no-var, comma-dangle, object-shorthand */
-
+/* eslint-disable no-var, object-shorthand */
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@@ -7,480 +6,228 @@ import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
-import Diff from '~/diff';
-import Notes from '~/notes';
import 'vendor/jquery.scrollTo';
-
-(function () {
- describe('MergeRequestTabs', function () {
- var stubLocation = {};
- var setLocation = function (stubs) {
- var defaults = {
- pathname: '',
- search: '',
- hash: ''
- };
- $.extend(stubLocation, defaults, stubs || {});
+import initMrPage from './helpers/init_vue_mr_page_helper';
+
+describe('MergeRequestTabs', function() {
+ let mrPageMock;
+ var stubLocation = {};
+ var setLocation = function(stubs) {
+ var defaults = {
+ pathname: '',
+ search: '',
+ hash: '',
};
+ $.extend(stubLocation, defaults, stubs || {});
+ };
- const inlineChangesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
- const parallelChangesTabJsonFixture = 'merge_request_diffs/parallel_changes_tab_with_comments.json';
- preloadFixtures(
- 'merge_requests/merge_request_with_task_list.html.raw',
- 'merge_requests/diff_comment.html.raw',
- inlineChangesTabJsonFixture,
- parallelChangesTabJsonFixture
- );
-
- beforeEach(function () {
- this.class = new MergeRequestTabs({ stubLocation: stubLocation });
- setLocation();
-
- this.spies = {
- history: spyOn(window.history, 'replaceState').and.callFake(function () {})
- };
- });
-
- afterEach(function () {
- this.class.unbindEvents();
- this.class.destroyPipelinesView();
- });
-
- describe('activateTab', function () {
- beforeEach(function () {
- spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
- this.subject = this.class.activateTab;
- });
- it('shows the notes tab when action is show', function () {
- this.subject('show');
- expect($('#notes')).toHaveClass('active');
- });
- it('shows the commits tab when action is commits', function () {
- this.subject('commits');
- expect($('#commits')).toHaveClass('active');
- });
- it('shows the diffs tab when action is diffs', function () {
- this.subject('diffs');
- expect($('#diffs')).toHaveClass('active');
- });
- });
-
- describe('opensInNewTab', function () {
- var tabUrl;
- var windowTarget = '_blank';
-
- beforeEach(function () {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
-
- tabUrl = $('.commits-tab a').attr('href');
+ preloadFixtures(
+ 'merge_requests/merge_request_with_task_list.html.raw',
+ 'merge_requests/diff_comment.html.raw',
+ );
- spyOn($.fn, 'attr').and.returnValue(tabUrl);
- });
-
- describe('meta click', () => {
- let metakeyEvent;
- beforeEach(function () {
- metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
- });
+ beforeEach(function() {
+ mrPageMock = initMrPage();
+ this.class = new MergeRequestTabs({ stubLocation: stubLocation });
+ setLocation();
- it('opens page when commits link is clicked', function () {
- spyOn(window, 'open').and.callFake(function (url, name) {
- expect(url).toEqual(tabUrl);
- expect(name).toEqual(windowTarget);
- });
+ this.spies = {
+ history: spyOn(window.history, 'replaceState').and.callFake(function() {}),
+ };
+ });
- this.class.bindEvents();
- $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
- });
+ afterEach(function() {
+ this.class.unbindEvents();
+ this.class.destroyPipelinesView();
+ mrPageMock.restore();
+ });
- it('opens page when commits badge is clicked', function () {
- spyOn(window, 'open').and.callFake(function (url, name) {
- expect(url).toEqual(tabUrl);
- expect(name).toEqual(windowTarget);
- });
+ describe('opensInNewTab', function() {
+ var tabUrl;
+ var windowTarget = '_blank';
- this.class.bindEvents();
- $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
- });
- });
+ beforeEach(function() {
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
- it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function () {
- spyOn(window, 'open').and.callFake(function (url, name) {
- expect(url).toEqual(tabUrl);
- expect(name).toEqual(windowTarget);
- });
+ tabUrl = $('.commits-tab a').attr('href');
+ });
- this.class.clickTab({
- metaKey: false,
- ctrlKey: true,
- which: 1,
- stopImmediatePropagation: function () {}
- });
+ describe('meta click', () => {
+ let metakeyEvent;
+ beforeEach(function() {
+ metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
});
- 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 when commits link is clicked', function() {
+ spyOn(window, 'open').and.callFake(function(url, name) {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({
- metaKey: true,
- ctrlKey: false,
- which: 1,
- stopImmediatePropagation: function () {}
- });
+ this.class.bindEvents();
+ $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
});
- 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 when commits badge is clicked', function() {
+ spyOn(window, 'open').and.callFake(function(url, name) {
expect(url).toEqual(tabUrl);
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({
- metaKey: false,
- ctrlKey: false,
- which: 2,
- stopImmediatePropagation: function () {}
- });
+ this.class.bindEvents();
+ $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
});
});
- describe('setCurrentAction', function () {
- beforeEach(function () {
- spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- this.subject = this.class.setCurrentAction;
- });
-
- it('changes from commits', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1/commits'
- });
- expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
- expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
- });
-
- it('changes from diffs', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1/diffs'
- });
-
- expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
- expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function() {
+ spyOn(window, 'open').and.callFake(function(url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
});
- it('changes from diffs.html', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1/diffs.html'
- });
- expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
- expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ this.class.clickTab({
+ metaKey: false,
+ ctrlKey: true,
+ which: 1,
+ stopImmediatePropagation: function() {},
});
+ });
- it('changes from notes', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1'
- });
- expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
- expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ it('opens page tab in a new browser tab with Cmd+Click - Mac', function() {
+ spyOn(window, 'open').and.callFake(function(url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
});
- it('includes search parameters and hash string', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1/diffs',
- search: '?view=parallel',
- hash: '#L15-35'
- });
- expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
+ this.class.clickTab({
+ metaKey: true,
+ ctrlKey: false,
+ which: 1,
+ stopImmediatePropagation: function() {},
});
+ });
- it('replaces the current history state', function () {
- var newState;
- setLocation({
- pathname: '/foo/bar/merge_requests/1'
- });
- newState = this.subject('commits');
- expect(this.spies.history).toHaveBeenCalledWith({
- url: newState
- }, document.title, newState);
+ it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() {
+ spyOn(window, 'open').and.callFake(function(url, name) {
+ expect(url).toEqual(tabUrl);
+ expect(name).toEqual(windowTarget);
});
- it('treats "show" like "notes"', function () {
- setLocation({
- pathname: '/foo/bar/merge_requests/1/commits'
- });
- expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+ this.class.clickTab({
+ metaKey: false,
+ ctrlKey: false,
+ which: 2,
+ stopImmediatePropagation: function() {},
});
});
+ });
- describe('tabShown', () => {
- let mock;
+ describe('setCurrentAction', function() {
+ let mock;
- beforeEach(function () {
- mock = new MockAdapter(axios);
- mock.onGet(/(.*)\/diffs\.json/).reply(200, {
- data: { html: '' },
- });
+ beforeEach(function() {
+ mock = new MockAdapter(axios);
+ mock.onAny().reply({ data: {} });
+ this.subject = this.class.setCurrentAction;
+ });
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
- });
+ afterEach(() => {
+ mock.restore();
+ });
- afterEach(() => {
- mock.restore();
+ it('changes from commits', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1/commits',
});
- describe('with "Side-by-side"/parallel diff view', () => {
- beforeEach(function () {
- this.class.diffViewType = () => 'parallel';
- Diff.prototype.diffViewType = () => 'parallel';
- });
-
- it('maintains `container-limited` for pipelines tab', function (done) {
- const asyncClick = function (selector) {
- return new Promise((resolve) => {
- setTimeout(() => {
- document.querySelector(selector).click();
- resolve();
- });
- });
- };
- asyncClick('.merge-request-tabs .pipelines-tab a')
- .then(() => asyncClick('.merge-request-tabs .diffs-tab a'))
- .then(() => asyncClick('.merge-request-tabs .pipelines-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}`);
- });
- });
-
- 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}`);
- });
- });
- });
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
});
- describe('loadDiff', function () {
- beforeEach(() => {
- loadFixtures('merge_requests/diff_comment.html.raw');
- $('body').attr('data-page', 'projects:merge_requests:show');
- window.gl.ImageFile = () => {};
- Notes.initialize('', []);
- spyOn(Notes.instance, 'toggleDiffNote').and.callThrough();
+ it('changes from diffs', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs',
});
- afterEach(() => {
- delete window.gl.ImageFile;
- delete window.notes;
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
- // Undo what we did to the shared <body>
- $('body').removeAttr('data-page');
+ it('changes from diffs.html', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs.html',
});
- it('triggers Ajax request to JSON endpoint', function (done) {
- const url = '/foo/bar/merge_requests/1/diffs';
-
- spyOn(axios, 'get').and.callFake((reqUrl) => {
- expect(reqUrl).toBe(`${url}.json`);
-
- done();
-
- return Promise.resolve({ data: {} });
- });
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
+ expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
- this.class.loadDiff(url);
+ it('changes from notes', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1',
});
- it('triggers scroll event when diff already loaded', function (done) {
- spyOn(axios, 'get').and.callFake(done.fail);
- spyOn(document, 'dispatchEvent');
-
- this.class.diffsLoaded = true;
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+ expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
+ expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
+ });
- expect(
- document.dispatchEvent,
- ).toHaveBeenCalledWith(new CustomEvent('scroll'));
- done();
+ it('includes search parameters and hash string', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs',
+ search: '?view=parallel',
+ hash: '#L15-35',
});
- describe('with inline diff', () => {
- let noteId;
- let noteLineNumId;
- let mock;
-
- beforeEach(() => {
- const diffsResponse = getJSONFixture(inlineChangesTabJsonFixture);
-
- const $html = $(diffsResponse.html);
- noteId = $html.find('.note').attr('id');
- noteLineNumId = $html
- .find('.note')
- .closest('.notes_holder')
- .prev('.line_holder')
- .find('a[data-linenumber]')
- .attr('href')
- .replace('#', '');
-
- mock = new MockAdapter(axios);
- mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
-
- setTimeout(() => {
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'old',
- forceShow: true,
- });
-
- done();
- });
- });
-
- it('should gracefully ignore non-existant fragment hash', function (done) {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
-
- setTimeout(() => {
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
-
- done();
- });
- });
- });
-
- describe('with line number fragment hash', () => {
- it('should gracefully ignore line number fragment hash', function () {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
+ });
- expect(noteLineNumId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
- });
- });
+ it('replaces the current history state', function() {
+ var newState;
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1',
});
+ newState = this.subject('commits');
- describe('with parallel diff', () => {
- let noteId;
- let noteLineNumId;
- let mock;
-
- beforeEach(() => {
- const diffsResponse = getJSONFixture(parallelChangesTabJsonFixture);
-
- const $html = $(diffsResponse.html);
- noteId = $html.find('.note').attr('id');
- noteLineNumId = $html
- .find('.note')
- .closest('.notes_holder')
- .prev('.line_holder')
- .find('a[data-linenumber]')
- .attr('href')
- .replace('#', '');
-
- mock = new MockAdapter(axios);
- mock.onGet(/(.*)\/diffs\.json/).reply(200, diffsResponse);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('with note fragment hash', () => {
- it('should expand and scroll to linked fragment hash #note_xxx', function (done) {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteId);
-
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
-
- setTimeout(() => {
- expect(noteId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).toHaveBeenCalledWith({
- target: jasmine.any(Object),
- lineType: 'new',
- forceShow: true,
- });
-
- done();
- });
- });
-
- it('should gracefully ignore non-existant fragment hash', function (done) {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
-
- setTimeout(() => {
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
- done();
- });
- });
- });
-
- describe('with line number fragment hash', () => {
- it('should gracefully ignore line number fragment hash', function () {
- spyOnDependency(MergeRequestTabs, 'getLocationHash').and.returnValue(noteLineNumId);
- this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+ expect(this.spies.history).toHaveBeenCalledWith(
+ {
+ url: newState,
+ },
+ document.title,
+ newState,
+ );
+ });
- expect(noteLineNumId.length).toBeGreaterThan(0);
- expect(Notes.instance.toggleDiffNote).not.toHaveBeenCalled();
- });
- });
+ it('treats "show" like "notes"', function() {
+ setLocation({
+ pathname: '/foo/bar/merge_requests/1/commits',
});
+
+ expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
});
+ });
- describe('expandViewContainer', function () {
- beforeEach(() => {
- $('body').append('<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>');
- });
+ describe('expandViewContainer', function() {
+ beforeEach(() => {
+ $('body').append(
+ '<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>',
+ );
+ });
- afterEach(() => {
- $('.content-wrapper').remove();
- });
+ afterEach(() => {
+ $('.content-wrapper').remove();
+ });
- it('removes container-limited from containers', function () {
- this.class.expandViewContainer();
+ it('removes container-limited from containers', function() {
+ this.class.expandViewContainer();
- expect($('.content-wrapper')).not.toContainElement('.container-limited');
- });
+ expect($('.content-wrapper')).not.toContainElement('.container-limited');
+ });
- it('does remove container-limited from breadcrumbs', function () {
- $('.container-limited').addClass('breadcrumbs');
- this.class.expandViewContainer();
+ it('does remove container-limited from breadcrumbs', function() {
+ $('.container-limited').addClass('breadcrumbs');
+ this.class.expandViewContainer();
- expect($('.content-wrapper')).toContainElement('.container-limited');
- });
+ expect($('.content-wrapper')).toContainElement('.container-limited');
});
});
-}).call(window);
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index a7d1e4331eb..155c91dcc46 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -1,23 +1,27 @@
import $ from 'jquery';
import Vue from 'vue';
import Autosize from 'autosize';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import CommentForm from '~/notes/components/comment_form.vue';
+import * as constants from '~/notes/constants';
import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_comment_form component', () => {
+ let store;
let vm;
const Component = Vue.extend(CommentForm);
let mountComponent;
beforeEach(() => {
- mountComponent = (noteableType = 'issue') => new Component({
- propsData: {
- noteableType,
- },
- store,
- }).$mount();
+ store = createStore();
+ mountComponent = (noteableType = 'issue') =>
+ new Component({
+ propsData: {
+ noteableType,
+ },
+ store,
+ }).$mount();
});
afterEach(() => {
@@ -34,7 +38,9 @@ describe('issue_comment_form component', () => {
});
it('should render user avatar with link', () => {
- expect(vm.$el.querySelector('.timeline-icon .user-avatar-link').getAttribute('href')).toEqual(userDataMock.path);
+ expect(vm.$el.querySelector('.timeline-icon .user-avatar-link').getAttribute('href')).toEqual(
+ userDataMock.path,
+ );
});
describe('handleSave', () => {
@@ -60,7 +66,7 @@ describe('issue_comment_form component', () => {
expect(vm.toggleIssueState).toHaveBeenCalled();
});
- it('should disable action button whilst submitting', (done) => {
+ it('should disable action button whilst submitting', done => {
const saveNotePromise = Promise.resolve();
vm.note = 'hello world';
spyOn(vm, 'saveNote').and.returnValue(saveNotePromise);
@@ -87,16 +93,18 @@ describe('issue_comment_form component', () => {
).toEqual('Write a comment or drag your files here…');
});
- it('should make textarea disabled while requesting', (done) => {
+ it('should make textarea disabled while requesting', done => {
const $submitButton = $(vm.$el.querySelector('.js-comment-submit-button'));
vm.note = 'hello world';
spyOn(vm, 'stopPolling');
spyOn(vm, 'saveNote').and.returnValue(new Promise(() => {}));
- vm.$nextTick(() => { // Wait for vm.note change triggered. It should enable $submitButton.
+ vm.$nextTick(() => {
+ // Wait for vm.note change triggered. It should enable $submitButton.
$submitButton.trigger('click');
- vm.$nextTick(() => { // Wait for vm.isSubmitting triggered. It should disable textarea.
+ vm.$nextTick(() => {
+ // Wait for vm.isSubmitting triggered. It should disable textarea.
expect(vm.$el.querySelector('.js-main-target-form textarea').disabled).toBeTruthy();
done();
});
@@ -105,21 +113,27 @@ describe('issue_comment_form component', () => {
it('should support quick actions', () => {
expect(
- vm.$el.querySelector('.js-main-target-form textarea').getAttribute('data-supports-quick-actions'),
+ vm.$el
+ .querySelector('.js-main-target-form textarea')
+ .getAttribute('data-supports-quick-actions'),
).toEqual('true');
});
it('should link to markdown docs', () => {
const { markdownDocsPath } = notesDataMock;
- expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual('Markdown');
+ expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual(
+ 'Markdown',
+ );
});
it('should link to quick actions docs', () => {
const { quickActionsDocsPath } = notesDataMock;
- expect(vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim()).toEqual('quick actions');
+ expect(
+ vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim(),
+ ).toEqual('quick actions');
});
- it('should resize textarea after note discarded', (done) => {
+ it('should resize textarea after note discarded', done => {
spyOn(Autosize, 'update');
spyOn(vm, 'discard').and.callThrough();
@@ -136,7 +150,9 @@ describe('issue_comment_form component', () => {
it('should enter edit mode when arrow up is pressed', () => {
spyOn(vm, 'editCurrentUserLastNote').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
- vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(38, true));
+ vm.$el
+ .querySelector('.js-main-target-form textarea')
+ .dispatchEvent(keyboardDownEvent(38, true));
expect(vm.editCurrentUserLastNote).toHaveBeenCalled();
});
@@ -151,7 +167,9 @@ describe('issue_comment_form component', () => {
it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
- vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
+ vm.$el
+ .querySelector('.js-main-target-form textarea')
+ .dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled();
});
@@ -159,7 +177,9 @@ describe('issue_comment_form component', () => {
it('should save note when ctrl+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
- vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+ vm.$el
+ .querySelector('.js-main-target-form textarea')
+ .dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleSave).toHaveBeenCalled();
});
@@ -168,41 +188,51 @@ describe('issue_comment_form component', () => {
describe('actions', () => {
it('should be possible to close the issue', () => {
- expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual('Close issue');
+ expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual(
+ 'Close issue',
+ );
});
it('should render comment button as disabled', () => {
- expect(vm.$el.querySelector('.js-comment-submit-button').getAttribute('disabled')).toEqual('disabled');
+ expect(vm.$el.querySelector('.js-comment-submit-button').getAttribute('disabled')).toEqual(
+ 'disabled',
+ );
});
- it('should enable comment button if it has note', (done) => {
+ it('should enable comment button if it has note', done => {
vm.note = 'Foo';
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-comment-submit-button').getAttribute('disabled')).toEqual(null);
+ expect(
+ vm.$el.querySelector('.js-comment-submit-button').getAttribute('disabled'),
+ ).toEqual(null);
done();
});
});
- it('should update buttons texts when it has note', (done) => {
+ it('should update buttons texts when it has note', done => {
vm.note = 'Foo';
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual('Comment & close issue');
+ expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual(
+ 'Comment & close issue',
+ );
expect(vm.$el.querySelector('.js-note-discard')).toBeDefined();
done();
});
});
- it('updates button text with noteable type', (done) => {
- vm.noteableType = 'merge_request';
+ it('updates button text with noteable type', done => {
+ vm.noteableType = constants.MERGE_REQUEST_NOTEABLE_TYPE;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual('Close merge request');
+ expect(vm.$el.querySelector('.btn-comment-and-close').textContent.trim()).toEqual(
+ 'Close merge request',
+ );
done();
});
});
describe('when clicking close/reopen button', () => {
- it('should disable button and show a loading spinner', (done) => {
+ it('should disable button and show a loading spinner', done => {
const toggleStateButton = vm.$el.querySelector('.js-action-button');
toggleStateButton.click();
@@ -217,7 +247,7 @@ describe('issue_comment_form component', () => {
});
describe('issue is confidential', () => {
- it('shows information warning', (done) => {
+ it('shows information warning', done => {
store.dispatch('setNoteableData', Object.assign(noteableDataMock, { confidential: true }));
Vue.nextTick(() => {
expect(vm.$el.querySelector('.confidential-issue-warning')).toBeDefined();
@@ -237,7 +267,9 @@ describe('issue_comment_form component', () => {
});
it('should render signed out widget', () => {
- expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('Please register or sign in to reply');
+ expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual(
+ 'Please register or sign in to reply',
+ );
});
it('should not render submission form', () => {
diff --git a/spec/javascripts/notes/components/diff_file_header_spec.js b/spec/javascripts/notes/components/diff_file_header_spec.js
deleted file mode 100644
index ef6d513444a..00000000000
--- a/spec/javascripts/notes/components/diff_file_header_spec.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import Vue from 'vue';
-import DiffFileHeader from '~/notes/components/diff_file_header.vue';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const discussionFixture = 'merge_requests/diff_discussion.json';
-
-describe('diff_file_header', () => {
- let vm;
- const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
- const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file);
- const props = {
- diffFile,
- };
- const Component = Vue.extend(DiffFileHeader);
- const selectors = {
- get copyButton() {
- return vm.$el.querySelector('button[data-original-title="Copy file path to clipboard"]');
- },
- get fileName() {
- return vm.$el.querySelector('.file-title-name');
- },
- get titleWrapper() {
- return vm.$refs.titleWrapper;
- },
- };
-
- describe('submodule', () => {
- beforeEach(() => {
- props.diffFile.submodule = true;
- props.diffFile.submoduleLink = '<a href="/bha">Submodule</a>';
-
- vm = mountComponent(Component, props);
- });
-
- it('shows submoduleLink', () => {
- expect(selectors.fileName.innerHTML).toBe(props.diffFile.submoduleLink);
- });
-
- it('has button to copy blob path', () => {
- expect(selectors.copyButton).toExist();
- expect(selectors.copyButton.getAttribute('data-clipboard-text')).toBe(props.diffFile.submoduleLink);
- });
- });
-
- describe('changed file', () => {
- beforeEach(() => {
- props.diffFile.submodule = false;
- props.diffFile.discussionPath = 'some/discussion/id';
-
- vm = mountComponent(Component, props);
- });
-
- it('shows file type icon', () => {
- expect(vm.$el.innerHTML).toContain('fa-file-text-o');
- });
-
- it('links to discussion path', () => {
- expect(selectors.titleWrapper).toExist();
- expect(selectors.titleWrapper.tagName).toBe('A');
- expect(selectors.titleWrapper.getAttribute('href')).toBe(props.diffFile.discussionPath);
- });
-
- it('shows plain title if no link given', () => {
- props.diffFile.discussionPath = undefined;
- vm = mountComponent(Component, props);
-
- expect(selectors.titleWrapper.tagName).not.toBe('A');
- expect(selectors.titleWrapper.href).toBeFalsy();
- });
-
- it('has button to copy file path', () => {
- expect(selectors.copyButton).toExist();
- expect(selectors.copyButton.getAttribute('data-clipboard-text')).toBe(props.diffFile.filePath);
- });
-
- it('shows file mode change', (done) => {
- vm.diffFile = {
- ...props.diffFile,
- modeChanged: true,
- aMode: '100755',
- bMode: '100644',
- };
-
- Vue.nextTick(() => {
- expect(
- vm.$refs.fileMode.textContent.trim(),
- ).toBe('100755 → 100644');
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js
index f4ec7132dbd..239d7950907 100644
--- a/spec/javascripts/notes/components/diff_with_note_spec.js
+++ b/spec/javascripts/notes/components/diff_with_note_spec.js
@@ -1,12 +1,14 @@
import Vue from 'vue';
import DiffWithNote from '~/notes/components/diff_with_note.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import createStore from '~/notes/stores';
+import { mountComponentWithStore } from 'spec/helpers';
const discussionFixture = 'merge_requests/diff_discussion.json';
const imageDiscussionFixture = 'merge_requests/image_diff_discussion.json';
describe('diff_with_note', () => {
+ let store;
let vm;
const diffDiscussionMock = getJSONFixture(discussionFixture)[0];
const diffDiscussion = convertObjectPropsToCamelCase(diffDiscussionMock);
@@ -29,9 +31,21 @@ describe('diff_with_note', () => {
},
};
+ beforeEach(() => {
+ store = createStore();
+ store.replaceState({
+ ...store.state,
+ notes: {
+ noteableData: {
+ current_user: {},
+ },
+ },
+ });
+ });
+
describe('text diff', () => {
beforeEach(() => {
- vm = mountComponent(Component, props);
+ vm = mountComponentWithStore(Component, { props, store });
});
it('shows text diff', () => {
@@ -55,7 +69,7 @@ describe('diff_with_note', () => {
});
it('shows image diff', () => {
- vm = mountComponent(Component, props);
+ vm = mountComponentWithStore(Component, { props, store });
expect(selectors.container).toHaveClass('js-image-file');
expect(selectors.diffTable).not.toExist();
diff --git a/spec/javascripts/notes/components/discussion_counter_spec.js b/spec/javascripts/notes/components/discussion_counter_spec.js
new file mode 100644
index 00000000000..7b2302e6f47
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_counter_spec.js
@@ -0,0 +1,58 @@
+import Vue from 'vue';
+import createStore from '~/notes/stores';
+import DiscussionCounter from '~/notes/components/discussion_counter.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
+
+describe('DiscussionCounter component', () => {
+ let store;
+ let vm;
+
+ beforeEach(() => {
+ window.mrTabs = {};
+
+ const Component = Vue.extend(DiscussionCounter);
+
+ store = createStore();
+ store.dispatch('setNoteableData', noteableDataMock);
+ store.dispatch('setNotesData', notesDataMock);
+
+ vm = createComponentWithStore(Component, store);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('methods', () => {
+ describe('jumpToFirstUnresolvedDiscussion', () => {
+ it('expands unresolved discussion', () => {
+ spyOn(vm, 'expandDiscussion').and.stub();
+ const discussions = [
+ {
+ ...discussionMock,
+ id: discussionMock.id,
+ notes: [{ ...discussionMock.notes[0], resolved: true }],
+ },
+ {
+ ...discussionMock,
+ id: discussionMock.id + 1,
+ notes: [{ ...discussionMock.notes[0], resolved: false }],
+ },
+ ];
+ const firstDiscussionId = discussionMock.id + 1;
+ store.replaceState({
+ ...store.state,
+ discussions,
+ });
+ setFixtures(`
+ <div data-discussion-id="${firstDiscussionId}"></div>
+ `);
+
+ vm.jumpToFirstUnresolvedDiscussion();
+
+ expect(vm.expandDiscussion).toHaveBeenCalledWith({ discussionId: firstDiscussionId });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index c9e549d2096..52cc42cb53d 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -1,14 +1,16 @@
import Vue from 'vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
describe('issue_note_actions component', () => {
let vm;
+ let store;
let Component;
beforeEach(() => {
Component = Vue.extend(noteActions);
+ store = createStore();
});
afterEach(() => {
@@ -27,7 +29,9 @@ describe('issue_note_actions component', () => {
canAwardEmoji: true,
canReportAsAbuse: true,
noteId: 539,
- reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
+ noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1',
+ reportAbusePath:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
};
store.dispatch('setUserData', userDataMock);
@@ -74,7 +78,9 @@ describe('issue_note_actions component', () => {
canAwardEmoji: false,
canReportAsAbuse: false,
noteId: 539,
- reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
+ noteUrl: 'https://localhost:3000/group/project/merge_requests/1#note_1',
+ reportAbusePath:
+ '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
};
vm = new Component({
store,
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index d494c63ff11..7eb4d3aed29 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -3,7 +3,9 @@ import _ from 'underscore';
import Vue from 'vue';
import notesApp from '~/notes/components/notes_app.vue';
import service from '~/notes/services/notes_service';
+import createStore from '~/notes/stores';
import '~/behaviors/markdown/render_gfm';
+import { mountComponentWithStore } from 'spec/helpers';
import * as mockData from '../mock_data';
const vueMatchers = {
@@ -22,6 +24,7 @@ const vueMatchers = {
describe('note_app', () => {
let mountComponent;
let vm;
+ let store;
beforeEach(() => {
jasmine.addMatchers(vueMatchers);
@@ -29,16 +32,18 @@ describe('note_app', () => {
const IssueNotesApp = Vue.extend(notesApp);
- mountComponent = (data) => {
+ store = createStore();
+ mountComponent = data => {
const props = data || {
noteableData: mockData.noteableDataMock,
notesData: mockData.notesDataMock,
userData: mockData.userDataMock,
};
- return new IssueNotesApp({
- propsData: props,
- }).$mount();
+ return mountComponentWithStore(IssueNotesApp, {
+ props,
+ store,
+ });
};
});
@@ -48,9 +53,11 @@ describe('note_app', () => {
describe('set data', () => {
const responseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 200,
- }));
+ next(
+ request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }),
+ );
};
beforeEach(() => {
@@ -74,8 +81,8 @@ describe('note_app', () => {
expect(vm.$store.state.userData).toEqual(mockData.userDataMock);
});
- it('should fetch notes', () => {
- expect(vm.$store.state.notes).toEqual([]);
+ it('should fetch discussions', () => {
+ expect(vm.$store.state.discussions).toEqual([]);
});
});
@@ -89,15 +96,20 @@ describe('note_app', () => {
Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
});
- it('should render list of notes', (done) => {
- const note = mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET['/gitlab-org/gitlab-ce/issues/26/discussions.json'][0].notes[0];
+ it('should render list of notes', done => {
+ const note =
+ mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
+ '/gitlab-org/gitlab-ce/issues/26/discussions.json'
+ ][0].notes[0];
setTimeout(() => {
expect(
vm.$el.querySelector('.main-notes-list .note-header-author-name').textContent.trim(),
).toEqual(note.author.name);
- expect(vm.$el.querySelector('.main-notes-list .note-text').innerHTML).toEqual(note.note_html);
+ expect(vm.$el.querySelector('.main-notes-list .note-text').innerHTML).toEqual(
+ note.note_html,
+ );
done();
}, 0);
});
@@ -110,9 +122,9 @@ describe('note_app', () => {
});
it('should render form comment button as disabled', () => {
- expect(
- vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled'),
- ).toEqual('disabled');
+ expect(vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled')).toEqual(
+ 'disabled',
+ );
});
});
@@ -135,7 +147,7 @@ describe('note_app', () => {
describe('update note', () => {
describe('individual note', () => {
- beforeEach((done) => {
+ beforeEach(done => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
spyOn(service, 'updateNote').and.callThrough();
vm = mountComponent();
@@ -156,7 +168,7 @@ describe('note_app', () => {
expect(vm).toIncludeElement('.js-vue-issue-note-form');
});
- it('calls the service to update the note', (done) => {
+ it('calls the service to update the note', done => {
vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note';
vm.$el.querySelector('.js-vue-issue-save').click();
@@ -169,7 +181,7 @@ describe('note_app', () => {
});
describe('discussion note', () => {
- beforeEach((done) => {
+ beforeEach(done => {
Vue.http.interceptors.push(mockData.discussionNoteInterceptor);
spyOn(service, 'updateNote').and.callThrough();
vm = mountComponent();
@@ -191,7 +203,7 @@ describe('note_app', () => {
expect(vm).toIncludeElement('.js-vue-issue-note-form');
});
- it('updates the note and resets the edit form', (done) => {
+ it('updates the note and resets the edit form', done => {
vm.$el.querySelector('.js-vue-issue-note-form').value = 'this is a note';
vm.$el.querySelector('.js-vue-issue-save').click();
@@ -211,12 +223,16 @@ describe('note_app', () => {
it('should render markdown docs url', () => {
const { markdownDocsPath } = mockData.notesDataMock;
- expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual('Markdown');
+ expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual(
+ 'Markdown',
+ );
});
it('should render quick action docs url', () => {
const { quickActionsDocsPath } = mockData.notesDataMock;
- expect(vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim()).toEqual('quick actions');
+ expect(vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim()).toEqual(
+ 'quick actions',
+ );
});
});
@@ -230,7 +246,7 @@ describe('note_app', () => {
Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
});
- it('should render markdown docs url', (done) => {
+ it('should render markdown docs url', done => {
setTimeout(() => {
vm.$el.querySelector('.js-note-edit').click();
const { markdownDocsPath } = mockData.notesDataMock;
@@ -244,15 +260,15 @@ describe('note_app', () => {
}, 0);
});
- it('should not render quick actions docs url', (done) => {
+ it('should not render quick actions docs url', done => {
setTimeout(() => {
vm.$el.querySelector('.js-note-edit').click();
const { quickActionsDocsPath } = mockData.notesDataMock;
Vue.nextTick(() => {
- expect(
- vm.$el.querySelector(`.edit-note a[href="${quickActionsDocsPath}"]`),
- ).toEqual(null);
+ expect(vm.$el.querySelector(`.edit-note a[href="${quickActionsDocsPath}"]`)).toEqual(
+ null,
+ );
done();
});
}, 0);
diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js
index 1c30d8691b1..9d98ba219da 100644
--- a/spec/javascripts/notes/components/note_awards_list_spec.js
+++ b/spec/javascripts/notes/components/note_awards_list_spec.js
@@ -1,15 +1,17 @@
import Vue from 'vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import awardsNote from '~/notes/components/note_awards_list.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
describe('note_awards_list component', () => {
+ let store;
let vm;
let awardsMock;
beforeEach(() => {
const Component = Vue.extend(awardsNote);
+ store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
awardsMock = [
@@ -41,7 +43,9 @@ describe('note_awards_list component', () => {
it('should render awarded emojis', () => {
expect(vm.$el.querySelector('.js-awards-block button [data-name="flag_tz"]')).toBeDefined();
- expect(vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]')).toBeDefined();
+ expect(
+ vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]'),
+ ).toBeDefined();
});
it('should be possible to remove awarded emoji', () => {
diff --git a/spec/javascripts/notes/components/note_body_spec.js b/spec/javascripts/notes/components/note_body_spec.js
index 4e551496ff0..efad0785afe 100644
--- a/spec/javascripts/notes/components/note_body_spec.js
+++ b/spec/javascripts/notes/components/note_body_spec.js
@@ -1,15 +1,16 @@
-
import Vue from 'vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import noteBody from '~/notes/components/note_body.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note_body component', () => {
+ let store;
let vm;
beforeEach(() => {
const Component = Vue.extend(noteBody);
+ store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
@@ -37,7 +38,7 @@ describe('issue_note_body component', () => {
});
describe('isEditing', () => {
- beforeEach((done) => {
+ beforeEach(done => {
vm.isEditing = true;
Vue.nextTick(done);
});
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 413d4f69434..95d400ab3df 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -1,16 +1,18 @@
import Vue from 'vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import issueNoteForm from '~/notes/components/note_form.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_note_form component', () => {
+ let store;
let vm;
let props;
beforeEach(() => {
const Component = Vue.extend(issueNoteForm);
+ store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
@@ -31,14 +33,18 @@ describe('issue_note_form component', () => {
});
describe('conflicts editing', () => {
- it('should show conflict message if note changes outside the component', (done) => {
+ it('should show conflict message if note changes outside the component', done => {
vm.isEditing = true;
vm.noteBody = 'Foo';
- const message = 'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.';
+ const message =
+ 'This comment has changed since you started editing, please review the updated comment to ensure information is not lost.';
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.js-conflict-edit-warning').textContent.replace(/\s+/g, ' ').trim(),
+ vm.$el
+ .querySelector('.js-conflict-edit-warning')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
).toEqual(message);
done();
});
@@ -47,14 +53,16 @@ describe('issue_note_form component', () => {
describe('form', () => {
it('should render text area with placeholder', () => {
- expect(
- vm.$el.querySelector('textarea').getAttribute('placeholder'),
- ).toEqual('Write a comment or drag your files here…');
+ expect(vm.$el.querySelector('textarea').getAttribute('placeholder')).toEqual(
+ 'Write a comment or drag your files here…',
+ );
});
it('should link to markdown docs', () => {
const { markdownDocsPath } = notesDataMock;
- expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual('Markdown');
+ expect(vm.$el.querySelector(`a[href="${markdownDocsPath}"]`).textContent.trim()).toEqual(
+ 'Markdown',
+ );
});
describe('keyboard events', () => {
@@ -87,7 +95,7 @@ describe('issue_note_form component', () => {
});
describe('actions', () => {
- it('should be possible to cancel', (done) => {
+ it('should be possible to cancel', done => {
spyOn(vm, 'cancelHandler').and.callThrough();
vm.isEditing = true;
@@ -101,7 +109,7 @@ describe('issue_note_form component', () => {
});
});
- it('should be possible to update the note', (done) => {
+ it('should be possible to update the note', done => {
vm.isEditing = true;
Vue.nextTick(() => {
diff --git a/spec/javascripts/notes/components/note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js
index 5636f8d1a9f..a3c6bf78988 100644
--- a/spec/javascripts/notes/components/note_header_spec.js
+++ b/spec/javascripts/notes/components/note_header_spec.js
@@ -1,13 +1,15 @@
import Vue from 'vue';
import noteHeader from '~/notes/components/note_header.vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
describe('note_header component', () => {
+ let store;
let vm;
let Component;
beforeEach(() => {
Component = Vue.extend(noteHeader);
+ store = createStore();
});
afterEach(() => {
@@ -38,12 +40,8 @@ describe('note_header component', () => {
});
it('should render user information', () => {
- expect(
- vm.$el.querySelector('.note-header-author-name').textContent.trim(),
- ).toEqual('Root');
- expect(
- vm.$el.querySelector('.note-header-info a').getAttribute('href'),
- ).toEqual('/root');
+ expect(vm.$el.querySelector('.note-header-author-name').textContent.trim()).toEqual('Root');
+ expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual('/root');
});
it('should render timestamp link', () => {
@@ -78,7 +76,7 @@ describe('note_header component', () => {
expect(vm.$el.querySelector('.js-vue-toggle-button')).toBeDefined();
});
- it('emits toggle event on click', (done) => {
+ it('emits toggle event on click', done => {
spyOn(vm, '$emit');
vm.$el.querySelector('.js-vue-toggle-button').click();
@@ -89,24 +87,24 @@ describe('note_header component', () => {
});
});
- it('renders up arrow when open', (done) => {
+ it('renders up arrow when open', done => {
vm.expanded = true;
Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('.js-vue-toggle-button i').classList,
- ).toContain('fa-chevron-up');
+ expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
+ 'fa-chevron-up',
+ );
done();
});
});
- it('renders down arrow when closed', (done) => {
+ it('renders down arrow when closed', done => {
vm.expanded = false;
Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('.js-vue-toggle-button i').classList,
- ).toContain('fa-chevron-down');
+ expect(vm.$el.querySelector('.js-vue-toggle-button i').classList).toContain(
+ 'fa-chevron-down',
+ );
done();
});
});
diff --git a/spec/javascripts/notes/components/note_signed_out_widget_spec.js b/spec/javascripts/notes/components/note_signed_out_widget_spec.js
index 6cba8053888..e217a2caa73 100644
--- a/spec/javascripts/notes/components/note_signed_out_widget_spec.js
+++ b/spec/javascripts/notes/components/note_signed_out_widget_spec.js
@@ -1,13 +1,15 @@
import Vue from 'vue';
import noteSignedOut from '~/notes/components/note_signed_out_widget.vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import { notesDataMock } from '../mock_data';
describe('note_signed_out_widget component', () => {
+ let store;
let vm;
beforeEach(() => {
const Component = Vue.extend(noteSignedOut);
+ store = createStore();
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
@@ -20,18 +22,20 @@ describe('note_signed_out_widget component', () => {
});
it('should render sign in link provided in the store', () => {
- expect(
- vm.$el.querySelector(`a[href="${notesDataMock.newSessionPath}"]`).textContent,
- ).toEqual('sign in');
+ expect(vm.$el.querySelector(`a[href="${notesDataMock.newSessionPath}"]`).textContent).toEqual(
+ 'sign in',
+ );
});
it('should render register link provided in the store', () => {
- expect(
- vm.$el.querySelector(`a[href="${notesDataMock.registerPath}"]`).textContent,
- ).toEqual('register');
+ expect(vm.$el.querySelector(`a[href="${notesDataMock.registerPath}"]`).textContent).toEqual(
+ 'register',
+ );
});
it('should render information text', () => {
- expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('Please register or sign in to reply');
+ expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual(
+ 'Please register or sign in to reply',
+ );
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index cda550760fe..058ddb6202f 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -1,21 +1,24 @@
import Vue from 'vue';
-import store from '~/notes/stores';
-import issueDiscussion from '~/notes/components/noteable_discussion.vue';
+import createStore from '~/notes/stores';
+import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
+import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
-describe('issue_discussion component', () => {
+describe('noteable_discussion component', () => {
+ let store;
let vm;
beforeEach(() => {
- const Component = Vue.extend(issueDiscussion);
+ const Component = Vue.extend(noteableDiscussion);
+ store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
store,
propsData: {
- note: discussionMock,
+ discussion: discussionMock,
},
}).$mount();
});
@@ -55,4 +58,74 @@ describe('issue_discussion component', () => {
).toBeNull();
});
});
+
+ describe('computed', () => {
+ describe('hasMultipleUnresolvedDiscussions', () => {
+ it('is false if there are no unresolved discussions', done => {
+ spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([]);
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('is false if there is one unresolved discussion', done => {
+ spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([discussionMock]);
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.hasMultipleUnresolvedDiscussions).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('is true if there are two unresolved discussions', done => {
+ spyOnProperty(vm, 'unresolvedDiscussions').and.returnValue([{}, {}]);
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.hasMultipleUnresolvedDiscussions).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('jumpToNextDiscussion', () => {
+ it('expands next unresolved discussion', () => {
+ spyOn(vm, 'expandDiscussion').and.stub();
+ const discussions = [
+ discussionMock,
+ {
+ ...discussionMock,
+ id: discussionMock.id + 1,
+ notes: [{ ...discussionMock.notes[0], resolved: true }],
+ },
+ {
+ ...discussionMock,
+ id: discussionMock.id + 2,
+ notes: [{ ...discussionMock.notes[0], resolved: false }],
+ },
+ ];
+ const nextDiscussionId = discussionMock.id + 2;
+ store.replaceState({
+ ...store.state,
+ discussions,
+ });
+ setFixtures(`
+ <div data-discussion-id="${nextDiscussionId}"></div>
+ `);
+
+ vm.jumpToNextDiscussion();
+
+ expect(vm.expandDiscussion).toHaveBeenCalledWith({ discussionId: nextDiscussionId });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index 2ffdec7314d..a31d17cacbb 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,16 +1,18 @@
import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note', () => {
+ let store;
let vm;
beforeEach(() => {
const Component = Vue.extend(issueNote);
+ store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
@@ -27,11 +29,14 @@ describe('issue_note', () => {
});
it('should render user information', () => {
- expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(note.author.avatar_url);
+ expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
+ note.author.avatar_url,
+ );
});
it('should render note header content', () => {
- expect(vm.$el.querySelector('.note-header .note-header-author-name').textContent.trim()).toEqual(note.author.name);
+ const el = vm.$el.querySelector('.note-header .note-header-author-name');
+ expect(el.textContent.trim()).toEqual(note.author.name);
});
it('should render note actions', () => {
@@ -42,7 +47,7 @@ describe('issue_note', () => {
expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html);
});
- it('prevents note preview xss', (done) => {
+ it('prevents note preview xss', done => {
const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`;
const alertSpy = spyOn(window, 'alert');
@@ -58,7 +63,7 @@ describe('issue_note', () => {
});
describe('cancel edit', () => {
- it('restores content of updated note', (done) => {
+ it('restores content of updated note', done => {
const noteBody = 'updated note text';
vm.updateNote = () => Promise.resolve();
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index fa7adc32193..547efa32694 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -51,6 +51,7 @@ export const noteableDataMock = {
time_estimate: 0,
title: '14',
total_time_spent: 0,
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
updated_at: '2017-08-04T09:53:01.226Z',
updated_by_id: 1,
web_url: '/gitlab-org/gitlab-ce/issues/26',
@@ -99,6 +100,8 @@ export const individualNote = {
{ name: 'art', user: { id: 1, name: 'Root', username: 'root' } },
],
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
+ note_url: '/group/project/merge_requests/1#note_1',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390&user_id=1',
path: '/gitlab-org/gitlab-ce/notes/1390',
@@ -157,6 +160,8 @@ export const note = {
},
],
toggle_award_path: '/gitlab-org/gitlab-ce/notes/546/toggle_award_emoji',
+ note_url: '/group/project/merge_requests/1#note_1',
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1',
path: '/gitlab-org/gitlab-ce/notes/546',
@@ -198,6 +203,7 @@ export const discussionMock = {
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
award_emoji: [],
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1395/toggle_award_emoji',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1395&user_id=1',
@@ -244,6 +250,7 @@ export const discussionMock = {
emoji_awardable: true,
award_emoji: [],
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1396/toggle_award_emoji',
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1396&user_id=1',
path: '/gitlab-org/gitlab-ce/notes/1396',
@@ -288,6 +295,7 @@ export const discussionMock = {
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
award_emoji: [],
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1437/toggle_award_emoji',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1437&user_id=1',
@@ -335,6 +343,7 @@ export const loggedOutnoteableData = {
can_create_note: false,
can_update: false,
},
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
create_note_path: '/gitlab-org/gitlab-ce/notes?target_id=98&target_type=issue',
preview_note_path:
'/gitlab-org/gitlab-ce/preview_markdown?quick_actions_target_id=98&quick_actions_target_type=Issue',
@@ -469,6 +478,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
},
},
],
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1390/toggle_award_emoji',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1390\u0026user_id=1',
@@ -513,6 +523,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
emoji_awardable: true,
award_emoji: [],
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1391/toggle_award_emoji',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F26%23note_1391\u0026user_id=1',
@@ -567,6 +578,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true,
award_emoji: [],
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
@@ -618,6 +630,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
emoji_awardable: true,
award_emoji: [],
toggle_award_path: '/gitlab-org/gitlab-ce/notes/1471/toggle_award_emoji',
+ noteable_note_url: '/group/project/merge_requests/1#note_1',
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F29%23note_1471\u0026user_id=1',
path: '/gitlab-org/gitlab-ce/notes/1471',
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 520a25cc5c6..985c2f81ef3 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import testAction from '../../helpers/vuex_action_helper';
import { resetStore } from '../helpers';
import {
@@ -14,6 +14,12 @@ import {
} from '../mock_data';
describe('Actions Notes Store', () => {
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
afterEach(() => {
resetStore(store);
});
@@ -76,7 +82,7 @@ describe('Actions Notes Store', () => {
actions.setInitialNotes,
[individualNote],
{ notes: [] },
- [{ type: 'SET_INITIAL_NOTES', payload: [individualNote] }],
+ [{ type: 'SET_INITIAL_DISCUSSIONS', payload: [individualNote] }],
[],
done,
);
@@ -109,6 +115,19 @@ describe('Actions Notes Store', () => {
});
});
+ describe('expandDiscussion', () => {
+ it('should expand discussion', done => {
+ testAction(
+ actions.expandDiscussion,
+ { discussionId: discussionMock.id },
+ { notes: [discussionMock] },
+ [{ type: 'EXPAND_DISCUSSION', payload: { discussionId: discussionMock.id } }],
+ [],
+ done,
+ );
+ });
+ });
+
describe('async methods', () => {
const interceptor = (request, next) => {
next(
@@ -194,7 +213,14 @@ describe('Actions Notes Store', () => {
});
it('sets issue state as reopened', done => {
- testAction(actions.toggleIssueLocalState, 'reopened', {}, [{ type: 'REOPEN_ISSUE' }], [], done);
+ testAction(
+ actions.toggleIssueLocalState,
+ 'reopened',
+ {},
+ [{ type: 'REOPEN_ISSUE' }],
+ [],
+ done,
+ );
});
});
@@ -239,13 +265,7 @@ describe('Actions Notes Store', () => {
.dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
- expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
- url: jasmine.anything(),
- method: 'get',
- headers: {
- 'X-Last-Fetched-At': undefined,
- },
- });
+ expect(Vue.http.get).toHaveBeenCalled();
expect(store.state.lastFetchedAt).toBe('123456');
jasmine.clock().tick(1500);
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index e5550580bf8..5501e50e97b 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -1,12 +1,18 @@
import * as getters from '~/notes/stores/getters';
-import { notesDataMock, userDataMock, noteableDataMock, individualNote, collapseNotesMock } from '../mock_data';
+import {
+ notesDataMock,
+ userDataMock,
+ noteableDataMock,
+ individualNote,
+ collapseNotesMock,
+} from '../mock_data';
describe('Getters Notes Store', () => {
let state;
beforeEach(() => {
state = {
- notes: [individualNote],
+ discussions: [individualNote],
targetNoteHash: 'hash',
lastFetchedAt: 'timestamp',
@@ -15,15 +21,15 @@ describe('Getters Notes Store', () => {
noteableData: noteableDataMock,
};
});
- describe('notes', () => {
- it('should return all notes in the store', () => {
- expect(getters.notes(state)).toEqual([individualNote]);
+ describe('discussions', () => {
+ it('should return all discussions in the store', () => {
+ expect(getters.discussions(state)).toEqual([individualNote]);
});
});
describe('Collapsed notes', () => {
const stateCollapsedNotes = {
- notes: collapseNotesMock,
+ discussions: collapseNotesMock,
targetNoteHash: 'hash',
lastFetchedAt: 'timestamp',
@@ -33,7 +39,7 @@ describe('Getters Notes Store', () => {
};
it('should return a single system note when a description was updated multiple times', () => {
- expect(getters.notes(stateCollapsedNotes).length).toEqual(1);
+ expect(getters.discussions(stateCollapsedNotes).length).toEqual(1);
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 98f101d6bc5..556a1c244c0 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -1,5 +1,12 @@
import mutations from '~/notes/stores/mutations';
-import { note, discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
+import {
+ note,
+ discussionMock,
+ notesDataMock,
+ userDataMock,
+ noteableDataMock,
+ individualNote,
+} from '../mock_data';
describe('Notes Store mutations', () => {
describe('ADD_NEW_NOTE', () => {
@@ -7,7 +14,7 @@ describe('Notes Store mutations', () => {
let noteData;
beforeEach(() => {
- state = { notes: [] };
+ state = { discussions: [] };
noteData = {
expanded: true,
id: note.discussion_id,
@@ -20,46 +27,60 @@ describe('Notes Store mutations', () => {
it('should add a new note to an array of notes', () => {
expect(state).toEqual({
- notes: [noteData],
+ discussions: [noteData],
});
- expect(state.notes.length).toBe(1);
+ expect(state.discussions.length).toBe(1);
});
it('should not add the same note to the notes array', () => {
mutations.ADD_NEW_NOTE(state, note);
- expect(state.notes.length).toBe(1);
+ expect(state.discussions.length).toBe(1);
});
});
describe('ADD_NEW_REPLY_TO_DISCUSSION', () => {
it('should add a reply to a specific discussion', () => {
- const state = { notes: [discussionMock] };
+ const state = { discussions: [discussionMock] };
const newReply = Object.assign({}, note, { discussion_id: discussionMock.id });
mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply);
- expect(state.notes[0].notes.length).toEqual(4);
+ expect(state.discussions[0].notes.length).toEqual(4);
});
});
describe('DELETE_NOTE', () => {
it('should delete a note ', () => {
- const state = { notes: [discussionMock] };
+ const state = { discussions: [discussionMock] };
const toDelete = discussionMock.notes[0];
const lengthBefore = discussionMock.notes.length;
mutations.DELETE_NOTE(state, toDelete);
- expect(state.notes[0].notes.length).toEqual(lengthBefore - 1);
+ expect(state.discussions[0].notes.length).toEqual(lengthBefore - 1);
+ });
+ });
+
+ describe('EXPAND_DISCUSSION', () => {
+ it('should expand a collapsed discussion', () => {
+ const discussion = Object.assign({}, discussionMock, { expanded: false });
+
+ const state = {
+ discussions: [discussion],
+ };
+
+ mutations.EXPAND_DISCUSSION(state, { discussionId: discussion.id });
+
+ expect(state.discussions[0].expanded).toEqual(true);
});
});
describe('REMOVE_PLACEHOLDER_NOTES', () => {
it('should remove all placeholder notes in indivudal notes and discussion', () => {
const placeholderNote = Object.assign({}, individualNote, { isPlaceholderNote: true });
- const state = { notes: [placeholderNote] };
+ const state = { discussions: [placeholderNote] };
mutations.REMOVE_PLACEHOLDER_NOTES(state);
- expect(state.notes).toEqual([]);
+ expect(state.discussions).toEqual([]);
});
});
@@ -96,26 +117,29 @@ describe('Notes Store mutations', () => {
});
});
- describe('SET_INITIAL_NOTES', () => {
+ describe('SET_INITIAL_DISCUSSIONS', () => {
it('should set the initial notes received', () => {
const state = {
- notes: [],
+ discussions: [],
};
const legacyNote = {
id: 2,
individual_note: true,
- notes: [{
- note: '1',
- }, {
- note: '2',
- }],
+ notes: [
+ {
+ note: '1',
+ },
+ {
+ note: '2',
+ },
+ ],
};
- mutations.SET_INITIAL_NOTES(state, [note, legacyNote]);
- expect(state.notes[0].id).toEqual(note.id);
- expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note);
- expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note);
- expect(state.notes.length).toEqual(3);
+ mutations.SET_INITIAL_DISCUSSIONS(state, [note, legacyNote]);
+ expect(state.discussions[0].id).toEqual(note.id);
+ expect(state.discussions[1].notes[0].note).toBe(legacyNote.notes[0].note);
+ expect(state.discussions[2].notes[0].note).toBe(legacyNote.notes[1].note);
+ expect(state.discussions.length).toEqual(3);
});
});
@@ -144,17 +168,17 @@ describe('Notes Store mutations', () => {
describe('SHOW_PLACEHOLDER_NOTE', () => {
it('should set a placeholder note', () => {
const state = {
- notes: [],
+ discussions: [],
};
mutations.SHOW_PLACEHOLDER_NOTE(state, note);
- expect(state.notes[0].isPlaceholderNote).toEqual(true);
+ expect(state.discussions[0].isPlaceholderNote).toEqual(true);
});
});
describe('TOGGLE_AWARD', () => {
it('should add award if user has not reacted yet', () => {
const state = {
- notes: [note],
+ discussions: [note],
userData: userDataMock,
};
@@ -164,9 +188,9 @@ describe('Notes Store mutations', () => {
};
mutations.TOGGLE_AWARD(state, data);
- const lastIndex = state.notes[0].award_emoji.length - 1;
+ const lastIndex = state.discussions[0].award_emoji.length - 1;
- expect(state.notes[0].award_emoji[lastIndex]).toEqual({
+ expect(state.discussions[0].award_emoji[lastIndex]).toEqual({
name: 'cartwheel',
user: { id: userDataMock.id, name: userDataMock.name, username: userDataMock.username },
});
@@ -174,7 +198,7 @@ describe('Notes Store mutations', () => {
it('should remove award if user already reacted', () => {
const state = {
- notes: [note],
+ discussions: [note],
userData: {
id: 1,
name: 'Administrator',
@@ -187,7 +211,7 @@ describe('Notes Store mutations', () => {
awardName: 'bath_tone3',
};
mutations.TOGGLE_AWARD(state, data);
- expect(state.notes[0].award_emoji.length).toEqual(2);
+ expect(state.discussions[0].award_emoji.length).toEqual(2);
});
});
@@ -196,43 +220,43 @@ describe('Notes Store mutations', () => {
const discussion = Object.assign({}, discussionMock, { expanded: false });
const state = {
- notes: [discussion],
+ discussions: [discussion],
};
mutations.TOGGLE_DISCUSSION(state, { discussionId: discussion.id });
- expect(state.notes[0].expanded).toEqual(true);
+ expect(state.discussions[0].expanded).toEqual(true);
});
it('should close a opened discussion', () => {
const state = {
- notes: [discussionMock],
+ discussions: [discussionMock],
};
mutations.TOGGLE_DISCUSSION(state, { discussionId: discussionMock.id });
- expect(state.notes[0].expanded).toEqual(false);
+ expect(state.discussions[0].expanded).toEqual(false);
});
});
describe('UPDATE_NOTE', () => {
it('should update a note', () => {
const state = {
- notes: [individualNote],
+ discussions: [individualNote],
};
const updated = Object.assign({}, individualNote.notes[0], { note: 'Foo' });
mutations.UPDATE_NOTE(state, updated);
- expect(state.notes[0].notes[0].note).toEqual('Foo');
+ expect(state.discussions[0].notes[0].note).toEqual('Foo');
});
});
describe('CLOSE_ISSUE', () => {
it('should set issue as closed', () => {
const state = {
- notes: [],
+ discussions: [],
targetNoteHash: null,
lastFetchedAt: null,
isToggleStateButtonLoading: false,
@@ -249,7 +273,7 @@ describe('Notes Store mutations', () => {
describe('REOPEN_ISSUE', () => {
it('should set issue as closed', () => {
const state = {
- notes: [],
+ discussions: [],
targetNoteHash: null,
lastFetchedAt: null,
isToggleStateButtonLoading: false,
@@ -266,7 +290,7 @@ describe('Notes Store mutations', () => {
describe('TOGGLE_STATE_BUTTON_LOADING', () => {
it('should set isToggleStateButtonLoading as true', () => {
const state = {
- notes: [],
+ discussions: [],
targetNoteHash: null,
lastFetchedAt: null,
isToggleStateButtonLoading: false,
@@ -281,7 +305,7 @@ describe('Notes Store mutations', () => {
it('should set isToggleStateButtonLoading as false', () => {
const state = {
- notes: [],
+ discussions: [],
targetNoteHash: null,
lastFetchedAt: null,
isToggleStateButtonLoading: true,
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 2854263a25a..faeedae40e9 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -35,11 +35,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Notes', function() {
const FLASH_TYPE_ALERT = 'alert';
const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
- var commentsTemplate = 'merge_requests/merge_request_with_comment.html.raw';
- preloadFixtures(commentsTemplate);
+ var fixture = 'snippets/show.html.raw';
+ preloadFixtures(fixture);
beforeEach(function() {
- loadFixtures(commentsTemplate);
+ loadFixtures(fixture);
gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads';
$('body').attr('data-page', 'projects:merge_requets:show');
@@ -65,16 +65,9 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
let mock;
beforeEach(function() {
- spyOn(axios, 'patch').and.callThrough();
+ spyOn(axios, 'patch').and.callFake(() => new Promise(() => {}));
mock = new MockAdapter(axios);
-
- mock
- .onPatch(
- `${
- gl.TEST_HOST
- }/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
- )
- .reply(200, {});
+ mock.onAny().reply(200, {});
$('.js-comment-button').on('click', function(e) {
e.preventDefault();
@@ -90,26 +83,17 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
$('input[type=checkbox]')
- .attr('checked', true)[1]
+ .attr('checked', true)[0]
.dispatchEvent(changeEvent);
- expect($('.js-task-list-field.original-task-list').val()).toBe(
- '- [x] Task List Item',
- );
+ expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
});
it('submits an ajax request on tasklist:changed', function(done) {
$('.js-task-list-container').trigger('tasklist:changed');
setTimeout(() => {
- expect(axios.patch).toHaveBeenCalledWith(
- `${
- gl.TEST_HOST
- }/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
- {
- note: { note: '' },
- },
- );
+ expect(axios.patch).toHaveBeenCalled();
done();
});
});
@@ -200,9 +184,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
updatedNote.note = 'bar';
this.notes.updateNote(updatedNote, $targetNote);
- expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith(
- $targetNote,
- );
+ expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
expect(this.notes.setupNewNote).toHaveBeenCalled();
done();
@@ -282,10 +264,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
Notes.isNewNote.and.returnValue(true);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
- expect(Notes.animateAppendNote).toHaveBeenCalledWith(
- note.html,
- $notesList,
- );
+ expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
});
});
@@ -300,10 +279,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
Notes.prototype.renderNote.call(notes, note, null, $notesList);
- expect(Notes.animateUpdateNote).toHaveBeenCalledWith(
- note.html,
- $note,
- );
+ expect(Notes.animateUpdateNote).toHaveBeenCalledWith(note.html, $note);
expect(notes.setupNewNote).toHaveBeenCalledWith($newNote);
});
@@ -331,10 +307,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
$notesList.find.and.returnValue($note);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
- expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(
- note,
- $note,
- );
+ expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
});
});
});
@@ -400,10 +373,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
$form.length = 1;
row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
- notes = jasmine.createSpyObj('notes', [
- 'isParallelView',
- 'updateNotesCount',
- ]);
+ notes = jasmine.createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
notes.note_ids = [];
spyOn(Notes, 'isNewNote');
@@ -464,10 +434,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('should call Notes.animateAppendNote', () => {
- expect(Notes.animateAppendNote).toHaveBeenCalledWith(
- note.html,
- discussionContainer,
- );
+ expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, discussionContainer);
});
});
});
@@ -571,9 +538,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
mockNotesPost();
$('.js-comment-button').click();
- expect($notesContainer.find('.note.being-posted').length > 0).toEqual(
- true,
- );
+ expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
});
it('should remove placeholder note when new comment is done posting', done => {
@@ -617,9 +582,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
$('.js-comment-button').click();
setTimeout(() => {
- expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(
- true,
- );
+ expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
done();
});
@@ -734,14 +697,10 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
$('.js-comment-button').click();
- expect(
- $notesContainer.find('.system-note.being-posted').length,
- ).toEqual(1); // Placeholder shown
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
setTimeout(() => {
- expect(
- $notesContainer.find('.system-note.being-posted').length,
- ).toEqual(0); // Placeholder removed
+ expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
done();
});
});
@@ -815,9 +774,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
it('should return form metadata object from form reference', () => {
$form.find('textarea.js-note-text').val(sampleComment);
- const { formData, formContent, formAction } = this.notes.getFormData(
- $form,
- );
+ const { formData, formContent, formAction } = this.notes.getFormData($form);
expect(formData.indexOf(sampleComment) > -1).toBe(true);
expect(formContent).toEqual(sampleComment);
@@ -833,9 +790,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
const { formContent } = this.notes.getFormData($form);
expect(_.escape).toHaveBeenCalledWith(sampleComment);
- expect(formContent).toEqual(
- '&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;',
- );
+ expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
});
});
@@ -845,8 +800,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('should return true when comment begins with a quick action', () => {
- const sampleComment =
- '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeTruthy();
@@ -870,8 +824,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('stripQuickActions', () => {
it('should strip quick actions from the comment which begins with a quick action', () => {
this.notes = new Notes();
- const sampleComment =
- '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('');
@@ -879,8 +832,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
this.notes = new Notes();
- const sampleComment =
- '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
+ const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('Merging this');
@@ -888,8 +840,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
it('should NOT strip string that has slashes within', () => {
this.notes = new Notes();
- const sampleComment =
- 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
+ const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe(sampleComment);
@@ -909,29 +860,21 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
it('should return executing quick action description when note has single quick action', () => {
const sampleComment = '/close';
- expect(
- this.notes.getQuickActionDescription(
- sampleComment,
- availableQuickActions,
- ),
- ).toBe('Applying command to close this issue');
+ expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ 'Applying command to close this issue',
+ );
});
it('should return generic multiple quick action description when note has multiple quick actions', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
- expect(
- this.notes.getQuickActionDescription(
- sampleComment,
- availableQuickActions,
- ),
- ).toBe('Applying multiple commands');
+ expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ 'Applying multiple commands',
+ );
});
it('should return generic quick action description when available quick actions list is not populated', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
- expect(this.notes.getQuickActionDescription(sampleComment)).toBe(
- 'Applying command',
- );
+ expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
});
});
@@ -961,17 +904,11 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
expect($tempNote.attr('id')).toEqual(uniqueId);
expect($tempNote.hasClass('being-posted')).toBeTruthy();
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
- $tempNote
- .find('.timeline-icon > a, .note-header-info > a')
- .each(function() {
- expect($(this).attr('href')).toEqual(`/${currentUsername}`);
- });
- expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(
- currentUserAvatar,
- );
- expect(
- $tempNote.find('.timeline-content').hasClass('discussion'),
- ).toBeFalsy();
+ $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
+ expect($(this).attr('href')).toEqual(`/${currentUsername}`);
+ });
+ expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
+ expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
expect(
$tempNoteHeader
.find('.d-none.d-sm-inline-block')
@@ -1002,9 +939,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
expect($tempNote.prop('nodeName')).toEqual('LI');
- expect(
- $tempNote.find('.timeline-content').hasClass('discussion'),
- ).toBeTruthy();
+ expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
});
it('should return a escaped user name', () => {
@@ -1061,11 +996,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('shows a flash message', () => {
- this.notes.addFlash(
- 'Error message',
- FLASH_TYPE_ALERT,
- this.notes.parentTimeline.get(0),
- );
+ this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
expect($('.flash-alert').is(':visible')).toBeTruthy();
});
@@ -1078,11 +1009,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
});
it('hides visible flash message', () => {
- this.notes.addFlash(
- 'Error message 1',
- FLASH_TYPE_ALERT,
- this.notes.parentTimeline.get(0),
- );
+ this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
this.notes.clearFlash();
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index 68043b91bd0..78d8e9e572e 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -4,7 +4,7 @@ import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
- const buildComponent = (pipeline) => {
+ const buildComponent = pipeline => {
const PipelinesTableRowComponent = Vue.extend(tableRowComp);
return new PipelinesTableRowComponent({
el: document.querySelector('.test-dom-element'),
@@ -52,9 +52,9 @@ describe('Pipelines Table Row', () => {
});
it('should render status text', () => {
- expect(
- component.$el.querySelector('.table-section.commit-link a').textContent,
- ).toContain(pipeline.details.status.text);
+ expect(component.$el.querySelector('.table-section.commit-link a').textContent).toContain(
+ pipeline.details.status.text,
+ );
});
});
@@ -78,11 +78,15 @@ describe('Pipelines Table Row', () => {
describe('when a user is provided', () => {
it('should render user information', () => {
expect(
- component.$el.querySelector('.table-section:nth-child(2) a:nth-child(3)').getAttribute('href'),
+ component.$el
+ .querySelector('.table-section:nth-child(2) a:nth-child(3)')
+ .getAttribute('href'),
).toEqual(pipeline.user.path);
expect(
- component.$el.querySelector('.table-section:nth-child(2) img').getAttribute('data-original-title'),
+ component.$el
+ .querySelector('.table-section:nth-child(2) img')
+ .getAttribute('data-original-title'),
).toEqual(pipeline.user.name);
});
});
@@ -105,7 +109,9 @@ describe('Pipelines Table Row', () => {
}
const commitAuthorLink = commitAuthorElement.getAttribute('href');
- const commitAuthorName = commitAuthorElement.querySelector('img.avatar').getAttribute('data-original-title');
+ const commitAuthorName = commitAuthorElement
+ .querySelector('img.avatar')
+ .getAttribute('data-original-title');
return { commitAuthorElement, commitAuthorLink, commitAuthorName };
};
@@ -145,7 +151,8 @@ describe('Pipelines Table Row', () => {
it('should render an icon for each stage', () => {
expect(
- component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button').length,
+ component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button')
+ .length,
).toEqual(pipeline.details.stages.length);
});
});
@@ -167,7 +174,7 @@ describe('Pipelines Table Row', () => {
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
- eventHub.$on('retryPipeline', (endpoint) => {
+ eventHub.$on('retryPipeline', endpoint => {
expect(endpoint).toEqual('/retry');
});
@@ -176,7 +183,7 @@ describe('Pipelines Table Row', () => {
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
- eventHub.$on('openConfirmationModal', (data) => {
+ eventHub.$once('openConfirmationModal', data => {
expect(data.endpoint).toEqual('/cancel');
expect(data.pipelineId).toEqual(pipeline.id);
});
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index b10d8be6781..a4753ab7cde 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -4,71 +4,102 @@ import ShortcutsIssuable from '~/shortcuts_issuable';
initCopyAsGFM();
-describe('ShortcutsIssuable', function () {
- const fixtureName = 'merge_requests/diff_comment.html.raw';
+const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
+
+describe('ShortcutsIssuable', function() {
+ const fixtureName = 'snippets/show.html.raw';
preloadFixtures(fixtureName);
+
beforeEach(() => {
loadFixtures(fixtureName);
+ $('body').append(
+ `<div class="js-main-target-form">
+ <textare class="js-vue-comment-form"></textare>
+ </div>`,
+ );
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
this.shortcut = new ShortcutsIssuable(true);
});
+
+ afterEach(() => {
+ $(FORM_SELECTOR).remove();
+ });
+
describe('replyWithSelectedText', () => {
// Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
- const stubSelection = (html) => {
+ const stubSelection = html => {
window.gl.utils.getSelectedFragment = () => {
const node = document.createElement('div');
node.innerHTML = html;
+
return node;
};
};
- beforeEach(() => {
- this.selector = '.js-main-target-form #note_note';
- });
describe('with empty selection', () => {
it('does not return an error', () => {
- this.shortcut.replyWithSelectedText(true);
- expect($(this.selector).val()).toBe('');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect($(FORM_SELECTOR).val()).toBe('');
});
+
it('triggers `focus`', () => {
- this.shortcut.replyWithSelectedText(true);
- expect(document.activeElement).toBe(document.querySelector(this.selector));
+ const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect(spy).toHaveBeenCalled();
});
});
+
describe('with any selection', () => {
beforeEach(() => {
stubSelection('<p>Selected text.</p>');
});
+
it('leaves existing input intact', () => {
- $(this.selector).val('This text was already here.');
- expect($(this.selector).val()).toBe('This text was already here.');
- this.shortcut.replyWithSelectedText(true);
- expect($(this.selector).val()).toBe('This text was already here.\n\n> Selected text.\n\n');
+ $(FORM_SELECTOR).val('This text was already here.');
+ expect($(FORM_SELECTOR).val()).toBe('This text was already here.');
+
+ ShortcutsIssuable.replyWithSelectedText(true);
+ expect($(FORM_SELECTOR).val()).toBe('This text was already here.\n\n> Selected text.\n\n');
});
+
it('triggers `input`', () => {
let triggered = false;
- $(this.selector).on('input', () => {
+ $(FORM_SELECTOR).on('input', () => {
triggered = true;
});
- this.shortcut.replyWithSelectedText(true);
+
+ ShortcutsIssuable.replyWithSelectedText(true);
expect(triggered).toBe(true);
});
+
it('triggers `focus`', () => {
- this.shortcut.replyWithSelectedText(true);
- expect(document.activeElement).toBe(document.querySelector(this.selector));
+ const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect(spy).toHaveBeenCalled();
});
});
+
describe('with a one-line selection', () => {
it('quotes the selection', () => {
stubSelection('<p>This text has been selected.</p>');
- this.shortcut.replyWithSelectedText(true);
- expect($(this.selector).val()).toBe('> This text has been selected.\n\n');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
});
});
+
describe('with a multi-line selection', () => {
it('quotes the selected lines as a group', () => {
- stubSelection('<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>');
- this.shortcut.replyWithSelectedText(true);
- expect($(this.selector).val()).toBe('> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n');
+ stubSelection(
+ '<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>',
+ );
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect($(FORM_SELECTOR).val()).toBe(
+ '> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
+ );
});
});
});
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
index ee92295ef5e..94cded7ee37 100644
--- a/spec/javascripts/shortcuts_spec.js
+++ b/spec/javascripts/shortcuts_spec.js
@@ -2,10 +2,11 @@ import $ from 'jquery';
import Shortcuts from '~/shortcuts';
describe('Shortcuts', () => {
- const fixtureName = 'merge_requests/diff_comment.html.raw';
- const createEvent = (type, target) => $.Event(type, {
- target,
- });
+ const fixtureName = 'snippets/show.html.raw';
+ const createEvent = (type, target) =>
+ $.Event(type, {
+ target,
+ });
preloadFixtures(fixtureName);
@@ -21,19 +22,19 @@ describe('Shortcuts', () => {
it('focuses preview button in form', () => {
Shortcuts.toggleMarkdownPreview(
- createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text'),
- ));
+ 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) => {
+ 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'),
- ));
+ 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');
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 994011b262a..2626b439ca6 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -91,7 +91,8 @@ testsContext.keys().forEach(function(path) {
try {
testsContext(path);
} catch (err) {
- console.error('[ERROR] Unable to load spec: ', path);
+ console.log(err);
+ console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
describe('Test bundle', function() {
it(`includes '${path}'`, function() {
expect(err).toBeNull();
diff --git a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js
index ba8ab0b2cd7..7e57c51bf29 100644
--- a/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js
+++ b/spec/javascripts/vue_shared/components/notes/placeholder_note_spec.js
@@ -1,13 +1,15 @@
import Vue from 'vue';
import issuePlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
import { userDataMock } from '../../../notes/mock_data';
describe('issue placeholder system note component', () => {
+ let store;
let vm;
beforeEach(() => {
const Component = Vue.extend(issuePlaceholderNote);
+ store = createStore();
store.dispatch('setUserData', userDataMock);
vm = new Component({
store,
@@ -21,15 +23,23 @@ describe('issue placeholder system note component', () => {
describe('user information', () => {
it('should render user avatar with link', () => {
- expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual(userDataMock.path);
- expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(userDataMock.avatar_url);
+ expect(vm.$el.querySelector('.user-avatar-link').getAttribute('href')).toEqual(
+ userDataMock.path,
+ );
+ expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
+ userDataMock.avatar_url,
+ );
});
});
describe('note content', () => {
it('should render note header information', () => {
- expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual(userDataMock.path);
- expect(vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim()).toEqual(`@${userDataMock.username}`);
+ expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual(
+ userDataMock.path,
+ );
+ expect(
+ vm.$el.querySelector('.note-header-info .note-headline-light').textContent.trim(),
+ ).toEqual(`@${userDataMock.username}`);
});
it('should render note body', () => {
diff --git a/spec/javascripts/vue_shared/components/notes/system_note_spec.js b/spec/javascripts/vue_shared/components/notes/system_note_spec.js
index 36aaf0a6c2e..aa4c9c4c88c 100644
--- a/spec/javascripts/vue_shared/components/notes/system_note_spec.js
+++ b/spec/javascripts/vue_shared/components/notes/system_note_spec.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import issueSystemNote from '~/vue_shared/components/notes/system_note.vue';
-import store from '~/notes/stores';
+import createStore from '~/notes/stores';
-describe('issue system note', () => {
+describe('system note component', () => {
let vm;
let props;
@@ -24,6 +24,7 @@ describe('issue system note', () => {
},
};
+ const store = createStore();
store.dispatch('setTargetNoteHash', `note_${props.note.id}`);
const Component = Vue.extend(issueSystemNote);
@@ -49,9 +50,10 @@ describe('issue system note', () => {
expect(vm.$el.querySelector('.timeline-icon svg')).toBeDefined();
});
- it('should render note header component', () => {
- expect(
- vm.$el.querySelector('.system-note-message').innerHTML,
- ).toEqual(props.note.note_html);
+ // Redcarpet Markdown renderer wraps text in `<p>` tags
+ // we need to strip them because they break layout of commit lists in system notes:
+ // https://gitlab.com/gitlab-org/gitlab-ce/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png
+ it('removes wrapping paragraph from note HTML', () => {
+ expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('<span>closed</span>');
});
});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 7fe3bd92049..bdeebe0de75 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,11 +1,12 @@
import $ from 'jquery';
-import Mousetrap from 'mousetrap';
import Dropzone from 'dropzone';
+import Mousetrap from 'mousetrap';
import ZenMode from '~/zen_mode';
describe('ZenMode', () => {
let zen;
- const fixtureName = 'merge_requests/merge_request_with_comment.html.raw';
+ let dropzoneForElementSpy;
+ const fixtureName = 'snippets/show.html.raw';
preloadFixtures(fixtureName);
@@ -18,15 +19,17 @@ describe('ZenMode', () => {
}
function escapeKeydown() {
- $('.notes-form textarea').trigger($.Event('keydown', {
- keyCode: 27,
- }));
+ $('.notes-form textarea').trigger(
+ $.Event('keydown', {
+ keyCode: 27,
+ }),
+ );
}
beforeEach(() => {
loadFixtures(fixtureName);
- spyOn(Dropzone, 'forElement').and.callFake(() => ({
+ dropzoneForElementSpy = spyOn(Dropzone, 'forElement').and.callFake(() => ({
enable: () => true,
}));
zen = new ZenMode();
@@ -35,11 +38,29 @@ describe('ZenMode', () => {
zen.scroll_position = 456;
});
+ describe('enabling dropzone', () => {
+ beforeEach(() => {
+ enterZen();
+ });
+
+ it('should not call dropzone if element is not dropzone valid', () => {
+ $('.div-dropzone').addClass('js-invalid-dropzone');
+ exitZen();
+ expect(dropzoneForElementSpy.calls.count()).toEqual(0);
+ });
+
+ it('should call dropzone if element is dropzone valid', () => {
+ $('.div-dropzone').removeClass('js-invalid-dropzone');
+ exitZen();
+ expect(dropzoneForElementSpy.calls.count()).toEqual(2);
+ });
+ });
+
describe('on enter', () => {
it('pauses Mousetrap', () => {
- spyOn(Mousetrap, 'pause');
+ const mouseTrapPauseSpy = spyOn(Mousetrap, 'pause');
enterZen();
- expect(Mousetrap.pause).toHaveBeenCalled();
+ expect(mouseTrapPauseSpy).toHaveBeenCalled();
});
it('removes textarea styling', () => {
@@ -62,9 +83,9 @@ describe('ZenMode', () => {
beforeEach(enterZen);
it('unpauses Mousetrap', () => {
- spyOn(Mousetrap, 'unpause');
+ const mouseTrapUnpauseSpy = spyOn(Mousetrap, 'unpause');
exitZen();
- expect(Mousetrap.unpause).toHaveBeenCalled();
+ expect(mouseTrapUnpauseSpy).toHaveBeenCalled();
});
it('restores the scroll position', () => {
@@ -73,22 +94,4 @@ describe('ZenMode', () => {
expect(zen.scrollTo).toHaveBeenCalled();
});
});
-
- describe('enabling dropzone', () => {
- beforeEach(() => {
- enterZen();
- });
-
- it('should not call dropzone if element is not dropzone valid', () => {
- $('.div-dropzone').addClass('js-invalid-dropzone');
- exitZen();
- expect(Dropzone.forElement).not.toHaveBeenCalled();
- });
-
- it('should call dropzone if element is dropzone valid', () => {
- $('.div-dropzone').removeClass('js-invalid-dropzone');
- exitZen();
- expect(Dropzone.forElement).toHaveBeenCalled();
- });
- });
});