summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Ivan Vargas Lopez <jvargas@gitlab.com>2017-10-06 17:09:40 +0000
committerJose Ivan Vargas Lopez <jvargas@gitlab.com>2017-10-06 17:09:40 +0000
commitbab2cbcc2ae0a0683b0d37212ce0a5f5187fbf3a (patch)
tree997006f03eede948bda57f5d0b6aa1d20b45726e
parentdf5a344df1e854fc518fecb81f548ac644514516 (diff)
parent8915a6cf4c51bdb668398270c716fb1bc90916d2 (diff)
downloadgitlab-ce-bab2cbcc2ae0a0683b0d37212ce0a5f5187fbf3a.tar.gz
Merge branch 'ch-add-specs' into 'issue_35873'
Add javascript specs for image commenting See merge request gitlab-org/gitlab-ce!14730
-rw-r--r--app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js2
-rw-r--r--app/assets/javascripts/image_diff/helpers/dom_helper.js2
-rw-r--r--app/assets/javascripts/image_diff/helpers/utils_helper.js22
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js50
-rw-r--r--app/assets/javascripts/image_diff/replaced_image_diff.js34
-rw-r--r--app/assets/javascripts/image_diff/view_types.js2
-rw-r--r--app/assets/javascripts/lib/utils/image_utility.js4
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/stylesheets/pages/diff.scss3
-rw-r--r--spec/javascripts/image_diff/helpers/badge_helper_spec.js132
-rw-r--r--spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js139
-rw-r--r--spec/javascripts/image_diff/helpers/dom_helper_spec.js118
-rw-r--r--spec/javascripts/image_diff/helpers/utils_helper_spec.js208
-rw-r--r--spec/javascripts/image_diff/image_badge_spec.js84
-rw-r--r--spec/javascripts/image_diff/image_diff_spec.js361
-rw-r--r--spec/javascripts/image_diff/init_discussion_tab_spec.js37
-rw-r--r--spec/javascripts/image_diff/mock_data.js35
-rw-r--r--spec/javascripts/image_diff/replaced_image_diff_spec.js312
-rw-r--r--spec/javascripts/image_diff/view_types_spec.js24
-rw-r--r--spec/javascripts/lib/utils/image_utility_spec.js32
20 files changed, 1544 insertions, 59 deletions
diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
index befea8a878f..05000c73052 100644
--- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
@@ -30,7 +30,7 @@ export function removeCommentIndicator(imageFrameEl) {
commentIndicatorEl.remove();
}
- return Object.assign(meta, {
+ return Object.assign({}, meta, {
removed: willRemove,
});
}
diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js
index 547ce745b2a..0923e27a7b8 100644
--- a/app/assets/javascripts/image_diff/helpers/dom_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js
@@ -3,7 +3,7 @@ export function setPositionDataAttribute(el, options) {
// new comment form can use this data for ajax request
const { x, y, width, height } = options;
const position = el.dataset.position;
- const positionObject = Object.assign(JSON.parse(position), {
+ const positionObject = Object.assign({}, JSON.parse(position), {
x_axis: x,
y_axis: y,
width,
diff --git a/app/assets/javascripts/image_diff/helpers/utils_helper.js b/app/assets/javascripts/image_diff/helpers/utils_helper.js
index d0cd42df073..c9b747f7130 100644
--- a/app/assets/javascripts/image_diff/helpers/utils_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/utils_helper.js
@@ -1,6 +1,7 @@
import ImageBadge from '../image_badge';
import ImageDiff from '../image_diff';
import ReplacedImageDiff from '../replaced_image_diff';
+import '../../commit/image_file';
export function resizeCoordinatesToImageElement(imageEl, meta) {
const { x, y, width, height } = meta;
@@ -76,21 +77,24 @@ export function getTargetSelection(event) {
};
}
-export function initImageDiff(file, canCreateNote, renderCommentBadge) {
+export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) {
const options = {
canCreateNote,
renderCommentBadge,
};
+ let diff;
// ImageFile needs to be invoked before initImageDiff so that badges
// can mount to the correct location
- new gl.ImageFile(file); // eslint-disable-line no-new
-
- if (file.querySelector('.diff-file .js-single-image')) {
- const imageDiff = new ImageDiff(file, options);
- imageDiff.init();
- } else if (file.querySelector('.diff-file .js-replaced-image')) {
- const replacedImageDiff = new ReplacedImageDiff(file, options);
- replacedImageDiff.init();
+ new gl.ImageFile(fileEl); // eslint-disable-line no-new
+
+ if (fileEl.querySelector('.diff-file .js-single-image')) {
+ diff = new ImageDiff(fileEl, options);
+ diff.init();
+ } else if (fileEl.querySelector('.diff-file .js-replaced-image')) {
+ diff = new ReplacedImageDiff(fileEl, options);
+ diff.init();
}
+
+ return diff;
}
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index 64af534eeb4..f3af92cf2b0 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -3,13 +3,10 @@ import ImageBadge from './image_badge';
import { isImageLoaded } from '../lib/utils/image_utility';
export default class ImageDiff {
- constructor(el, {
- canCreateNote = false,
- renderCommentBadge = false,
- }) {
+ constructor(el, options) {
this.el = el;
- this.canCreateNote = canCreateNote;
- this.renderCommentBadge = renderCommentBadge;
+ this.canCreateNote = !!(options && options.canCreateNote);
+ this.renderCommentBadge = !!(options && options.renderCommentBadge);
this.$noteContainer = $('.note-container', this.el);
this.imageBadges = [];
}
@@ -23,7 +20,7 @@ export default class ImageDiff {
bindEvents() {
this.imageClickedWrapper = this.imageClicked.bind(this);
- this.imageBlurredWrapper = this.imageBlurred.bind(this);
+ this.imageBlurredWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl);
this.addBadgeWrapper = this.addBadge.bind(this);
this.removeBadgeWrapper = this.removeBadge.bind(this);
this.renderBadgesWrapper = this.renderBadges.bind(this);
@@ -56,34 +53,31 @@ export default class ImageDiff {
imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser);
}
- imageBlurred() {
- return imageDiffHelper.removeCommentIndicator(this.imageFrameEl);
- }
-
renderBadges() {
const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes');
+ [...discussionsEls].forEach(this.renderBadge.bind(this));
+ }
- [...discussionsEls].forEach((discussionEl, index) => {
- const imageBadge = imageDiffHelper
- .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
+ renderBadge(discussionEl, index) {
+ const imageBadge = imageDiffHelper
+ .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
- this.imageBadges.push(imageBadge);
+ this.imageBadges.push(imageBadge);
- const options = {
- coordinate: imageBadge.browser,
- noteId: imageBadge.noteId,
- };
+ const options = {
+ coordinate: imageBadge.browser,
+ noteId: imageBadge.noteId,
+ };
- if (this.renderCommentBadge) {
- imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
- } else {
- const numberBadgeOptions = Object.assign(options, {
- badgeText: index + 1,
- });
+ if (this.renderCommentBadge) {
+ imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
+ } else {
+ const numberBadgeOptions = Object.assign({}, options, {
+ badgeText: index + 1,
+ });
- imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
- }
- });
+ imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
+ }
}
addBadge(event) {
diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js
index 5c7e4d5e5a6..4abd13fb472 100644
--- a/app/assets/javascripts/image_diff/replaced_image_diff.js
+++ b/app/assets/javascripts/image_diff/replaced_image_diff.js
@@ -70,21 +70,23 @@ export default class ReplacedImageDiff extends ImageDiff {
// Image_file.js has a fade animation of 200ms for loading the view
// Need to wait an additional 250ms for the images to be displayed
// on window in order to re-normalize their dimensions
- setTimeout(() => {
- // Generate badge coordinates on new view
- this.renderBadges();
-
- // Re-render indicator in new view
- if (indicator.removed) {
- const normalizedIndicator = imageDiffHelper
- .resizeCoordinatesToImageElement(this.imageEl, {
- x: indicator.x,
- y: indicator.y,
- width: indicator.image.width,
- height: indicator.image.height,
- });
- imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
- }
- }, 250);
+ setTimeout(this.renderNewView.bind(this, indicator), 250);
+ }
+
+ renderNewView(indicator) {
+ // Generate badge coordinates on new view
+ this.renderBadges();
+
+ // Re-render indicator in new view
+ if (indicator.removed) {
+ const normalizedIndicator = imageDiffHelper
+ .resizeCoordinatesToImageElement(this.imageEl, {
+ x: indicator.x,
+ y: indicator.y,
+ width: indicator.image.width,
+ height: indicator.image.height,
+ });
+ imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
+ }
}
}
diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js
index 2bcfe05420a..ab0a595571f 100644
--- a/app/assets/javascripts/image_diff/view_types.js
+++ b/app/assets/javascripts/image_diff/view_types.js
@@ -5,5 +5,5 @@ export const viewTypes = {
};
export function isValidViewType(validate) {
- return Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
+ return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
}
diff --git a/app/assets/javascripts/lib/utils/image_utility.js b/app/assets/javascripts/lib/utils/image_utility.js
index f906e595cc7..2977ec821cb 100644
--- a/app/assets/javascripts/lib/utils/image_utility.js
+++ b/app/assets/javascripts/lib/utils/image_utility.js
@@ -1,3 +1,5 @@
/* eslint-disable import/prefer-default-export */
-export const isImageLoaded = element => element.complete && element.naturalHeight !== 0;
+export function isImageLoaded(element) {
+ return element.complete && element.naturalHeight !== 0;
+}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 3935407f661..5898ce65c5b 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1482,7 +1482,7 @@ export default class Notes {
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
- let $diffFile = $form.closest('.diff-file');
+ const $diffFile = $form.closest('.diff-file');
if ($diffFile.length > 0) {
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 45995fb21c7..ffb5fc94475 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -750,7 +750,8 @@
}
}
-.frame .badge {
+.frame .badge,
+.frame .image-comment-badge {
// Center align badges on the frame
transform: translate3d(-50%, -50%, 0);
}
diff --git a/spec/javascripts/image_diff/helpers/badge_helper_spec.js b/spec/javascripts/image_diff/helpers/badge_helper_spec.js
new file mode 100644
index 00000000000..fb9c7e59031
--- /dev/null
+++ b/spec/javascripts/image_diff/helpers/badge_helper_spec.js
@@ -0,0 +1,132 @@
+import * as badgeHelper from '~/image_diff/helpers/badge_helper';
+import * as mockData from '../mock_data';
+
+describe('badge helper', () => {
+ const { coordinate, noteId, badgeText, badgeNumber } = mockData;
+ let containerEl;
+ let buttonEl;
+
+ beforeEach(() => {
+ containerEl = document.createElement('div');
+ });
+
+ describe('createImageBadge', () => {
+ beforeEach(() => {
+ buttonEl = badgeHelper.createImageBadge(noteId, coordinate);
+ });
+
+ it('should create button', () => {
+ expect(buttonEl.tagName).toEqual('BUTTON');
+ expect(buttonEl.getAttribute('type')).toEqual('button');
+ });
+
+ it('should set disabled attribute', () => {
+ expect(buttonEl.hasAttribute('disabled')).toEqual(true);
+ });
+
+ it('should set noteId', () => {
+ expect(buttonEl.dataset.noteId).toEqual(noteId);
+ });
+
+ it('should set coordinate', () => {
+ expect(buttonEl.style.left).toEqual(`${coordinate.x}px`);
+ expect(buttonEl.style.top).toEqual(`${coordinate.y}px`);
+ });
+
+ describe('classNames', () => {
+ it('should set .js-image-badge by default', () => {
+ expect(buttonEl.className).toEqual('js-image-badge');
+ });
+
+ it('should add additional class names if parameter is passed', () => {
+ const classNames = ['first-class', 'second-class'];
+ buttonEl = badgeHelper.createImageBadge(noteId, coordinate, classNames);
+
+ expect(buttonEl.className).toEqual(classNames.concat('js-image-badge').join(' '));
+ });
+ });
+ });
+
+ describe('addImageBadge', () => {
+ beforeEach(() => {
+ badgeHelper.addImageBadge(containerEl, {
+ coordinate,
+ badgeText,
+ noteId,
+ });
+ buttonEl = containerEl.querySelector('button');
+ });
+
+ it('should appends button to container', () => {
+ expect(buttonEl).toBeDefined();
+ });
+
+ it('should set the badge text', () => {
+ expect(buttonEl.innerText).toEqual(badgeText);
+ });
+
+ it('should set the button coordinates', () => {
+ expect(buttonEl.style.left).toEqual(`${coordinate.x}px`);
+ expect(buttonEl.style.top).toEqual(`${coordinate.y}px`);
+ });
+
+ it('should set the button noteId', () => {
+ expect(buttonEl.dataset.noteId).toEqual(noteId);
+ });
+ });
+
+ describe('addImageCommentBadge', () => {
+ beforeEach(() => {
+ badgeHelper.addImageCommentBadge(containerEl, {
+ coordinate,
+ noteId,
+ });
+ buttonEl = containerEl.querySelector('button');
+ });
+
+ it('should append icon button to container', () => {
+ expect(buttonEl).toBeDefined();
+ });
+
+ it('should create icon comment button', () => {
+ const iconEl = buttonEl.querySelector('i');
+ expect(iconEl).toBeDefined();
+ expect(iconEl.classList.contains('fa')).toEqual(true);
+ expect(iconEl.classList.contains('fa-comment-o')).toEqual(true);
+ });
+
+ it('should have .image-comment-badge.inverted in button class', () => {
+ expect(buttonEl.classList.contains('image-comment-badge')).toEqual(true);
+ expect(buttonEl.classList.contains('inverted')).toEqual(true);
+ });
+ });
+
+ describe('addAvatarBadge', () => {
+ let avatarBadgeEl;
+
+ beforeEach(() => {
+ containerEl.innerHTML = `
+ <div id="${noteId}">
+ <div class="badge hidden">
+ </div>
+ </div>
+ `;
+
+ badgeHelper.addAvatarBadge(containerEl, {
+ detail: {
+ noteId,
+ badgeNumber,
+ },
+ });
+ avatarBadgeEl = containerEl.querySelector(`#${noteId} .badge`);
+ });
+
+ it('should update badge number', () => {
+ expect(avatarBadgeEl.innerText).toEqual(badgeNumber.toString());
+ });
+
+ it('should remove hidden class', () => {
+ expect(avatarBadgeEl.classList.contains('hidden')).toEqual(false);
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
new file mode 100644
index 00000000000..a284b981d2a
--- /dev/null
+++ b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
@@ -0,0 +1,139 @@
+import * as commentIndicatorHelper from '~/image_diff/helpers/comment_indicator_helper';
+import * as mockData from '../mock_data';
+
+describe('commentIndicatorHelper', () => {
+ const { coordinate } = mockData;
+ let containerEl;
+
+ beforeEach(() => {
+ containerEl = document.createElement('div');
+ });
+
+ describe('addCommentIndicator', () => {
+ let buttonEl;
+
+ beforeEach(() => {
+ commentIndicatorHelper.addCommentIndicator(containerEl, coordinate);
+ buttonEl = containerEl.querySelector('button');
+ });
+
+ it('should append button to container', () => {
+ expect(buttonEl).toBeDefined();
+ });
+
+ describe('button', () => {
+ it('should set coordinate', () => {
+ expect(buttonEl.style.left).toEqual(`${coordinate.x}px`);
+ expect(buttonEl.style.top).toEqual(`${coordinate.y}px`);
+ });
+
+ it('should contain image-comment-dark svg', () => {
+ const svgEl = buttonEl.querySelector('svg');
+ expect(svgEl).toBeDefined();
+
+ const svgLink = svgEl.querySelector('use').getAttribute('xlink:href');
+ expect(svgLink.indexOf('image-comment-dark') !== -1).toEqual(true);
+ });
+ });
+ });
+
+ describe('removeCommentIndicator', () => {
+ it('should return removed false if there is no comment-indicator', () => {
+ const result = commentIndicatorHelper.removeCommentIndicator(containerEl);
+ expect(result.removed).toEqual(false);
+ });
+
+ describe('has comment indicator', () => {
+ let result;
+
+ beforeEach(() => {
+ containerEl.innerHTML = `
+ <div class="comment-indicator" style="left:${coordinate.x}px; top: ${coordinate.y}px;">
+ <img src="${gl.TEST_HOST}/image.png">
+ </div>
+ `;
+ result = commentIndicatorHelper.removeCommentIndicator(containerEl);
+ });
+
+ it('should remove comment indicator', () => {
+ expect(containerEl.querySelector('.comment-indicator')).toBeNull();
+ });
+
+ it('should return removed true', () => {
+ expect(result.removed).toEqual(true);
+ });
+
+ it('should return indicator meta', () => {
+ expect(result.x).toEqual(coordinate.x);
+ expect(result.y).toEqual(coordinate.y);
+ expect(result.image).toBeDefined();
+ expect(result.image.width).toBeDefined();
+ expect(result.image.height).toBeDefined();
+ });
+ });
+ });
+
+ describe('showCommentIndicator', () => {
+ describe('commentIndicator exists', () => {
+ beforeEach(() => {
+ containerEl.innerHTML = `
+ <button class="comment-indicator"></button>
+ `;
+ commentIndicatorHelper.showCommentIndicator(containerEl, coordinate);
+ });
+
+ it('should set commentIndicator coordinates', () => {
+ const commentIndicatorEl = containerEl.querySelector('.comment-indicator');
+ expect(commentIndicatorEl.style.left).toEqual(`${coordinate.x}px`);
+ expect(commentIndicatorEl.style.top).toEqual(`${coordinate.y}px`);
+ });
+ });
+
+ describe('commentIndicator does not exist', () => {
+ beforeEach(() => {
+ commentIndicatorHelper.showCommentIndicator(containerEl, coordinate);
+ });
+
+ it('should addCommentIndicator', () => {
+ const buttonEl = containerEl.querySelector('.comment-indicator');
+ expect(buttonEl).toBeDefined();
+ expect(buttonEl.style.left).toEqual(`${coordinate.x}px`);
+ expect(buttonEl.style.top).toEqual(`${coordinate.y}px`);
+ });
+ });
+ });
+
+ describe('commentIndicatorOnClick', () => {
+ let event;
+ let textAreaEl;
+
+ beforeEach(() => {
+ containerEl.innerHTML = `
+ <div class="diff-viewer">
+ <button></button>
+ <div class="note-container">
+ <textarea class="note-textarea"></textarea>
+ </div>
+ </div>
+ `;
+ textAreaEl = containerEl.querySelector('textarea');
+
+ event = {
+ stopPropagation: () => {},
+ currentTarget: containerEl.querySelector('button'),
+ };
+
+ spyOn(event, 'stopPropagation');
+ spyOn(textAreaEl, 'focus');
+ commentIndicatorHelper.commentIndicatorOnClick(event);
+ });
+
+ it('should stopPropagation', () => {
+ expect(event.stopPropagation).toHaveBeenCalled();
+ });
+
+ it('should focus textAreaEl', () => {
+ expect(textAreaEl.focus).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/helpers/dom_helper_spec.js b/spec/javascripts/image_diff/helpers/dom_helper_spec.js
new file mode 100644
index 00000000000..f49248dd0eb
--- /dev/null
+++ b/spec/javascripts/image_diff/helpers/dom_helper_spec.js
@@ -0,0 +1,118 @@
+import * as domHelper from '~/image_diff/helpers/dom_helper';
+import * as mockData from '../mock_data';
+
+describe('domHelper', () => {
+ const { imageMeta, badgeNumber } = mockData;
+
+ describe('setPositionDataAttribute', () => {
+ let containerEl;
+ let attributeAfterCall;
+ const position = {
+ myProperty: 'myProperty',
+ };
+
+ beforeEach(() => {
+ containerEl = document.createElement('div');
+ containerEl.dataset.position = JSON.stringify(position);
+ domHelper.setPositionDataAttribute(containerEl, imageMeta);
+ attributeAfterCall = JSON.parse(containerEl.dataset.position);
+ });
+
+ it('should set x, y, width, height', () => {
+ expect(attributeAfterCall.x_axis).toEqual(imageMeta.x);
+ expect(attributeAfterCall.y_axis).toEqual(imageMeta.y);
+ expect(attributeAfterCall.width).toEqual(imageMeta.width);
+ expect(attributeAfterCall.height).toEqual(imageMeta.height);
+ });
+
+ it('should not override other properties', () => {
+ expect(attributeAfterCall.myProperty).toEqual('myProperty');
+ });
+ });
+
+ describe('updateDiscussionAvatarBadgeNumber', () => {
+ let discussionEl;
+
+ beforeEach(() => {
+ discussionEl = document.createElement('div');
+ discussionEl.innerHTML = `
+ <a href="#" class="image-diff-avatar-link">
+ <div class="badge"></div>
+ </a>
+ `;
+ domHelper.updateDiscussionAvatarBadgeNumber(discussionEl, badgeNumber);
+ });
+
+ it('should update avatar badge number', () => {
+ expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString());
+ });
+ });
+
+ describe('updateDiscussionBadgeNumber', () => {
+ let discussionEl;
+
+ beforeEach(() => {
+ discussionEl = document.createElement('div');
+ discussionEl.innerHTML = `
+ <div class="badge"></div>
+ `;
+ domHelper.updateDiscussionBadgeNumber(discussionEl, badgeNumber);
+ });
+
+ it('should update discussion badge number', () => {
+ expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString());
+ });
+ });
+
+ describe('toggleCollapsed', () => {
+ let element;
+ let discussionNotesEl;
+
+ beforeEach(() => {
+ element = document.createElement('div');
+ element.innerHTML = `
+ <div class="discussion-notes">
+ <button></button>
+ <form class="discussion-form"></form>
+ </div>
+ `;
+ discussionNotesEl = element.querySelector('.discussion-notes');
+ });
+
+ describe('not collapsed', () => {
+ beforeEach(() => {
+ domHelper.toggleCollapsed({
+ currentTarget: element.querySelector('button'),
+ });
+ });
+
+ it('should add collapsed class', () => {
+ expect(discussionNotesEl.classList.contains('collapsed')).toEqual(true);
+ });
+
+ it('should force formEl to display none', () => {
+ const formEl = element.querySelector('.discussion-form');
+ expect(formEl.style.display).toEqual('none');
+ });
+ });
+
+ describe('collapsed', () => {
+ beforeEach(() => {
+ discussionNotesEl.classList.add('collapsed');
+
+ domHelper.toggleCollapsed({
+ currentTarget: element.querySelector('button'),
+ });
+ });
+
+ it('should remove collapsed class', () => {
+ expect(discussionNotesEl.classList.contains('collapsed')).toEqual(false);
+ });
+
+ it('should force formEl to display block', () => {
+ const formEl = element.querySelector('.discussion-form');
+ expect(formEl.style.display).toEqual('block');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/javascripts/image_diff/helpers/utils_helper_spec.js
new file mode 100644
index 00000000000..8de467f3c94
--- /dev/null
+++ b/spec/javascripts/image_diff/helpers/utils_helper_spec.js
@@ -0,0 +1,208 @@
+import * as utilsHelper from '~/image_diff/helpers/utils_helper';
+import ImageDiff from '~/image_diff/image_diff';
+import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
+import ImageBadge from '~/image_diff/image_badge';
+import * as mockData from '../mock_data';
+
+describe('utilsHelper', () => {
+ const {
+ noteId,
+ discussionId,
+ image,
+ imageProperties,
+ imageMeta,
+ imagePositionAttr,
+ } = mockData;
+
+ describe('resizeCoordinatesToImageElement', () => {
+ let result;
+
+ beforeEach(() => {
+ result = utilsHelper.resizeCoordinatesToImageElement(image, imageMeta);
+ });
+
+ it('should return x based on widthRatio', () => {
+ expect(result.x).toEqual(imageMeta.x * 0.5);
+ });
+
+ it('should return y based on heightRatio', () => {
+ expect(result.y).toEqual(imageMeta.y * 0.5);
+ });
+
+ it('should return image width', () => {
+ expect(result.width).toEqual(image.width);
+ });
+
+ it('should return image height', () => {
+ expect(result.height).toEqual(image.height);
+ });
+ });
+
+ describe('generateBadgeFromDiscussionDOM', () => {
+ let discussionEl;
+ let result;
+
+ beforeEach(() => {
+ const imageFrameEl = document.createElement('div');
+ imageFrameEl.innerHTML = `
+ <img src="${gl.TEST_HOST}/image.png">
+ `;
+ discussionEl = document.createElement('div');
+ discussionEl.dataset.discussionId = discussionId;
+ discussionEl.innerHTML = `
+ <div class="note" id="${noteId}"></div>
+ `;
+ discussionEl.dataset.position = JSON.stringify(imagePositionAttr);
+ result = utilsHelper.generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl);
+ });
+
+ it('should return actual image properties', () => {
+ const { actual } = result;
+ expect(actual.x).toEqual(imagePositionAttr.x_axis);
+ expect(actual.y).toEqual(imagePositionAttr.y_axis);
+ expect(actual.width).toEqual(imagePositionAttr.width);
+ expect(actual.height).toEqual(imagePositionAttr.height);
+ });
+
+ it('should return browser image properties', () => {
+ const { browser } = result;
+ expect(browser.x).toBeDefined();
+ expect(browser.y).toBeDefined();
+ expect(browser.width).toBeDefined();
+ expect(browser.height).toBeDefined();
+ });
+
+ it('should return instance of ImageBadge', () => {
+ expect(result instanceof ImageBadge).toEqual(true);
+ });
+
+ it('should return noteId', () => {
+ expect(result.noteId).toEqual(noteId);
+ });
+
+ it('should return discussionId', () => {
+ expect(result.discussionId).toEqual(discussionId);
+ });
+ });
+
+ describe('getTargetSelection', () => {
+ let containerEl;
+
+ beforeEach(() => {
+ containerEl = {
+ querySelector: () => imageProperties,
+ };
+ });
+
+ function generateEvent(offsetX, offsetY) {
+ return {
+ currentTarget: containerEl,
+ offsetX,
+ offsetY,
+ };
+ }
+
+ it('should return browser properties', () => {
+ const event = generateEvent(25, 25);
+ const result = utilsHelper.getTargetSelection(event);
+
+ const { browser } = result;
+ expect(browser.x).toEqual(event.offsetX);
+ expect(browser.y).toEqual(event.offsetY);
+ expect(browser.width).toEqual(imageProperties.width);
+ expect(browser.height).toEqual(imageProperties.height);
+ });
+
+ it('should return resized actual image properties', () => {
+ const event = generateEvent(50, 50);
+ const result = utilsHelper.getTargetSelection(event);
+
+ const { actual } = result;
+ expect(actual.x).toEqual(100);
+ expect(actual.y).toEqual(100);
+ expect(actual.width).toEqual(imageProperties.naturalWidth);
+ expect(actual.height).toEqual(imageProperties.naturalHeight);
+ });
+
+ describe('normalize coordinates', () => {
+ it('should return x = 0 if x < 0', () => {
+ const event = generateEvent(-5, 50);
+ const result = utilsHelper.getTargetSelection(event);
+ expect(result.browser.x).toEqual(0);
+ });
+
+ it('should return x = width if x > width', () => {
+ const event = generateEvent(1000, 50);
+ const result = utilsHelper.getTargetSelection(event);
+ expect(result.browser.x).toEqual(imageProperties.width);
+ });
+
+ it('should return y = 0 if y < 0', () => {
+ const event = generateEvent(50, -10);
+ const result = utilsHelper.getTargetSelection(event);
+ expect(result.browser.y).toEqual(0);
+ });
+
+ it('should return y = height if y > height', () => {
+ const event = generateEvent(50, 1000);
+ const result = utilsHelper.getTargetSelection(event);
+ expect(result.browser.y).toEqual(imageProperties.height);
+ });
+ });
+ });
+
+ describe('initImageDiff', () => {
+ let glCache;
+ let fileEl;
+
+ beforeEach(() => {
+ window.gl = window.gl || (window.gl = {});
+ glCache = window.gl;
+ window.gl.ImageFile = () => {};
+ fileEl = document.createElement('div');
+ fileEl.innerHTML = `
+ <div class="diff-file"></div>
+ `;
+
+ spyOn(ImageDiff.prototype, 'init').and.callFake(() => {});
+ spyOn(ReplacedImageDiff.prototype, 'init').and.callFake(() => {});
+ });
+
+ afterEach(() => {
+ window.gl = glCache;
+ });
+
+ it('should initialize gl.ImageFile', () => {
+ spyOn(window.gl, 'ImageFile');
+
+ utilsHelper.initImageDiff(fileEl, false, false);
+ expect(gl.ImageFile).toHaveBeenCalled();
+ });
+
+ it('should initialize ImageDiff if js-single-image', () => {
+ const diffFileEl = fileEl.querySelector('.diff-file');
+ diffFileEl.innerHTML = `
+ <div class="js-single-image">
+ </div>
+ `;
+
+ const imageDiff = utilsHelper.initImageDiff(fileEl, true, false);
+ expect(ImageDiff.prototype.init).toHaveBeenCalled();
+ expect(imageDiff.canCreateNote).toEqual(true);
+ expect(imageDiff.renderCommentBadge).toEqual(false);
+ });
+
+ it('should initialize ReplacedImageDiff if js-replaced-image', () => {
+ const diffFileEl = fileEl.querySelector('.diff-file');
+ diffFileEl.innerHTML = `
+ <div class="js-replaced-image">
+ </div>
+ `;
+
+ const replacedImageDiff = utilsHelper.initImageDiff(fileEl, false, true);
+ expect(ReplacedImageDiff.prototype.init).toHaveBeenCalled();
+ expect(replacedImageDiff.canCreateNote).toEqual(false);
+ expect(replacedImageDiff.renderCommentBadge).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/image_badge_spec.js b/spec/javascripts/image_diff/image_badge_spec.js
new file mode 100644
index 00000000000..87f98fc0926
--- /dev/null
+++ b/spec/javascripts/image_diff/image_badge_spec.js
@@ -0,0 +1,84 @@
+import ImageBadge from '~/image_diff/image_badge';
+import imageDiffHelper from '~/image_diff/helpers/index';
+import * as mockData from './mock_data';
+
+describe('ImageBadge', () => {
+ const { noteId, discussionId, imageMeta } = mockData;
+ const options = {
+ noteId,
+ discussionId,
+ };
+
+ it('should save actual property', () => {
+ const imageBadge = new ImageBadge(Object.assign({}, options, {
+ actual: imageMeta,
+ }));
+
+ const { actual } = imageBadge;
+ expect(actual.x).toEqual(imageMeta.x);
+ expect(actual.y).toEqual(imageMeta.y);
+ expect(actual.width).toEqual(imageMeta.width);
+ expect(actual.height).toEqual(imageMeta.height);
+ });
+
+ it('should save browser property', () => {
+ const imageBadge = new ImageBadge(Object.assign({}, options, {
+ browser: imageMeta,
+ }));
+
+ const { browser } = imageBadge;
+ expect(browser.x).toEqual(imageMeta.x);
+ expect(browser.y).toEqual(imageMeta.y);
+ expect(browser.width).toEqual(imageMeta.width);
+ expect(browser.height).toEqual(imageMeta.height);
+ });
+
+ it('should save noteId', () => {
+ const imageBadge = new ImageBadge(options);
+ expect(imageBadge.noteId).toEqual(noteId);
+ });
+
+ it('should save discussionId', () => {
+ const imageBadge = new ImageBadge(options);
+ expect(imageBadge.discussionId).toEqual(discussionId);
+ });
+
+ describe('default values', () => {
+ let imageBadge;
+
+ beforeEach(() => {
+ imageBadge = new ImageBadge(options);
+ });
+
+ it('should return defaultimageMeta if actual property is not provided', () => {
+ const { actual } = imageBadge;
+ expect(actual.x).toEqual(0);
+ expect(actual.y).toEqual(0);
+ expect(actual.width).toEqual(0);
+ expect(actual.height).toEqual(0);
+ });
+
+ it('should return defaultimageMeta if browser property is not provided', () => {
+ const { browser } = imageBadge;
+ expect(browser.x).toEqual(0);
+ expect(browser.y).toEqual(0);
+ expect(browser.width).toEqual(0);
+ expect(browser.height).toEqual(0);
+ });
+ });
+
+ describe('imageEl property is provided and not browser property', () => {
+ beforeEach(() => {
+ spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(true);
+ });
+
+ it('should generate browser property', () => {
+ const imageBadge = new ImageBadge(Object.assign({}, options, {
+ imageEl: document.createElement('img'),
+ }));
+
+ expect(imageDiffHelper.resizeCoordinatesToImageElement).toHaveBeenCalled();
+ expect(imageBadge.browser).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/javascripts/image_diff/image_diff_spec.js
new file mode 100644
index 00000000000..346282328c7
--- /dev/null
+++ b/spec/javascripts/image_diff/image_diff_spec.js
@@ -0,0 +1,361 @@
+import ImageDiff from '~/image_diff/image_diff';
+import * as imageUtility from '~/lib/utils/image_utility';
+import imageDiffHelper from '~/image_diff/helpers/index';
+import * as mockData from './mock_data';
+
+describe('ImageDiff', () => {
+ let element;
+ let imageDiff;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="element">
+ <div class="diff-file">
+ <div class="js-image-frame">
+ <img src="${gl.TEST_HOST}/image.png">
+ <div class="comment-indicator"></div>
+ <div id="badge-1" class="badge">1</div>
+ <div id="badge-2" class="badge">2</div>
+ <div id="badge-3" class="badge">3</div>
+ </div>
+ <div class="note-container">
+ <div class="discussion-notes">
+ <div class="js-diff-notes-toggle"></div>
+ <div class="notes"></div>
+ </div>
+ <div class="discussion-notes">
+ <div class="js-diff-notes-toggle"></div>
+ <div class="notes"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ `);
+ element = document.getElementById('element');
+ });
+
+ describe('constructor', () => {
+ beforeEach(() => {
+ imageDiff = new ImageDiff(element, {
+ canCreateNote: true,
+ renderCommentBadge: true,
+ });
+ });
+
+ it('should set el', () => {
+ expect(imageDiff.el).toEqual(element);
+ });
+
+ it('should set canCreateNote', () => {
+ expect(imageDiff.canCreateNote).toEqual(true);
+ });
+
+ it('should set renderCommentBadge', () => {
+ expect(imageDiff.renderCommentBadge).toEqual(true);
+ });
+
+ it('should set $noteContainer', () => {
+ expect(imageDiff.$noteContainer[0]).toEqual(element.querySelector('.note-container'));
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ imageDiff = new ImageDiff(element);
+ });
+
+ it('should set canCreateNote as false', () => {
+ expect(imageDiff.canCreateNote).toEqual(false);
+ });
+
+ it('should set renderCommentBadge as false', () => {
+ expect(imageDiff.renderCommentBadge).toEqual(false);
+ });
+ });
+ });
+
+ describe('init', () => {
+ beforeEach(() => {
+ spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ imageDiff = new ImageDiff(element);
+ imageDiff.init();
+ });
+
+ it('should set imageFrameEl', () => {
+ expect(imageDiff.imageFrameEl).toEqual(element.querySelector('.diff-file .js-image-frame'));
+ });
+
+ it('should set imageEl', () => {
+ expect(imageDiff.imageEl).toEqual(element.querySelector('.diff-file .js-image-frame img'));
+ });
+
+ it('should call bindEvents', () => {
+ expect(imageDiff.bindEvents).toHaveBeenCalled();
+ });
+ });
+
+ describe('bindEvents', () => {
+ let imageEl;
+
+ beforeEach(() => {
+ spyOn(imageDiffHelper, 'toggleCollapsed').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'commentIndicatorOnClick').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'removeCommentIndicator').and.callFake(() => {});
+ spyOn(ImageDiff.prototype, 'imageClicked').and.callFake(() => {});
+ spyOn(ImageDiff.prototype, 'addBadge').and.callFake(() => {});
+ spyOn(ImageDiff.prototype, 'removeBadge').and.callFake(() => {});
+ spyOn(ImageDiff.prototype, 'renderBadges').and.callFake(() => {});
+ imageEl = element.querySelector('.diff-file .js-image-frame img');
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageEl = imageEl;
+ imageDiff.bindEvents();
+ });
+
+ it('should register click event delegation to js-diff-notes-toggle', () => {
+ element.querySelector('.js-diff-notes-toggle').click();
+ expect(imageDiffHelper.toggleCollapsed).toHaveBeenCalled();
+ });
+
+ it('should register click event delegation to comment-indicator', () => {
+ element.querySelector('.comment-indicator').click();
+ expect(imageDiffHelper.commentIndicatorOnClick).toHaveBeenCalled();
+ });
+ });
+
+ describe('image loaded', () => {
+ beforeEach(() => {
+ spyOn(imageUtility, 'isImageLoaded').and.returnValue(true);
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageEl = imageEl;
+ });
+
+ it('should renderBadges', () => {});
+ });
+
+ describe('image not loaded', () => {
+ beforeEach(() => {
+ spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageEl = imageEl;
+ imageDiff.bindEvents();
+ });
+
+ it('should registers load eventListener', () => {
+ const loadEvent = new Event('load');
+ imageEl.dispatchEvent(loadEvent);
+ expect(imageDiff.renderBadges).toHaveBeenCalled();
+ });
+ });
+
+ describe('canCreateNote', () => {
+ beforeEach(() => {
+ spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ imageDiff = new ImageDiff(element, {
+ canCreateNote: true,
+ });
+ imageDiff.imageEl = imageEl;
+ imageDiff.bindEvents();
+ });
+
+ it('should register click.imageDiff event', () => {
+ const event = new CustomEvent('click.imageDiff');
+ element.dispatchEvent(event);
+ expect(imageDiff.imageClicked).toHaveBeenCalled();
+ });
+
+ it('should register blur.imageDiff event', () => {
+ const event = new CustomEvent('blur.imageDiff');
+ element.dispatchEvent(event);
+ expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled();
+ });
+
+ it('should register addBadge.imageDiff event', () => {
+ const event = new CustomEvent('addBadge.imageDiff');
+ element.dispatchEvent(event);
+ expect(imageDiff.addBadge).toHaveBeenCalled();
+ });
+
+ it('should register removeBadge.imageDiff event', () => {
+ const event = new CustomEvent('removeBadge.imageDiff');
+ element.dispatchEvent(event);
+ expect(imageDiff.removeBadge).toHaveBeenCalled();
+ });
+ });
+
+ describe('canCreateNote is false', () => {
+ beforeEach(() => {
+ spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageEl = imageEl;
+ imageDiff.bindEvents();
+ });
+
+ it('should not register click.imageDiff event', () => {
+ const event = new CustomEvent('click.imageDiff');
+ element.dispatchEvent(event);
+ expect(imageDiff.imageClicked).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('imageClicked', () => {
+ beforeEach(() => {
+ spyOn(imageDiffHelper, 'getTargetSelection').and.returnValue({
+ actual: {},
+ browser: {},
+ });
+ spyOn(imageDiffHelper, 'setPositionDataAttribute').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {});
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageClicked({
+ detail: {
+ currentTarget: {},
+ },
+ });
+ });
+
+ it('should call getTargetSelection', () => {
+ expect(imageDiffHelper.getTargetSelection).toHaveBeenCalled();
+ });
+
+ it('should call setPositionDataAttribute', () => {
+ expect(imageDiffHelper.setPositionDataAttribute).toHaveBeenCalled();
+ });
+
+ it('should call showCommentIndicator', () => {
+ expect(imageDiffHelper.showCommentIndicator).toHaveBeenCalled();
+ });
+ });
+
+ describe('renderBadges', () => {
+ beforeEach(() => {
+ spyOn(ImageDiff.prototype, 'renderBadge').and.callFake(() => {});
+ imageDiff = new ImageDiff(element);
+ imageDiff.renderBadges();
+ });
+
+ it('should call renderBadge for each discussionEl', () => {
+ const discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes');
+ expect(imageDiff.renderBadge.calls.count()).toEqual(discussionEls.length);
+ });
+ });
+
+ describe('renderBadge', () => {
+ let discussionEls;
+
+ beforeEach(() => {
+ spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'addImageCommentBadge').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'generateBadgeFromDiscussionDOM').and.returnValue({
+ browser: {},
+ noteId: 'noteId',
+ });
+ discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes');
+ imageDiff = new ImageDiff(element);
+ imageDiff.renderBadge(discussionEls[0], 0);
+ });
+
+ it('should populate imageBadges', () => {
+ expect(imageDiff.imageBadges.length).toEqual(1);
+ });
+
+ describe('renderCommentBadge', () => {
+ beforeEach(() => {
+ imageDiff.renderCommentBadge = true;
+ imageDiff.renderBadge(discussionEls[0], 0);
+ });
+
+ it('should call addImageCommentBadge', () => {
+ expect(imageDiffHelper.addImageCommentBadge).toHaveBeenCalled();
+ });
+ });
+
+ describe('renderCommentBadge is false', () => {
+ it('should call addImageBadge', () => {
+ expect(imageDiffHelper.addImageBadge).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('addBadge', () => {
+ beforeEach(() => {
+ spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'addAvatarBadge').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {});
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame');
+ imageDiff.addBadge({
+ detail: {
+ x: 0,
+ y: 1,
+ width: 25,
+ height: 50,
+ noteId: 'noteId',
+ discussionId: 'discussionId',
+ },
+ });
+ });
+
+ it('should add imageBadge to imageBadges', () => {
+ expect(imageDiff.imageBadges.length).toEqual(1);
+ });
+
+ it('should call addImageBadge', () => {
+ expect(imageDiffHelper.addImageBadge).toHaveBeenCalled();
+ });
+
+ it('should call addAvatarBadge', () => {
+ expect(imageDiffHelper.addAvatarBadge).toHaveBeenCalled();
+ });
+
+ it('should call updateDiscussionBadgeNumber', () => {
+ expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled();
+ });
+ });
+
+ describe('removeBadge', () => {
+ beforeEach(() => {
+ const { imageMeta } = mockData;
+
+ spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'updateDiscussionAvatarBadgeNumber').and.callFake(() => {});
+ imageDiff = new ImageDiff(element);
+ imageDiff.imageBadges = [imageMeta, imageMeta, imageMeta];
+ imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame');
+ imageDiff.removeBadge({
+ detail: {
+ badgeNumber: 2,
+ },
+ });
+ });
+
+ describe('cascade badge count', () => {
+ it('should update next imageBadgeEl value', () => {
+ const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.badge');
+ expect(imageBadgeEls[0].innerText).toEqual('1');
+ expect(imageBadgeEls[1].innerText).toEqual('2');
+ expect(imageBadgeEls.length).toEqual(2);
+ });
+
+ it('should call updateDiscussionBadgeNumber', () => {
+ expect(imageDiffHelper.updateDiscussionBadgeNumber).toHaveBeenCalled();
+ });
+
+ it('should call updateDiscussionAvatarBadgeNumber', () => {
+ expect(imageDiffHelper.updateDiscussionAvatarBadgeNumber).toHaveBeenCalled();
+ });
+ });
+
+ it('should remove badge from imageBadges', () => {
+ expect(imageDiff.imageBadges.length).toEqual(2);
+ });
+
+ it('should remove imageBadgeEl', () => {
+ expect(imageDiff.imageFrameEl.querySelector('#badge-2')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/init_discussion_tab_spec.js b/spec/javascripts/image_diff/init_discussion_tab_spec.js
new file mode 100644
index 00000000000..7c447d6f70d
--- /dev/null
+++ b/spec/javascripts/image_diff/init_discussion_tab_spec.js
@@ -0,0 +1,37 @@
+import initDiscussionTab from '~/image_diff/init_discussion_tab';
+import imageDiffHelper from '~/image_diff/helpers/index';
+
+describe('initDiscussionTab', () => {
+ beforeEach(() => {
+ setFixtures(`
+ <div class="timeline-content">
+ <div class="diff-file js-image-file"></div>
+ <div class="diff-file js-image-file"></div>
+ </div>
+ `);
+ });
+
+ it('should pass canCreateNote as false to initImageDiff', (done) => {
+ spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote) => {
+ expect(canCreateNote).toEqual(false);
+ done();
+ });
+
+ initDiscussionTab();
+ });
+
+ it('should pass renderCommentBadge as true to initImageDiff', (done) => {
+ spyOn(imageDiffHelper, 'initImageDiff').and.callFake((diffFileEl, canCreateNote, renderCommentBadge) => {
+ expect(renderCommentBadge).toEqual(true);
+ done();
+ });
+
+ initDiscussionTab();
+ });
+
+ it('should call initImageDiff for each diffFileEls', () => {
+ spyOn(imageDiffHelper, 'initImageDiff').and.callFake(() => {});
+ initDiscussionTab();
+ expect(imageDiffHelper.initImageDiff.calls.count()).toEqual(2);
+ });
+});
diff --git a/spec/javascripts/image_diff/mock_data.js b/spec/javascripts/image_diff/mock_data.js
new file mode 100644
index 00000000000..6b522047053
--- /dev/null
+++ b/spec/javascripts/image_diff/mock_data.js
@@ -0,0 +1,35 @@
+export const noteId = 'noteId';
+export const discussionId = 'discussionId';
+export const badgeText = 'badgeText';
+export const badgeNumber = 5;
+
+export const coordinate = {
+ x: 100,
+ y: 100,
+};
+
+export const image = {
+ width: 100,
+ height: 100,
+};
+
+export const imageProperties = {
+ width: image.width,
+ height: image.height,
+ naturalWidth: image.width * 2,
+ naturalHeight: image.height * 2,
+};
+
+export const imageMeta = {
+ x: coordinate.x,
+ y: coordinate.y,
+ width: imageProperties.naturalWidth,
+ height: imageProperties.naturalHeight,
+};
+
+export const imagePositionAttr = {
+ x_axis: coordinate.x,
+ y_axis: coordinate.y,
+ width: image.width,
+ height: image.height,
+};
diff --git a/spec/javascripts/image_diff/replaced_image_diff_spec.js b/spec/javascripts/image_diff/replaced_image_diff_spec.js
new file mode 100644
index 00000000000..5f8cd7c531a
--- /dev/null
+++ b/spec/javascripts/image_diff/replaced_image_diff_spec.js
@@ -0,0 +1,312 @@
+import ReplacedImageDiff from '~/image_diff/replaced_image_diff';
+import ImageDiff from '~/image_diff/image_diff';
+import { viewTypes } from '~/image_diff/view_types';
+import imageDiffHelper from '~/image_diff/helpers/index';
+
+describe('ReplacedImageDiff', () => {
+ let element;
+ let replacedImageDiff;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="element">
+ <div class="two-up">
+ <div class="js-image-frame">
+ <img src="${gl.TEST_HOST}/image.png">
+ </div>
+ </div>
+ <div class="swipe">
+ <div class="js-image-frame">
+ <img src="${gl.TEST_HOST}/image.png">
+ </div>
+ </div>
+ <div class="onion-skin">
+ <div class="js-image-frame">
+ <img src="${gl.TEST_HOST}/image.png">
+ </div>
+ </div>
+ <div class="view-modes-menu">
+ <div class="two-up">2-up</div>
+ <div class="swipe">Swipe</div>
+ <div class="onion-skin">Onion skin</div>
+ </div>
+ </div>
+ `);
+ element = document.getElementById('element');
+ });
+
+ function setupImageFrameEls() {
+ replacedImageDiff.imageFrameEls = [];
+ replacedImageDiff.imageFrameEls[viewTypes.TWO_UP] = element.querySelector('.two-up .js-image-frame');
+ replacedImageDiff.imageFrameEls[viewTypes.SWIPE] = element.querySelector('.swipe .js-image-frame');
+ replacedImageDiff.imageFrameEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin .js-image-frame');
+ }
+
+ function setupViewModesEls() {
+ replacedImageDiff.viewModesEls = [];
+ replacedImageDiff.viewModesEls[viewTypes.TWO_UP] = element.querySelector('.view-modes-menu .two-up');
+ replacedImageDiff.viewModesEls[viewTypes.SWIPE] = element.querySelector('.view-modes-menu .swipe');
+ replacedImageDiff.viewModesEls[viewTypes.ONION_SKIN] = element.querySelector('.view-modes-menu .onion-skin');
+ }
+
+ function setupImageEls() {
+ replacedImageDiff.imageEls = [];
+ replacedImageDiff.imageEls[viewTypes.TWO_UP] = element.querySelector('.two-up img');
+ replacedImageDiff.imageEls[viewTypes.SWIPE] = element.querySelector('.swipe img');
+ replacedImageDiff.imageEls[viewTypes.ONION_SKIN] = element.querySelector('.onion-skin img');
+ }
+
+ it('should extend ImageDiff', () => {
+ replacedImageDiff = new ReplacedImageDiff(element);
+ expect(replacedImageDiff instanceof ImageDiff).toEqual(true);
+ });
+
+ describe('init', () => {
+ beforeEach(() => {
+ spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ spyOn(ReplacedImageDiff.prototype, 'generateImageEls').and.callFake(() => {});
+
+ replacedImageDiff = new ReplacedImageDiff(element);
+ replacedImageDiff.init();
+ });
+
+ it('should set imageFrameEls', () => {
+ const { imageFrameEls } = replacedImageDiff;
+ expect(imageFrameEls).toBeDefined();
+ expect(imageFrameEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up .js-image-frame'));
+ expect(imageFrameEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe .js-image-frame'));
+ expect(imageFrameEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin .js-image-frame'));
+ });
+
+ it('should set viewModesEls', () => {
+ const { viewModesEls } = replacedImageDiff;
+ expect(viewModesEls).toBeDefined();
+ expect(viewModesEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.view-modes-menu .two-up'));
+ expect(viewModesEls[viewTypes.SWIPE]).toEqual(element.querySelector('.view-modes-menu .swipe'));
+ expect(viewModesEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.view-modes-menu .onion-skin'));
+ });
+
+ it('should generateImageEls', () => {
+ expect(ReplacedImageDiff.prototype.generateImageEls).toHaveBeenCalled();
+ });
+
+ it('should bindEvents', () => {
+ expect(ReplacedImageDiff.prototype.bindEvents).toHaveBeenCalled();
+ });
+
+ describe('currentView', () => {
+ it('should set currentView', () => {
+ replacedImageDiff.init(viewTypes.ONION_SKIN);
+ expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN);
+ });
+
+ it('should default to viewTypes.TWO_UP', () => {
+ expect(replacedImageDiff.currentView).toEqual(viewTypes.TWO_UP);
+ });
+ });
+ });
+
+ describe('generateImageEls', () => {
+ beforeEach(() => {
+ spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+
+ replacedImageDiff = new ReplacedImageDiff(element, {
+ canCreateNote: false,
+ renderCommentBadge: false,
+ });
+
+ setupImageFrameEls();
+ });
+
+ it('should set imageEls', () => {
+ replacedImageDiff.generateImageEls();
+ const { imageEls } = replacedImageDiff;
+ expect(imageEls).toBeDefined();
+ expect(imageEls[viewTypes.TWO_UP]).toEqual(element.querySelector('.two-up img'));
+ expect(imageEls[viewTypes.SWIPE]).toEqual(element.querySelector('.swipe img'));
+ expect(imageEls[viewTypes.ONION_SKIN]).toEqual(element.querySelector('.onion-skin img'));
+ });
+ });
+
+ describe('bindEvents', () => {
+ beforeEach(() => {
+ spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ replacedImageDiff = new ReplacedImageDiff(element);
+
+ setupViewModesEls();
+ });
+
+ it('should call super.bindEvents', () => {
+ replacedImageDiff.bindEvents();
+ expect(ImageDiff.prototype.bindEvents).toHaveBeenCalled();
+ });
+
+ it('should register click eventlistener to 2-up view mode', (done) => {
+ spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => {
+ expect(viewMode).toEqual(viewTypes.TWO_UP);
+ done();
+ });
+
+ replacedImageDiff.bindEvents();
+ replacedImageDiff.viewModesEls[viewTypes.TWO_UP].click();
+ });
+
+ it('should register click eventlistener to swipe view mode', (done) => {
+ spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => {
+ expect(viewMode).toEqual(viewTypes.SWIPE);
+ done();
+ });
+
+ replacedImageDiff.bindEvents();
+ replacedImageDiff.viewModesEls[viewTypes.SWIPE].click();
+ });
+
+ it('should register click eventlistener to onion skin view mode', (done) => {
+ spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake((viewMode) => {
+ expect(viewMode).toEqual(viewTypes.SWIPE);
+ done();
+ });
+
+ replacedImageDiff.bindEvents();
+ replacedImageDiff.viewModesEls[viewTypes.SWIPE].click();
+ });
+ });
+
+ describe('getters', () => {
+ describe('imageEl', () => {
+ beforeEach(() => {
+ replacedImageDiff = new ReplacedImageDiff(element);
+ replacedImageDiff.currentView = viewTypes.TWO_UP;
+ setupImageEls();
+ });
+
+ it('should return imageEl based on currentView', () => {
+ expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.two-up img'));
+
+ replacedImageDiff.currentView = viewTypes.SWIPE;
+ expect(replacedImageDiff.imageEl).toEqual(element.querySelector('.swipe img'));
+ });
+ });
+
+ describe('imageFrameEl', () => {
+ beforeEach(() => {
+ replacedImageDiff = new ReplacedImageDiff(element);
+ replacedImageDiff.currentView = viewTypes.TWO_UP;
+ setupImageFrameEls();
+ });
+
+ it('should return imageFrameEl based on currentView', () => {
+ expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.two-up .js-image-frame'));
+
+ replacedImageDiff.currentView = viewTypes.ONION_SKIN;
+ expect(replacedImageDiff.imageFrameEl).toEqual(element.querySelector('.onion-skin .js-image-frame'));
+ });
+ });
+ });
+
+ describe('changeView', () => {
+ beforeEach(() => {
+ replacedImageDiff = new ReplacedImageDiff(element);
+ spyOn(imageDiffHelper, 'removeCommentIndicator').and.returnValue({
+ removed: false,
+ });
+ setupImageFrameEls();
+ });
+
+ describe('invalid viewType', () => {
+ beforeEach(() => {
+ replacedImageDiff.changeView('some-view-name');
+ });
+
+ it('should not call removeCommentIndicator', () => {
+ expect(imageDiffHelper.removeCommentIndicator).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('valid viewType', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+ spyOn(ReplacedImageDiff.prototype, 'renderNewView').and.callFake(() => {});
+ replacedImageDiff.changeView(viewTypes.ONION_SKIN);
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('should call removeCommentIndicator', () => {
+ expect(imageDiffHelper.removeCommentIndicator).toHaveBeenCalled();
+ });
+
+ it('should update currentView to newView', () => {
+ expect(replacedImageDiff.currentView).toEqual(viewTypes.ONION_SKIN);
+ });
+
+ it('should clear imageBadges', () => {
+ expect(replacedImageDiff.imageBadges.length).toEqual(0);
+ });
+
+ it('should call renderNewView', () => {
+ jasmine.clock().tick(251);
+ expect(replacedImageDiff.renderNewView).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('renderNewView', () => {
+ beforeEach(() => {
+ replacedImageDiff = new ReplacedImageDiff(element);
+ });
+
+ it('should call renderBadges', () => {
+ spyOn(ReplacedImageDiff.prototype, 'renderBadges').and.callFake(() => {});
+
+ replacedImageDiff.renderNewView({
+ removed: false,
+ });
+
+ expect(replacedImageDiff.renderBadges).toHaveBeenCalled();
+ });
+
+ describe('removeIndicator', () => {
+ const indicator = {
+ removed: true,
+ x: 0,
+ y: 1,
+ image: {
+ width: 50,
+ height: 100,
+ },
+ };
+
+ beforeEach(() => {
+ setupImageEls();
+ setupImageFrameEls();
+ });
+
+ it('should pass showCommentIndicator normalized indicator values', (done) => {
+ spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {});
+ spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.callFake((imageEl, meta) => {
+ expect(meta.x).toEqual(indicator.x);
+ expect(meta.y).toEqual(indicator.y);
+ expect(meta.width).toEqual(indicator.image.width);
+ expect(meta.height).toEqual(indicator.image.height);
+ done();
+ });
+ replacedImageDiff.renderNewView(indicator);
+ });
+
+ it('should call showCommentIndicator', (done) => {
+ const normalized = {
+ normalized: true,
+ };
+ spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(normalized);
+ spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake((imageFrameEl, normalizedIndicator) => {
+ expect(normalizedIndicator).toEqual(normalized);
+ done();
+ });
+ replacedImageDiff.renderNewView(indicator);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/javascripts/image_diff/view_types_spec.js
new file mode 100644
index 00000000000..e9639f46497
--- /dev/null
+++ b/spec/javascripts/image_diff/view_types_spec.js
@@ -0,0 +1,24 @@
+import { viewTypes, isValidViewType } from '~/image_diff/view_types';
+
+describe('viewTypes', () => {
+ describe('isValidViewType', () => {
+ it('should return true for TWO_UP', () => {
+ expect(isValidViewType(viewTypes.TWO_UP)).toEqual(true);
+ });
+
+ it('should return true for SWIPE', () => {
+ expect(isValidViewType(viewTypes.SWIPE)).toEqual(true);
+ });
+
+ it('should return true for ONION_SKIN', () => {
+ expect(isValidViewType(viewTypes.ONION_SKIN)).toEqual(true);
+ });
+
+ it('should return false for non view types', () => {
+ expect(isValidViewType('some-view-type')).toEqual(false);
+ expect(isValidViewType(null)).toEqual(false);
+ expect(isValidViewType(undefined)).toEqual(false);
+ expect(isValidViewType('')).toEqual(false);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/javascripts/lib/utils/image_utility_spec.js
new file mode 100644
index 00000000000..75addfcc833
--- /dev/null
+++ b/spec/javascripts/lib/utils/image_utility_spec.js
@@ -0,0 +1,32 @@
+import * as imageUtility from '~/lib/utils/image_utility';
+
+describe('imageUtility', () => {
+ describe('isImageLoaded', () => {
+ it('should return false when image.complete is false', () => {
+ const element = {
+ complete: false,
+ naturalHeight: 100,
+ };
+
+ expect(imageUtility.isImageLoaded(element)).toEqual(false);
+ });
+
+ it('should return false when naturalHeight = 0', () => {
+ const element = {
+ complete: true,
+ naturalHeight: 0,
+ };
+
+ expect(imageUtility.isImageLoaded(element)).toEqual(false);
+ });
+
+ it('should return true when image.complete and naturalHeight != 0', () => {
+ const element = {
+ complete: true,
+ naturalHeight: 100,
+ };
+
+ expect(imageUtility.isImageLoaded(element)).toEqual(true);
+ });
+ });
+});