'));
Notes.prototype.putEditFormInPlace.call({
getEditFormSelector: () => '',
enableGFM: true
}, $el);
expect(gl.GLForm).toHaveBeenCalledWith(jasmine.any(Object), true);
});
});
describe('postComment & updateComment', () => {
const sampleComment = 'foo';
const updatedComment = 'bar';
const note = {
id: 1234,
html: `
${sampleComment}
`,
note: sampleComment,
valid: true
};
let $form;
let $notesContainer;
beforeEach(() => {
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').val(sampleComment);
});
it('should show placeholder note while new comment is being posted', () => {
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
});
it('should remove placeholder note when new comment is done posting', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
expect($notesContainer.find('.note.being-posted').length).toEqual(0);
});
it('should show actual note element when new comment is done posting', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
});
it('should reset Form when new comment is done posting', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
expect($form.find('textarea.js-note-text').val()).toEqual('');
});
it('should show flash error message when new comment failed to be posted', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.reject();
expect($notesContainer.parent().find('.flash-container .flash-text').is(':visible')).toEqual(true);
});
it('should show flash error message when comment failed to be updated', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').val(updatedComment);
$noteEl.find('.js-comment-save-button').click();
deferred.reject();
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
expect($updatedNoteEl.find('.note-text').text().trim()).toEqual(sampleComment); // See if comment reverted back to original
expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
});
});
describe('postComment with Slash commands', () => {
const sampleComment = '/assign @root\n/award :100:';
const note = {
commands_changes: {
assignee_id: 1,
emoji_award: '100'
},
errors: {
commands_only: ['Commands applied']
},
valid: false
};
let $form;
let $notesContainer;
beforeEach(() => {
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
gl.awardsHandler = {
addAwardToEmojiBar: () => {},
scrollToAwards: () => {}
};
gl.GfmAutoComplete = {
dataSources: {
commands: '/root/test-project/autocomplete_sources/commands'
}
};
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').val(sampleComment);
});
it('should remove slash command placeholder when comment with slash commands is done posting', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
$('.js-comment-button').click();
expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
deferred.resolve(note);
expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
});
});
describe('update comment with script tags', () => {
const sampleComment = '';
const updatedComment = '';
const note = {
id: 1234,
html: `
${sampleComment}
`,
note: sampleComment,
valid: true
};
let $form;
let $notesContainer;
beforeEach(() => {
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').html(sampleComment);
});
it('should not render a script tag', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').html(updatedComment);
$noteEl.find('.js-comment-save-button').click();
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
});
});
describe('getFormData', () => {
let $form;
let sampleComment;
beforeEach(() => {
this.notes = new Notes('', []);
$form = $('form');
sampleComment = 'foobar';
});
it('should return form metadata object from form reference', () => {
$form.find('textarea.js-note-text').val(sampleComment);
const { formData, formContent, formAction } = this.notes.getFormData($form);
expect(formData.indexOf(sampleComment) > -1).toBe(true);
expect(formContent).toEqual(sampleComment);
expect(formAction).toEqual($form.attr('action'));
});
it('should return form metadata with sanitized formContent from form reference', () => {
spyOn(_, 'escape').and.callFake(htmlEscape);
sampleComment = '';
$form.find('textarea.js-note-text').val(sampleComment);
const { formContent } = this.notes.getFormData($form);
expect(_.escape).toHaveBeenCalledWith(sampleComment);
expect(formContent).toEqual('<script>alert("Boom!");</script>');
});
});
describe('hasQuickActions', () => {
beforeEach(() => {
this.notes = new Notes('', []);
});
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 hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeTruthy();
});
it('should return false when comment does NOT begin with a quick action', () => {
const sampleComment = 'Hey, /unassign Merging this';
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeFalsy();
});
it('should return false when comment does NOT have any quick actions', () => {
const sampleComment = 'Looking good, Awesome!';
const hasQuickActions = this.notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeFalsy();
});
});
describe('stripQuickActions', () => {
it('should strip quick actions from the comment which begins with a quick action', () => {
this.notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('');
});
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
this.notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('Merging this');
});
it('should NOT strip string that has slashes within', () => {
this.notes = new Notes();
const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
const stripedComment = this.notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe(sampleComment);
});
});
describe('getQuickActionDescription', () => {
const availableQuickActions = [
{ name: 'close', description: 'Close this issue', params: [] },
{ name: 'title', description: 'Change title', params: [{}] },
{ name: 'estimate', description: 'Set time estimate', params: [{}] }
];
beforeEach(() => {
this.notes = new Notes();
});
it('should return executing quick action description when note has single quick action', () => {
const sampleComment = '/close';
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying command to close this issue');
});
it('should return generic multiple quick action description when note has multiple quick actions', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe('Applying multiple commands');
});
it('should return generic quick action description when available quick actions list is not populated', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
});
});
describe('createPlaceholderNote', () => {
const sampleComment = 'foobar';
const uniqueId = 'b1234-a4567';
const currentUsername = 'root';
const currentUserFullname = 'Administrator';
const currentUserAvatar = 'avatar_url';
beforeEach(() => {
this.notes = new Notes('', []);
});
it('should return constructed placeholder element for regular note based on form contents', () => {
const $tempNote = this.notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: false,
currentUsername,
currentUserFullname,
currentUserAvatar,
});
const $tempNoteHeader = $tempNote.find('.note-header');
expect($tempNote.prop('nodeName')).toEqual('LI');
expect($tempNote.attr('id')).toEqual(uniqueId);
expect($tempNote.hasClass('being-posted')).toBeTruthy();
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
$tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
expect($(this).attr('href')).toEqual(`/${currentUsername}`);
});
expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeFalsy();
expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual(currentUserFullname);
expect($tempNoteHeader.find('.note-headline-light').text().trim()).toEqual(`@${currentUsername}`);
expect($tempNote.find('.note-body .note-text p').text().trim()).toEqual(sampleComment);
});
it('should return constructed placeholder element for discussion note based on form contents', () => {
const $tempNote = this.notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: true,
currentUsername,
currentUserFullname
});
expect($tempNote.prop('nodeName')).toEqual('LI');
expect($tempNote.find('.timeline-content').hasClass('discussion')).toBeTruthy();
});
it('should return a escaped user name', () => {
const currentUserFullnameXSS = 'Foo ';
const $tempNote = this.notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: false,
currentUsername,
currentUserFullname: currentUserFullnameXSS,
currentUserAvatar,
});
const $tempNoteHeader = $tempNote.find('.note-header');
expect($tempNoteHeader.find('.hidden-xs').text().trim()).toEqual('Foo <script>alert("XSS")</script>');
});
});
describe('createPlaceholderSystemNote', () => {
const sampleCommandDescription = 'Applying command to close this issue';
const uniqueId = 'b1234-a4567';
beforeEach(() => {
this.notes = new Notes('', []);
spyOn(_, 'escape').and.callFake(htmlEscape);
});
it('should return constructed placeholder element for system note based on form contents', () => {
const $tempNote = this.notes.createPlaceholderSystemNote({
formContent: sampleCommandDescription,
uniqueId,
});
expect($tempNote.prop('nodeName')).toEqual('LI');
expect($tempNote.attr('id')).toEqual(uniqueId);
expect($tempNote.hasClass('being-posted')).toBeTruthy();
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
expect($tempNote.find('.timeline-content i').text().trim()).toEqual(sampleCommandDescription);
});
});
describe('appendFlash', () => {
beforeEach(() => {
this.notes = new Notes();
});
it('shows a flash message', () => {
this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline);
expect($('.flash-alert').is(':visible')).toBeTruthy();
});
});
describe('clearFlash', () => {
beforeEach(() => {
$(document).off('ajax:success');
this.notes = new Notes();
});
it('hides visible flash message', () => {
this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline);
this.notes.clearFlash();
expect($('.flash-alert').is(':visible')).toBeFalsy();
});
});
});
}).call(window);