Merge branch 'mh/notes-spec' into 'master'
Migrate old notes app test from Karma to Jest Closes #60335 See merge request gitlab-org/gitlab-ce!28704
This commit is contained in:
commit
e6a88b024f
|
@ -7,6 +7,10 @@ no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */
|
||||||
/* global ResolveService */
|
/* global ResolveService */
|
||||||
/* global mrRefreshWidgetUrl */
|
/* global mrRefreshWidgetUrl */
|
||||||
|
|
||||||
|
/*
|
||||||
|
old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
|
||||||
|
*/
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
|
/*
|
||||||
|
@module
|
||||||
|
|
||||||
|
This method provides convenience functions to help migrating from Karma/Jasmine to Jest.
|
||||||
|
|
||||||
|
Try not to use these in new tests - this module is provided primarily for convenience of migrating tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a plain JS object pre-populated with Jest spy functions. Useful for making simple mocks classes.
|
||||||
|
*
|
||||||
|
* @see https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3EcreateSpyObj%3C/code%3E
|
||||||
|
* @param {string} baseName Human-readable name of the object. This is used for reporting purposes.
|
||||||
|
* @param methods {string[]} List of method names that will be added to the spy object.
|
||||||
|
*/
|
||||||
|
export function createSpyObj(baseName, methods) {
|
||||||
|
const obj = {};
|
||||||
|
methods.forEach(method => {
|
||||||
|
obj[method] = jest.fn().mockName(`${baseName}#${method}`);
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
const NS_PER_SEC = 1e9;
|
const NS_PER_SEC = 1e9;
|
||||||
const NS_PER_MS = 1e6;
|
const NS_PER_MS = 1e6;
|
||||||
|
const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk');
|
||||||
|
|
||||||
let testTimeoutNS;
|
let testTimeoutNS;
|
||||||
|
|
||||||
|
@ -8,6 +9,13 @@ export const setTestTimeout = newTimeoutMS => {
|
||||||
jest.setTimeout(newTimeoutMS);
|
jest.setTimeout(newTimeoutMS);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allows slow tests to set their own timeout.
|
||||||
|
// Useful for tests with jQuery, which is very slow in big DOMs.
|
||||||
|
let temporaryTimeoutNS = null;
|
||||||
|
export const setTestTimeoutOnce = newTimeoutMS => {
|
||||||
|
temporaryTimeoutNS = newTimeoutMS * NS_PER_MS;
|
||||||
|
};
|
||||||
|
|
||||||
export const initializeTestTimeout = defaultTimeoutMS => {
|
export const initializeTestTimeout = defaultTimeoutMS => {
|
||||||
setTestTimeout(defaultTimeoutMS);
|
setTestTimeout(defaultTimeoutMS);
|
||||||
|
|
||||||
|
@ -19,12 +27,20 @@ export const initializeTestTimeout = defaultTimeoutMS => {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
let timeoutNS = testTimeoutNS;
|
||||||
|
if (Number.isFinite(temporaryTimeoutNS)) {
|
||||||
|
timeoutNS = temporaryTimeoutNS;
|
||||||
|
temporaryTimeoutNS = null;
|
||||||
|
}
|
||||||
|
|
||||||
const [seconds, remainingNs] = process.hrtime(testStartTime);
|
const [seconds, remainingNs] = process.hrtime(testStartTime);
|
||||||
const elapsedNS = seconds * NS_PER_SEC + remainingNs;
|
const elapsedNS = seconds * NS_PER_SEC + remainingNs;
|
||||||
|
|
||||||
if (elapsedNS > testTimeoutNS) {
|
// Disable the timeout error when debugging. It is meaningless because
|
||||||
|
// debugging always takes longer than the test timeout.
|
||||||
|
if (elapsedNS > timeoutNS && !IS_DEBUGGING) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Test took too long (${elapsedNS / NS_PER_MS}ms > ${testTimeoutNS / NS_PER_MS}ms)!`,
|
`Test took too long (${elapsedNS / NS_PER_MS}ms > ${timeoutNS / NS_PER_MS}ms)!`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,84 +1,89 @@
|
||||||
/* eslint-disable no-unused-expressions, no-var, object-shorthand */
|
/* eslint-disable import/no-commonjs, no-new */
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import 'autosize';
|
import * as urlUtility from '~/lib/utils/url_utility';
|
||||||
import '~/gl_form';
|
|
||||||
import '~/lib/utils/text_utility';
|
|
||||||
import '~/behaviors/markdown/render_gfm';
|
import '~/behaviors/markdown/render_gfm';
|
||||||
import Notes from '~/notes';
|
import { createSpyObj } from 'helpers/jest_helpers';
|
||||||
import timeoutPromise from './helpers/set_timeout_promise_helper';
|
import { setTestTimeoutOnce } from 'helpers/timeout';
|
||||||
|
import { TEST_HOST } from 'helpers/test_constants';
|
||||||
|
|
||||||
window.gon || (window.gon = {});
|
// These must be imported synchronously because they pull dependencies
|
||||||
|
// from the DOM.
|
||||||
|
window.jQuery = $;
|
||||||
|
require('autosize');
|
||||||
|
require('~/commons');
|
||||||
|
require('~/notes');
|
||||||
|
|
||||||
|
const { Notes } = window;
|
||||||
|
const FLASH_TYPE_ALERT = 'alert';
|
||||||
|
const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
|
||||||
|
const fixture = 'snippets/show.html';
|
||||||
|
let mockAxios;
|
||||||
|
|
||||||
|
window.project_uploads_path = `${TEST_HOST}/uploads`;
|
||||||
|
window.gon = window.gon || {};
|
||||||
window.gl = window.gl || {};
|
window.gl = window.gl || {};
|
||||||
gl.utils = gl.utils || {};
|
gl.utils = gl.utils || {};
|
||||||
|
gl.utils.disableButtonIfEmptyField = () => {};
|
||||||
|
|
||||||
const htmlEscape = comment => {
|
describe('Old Notes (~/notes.js)', () => {
|
||||||
const escapedString = comment.replace(/["&'<>]/g, a => {
|
beforeEach(() => {
|
||||||
const escapedToken = {
|
jest.useFakeTimers();
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
'`': '`',
|
|
||||||
}[a];
|
|
||||||
|
|
||||||
return escapedToken;
|
|
||||||
});
|
|
||||||
|
|
||||||
return escapedString;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Notes', function() {
|
|
||||||
const FLASH_TYPE_ALERT = 'alert';
|
|
||||||
const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
|
|
||||||
var fixture = 'snippets/show.html';
|
|
||||||
preloadFixtures(fixture);
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
loadFixtures(fixture);
|
loadFixtures(fixture);
|
||||||
gl.utils.disableButtonIfEmptyField = _.noop;
|
|
||||||
window.project_uploads_path = 'http://test.host/uploads';
|
// Re-declare this here so that test_setup.js#beforeEach() doesn't
|
||||||
$('body').attr('data-page', 'projects:merge_requets:show');
|
// overwrite it.
|
||||||
|
mockAxios = new MockAdapter(axios);
|
||||||
|
|
||||||
|
$.ajax = () => {
|
||||||
|
throw new Error('$.ajax should not be called through!');
|
||||||
|
};
|
||||||
|
|
||||||
|
// These jQuery+DOM tests are super flaky so increase the timeout to avoid
|
||||||
|
// random failures.
|
||||||
|
// It seems that running tests in parallel increases failure rate.
|
||||||
|
jest.setTimeout(4000);
|
||||||
|
setTestTimeoutOnce(4000);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(done => {
|
||||||
// Undo what we did to the shared <body>
|
// The Notes component sets a polling interval. Clear it after every run.
|
||||||
$('body').removeAttr('data-page');
|
// Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
|
||||||
|
jest.clearAllTimers();
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
// Wait for any requests to resolve, otherwise we get failures about
|
||||||
|
// unmocked requests.
|
||||||
|
mockAxios.restore();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the Notes class into the DOM', () => {
|
||||||
|
expect(Notes).toBeDefined();
|
||||||
|
expect(Notes.name).toBe('Notes');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addBinding', () => {
|
describe('addBinding', () => {
|
||||||
it('calls postComment when comment button is clicked', () => {
|
it('calls postComment when comment button is clicked', () => {
|
||||||
spyOn(Notes.prototype, 'postComment');
|
jest.spyOn(Notes.prototype, 'postComment');
|
||||||
this.notes = new Notes('', []);
|
|
||||||
|
|
||||||
|
new window.Notes('', []);
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
expect(Notes.prototype.postComment).toHaveBeenCalled();
|
expect(Notes.prototype.postComment).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('task lists', function() {
|
describe('task lists', () => {
|
||||||
let mock;
|
beforeEach(() => {
|
||||||
|
mockAxios.onAny().reply(200, {});
|
||||||
beforeEach(function() {
|
new Notes('', []);
|
||||||
spyOn(axios, 'patch').and.callFake(() => new Promise(() => {}));
|
|
||||||
mock = new MockAdapter(axios);
|
|
||||||
mock.onAny().reply(200, {});
|
|
||||||
|
|
||||||
$('.js-comment-button').on('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
this.notes = new Notes('', []);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
it('modifies the Markdown field', () => {
|
||||||
mock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('modifies the Markdown field', function() {
|
|
||||||
const changeEvent = document.createEvent('HTMLEvents');
|
const changeEvent = document.createEvent('HTMLEvents');
|
||||||
changeEvent.initEvent('change', true, true);
|
changeEvent.initEvent('change', true, true);
|
||||||
$('input[type=checkbox]')
|
$('input[type=checkbox]')
|
||||||
|
@ -88,7 +93,9 @@ describe('Notes', function() {
|
||||||
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) {
|
it('submits an ajax request on tasklist:changed', () => {
|
||||||
|
jest.spyOn(axios, 'patch');
|
||||||
|
|
||||||
const lineNumber = 8;
|
const lineNumber = 8;
|
||||||
const lineSource = '- [ ] item 8';
|
const lineSource = '- [ ] item 8';
|
||||||
const index = 3;
|
const index = 3;
|
||||||
|
@ -99,76 +106,74 @@ describe('Notes', function() {
|
||||||
detail: { lineNumber, lineSource, index, checked },
|
detail: { lineNumber, lineSource, index, checked },
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
expect(axios.patch).toHaveBeenCalledWith(undefined, {
|
||||||
expect(axios.patch).toHaveBeenCalledWith(undefined, {
|
note: {
|
||||||
note: {
|
note: '',
|
||||||
note: '',
|
lock_version: undefined,
|
||||||
lock_version: undefined,
|
update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
|
||||||
update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
|
},
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('comments', function() {
|
describe('comments', () => {
|
||||||
var textarea = '.js-note-text';
|
let notes;
|
||||||
|
let autosizeSpy;
|
||||||
|
let textarea;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
|
|
||||||
this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
|
textarea = $('.js-note-text');
|
||||||
spyOn(this.notes, 'renderNote').and.stub();
|
textarea.data('autosave', {
|
||||||
|
reset: () => {},
|
||||||
$(textarea).data('autosave', {
|
|
||||||
reset: function() {},
|
|
||||||
});
|
});
|
||||||
|
autosizeSpy = jest.fn();
|
||||||
|
$(textarea).on('autosize:update', autosizeSpy);
|
||||||
|
|
||||||
|
jest.spyOn(notes, 'renderNote');
|
||||||
|
|
||||||
$('.js-comment-button').on('click', e => {
|
$('.js-comment-button').on('click', e => {
|
||||||
const $form = $(this);
|
const $form = $(this);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.notes.addNote($form);
|
notes.addNote($form, {});
|
||||||
this.notes.reenableTargetFormSubmitButton(e);
|
notes.reenableTargetFormSubmitButton(e);
|
||||||
this.notes.resetMainTargetForm(e);
|
notes.resetMainTargetForm(e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('autosizes after comment submission', function() {
|
it('autosizes after comment submission', () => {
|
||||||
$(textarea).text('This is an example comment note');
|
textarea.text('This is an example comment note');
|
||||||
|
expect(autosizeSpy).not.toHaveBeenCalled();
|
||||||
expect(this.autoSizeSpy).not.toHaveBeenTriggered();
|
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
expect(autosizeSpy).toHaveBeenCalled();
|
||||||
expect(this.autoSizeSpy).toHaveBeenTriggered();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not place escaped text in the comment box in case of error', function() {
|
it('should not place escaped text in the comment box in case of error', () => {
|
||||||
const deferred = $.Deferred();
|
const deferred = $.Deferred();
|
||||||
spyOn($, 'ajax').and.returnValue(deferred.promise());
|
jest.spyOn($, 'ajax').mockReturnValueOnce(deferred);
|
||||||
$(textarea).text('A comment with `markup`.');
|
$(textarea).text('A comment with `markup`.');
|
||||||
|
|
||||||
deferred.reject();
|
deferred.reject();
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
expect($(textarea).val()).toEqual('A comment with `markup`.');
|
expect($(textarea).val()).toBe('A comment with `markup`.');
|
||||||
|
|
||||||
|
$.ajax.mockRestore();
|
||||||
|
expect($.ajax.mock).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateNote', () => {
|
describe('updateNote', () => {
|
||||||
let sampleComment;
|
let notes;
|
||||||
let noteEntity;
|
let noteEntity;
|
||||||
let $form;
|
|
||||||
let $notesContainer;
|
let $notesContainer;
|
||||||
let mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
window.gon.current_username = 'root';
|
window.gon.current_username = 'root';
|
||||||
window.gon.current_user_fullname = 'Administrator';
|
window.gon.current_user_fullname = 'Administrator';
|
||||||
sampleComment = 'foo';
|
const sampleComment = 'foo';
|
||||||
noteEntity = {
|
noteEntity = {
|
||||||
id: 1234,
|
id: 1234,
|
||||||
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
|
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
|
||||||
|
@ -177,35 +182,27 @@ describe('Notes', function() {
|
||||||
note: sampleComment,
|
note: sampleComment,
|
||||||
valid: true,
|
valid: true,
|
||||||
};
|
};
|
||||||
$form = $('form.js-main-target-form');
|
|
||||||
$notesContainer = $('ul.main-notes-list');
|
$notesContainer = $('ul.main-notes-list');
|
||||||
|
const $form = $('form.js-main-target-form');
|
||||||
$form.find('textarea.js-note-text').val(sampleComment);
|
$form.find('textarea.js-note-text').val(sampleComment);
|
||||||
|
|
||||||
mock = new MockAdapter(axios);
|
mockAxios.onPost(NOTES_POST_PATH).reply(200, noteEntity);
|
||||||
mock.onPost(NOTES_POST_PATH).reply(200, noteEntity);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
it('updates note and resets edit form', () => {
|
||||||
mock.restore();
|
jest.spyOn(notes, 'revertNoteEditForm');
|
||||||
});
|
jest.spyOn(notes, 'setupNewNote');
|
||||||
|
|
||||||
it('updates note and resets edit form', done => {
|
|
||||||
spyOn(this.notes, 'revertNoteEditForm');
|
|
||||||
spyOn(this.notes, 'setupNewNote');
|
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
|
||||||
const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
|
const updatedNote = Object.assign({}, noteEntity);
|
||||||
const updatedNote = Object.assign({}, noteEntity);
|
updatedNote.note = 'bar';
|
||||||
updatedNote.note = 'bar';
|
notes.updateNote(updatedNote, $targetNote);
|
||||||
this.notes.updateNote(updatedNote, $targetNote);
|
|
||||||
|
|
||||||
expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
|
expect(notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
|
||||||
expect(this.notes.setupNewNote).toHaveBeenCalled();
|
expect(notes.setupNewNote).toHaveBeenCalled();
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,32 +212,44 @@ describe('Notes', function() {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
$note = $(`<div id="${hash}"></div>`);
|
$note = $(`<div id="${hash}"></div>`);
|
||||||
spyOn($note, 'filter').and.callThrough();
|
jest.spyOn($note, 'filter');
|
||||||
spyOn($note, 'toggleClass').and.callThrough();
|
jest.spyOn($note, 'toggleClass');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
expect(typeof urlUtility.getLocationHash.mock).toBe('object');
|
||||||
|
urlUtility.getLocationHash.mockRestore();
|
||||||
|
expect(urlUtility.getLocationHash.mock).toBeUndefined();
|
||||||
|
expect(urlUtility.getLocationHash()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally.
|
||||||
|
|
||||||
it('sets target when hash matches', () => {
|
it('sets target when hash matches', () => {
|
||||||
spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash);
|
jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash);
|
||||||
|
|
||||||
Notes.updateNoteTargetSelector($note);
|
Notes.updateNoteTargetSelector($note);
|
||||||
|
|
||||||
|
expect(urlUtility.getLocationHash).toHaveBeenCalled();
|
||||||
expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
|
expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
|
||||||
expect($note.toggleClass).toHaveBeenCalledWith('target', true);
|
expect($note.toggleClass).toHaveBeenCalledWith('target', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unsets target when hash does not match', () => {
|
it('unsets target when hash does not match', () => {
|
||||||
spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist');
|
jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce('note_doesnotexist');
|
||||||
|
|
||||||
Notes.updateNoteTargetSelector($note);
|
Notes.updateNoteTargetSelector($note);
|
||||||
|
|
||||||
|
expect(urlUtility.getLocationHash).toHaveBeenCalled();
|
||||||
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
|
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unsets target when there is not a hash fragment anymore', () => {
|
it('unsets target when there is not a hash fragment anymore', () => {
|
||||||
spyOnDependency(Notes, 'getLocationHash').and.returnValue(null);
|
jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(null);
|
||||||
|
|
||||||
Notes.updateNoteTargetSelector($note);
|
Notes.updateNoteTargetSelector($note);
|
||||||
|
|
||||||
|
expect(urlUtility.getLocationHash).toHaveBeenCalled();
|
||||||
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
|
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -257,28 +266,28 @@ describe('Notes', function() {
|
||||||
note: 'heya',
|
note: 'heya',
|
||||||
html: '<div>heya</div>',
|
html: '<div>heya</div>',
|
||||||
};
|
};
|
||||||
$notesList = jasmine.createSpyObj('$notesList', ['find', 'append']);
|
$notesList = createSpyObj('$notesList', ['find', 'append']);
|
||||||
|
|
||||||
notes = jasmine.createSpyObj('notes', [
|
notes = createSpyObj('notes', [
|
||||||
'setupNewNote',
|
'setupNewNote',
|
||||||
'refresh',
|
'refresh',
|
||||||
'collapseLongCommitList',
|
'collapseLongCommitList',
|
||||||
'updateNotesCount',
|
'updateNotesCount',
|
||||||
'putConflictEditWarningInPlace',
|
'putConflictEditWarningInPlace',
|
||||||
]);
|
]);
|
||||||
notes.taskList = jasmine.createSpyObj('tasklist', ['init']);
|
notes.taskList = createSpyObj('tasklist', ['init']);
|
||||||
notes.note_ids = [];
|
notes.note_ids = [];
|
||||||
notes.updatedNotesTrackingMap = {};
|
notes.updatedNotesTrackingMap = {};
|
||||||
|
|
||||||
spyOn(Notes, 'isNewNote').and.callThrough();
|
jest.spyOn(Notes, 'isNewNote');
|
||||||
spyOn(Notes, 'isUpdatedNote').and.callThrough();
|
jest.spyOn(Notes, 'isUpdatedNote');
|
||||||
spyOn(Notes, 'animateAppendNote').and.callThrough();
|
jest.spyOn(Notes, 'animateAppendNote');
|
||||||
spyOn(Notes, 'animateUpdateNote').and.callThrough();
|
jest.spyOn(Notes, 'animateUpdateNote');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when adding note', () => {
|
describe('when adding note', () => {
|
||||||
it('should call .animateAppendNote', () => {
|
it('should call .animateAppendNote', () => {
|
||||||
Notes.isNewNote.and.returnValue(true);
|
Notes.isNewNote.mockReturnValueOnce(true);
|
||||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||||
|
|
||||||
expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
|
expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
|
||||||
|
@ -287,12 +296,12 @@ describe('Notes', function() {
|
||||||
|
|
||||||
describe('when note was edited', () => {
|
describe('when note was edited', () => {
|
||||||
it('should call .animateUpdateNote', () => {
|
it('should call .animateUpdateNote', () => {
|
||||||
Notes.isNewNote.and.returnValue(false);
|
Notes.isNewNote.mockReturnValueOnce(false);
|
||||||
Notes.isUpdatedNote.and.returnValue(true);
|
Notes.isUpdatedNote.mockReturnValueOnce(true);
|
||||||
const $note = $('<div>');
|
const $note = $('<div>');
|
||||||
$notesList.find.and.returnValue($note);
|
$notesList.find.mockReturnValueOnce($note);
|
||||||
const $newNote = $(note.html);
|
const $newNote = $(note.html);
|
||||||
Notes.animateUpdateNote.and.returnValue($newNote);
|
Notes.animateUpdateNote.mockReturnValueOnce($newNote);
|
||||||
|
|
||||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||||
|
|
||||||
|
@ -302,26 +311,26 @@ describe('Notes', function() {
|
||||||
|
|
||||||
describe('while editing', () => {
|
describe('while editing', () => {
|
||||||
it('should update textarea if nothing has been touched', () => {
|
it('should update textarea if nothing has been touched', () => {
|
||||||
Notes.isNewNote.and.returnValue(false);
|
Notes.isNewNote.mockReturnValueOnce(false);
|
||||||
Notes.isUpdatedNote.and.returnValue(true);
|
Notes.isUpdatedNote.mockReturnValueOnce(true);
|
||||||
const $note = $(`<div class="is-editing">
|
const $note = $(`<div class="is-editing">
|
||||||
<div class="original-note-content">initial</div>
|
<div class="original-note-content">initial</div>
|
||||||
<textarea class="js-note-text">initial</textarea>
|
<textarea class="js-note-text">initial</textarea>
|
||||||
</div>`);
|
</div>`);
|
||||||
$notesList.find.and.returnValue($note);
|
$notesList.find.mockReturnValueOnce($note);
|
||||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||||
|
|
||||||
expect($note.find('.js-note-text').val()).toEqual(note.note);
|
expect($note.find('.js-note-text').val()).toEqual(note.note);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call .putConflictEditWarningInPlace', () => {
|
it('should call .putConflictEditWarningInPlace', () => {
|
||||||
Notes.isNewNote.and.returnValue(false);
|
Notes.isNewNote.mockReturnValueOnce(false);
|
||||||
Notes.isUpdatedNote.and.returnValue(true);
|
Notes.isUpdatedNote.mockReturnValueOnce(true);
|
||||||
const $note = $(`<div class="is-editing">
|
const $note = $(`<div class="is-editing">
|
||||||
<div class="original-note-content">initial</div>
|
<div class="original-note-content">initial</div>
|
||||||
<textarea class="js-note-text">different</textarea>
|
<textarea class="js-note-text">different</textarea>
|
||||||
</div>`);
|
</div>`);
|
||||||
$notesList.find.and.returnValue($note);
|
$notesList.find.mockReturnValueOnce($note);
|
||||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||||
|
|
||||||
expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
|
expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
|
||||||
|
@ -386,32 +395,32 @@ describe('Notes', function() {
|
||||||
discussion_resolvable: false,
|
discussion_resolvable: false,
|
||||||
diff_discussion_html: false,
|
diff_discussion_html: false,
|
||||||
};
|
};
|
||||||
$form = jasmine.createSpyObj('$form', ['closest', 'find']);
|
$form = createSpyObj('$form', ['closest', 'find']);
|
||||||
$form.length = 1;
|
$form.length = 1;
|
||||||
row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
|
row = createSpyObj('row', ['prevAll', 'first', 'find']);
|
||||||
|
|
||||||
notes = jasmine.createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
|
notes = createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
|
||||||
notes.note_ids = [];
|
notes.note_ids = [];
|
||||||
|
|
||||||
spyOn(Notes, 'isNewNote');
|
jest.spyOn(Notes, 'isNewNote');
|
||||||
spyOn(Notes, 'animateAppendNote');
|
jest.spyOn(Notes, 'animateAppendNote').mockImplementation();
|
||||||
Notes.isNewNote.and.returnValue(true);
|
Notes.isNewNote.mockReturnValue(true);
|
||||||
notes.isParallelView.and.returnValue(false);
|
notes.isParallelView.mockReturnValue(false);
|
||||||
row.prevAll.and.returnValue(row);
|
row.prevAll.mockReturnValue(row);
|
||||||
row.first.and.returnValue(row);
|
row.first.mockReturnValue(row);
|
||||||
row.find.and.returnValue(row);
|
row.find.mockReturnValue(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Discussion root note', () => {
|
describe('Discussion root note', () => {
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
body = jasmine.createSpyObj('body', ['attr']);
|
body = createSpyObj('body', ['attr']);
|
||||||
discussionContainer = { length: 0 };
|
discussionContainer = { length: 0 };
|
||||||
|
|
||||||
$form.closest.and.returnValues(row, $form);
|
$form.closest.mockReturnValueOnce(row).mockReturnValue($form);
|
||||||
$form.find.and.returnValues(discussionContainer);
|
$form.find.mockReturnValue(discussionContainer);
|
||||||
body.attr.and.returnValue('');
|
body.attr.mockReturnValue('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call Notes.animateAppendNote', () => {
|
it('should call Notes.animateAppendNote', () => {
|
||||||
|
@ -432,7 +441,9 @@ describe('Notes', function() {
|
||||||
line.id = note.discussion_line_code;
|
line.id = note.discussion_line_code;
|
||||||
document.body.appendChild(line);
|
document.body.appendChild(line);
|
||||||
|
|
||||||
$form.closest.and.returnValues($form);
|
// Override mocks for this single test
|
||||||
|
$form.closest.mockReset();
|
||||||
|
$form.closest.mockReturnValue($form);
|
||||||
|
|
||||||
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
||||||
|
|
||||||
|
@ -444,8 +455,8 @@ describe('Notes', function() {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
discussionContainer = { length: 1 };
|
discussionContainer = { length: 1 };
|
||||||
|
|
||||||
$form.closest.and.returnValues(row, $form);
|
$form.closest.mockReturnValueOnce(row).mockReturnValueOnce($form);
|
||||||
$form.find.and.returnValues(discussionContainer);
|
$form.find.mockReturnValue(discussionContainer);
|
||||||
|
|
||||||
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
||||||
});
|
});
|
||||||
|
@ -463,7 +474,7 @@ describe('Notes', function() {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
noteHTML = '<div></div>';
|
noteHTML = '<div></div>';
|
||||||
$notesList = jasmine.createSpyObj('$notesList', ['append']);
|
$notesList = createSpyObj('$notesList', ['append']);
|
||||||
|
|
||||||
$resultantNote = Notes.animateAppendNote(noteHTML, $notesList);
|
$resultantNote = Notes.animateAppendNote(noteHTML, $notesList);
|
||||||
});
|
});
|
||||||
|
@ -484,7 +495,7 @@ describe('Notes', function() {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
noteHTML = '<div></div>';
|
noteHTML = '<div></div>';
|
||||||
$note = jasmine.createSpyObj('$note', ['replaceWith']);
|
$note = createSpyObj('$note', ['replaceWith']);
|
||||||
|
|
||||||
$updatedNote = Notes.animateUpdateNote(noteHTML, $note);
|
$updatedNote = Notes.animateUpdateNote(noteHTML, $note);
|
||||||
});
|
});
|
||||||
|
@ -515,7 +526,6 @@ describe('Notes', function() {
|
||||||
|
|
||||||
describe('postComment & updateComment', () => {
|
describe('postComment & updateComment', () => {
|
||||||
const sampleComment = 'foo';
|
const sampleComment = 'foo';
|
||||||
const updatedComment = 'bar';
|
|
||||||
const note = {
|
const note = {
|
||||||
id: 1234,
|
id: 1234,
|
||||||
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
|
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
|
||||||
|
@ -524,22 +534,20 @@ describe('Notes', function() {
|
||||||
note: sampleComment,
|
note: sampleComment,
|
||||||
valid: true,
|
valid: true,
|
||||||
};
|
};
|
||||||
|
let notes;
|
||||||
let $form;
|
let $form;
|
||||||
let $notesContainer;
|
let $notesContainer;
|
||||||
let mock;
|
|
||||||
|
|
||||||
function mockNotesPost() {
|
function mockNotesPost() {
|
||||||
mock.onPost(NOTES_POST_PATH).reply(200, note);
|
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockNotesPostError() {
|
function mockNotesPostError() {
|
||||||
mock.onPost(NOTES_POST_PATH).networkError();
|
mockAxios.onPost(NOTES_POST_PATH).networkError();
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mock = new MockAdapter(axios);
|
notes = new Notes('', []);
|
||||||
|
|
||||||
this.notes = new Notes('', []);
|
|
||||||
window.gon.current_username = 'root';
|
window.gon.current_username = 'root';
|
||||||
window.gon.current_user_fullname = 'Administrator';
|
window.gon.current_user_fullname = 'Administrator';
|
||||||
$form = $('form.js-main-target-form');
|
$form = $('form.js-main-target-form');
|
||||||
|
@ -547,10 +555,6 @@ describe('Notes', function() {
|
||||||
$form.find('textarea.js-note-text').val(sampleComment);
|
$form.find('textarea.js-note-text').val(sampleComment);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show placeholder note while new comment is being posted', () => {
|
it('should show placeholder note while new comment is being posted', () => {
|
||||||
mockNotesPost();
|
mockNotesPost();
|
||||||
|
|
||||||
|
@ -564,9 +568,8 @@ describe('Notes', function() {
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
expect($notesContainer.find('.note.being-posted').length).toEqual(0);
|
expect($notesContainer.find('.note.being-posted').length).toEqual(0);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -580,12 +583,12 @@ describe('Notes', function() {
|
||||||
preventDefault() {},
|
preventDefault() {},
|
||||||
target: $submitButton,
|
target: $submitButton,
|
||||||
};
|
};
|
||||||
mock.onPost(NOTES_POST_PATH).replyOnce(() => {
|
mockAxios.onPost(NOTES_POST_PATH).replyOnce(() => {
|
||||||
expect($submitButton).toBeDisabled();
|
expect($submitButton).toBeDisabled();
|
||||||
return [200, note];
|
return [200, note];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.notes
|
notes
|
||||||
.postComment(dummyEvent)
|
.postComment(dummyEvent)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect($submitButton).not.toBeDisabled();
|
expect($submitButton).not.toBeDisabled();
|
||||||
|
@ -600,9 +603,8 @@ describe('Notes', function() {
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
|
expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -612,48 +614,49 @@ describe('Notes', function() {
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
expect($form.find('textarea.js-note-text').val()).toEqual('');
|
expect($form.find('textarea.js-note-text').val()).toEqual('');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show flash error message when new comment failed to be posted', done => {
|
it('should show flash error message when new comment failed to be posted', done => {
|
||||||
mockNotesPostError();
|
mockNotesPostError();
|
||||||
|
jest.spyOn(notes, 'addFlash');
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
expect(
|
expect(notes.addFlash).toHaveBeenCalled();
|
||||||
$notesContainer
|
// JSDom doesn't support the :visible selector yet
|
||||||
.parent()
|
expect(notes.flashContainer.style.display).not.toBe('none');
|
||||||
.find('.flash-container .flash-text')
|
|
||||||
.is(':visible'),
|
|
||||||
).toEqual(true);
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This is a bad test carried over from the Karma -> Jest migration.
|
||||||
|
// The corresponding test in the Karma suite tests for
|
||||||
|
// elements and methods that don't actually exist, and gives a false
|
||||||
|
// positive pass.
|
||||||
|
/*
|
||||||
it('should show flash error message when comment failed to be updated', done => {
|
it('should show flash error message when comment failed to be updated', done => {
|
||||||
mockNotesPost();
|
mockNotesPost();
|
||||||
|
jest.spyOn(notes, 'addFlash').mockName('addFlash');
|
||||||
|
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
timeoutPromise()
|
deferredPromise()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const $noteEl = $notesContainer.find(`#note_${note.id}`);
|
const $noteEl = $notesContainer.find(`#note_${note.id}`);
|
||||||
$noteEl.find('.js-note-edit').click();
|
$noteEl.find('.js-note-edit').click();
|
||||||
$noteEl.find('textarea.js-note-text').val(updatedComment);
|
$noteEl.find('textarea.js-note-text').val(updatedComment);
|
||||||
|
|
||||||
mock.restore();
|
|
||||||
|
|
||||||
mockNotesPostError();
|
mockNotesPostError();
|
||||||
|
|
||||||
$noteEl.find('.js-comment-save-button').click();
|
$noteEl.find('.js-comment-save-button').click();
|
||||||
|
notes.updateComment({preventDefault: () => {}});
|
||||||
})
|
})
|
||||||
.then(timeoutPromise)
|
.then(() => deferredPromise())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
|
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
|
||||||
|
|
||||||
|
@ -665,12 +668,13 @@ describe('Notes', function() {
|
||||||
.trim(),
|
.trim(),
|
||||||
).toEqual(sampleComment); // See if comment reverted back to original
|
).toEqual(sampleComment); // See if comment reverted back to original
|
||||||
|
|
||||||
expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
|
expect(notes.addFlash).toHaveBeenCalled();
|
||||||
|
expect(notes.flashContainer.style.display).not.toBe('none');
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
.catch(done.fail);
|
.catch(done.fail);
|
||||||
}, 2000);
|
}, 5000);
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('postComment with Slash commands', () => {
|
describe('postComment with Slash commands', () => {
|
||||||
|
@ -687,13 +691,11 @@ describe('Notes', function() {
|
||||||
};
|
};
|
||||||
let $form;
|
let $form;
|
||||||
let $notesContainer;
|
let $notesContainer;
|
||||||
let mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mock = new MockAdapter(axios);
|
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
|
||||||
mock.onPost(NOTES_POST_PATH).reply(200, note);
|
|
||||||
|
|
||||||
this.notes = new Notes('', []);
|
new Notes('', []);
|
||||||
window.gon.current_username = 'root';
|
window.gon.current_username = 'root';
|
||||||
window.gon.current_user_fullname = 'Administrator';
|
window.gon.current_user_fullname = 'Administrator';
|
||||||
gl.awardsHandler = {
|
gl.awardsHandler = {
|
||||||
|
@ -710,17 +712,13 @@ describe('Notes', function() {
|
||||||
$form.find('textarea.js-note-text').val(sampleComment);
|
$form.find('textarea.js-note-text').val(sampleComment);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove slash command placeholder when comment with slash commands is done posting', done => {
|
it('should remove slash command placeholder when comment with slash commands is done posting', done => {
|
||||||
spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
|
jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
|
||||||
$('.js-comment-button').click();
|
$('.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(() => {
|
setImmediate(() => {
|
||||||
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();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -740,13 +738,11 @@ describe('Notes', function() {
|
||||||
};
|
};
|
||||||
let $form;
|
let $form;
|
||||||
let $notesContainer;
|
let $notesContainer;
|
||||||
let mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mock = new MockAdapter(axios);
|
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
|
||||||
mock.onPost(NOTES_POST_PATH).reply(200, note);
|
|
||||||
|
|
||||||
this.notes = new Notes('', []);
|
new Notes('', []);
|
||||||
window.gon.current_username = 'root';
|
window.gon.current_username = 'root';
|
||||||
window.gon.current_user_fullname = 'Administrator';
|
window.gon.current_user_fullname = 'Administrator';
|
||||||
$form = $('form.js-main-target-form');
|
$form = $('form.js-main-target-form');
|
||||||
|
@ -754,14 +750,10 @@ describe('Notes', function() {
|
||||||
$form.find('textarea.js-note-text').html(sampleComment);
|
$form.find('textarea.js-note-text').html(sampleComment);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render a script tag', done => {
|
it('should not render a script tag', done => {
|
||||||
$('.js-comment-button').click();
|
$('.js-comment-button').click();
|
||||||
|
|
||||||
setTimeout(() => {
|
setImmediate(() => {
|
||||||
const $noteEl = $notesContainer.find(`#note_${note.id}`);
|
const $noteEl = $notesContainer.find(`#note_${note.id}`);
|
||||||
$noteEl.find('.js-note-edit').click();
|
$noteEl.find('.js-note-edit').click();
|
||||||
$noteEl.find('textarea.js-note-text').html(updatedComment);
|
$noteEl.find('textarea.js-note-text').html(updatedComment);
|
||||||
|
@ -786,9 +778,10 @@ describe('Notes', function() {
|
||||||
describe('getFormData', () => {
|
describe('getFormData', () => {
|
||||||
let $form;
|
let $form;
|
||||||
let sampleComment;
|
let sampleComment;
|
||||||
|
let notes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
|
|
||||||
$form = $('form');
|
$form = $('form');
|
||||||
sampleComment = 'foobar';
|
sampleComment = 'foobar';
|
||||||
|
@ -796,7 +789,7 @@ describe('Notes', function() {
|
||||||
|
|
||||||
it('should return form metadata object from form reference', () => {
|
it('should return form metadata object from form reference', () => {
|
||||||
$form.find('textarea.js-note-text').val(sampleComment);
|
$form.find('textarea.js-note-text').val(sampleComment);
|
||||||
const { formData, formContent, formAction } = this.notes.getFormData($form);
|
const { formData, formContent, formAction } = notes.getFormData($form);
|
||||||
|
|
||||||
expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
|
expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
|
||||||
expect(formContent).toEqual(sampleComment);
|
expect(formContent).toEqual(sampleComment);
|
||||||
|
@ -804,12 +797,12 @@ describe('Notes', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return form metadata with sanitized formContent from form reference', () => {
|
it('should return form metadata with sanitized formContent from form reference', () => {
|
||||||
spyOn(_, 'escape').and.callFake(htmlEscape);
|
jest.spyOn(_, 'escape');
|
||||||
|
|
||||||
sampleComment = '<script>alert("Boom!");</script>';
|
sampleComment = '<script>alert("Boom!");</script>';
|
||||||
$form.find('textarea.js-note-text').val(sampleComment);
|
$form.find('textarea.js-note-text').val(sampleComment);
|
||||||
|
|
||||||
const { formContent } = this.notes.getFormData($form);
|
const { formContent } = notes.getFormData($form);
|
||||||
|
|
||||||
expect(_.escape).toHaveBeenCalledWith(sampleComment);
|
expect(_.escape).toHaveBeenCalledWith(sampleComment);
|
||||||
expect(formContent).toEqual('<script>alert("Boom!");</script>');
|
expect(formContent).toEqual('<script>alert("Boom!");</script>');
|
||||||
|
@ -817,27 +810,29 @@ describe('Notes', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hasQuickActions', () => {
|
describe('hasQuickActions', () => {
|
||||||
|
let notes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true when comment begins with a quick action', () => {
|
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);
|
const hasQuickActions = notes.hasQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(hasQuickActions).toBeTruthy();
|
expect(hasQuickActions).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when comment does NOT begin with a quick action', () => {
|
it('should return false when comment does NOT begin with a quick action', () => {
|
||||||
const sampleComment = 'Hey, /unassign Merging this';
|
const sampleComment = 'Hey, /unassign Merging this';
|
||||||
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
|
const hasQuickActions = notes.hasQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(hasQuickActions).toBeFalsy();
|
expect(hasQuickActions).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when comment does NOT have any quick actions', () => {
|
it('should return false when comment does NOT have any quick actions', () => {
|
||||||
const sampleComment = 'Looking good, Awesome!';
|
const sampleComment = 'Looking good, Awesome!';
|
||||||
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
|
const hasQuickActions = notes.hasQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(hasQuickActions).toBeFalsy();
|
expect(hasQuickActions).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
@ -845,25 +840,25 @@ describe('Notes', function() {
|
||||||
|
|
||||||
describe('stripQuickActions', () => {
|
describe('stripQuickActions', () => {
|
||||||
it('should strip quick actions from the comment which begins with a quick action', () => {
|
it('should strip quick actions from the comment which begins with a quick action', () => {
|
||||||
this.notes = new Notes();
|
const 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);
|
const stripedComment = notes.stripQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(stripedComment).toBe('');
|
expect(stripedComment).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
|
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
|
||||||
this.notes = new Notes();
|
const 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);
|
const stripedComment = notes.stripQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(stripedComment).toBe('Merging this');
|
expect(stripedComment).toBe('Merging this');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT strip string that has slashes within', () => {
|
it('should NOT strip string that has slashes within', () => {
|
||||||
this.notes = new Notes();
|
const 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);
|
const stripedComment = notes.stripQuickActions(sampleComment);
|
||||||
|
|
||||||
expect(stripedComment).toBe(sampleComment);
|
expect(stripedComment).toBe(sampleComment);
|
||||||
});
|
});
|
||||||
|
@ -875,15 +870,16 @@ describe('Notes', function() {
|
||||||
{ name: 'title', description: 'Change title', params: [{}] },
|
{ name: 'title', description: 'Change title', params: [{}] },
|
||||||
{ name: 'estimate', description: 'Set time estimate', params: [{}] },
|
{ name: 'estimate', description: 'Set time estimate', params: [{}] },
|
||||||
];
|
];
|
||||||
|
let notes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes();
|
notes = new Notes();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return executing quick action description when note has single quick action', () => {
|
it('should return executing quick action description when note has single quick action', () => {
|
||||||
const sampleComment = '/close';
|
const sampleComment = '/close';
|
||||||
|
|
||||||
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
|
expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
|
||||||
'Applying command to close this issue',
|
'Applying command to close this issue',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -891,7 +887,7 @@ describe('Notes', function() {
|
||||||
it('should return generic multiple quick action description when note has multiple quick actions', () => {
|
it('should return generic multiple quick action description when note has multiple quick actions', () => {
|
||||||
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
|
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
|
||||||
|
|
||||||
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
|
expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
|
||||||
'Applying multiple commands',
|
'Applying multiple commands',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -899,7 +895,7 @@ describe('Notes', function() {
|
||||||
it('should return generic quick action description when available quick actions list is not populated', () => {
|
it('should return generic quick action description when available quick actions list is not populated', () => {
|
||||||
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
|
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
|
||||||
|
|
||||||
expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
|
expect(notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -909,13 +905,14 @@ describe('Notes', function() {
|
||||||
const currentUsername = 'root';
|
const currentUsername = 'root';
|
||||||
const currentUserFullname = 'Administrator';
|
const currentUserFullname = 'Administrator';
|
||||||
const currentUserAvatar = 'avatar_url';
|
const currentUserAvatar = 'avatar_url';
|
||||||
|
let notes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return constructed placeholder element for regular note based on form contents', () => {
|
it('should return constructed placeholder element for regular note based on form contents', () => {
|
||||||
const $tempNote = this.notes.createPlaceholderNote({
|
const $tempNote = notes.createPlaceholderNote({
|
||||||
formContent: sampleComment,
|
formContent: sampleComment,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
isDiscussionNote: false,
|
isDiscussionNote: false,
|
||||||
|
@ -929,8 +926,8 @@ describe('Notes', function() {
|
||||||
expect($tempNote.attr('id')).toEqual(uniqueId);
|
expect($tempNote.attr('id')).toEqual(uniqueId);
|
||||||
expect($tempNote.hasClass('being-posted')).toBeTruthy();
|
expect($tempNote.hasClass('being-posted')).toBeTruthy();
|
||||||
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
|
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
|
||||||
$tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
|
$tempNote.find('.timeline-icon > a, .note-header-info > a').each((i, el) => {
|
||||||
expect($(this).attr('href')).toEqual(`/${currentUsername}`);
|
expect(el.getAttribute('href')).toEqual(`/${currentUsername}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
|
expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
|
||||||
|
@ -958,7 +955,7 @@ describe('Notes', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return constructed placeholder element for discussion note based on form contents', () => {
|
it('should return constructed placeholder element for discussion note based on form contents', () => {
|
||||||
const $tempNote = this.notes.createPlaceholderNote({
|
const $tempNote = notes.createPlaceholderNote({
|
||||||
formContent: sampleComment,
|
formContent: sampleComment,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
isDiscussionNote: true,
|
isDiscussionNote: true,
|
||||||
|
@ -972,7 +969,7 @@ describe('Notes', function() {
|
||||||
|
|
||||||
it('should return a escaped user name', () => {
|
it('should return a escaped user name', () => {
|
||||||
const currentUserFullnameXSS = 'Foo <script>alert("XSS")</script>';
|
const currentUserFullnameXSS = 'Foo <script>alert("XSS")</script>';
|
||||||
const $tempNote = this.notes.createPlaceholderNote({
|
const $tempNote = notes.createPlaceholderNote({
|
||||||
formContent: sampleComment,
|
formContent: sampleComment,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
isDiscussionNote: false,
|
isDiscussionNote: false,
|
||||||
|
@ -994,14 +991,15 @@ describe('Notes', function() {
|
||||||
describe('createPlaceholderSystemNote', () => {
|
describe('createPlaceholderSystemNote', () => {
|
||||||
const sampleCommandDescription = 'Applying command to close this issue';
|
const sampleCommandDescription = 'Applying command to close this issue';
|
||||||
const uniqueId = 'b1234-a4567';
|
const uniqueId = 'b1234-a4567';
|
||||||
|
let notes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
this.notes = new Notes('', []);
|
notes = new Notes('', []);
|
||||||
spyOn(_, 'escape').and.callFake(htmlEscape);
|
jest.spyOn(_, 'escape');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return constructed placeholder element for system note based on form contents', () => {
|
it('should return constructed placeholder element for system note based on form contents', () => {
|
||||||
const $tempNote = this.notes.createPlaceholderSystemNote({
|
const $tempNote = notes.createPlaceholderSystemNote({
|
||||||
formContent: sampleCommandDescription,
|
formContent: sampleCommandDescription,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
});
|
});
|
||||||
|
@ -1020,29 +1018,28 @@ describe('Notes', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('appendFlash', () => {
|
describe('appendFlash', () => {
|
||||||
beforeEach(() => {
|
|
||||||
this.notes = new Notes();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a flash message', () => {
|
it('shows a flash message', () => {
|
||||||
this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
|
const notes = new Notes('', []);
|
||||||
|
notes.addFlash('Error message', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
|
||||||
|
|
||||||
expect($('.flash-alert').is(':visible')).toBeTruthy();
|
const flash = $('.flash-alert')[0];
|
||||||
|
expect(document.contains(flash)).toBe(true);
|
||||||
|
expect(flash.parentNode.style.display).toBe('block');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('clearFlash', () => {
|
describe('clearFlash', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
$(document).off('ajax:success');
|
$(document).off('ajax:success');
|
||||||
this.notes = new Notes();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides visible flash message', () => {
|
it('hides visible flash message', () => {
|
||||||
this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
|
const notes = new Notes('', []);
|
||||||
|
notes.addFlash('Error message 1', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
|
||||||
this.notes.clearFlash();
|
const flash = $('.flash-alert')[0];
|
||||||
|
notes.clearFlash();
|
||||||
expect($('.flash-alert').is(':visible')).toBeFalsy();
|
expect(flash.parentNode.style.display).toBe('none');
|
||||||
|
expect(notes.flashContainer).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue