Merge branch '32449-fix-note-comparison-trailing-newline-edge-case' into 'master'
Fix note flicker with note you can't edit and trailing new line edge case Closes #32449 See merge request !11472
This commit is contained in:
commit
010e1dacab
5 changed files with 156 additions and 75 deletions
|
@ -307,7 +307,7 @@ const normalizeNewlines = function(str) {
|
|||
}
|
||||
|
||||
const $note = $notesList.find(`#note_${noteEntity.id}`);
|
||||
if (this.isNewNote(noteEntity)) {
|
||||
if (Notes.isNewNote(noteEntity, this.note_ids)) {
|
||||
this.note_ids.push(noteEntity.id);
|
||||
|
||||
const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
|
||||
|
@ -320,7 +320,7 @@ const normalizeNewlines = function(str) {
|
|||
return this.updateNotesCount(1);
|
||||
}
|
||||
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
|
||||
else if (this.isUpdatedNote(noteEntity, $note)) {
|
||||
else if (Notes.isUpdatedNote(noteEntity, $note)) {
|
||||
const isEditing = $note.hasClass('is-editing');
|
||||
const initialContent = normalizeNewlines(
|
||||
$note.find('.original-note-content').text().trim()
|
||||
|
@ -348,23 +348,6 @@ const normalizeNewlines = function(str) {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Check if note does not exists on page
|
||||
*/
|
||||
|
||||
Notes.prototype.isNewNote = function(noteEntity) {
|
||||
return $.inArray(noteEntity.id, this.note_ids) === -1;
|
||||
};
|
||||
|
||||
Notes.prototype.isUpdatedNote = function(noteEntity, $note) {
|
||||
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
|
||||
const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
|
||||
const currentNoteText = normalizeNewlines(
|
||||
$note.find('.original-note-content').text().trim()
|
||||
);
|
||||
return sanitizedNoteNote !== currentNoteText;
|
||||
};
|
||||
|
||||
Notes.prototype.isParallelView = function() {
|
||||
return Cookies.get('diff_view') === 'parallel';
|
||||
};
|
||||
|
@ -377,7 +360,7 @@ const normalizeNewlines = function(str) {
|
|||
|
||||
Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
|
||||
var discussionContainer, form, row, lineType, diffAvatarContainer;
|
||||
if (!this.isNewNote(noteEntity)) {
|
||||
if (!Notes.isNewNote(noteEntity, this.note_ids)) {
|
||||
return;
|
||||
}
|
||||
this.note_ids.push(noteEntity.id);
|
||||
|
@ -1138,6 +1121,25 @@ const normalizeNewlines = function(str) {
|
|||
return $form;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if note does not exists on page
|
||||
*/
|
||||
Notes.isNewNote = function(noteEntity, noteIds) {
|
||||
return $.inArray(noteEntity.id, noteIds) === -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if $note already contains the `noteEntity` content
|
||||
*/
|
||||
Notes.isUpdatedNote = function(noteEntity, $note) {
|
||||
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
|
||||
const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
|
||||
const currentNoteText = normalizeNewlines(
|
||||
$note.find('.original-note-content').text().trim()
|
||||
);
|
||||
return sanitizedNoteEntityText !== currentNoteText;
|
||||
};
|
||||
|
||||
Notes.checkMergeRequestStatus = function() {
|
||||
if (gl.utils.getPagePath(1) === 'merge_requests') {
|
||||
gl.mrWidget.checkStatus();
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
|
||||
#{note.note}
|
||||
%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: note_url(note) } }= note.note
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
.note-text.md
|
||||
= note.redacted_note_html
|
||||
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
|
||||
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
|
||||
#{note.note}
|
||||
- if note_editable
|
||||
= render 'shared/notes/edit', note: note
|
||||
.note-awards
|
||||
|
|
|
@ -18,6 +18,7 @@ feature 'Issue notes polling', :feature, :js do
|
|||
end
|
||||
|
||||
describe 'updates' do
|
||||
context 'when from own user' do
|
||||
let(:user) { create(:user) }
|
||||
let(:note_text) { "Hello World" }
|
||||
let(:updated_text) { "Bye World" }
|
||||
|
@ -28,6 +29,16 @@ feature 'Issue notes polling', :feature, :js do
|
|||
visit namespace_project_issue_path(project.namespace, project, issue)
|
||||
end
|
||||
|
||||
it 'has .original-note-content to compare against' do
|
||||
expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
|
||||
expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
|
||||
|
||||
update_note(existing_note, updated_text)
|
||||
|
||||
expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
|
||||
expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
|
||||
end
|
||||
|
||||
it 'displays the updated content' do
|
||||
expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
|
||||
|
||||
|
@ -73,6 +84,30 @@ feature 'Issue notes polling', :feature, :js do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when from another user' do
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:note_text) { "Hello World" }
|
||||
let(:updated_text) { "Bye World" }
|
||||
let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) }
|
||||
|
||||
before do
|
||||
login_as(user2)
|
||||
visit namespace_project_issue_path(project.namespace, project, issue)
|
||||
end
|
||||
|
||||
it 'has .original-note-content to compare against' do
|
||||
expect(page).to have_selector("#note_#{existing_note.id}", text: note_text)
|
||||
expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
|
||||
|
||||
update_note(existing_note, updated_text)
|
||||
|
||||
expect(page).to have_selector("#note_#{existing_note.id}", text: updated_text)
|
||||
expect(page).to have_selector("#note_#{existing_note.id} .original-note-content", visible: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_note(note, new_text)
|
||||
note.update(note: new_text)
|
||||
page.execute_script('notes.refresh();')
|
||||
|
|
|
@ -99,8 +99,6 @@ import '~/notes';
|
|||
|
||||
notes = jasmine.createSpyObj('notes', [
|
||||
'refresh',
|
||||
'isNewNote',
|
||||
'isUpdatedNote',
|
||||
'collapseLongCommitList',
|
||||
'updateNotesCount',
|
||||
'putConflictEditWarningInPlace'
|
||||
|
@ -110,13 +108,15 @@ import '~/notes';
|
|||
notes.updatedNotesTrackingMap = {};
|
||||
|
||||
spyOn(gl.utils, 'localTimeAgo');
|
||||
spyOn(Notes, 'isNewNote').and.callThrough();
|
||||
spyOn(Notes, 'isUpdatedNote').and.callThrough();
|
||||
spyOn(Notes, 'animateAppendNote').and.callThrough();
|
||||
spyOn(Notes, 'animateUpdateNote').and.callThrough();
|
||||
});
|
||||
|
||||
describe('when adding note', () => {
|
||||
it('should call .animateAppendNote', () => {
|
||||
notes.isNewNote.and.returnValue(true);
|
||||
Notes.isNewNote.and.returnValue(true);
|
||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||
|
||||
expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
|
||||
|
@ -125,7 +125,8 @@ import '~/notes';
|
|||
|
||||
describe('when note was edited', () => {
|
||||
it('should call .animateUpdateNote', () => {
|
||||
notes.isUpdatedNote.and.returnValue(true);
|
||||
Notes.isNewNote.and.returnValue(false);
|
||||
Notes.isUpdatedNote.and.returnValue(true);
|
||||
const $note = $('<div>');
|
||||
$notesList.find.and.returnValue($note);
|
||||
Notes.prototype.renderNote.call(notes, note, null, $notesList);
|
||||
|
@ -135,7 +136,8 @@ import '~/notes';
|
|||
|
||||
describe('while editing', () => {
|
||||
it('should update textarea if nothing has been touched', () => {
|
||||
notes.isUpdatedNote.and.returnValue(true);
|
||||
Notes.isNewNote.and.returnValue(false);
|
||||
Notes.isUpdatedNote.and.returnValue(true);
|
||||
const $note = $(`<div class="is-editing">
|
||||
<div class="original-note-content">initial</div>
|
||||
<textarea class="js-note-text">initial</textarea>
|
||||
|
@ -147,7 +149,8 @@ import '~/notes';
|
|||
});
|
||||
|
||||
it('should call .putConflictEditWarningInPlace', () => {
|
||||
notes.isUpdatedNote.and.returnValue(true);
|
||||
Notes.isNewNote.and.returnValue(false);
|
||||
Notes.isUpdatedNote.and.returnValue(true);
|
||||
const $note = $(`<div class="is-editing">
|
||||
<div class="original-note-content">initial</div>
|
||||
<textarea class="js-note-text">different</textarea>
|
||||
|
@ -161,6 +164,47 @@ import '~/notes';
|
|||
});
|
||||
});
|
||||
|
||||
describe('isUpdatedNote', () => {
|
||||
it('should consider same note text as the same', () => {
|
||||
const result = Notes.isUpdatedNote(
|
||||
{
|
||||
note: 'initial'
|
||||
},
|
||||
$(`<div>
|
||||
<div class="original-note-content">initial</div>
|
||||
</div>`)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should consider same note with trailing newline as the same', () => {
|
||||
const result = Notes.isUpdatedNote(
|
||||
{
|
||||
note: 'initial\n'
|
||||
},
|
||||
$(`<div>
|
||||
<div class="original-note-content">initial\n</div>
|
||||
</div>`)
|
||||
);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should consider different notes as different', () => {
|
||||
const result = Notes.isUpdatedNote(
|
||||
{
|
||||
note: 'foo'
|
||||
},
|
||||
$(`<div>
|
||||
<div class="original-note-content">bar</div>
|
||||
</div>`)
|
||||
);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderDiscussionNote', () => {
|
||||
let discussionContainer;
|
||||
let note;
|
||||
|
@ -180,15 +224,15 @@ import '~/notes';
|
|||
row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
|
||||
|
||||
notes = jasmine.createSpyObj('notes', [
|
||||
'isNewNote',
|
||||
'isParallelView',
|
||||
'updateNotesCount',
|
||||
]);
|
||||
notes.note_ids = [];
|
||||
|
||||
spyOn(gl.utils, 'localTimeAgo');
|
||||
spyOn(Notes, 'isNewNote');
|
||||
spyOn(Notes, 'animateAppendNote');
|
||||
notes.isNewNote.and.returnValue(true);
|
||||
Notes.isNewNote.and.returnValue(true);
|
||||
notes.isParallelView.and.returnValue(false);
|
||||
row.prevAll.and.returnValue(row);
|
||||
row.first.and.returnValue(row);
|
||||
|
|
Loading…
Reference in a new issue