BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
f21a3155
Commit
f21a3155
authored
May 18, 2017
by
Eric Eastwood
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix missing .original-note-content and trailing new line edge case
Fix
https://gitlab.com/gitlab-org/gitlab-ce/issues/32449
parent
319cab41
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
144 additions
and
63 deletions
+144
-63
notes.js
app/assets/javascripts/notes.js
+22
-20
_edit.html.haml
app/views/shared/notes/_edit.html.haml
+0
-2
_note.html.haml
app/views/shared/notes/_note.html.haml
+2
-0
note_polling_spec.rb
spec/features/issues/note_polling_spec.rb
+68
-33
notes_spec.js
spec/javascripts/notes_spec.js
+52
-8
No files found.
app/assets/javascripts/notes.js
View file @
f21a3155
...
@@ -306,7 +306,7 @@ const normalizeNewlines = function(str) {
...
@@ -306,7 +306,7 @@ const normalizeNewlines = function(str) {
}
}
const
$note
=
$notesList
.
find
(
`#note_
${
noteEntity
.
id
}
`
);
const
$note
=
$notesList
.
find
(
`#note_
${
noteEntity
.
id
}
`
);
if
(
this
.
isNewNote
(
noteEntity
))
{
if
(
Notes
.
isNewNote
(
noteEntity
,
this
.
note_ids
))
{
this
.
note_ids
.
push
(
noteEntity
.
id
);
this
.
note_ids
.
push
(
noteEntity
.
id
);
const
$newNote
=
Notes
.
animateAppendNote
(
noteEntity
.
html
,
$notesList
);
const
$newNote
=
Notes
.
animateAppendNote
(
noteEntity
.
html
,
$notesList
);
...
@@ -319,7 +319,7 @@ const normalizeNewlines = function(str) {
...
@@ -319,7 +319,7 @@ const normalizeNewlines = function(str) {
return
this
.
updateNotesCount
(
1
);
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.
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
else
if
(
thi
s
.
isUpdatedNote
(
noteEntity
,
$note
))
{
else
if
(
Note
s
.
isUpdatedNote
(
noteEntity
,
$note
))
{
const
isEditing
=
$note
.
hasClass
(
'is-editing'
);
const
isEditing
=
$note
.
hasClass
(
'is-editing'
);
const
initialContent
=
normalizeNewlines
(
const
initialContent
=
normalizeNewlines
(
$note
.
find
(
'.original-note-content'
).
text
().
trim
()
$note
.
find
(
'.original-note-content'
).
text
().
trim
()
...
@@ -347,23 +347,6 @@ const normalizeNewlines = function(str) {
...
@@ -347,23 +347,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
()
{
Notes
.
prototype
.
isParallelView
=
function
()
{
return
Cookies
.
get
(
'diff_view'
)
===
'parallel'
;
return
Cookies
.
get
(
'diff_view'
)
===
'parallel'
;
};
};
...
@@ -376,7 +359,7 @@ const normalizeNewlines = function(str) {
...
@@ -376,7 +359,7 @@ const normalizeNewlines = function(str) {
Notes
.
prototype
.
renderDiscussionNote
=
function
(
noteEntity
,
$form
)
{
Notes
.
prototype
.
renderDiscussionNote
=
function
(
noteEntity
,
$form
)
{
var
discussionContainer
,
form
,
row
,
lineType
,
diffAvatarContainer
;
var
discussionContainer
,
form
,
row
,
lineType
,
diffAvatarContainer
;
if
(
!
this
.
isNewNote
(
noteEntity
))
{
if
(
!
Notes
.
isNewNote
(
noteEntity
,
this
.
note_ids
))
{
return
;
return
;
}
}
this
.
note_ids
.
push
(
noteEntity
.
id
);
this
.
note_ids
.
push
(
noteEntity
.
id
);
...
@@ -1137,6 +1120,25 @@ const normalizeNewlines = function(str) {
...
@@ -1137,6 +1120,25 @@ const normalizeNewlines = function(str) {
return
$form
;
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
()
{
Notes
.
checkMergeRequestStatus
=
function
()
{
if
(
gl
.
utils
.
getPagePath
(
1
)
===
'merge_requests'
)
{
if
(
gl
.
utils
.
getPagePath
(
1
)
===
'merge_requests'
)
{
gl
.
mrWidget
.
checkStatus
();
gl
.
mrWidget
.
checkStatus
();
...
...
app/views/shared/notes/_edit.html.haml
View file @
f21a3155
.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
%textarea
.hidden.js-task-list-field.original-task-list
{
data:
{
update_url:
note_url
(
note
)
}
}=
note
.
note
app/views/shared/notes/_note.html.haml
View file @
f21a3155
...
@@ -43,6 +43,8 @@
...
@@ -43,6 +43,8 @@
.note-text.md
.note-text.md
=
note
.
redacted_note_html
=
note
.
redacted_note_html
=
edited_time_ago_with_tooltip
(
note
,
placement:
'bottom'
,
html_class:
'note_edited_ago'
)
=
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
-
if
note_editable
=
render
'shared/notes/edit'
,
note:
note
=
render
'shared/notes/edit'
,
note:
note
.note-awards
.note-awards
...
...
spec/features/issues/note_polling_spec.rb
View file @
f21a3155
...
@@ -18,58 +18,93 @@ feature 'Issue notes polling', :feature, :js do
...
@@ -18,58 +18,93 @@ feature 'Issue notes polling', :feature, :js do
end
end
describe
'updates'
do
describe
'updates'
do
let
(
:user
)
{
create
(
:user
)
}
context
'when from own user'
do
let
(
:note_text
)
{
"Hello World"
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:updated_text
)
{
"Bye World"
}
let
(
:note_text
)
{
"Hello World"
}
let!
(
:existing_note
)
{
create
(
:note
,
noteable:
issue
,
project:
project
,
author:
user
,
note:
note_text
)
}
let
(
:updated_text
)
{
"Bye World"
}
let!
(
:existing_note
)
{
create
(
:note
,
noteable:
issue
,
project:
project
,
author:
user
,
note:
note_text
)
}
before
do
before
do
login_as
(
user
)
login_as
(
user
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
end
end
it
'displays the updated content'
do
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
}
"
,
text:
note_text
)
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
.original-note-content"
,
visible:
false
)
update_note
(
existing_note
,
updated_text
)
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
}
"
,
text:
updated_text
)
end
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
.original-note-content"
,
visible:
false
)
end
it
'when editing but have not changed anything, and an update comes in, show the updated content in the textarea
'
do
it
'displays the updated content
'
do
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
"
,
text:
note_text
)
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note
_text
)
update_note
(
existing_note
,
updated
_text
)
update_note
(
existing_note
,
updated_text
)
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
"
,
text:
updated_text
)
end
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
updated_text
)
it
'when editing but have not changed anything, and an update comes in, show the updated content in the textarea'
do
end
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
it
'when editing but you changed some things, and an update comes in, show a warning'
do
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note_text
)
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note
_text
)
update_note
(
existing_note
,
updated
_text
)
find
(
"#note_
#{
existing_note
.
id
}
.js-note-text"
).
set
(
'something random'
)
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
updated_text
)
end
update_note
(
existing_note
,
updated_text
)
it
'when editing but you changed some things, and an update comes in, show a warning'
do
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
expect
(
page
).
to
have_selector
(
".alert"
)
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note_text
)
end
find
(
"#note_
#{
existing_note
.
id
}
.js-note-text"
).
set
(
'something random'
)
update_note
(
existing_note
,
updated_text
)
it
'when editing but you changed some things, an update comes in, and you press cancel, show the updated content'
do
expect
(
page
).
to
have_selector
(
".alert"
)
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
end
it
'when editing but you changed some things, an update comes in, and you press cancel, show the updated content'
do
find
(
"#note_
#{
existing_note
.
id
}
.js-note-edit"
).
click
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note_text
)
find
(
"#note_
#{
existing_note
.
id
}
.js-note-text"
).
set
(
'something random'
)
update_note
(
existing_note
,
updated_text
)
find
(
"#note_
#{
existing_note
.
id
}
.note-edit-cancel"
).
click
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
"
,
text:
updated_text
)
end
end
expect
(
page
).
to
have_field
(
"note[note]"
,
with:
note_text
)
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
)
}
find
(
"#note_
#{
existing_note
.
id
}
.js-note-text"
).
set
(
'something random'
)
before
do
login_as
(
user2
)
visit
namespace_project_issue_path
(
project
.
namespace
,
project
,
issue
)
end
update_note
(
existing_note
,
updated_text
)
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
)
find
(
"#note_
#{
existing_note
.
id
}
.note-edit-cancel"
).
click
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
}
"
,
text:
updated_text
)
expect
(
page
).
to
have_selector
(
"#note_
#{
existing_note
.
id
}
.original-note-content"
,
visible:
false
)
end
end
end
end
end
...
...
spec/javascripts/notes_spec.js
View file @
f21a3155
...
@@ -99,8 +99,6 @@ import '~/notes';
...
@@ -99,8 +99,6 @@ import '~/notes';
notes
=
jasmine
.
createSpyObj
(
'notes'
,
[
notes
=
jasmine
.
createSpyObj
(
'notes'
,
[
'refresh'
,
'refresh'
,
'isNewNote'
,
'isUpdatedNote'
,
'collapseLongCommitList'
,
'collapseLongCommitList'
,
'updateNotesCount'
,
'updateNotesCount'
,
'putConflictEditWarningInPlace'
'putConflictEditWarningInPlace'
...
@@ -110,13 +108,15 @@ import '~/notes';
...
@@ -110,13 +108,15 @@ import '~/notes';
notes
.
updatedNotesTrackingMap
=
{};
notes
.
updatedNotesTrackingMap
=
{};
spyOn
(
gl
.
utils
,
'localTimeAgo'
);
spyOn
(
gl
.
utils
,
'localTimeAgo'
);
spyOn
(
Notes
,
'isNewNote'
).
and
.
callThrough
();
spyOn
(
Notes
,
'isUpdatedNote'
).
and
.
callThrough
();
spyOn
(
Notes
,
'animateAppendNote'
).
and
.
callThrough
();
spyOn
(
Notes
,
'animateAppendNote'
).
and
.
callThrough
();
spyOn
(
Notes
,
'animateUpdateNote'
).
and
.
callThrough
();
spyOn
(
Notes
,
'animateUpdateNote'
).
and
.
callThrough
();
});
});
describe
(
'when adding note'
,
()
=>
{
describe
(
'when adding note'
,
()
=>
{
it
(
'should call .animateAppendNote'
,
()
=>
{
it
(
'should call .animateAppendNote'
,
()
=>
{
n
otes
.
isNewNote
.
and
.
returnValue
(
true
);
N
otes
.
isNewNote
.
and
.
returnValue
(
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
);
...
@@ -125,7 +125,8 @@ import '~/notes';
...
@@ -125,7 +125,8 @@ import '~/notes';
describe
(
'when note was edited'
,
()
=>
{
describe
(
'when note was edited'
,
()
=>
{
it
(
'should call .animateUpdateNote'
,
()
=>
{
it
(
'should call .animateUpdateNote'
,
()
=>
{
notes
.
isUpdatedNote
.
and
.
returnValue
(
true
);
Notes
.
isNewNote
.
and
.
returnValue
(
false
);
Notes
.
isUpdatedNote
.
and
.
returnValue
(
true
);
const
$note
=
$
(
'<div>'
);
const
$note
=
$
(
'<div>'
);
$notesList
.
find
.
and
.
returnValue
(
$note
);
$notesList
.
find
.
and
.
returnValue
(
$note
);
Notes
.
prototype
.
renderNote
.
call
(
notes
,
note
,
null
,
$notesList
);
Notes
.
prototype
.
renderNote
.
call
(
notes
,
note
,
null
,
$notesList
);
...
@@ -135,7 +136,8 @@ import '~/notes';
...
@@ -135,7 +136,8 @@ import '~/notes';
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
.
isUpdatedNote
.
and
.
returnValue
(
true
);
Notes
.
isNewNote
.
and
.
returnValue
(
false
);
Notes
.
isUpdatedNote
.
and
.
returnValue
(
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>
...
@@ -147,7 +149,8 @@ import '~/notes';
...
@@ -147,7 +149,8 @@ import '~/notes';
});
});
it
(
'should call .putConflictEditWarningInPlace'
,
()
=>
{
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">
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>
...
@@ -161,6 +164,47 @@ import '~/notes';
...
@@ -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'
,
()
=>
{
describe
(
'renderDiscussionNote'
,
()
=>
{
let
discussionContainer
;
let
discussionContainer
;
let
note
;
let
note
;
...
@@ -180,15 +224,15 @@ import '~/notes';
...
@@ -180,15 +224,15 @@ import '~/notes';
row
=
jasmine
.
createSpyObj
(
'row'
,
[
'prevAll'
,
'first'
,
'find'
]);
row
=
jasmine
.
createSpyObj
(
'row'
,
[
'prevAll'
,
'first'
,
'find'
]);
notes
=
jasmine
.
createSpyObj
(
'notes'
,
[
notes
=
jasmine
.
createSpyObj
(
'notes'
,
[
'isNewNote'
,
'isParallelView'
,
'isParallelView'
,
'updateNotesCount'
,
'updateNotesCount'
,
]);
]);
notes
.
note_ids
=
[];
notes
.
note_ids
=
[];
spyOn
(
gl
.
utils
,
'localTimeAgo'
);
spyOn
(
gl
.
utils
,
'localTimeAgo'
);
spyOn
(
Notes
,
'isNewNote'
);
spyOn
(
Notes
,
'animateAppendNote'
);
spyOn
(
Notes
,
'animateAppendNote'
);
n
otes
.
isNewNote
.
and
.
returnValue
(
true
);
N
otes
.
isNewNote
.
and
.
returnValue
(
true
);
notes
.
isParallelView
.
and
.
returnValue
(
false
);
notes
.
isParallelView
.
and
.
returnValue
(
false
);
row
.
prevAll
.
and
.
returnValue
(
row
);
row
.
prevAll
.
and
.
returnValue
(
row
);
row
.
first
.
and
.
returnValue
(
row
);
row
.
first
.
and
.
returnValue
(
row
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment