Commit e6a88b02 authored by Mike Greiling's avatar Mike Greiling

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
parents fabca7ab c0e743bf
...@@ -7,6 +7,10 @@ no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */ ...@@ -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';
......
/* 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;
}
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 => { ...@@ -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 => { ...@@ -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)!`,
); );
} }
}); });
......
/* 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();
'&': '&amp;', loadFixtures(fixture);
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;',
}[a];
return escapedToken; // Re-declare this here so that test_setup.js#beforeEach() doesn't
}); // overwrite it.
mockAxios = new MockAdapter(axios);
return escapedString; $.ajax = () => {
}; throw new Error('$.ajax should not be called through!');
};
describe('Notes', function() { // These jQuery+DOM tests are super flaky so increase the timeout to avoid
const FLASH_TYPE_ALERT = 'alert'; // random failures.
const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; // It seems that running tests in parallel increases failure rate.
var fixture = 'snippets/show.html'; jest.setTimeout(4000);
preloadFixtures(fixture); setTestTimeoutOnce(4000);
});
beforeEach(function() { afterEach(done => {
loadFixtures(fixture); // The Notes component sets a polling interval. Clear it after every run.
gl.utils.disableButtonIfEmptyField = _.noop; // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
window.project_uploads_path = 'http://test.host/uploads'; jest.clearAllTimers();
$('body').attr('data-page', 'projects:merge_requets:show');
setImmediate(() => {
// Wait for any requests to resolve, otherwise we get failures about
// unmocked requests.
mockAxios.restore();
done();
});
}); });
afterEach(() => { it('loads the Notes class into the DOM', () => {
// Undo what we did to the shared <body> expect(Notes).toBeDefined();
$('body').removeAttr('data-page'); 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(() => {
mock.restore();
}); });
it('modifies the Markdown field', function() { it('modifies the Markdown field', () => {
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() { ...@@ -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,7 +106,6 @@ describe('Notes', function() { ...@@ -99,7 +106,6 @@ 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: '',
...@@ -107,68 +113,67 @@ describe('Notes', function() { ...@@ -107,68 +113,67 @@ describe('Notes', function() {
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;
beforeEach(function() { let textarea;
this.notes = new Notes('', []);
this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update'); beforeEach(() => {
spyOn(this.notes, 'renderNote').and.stub(); notes = new Notes('', []);
$(textarea).data('autosave', { textarea = $('.js-note-text');
reset: function() {}, textarea.data('autosave', {
reset: () => {},
}); });
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() { ...@@ -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(() => {
mock.restore();
}); });
it('updates note and resets edit form', done => { it('updates note and resets edit form', () => {
spyOn(this.notes, 'revertNoteEditForm'); jest.spyOn(notes, 'revertNoteEditForm');
spyOn(this.notes, 'setupNewNote'); jest.spyOn(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';
this.notes.updateNote(updatedNote, $targetNote); notes.updateNote(updatedNote, $targetNote);
expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
expect(this.notes.setupNewNote).toHaveBeenCalled();
done(); expect(notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
}); expect(notes.setupNewNote).toHaveBeenCalled();
}); });
}); });
...@@ -215,32 +212,44 @@ describe('Notes', function() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;'); expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
...@@ -817,27 +810,29 @@ describe('Notes', function() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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();
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment