Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
c0e743bf
Commit
c0e743bf
authored
Jun 05, 2019
by
Martin Hanzel
Committed by
Mike Greiling
Jun 05, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate old notes app test from Karma to Jest
parent
fabca7ab
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
291 additions
and
250 deletions
+291
-250
app/assets/javascripts/notes.js
app/assets/javascripts/notes.js
+4
-0
spec/frontend/helpers/jest_helpers.js
spec/frontend/helpers/jest_helpers.js
+24
-0
spec/frontend/helpers/timeout.js
spec/frontend/helpers/timeout.js
+18
-2
spec/frontend/notes/old_notes_spec.js
spec/frontend/notes/old_notes_spec.js
+245
-248
No files found.
app/assets/javascripts/notes.js
View file @
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
'
;
...
...
spec/frontend/helpers/jest_helpers.js
0 → 100644
View file @
c0e743bf
/* 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
;
}
spec/frontend/helpers/timeout.js
View file @
c0e743bf
const
NS_PER_SEC
=
1
e9
;
const
NS_PER_SEC
=
1
e9
;
const
NS_PER_MS
=
1
e6
;
const
NS_PER_MS
=
1
e6
;
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 >
${
t
estT
imeoutNS
/
NS_PER_MS
}
ms)!`
,
`Test took too long (
${
elapsedNS
/
NS_PER_MS
}
ms >
${
timeoutNS
/
NS_PER_MS
}
ms)!`
,
);
);
}
}
});
});
...
...
spec/
javascripts/
notes_spec.js
→
spec/
frontend/notes/old_
notes_spec.js
View file @
c0e743bf
/* 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
();
'
&
'
:
'
&
'
,
loadFixtures
(
fixture
);
'
<
'
:
'
<
'
,
'
>
'
:
'
>
'
,
// Re-declare this here so that test_setup.js#beforeEach() doesn't
'
"
'
:
'
"
'
,
// overwrite it.
"
'
"
:
'
'
'
,
mockAxios
=
new
MockAdapter
(
axios
);
'
`
'
:
'
`
'
,
}[
a
];
return
escapedToken
;
});
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,76 +106,74 @@ describe('Notes', function() {
...
@@ -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
;
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
'
;
notes
.
updateNote
(
updatedNote
,
$targetNote
);
this
.
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
.
returnValu
e
(
hash
);
jest
.
spyOn
(
urlUtility
,
'
getLocationHash
'
).
mockReturnValueOnc
e
(
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
.
returnValu
e
(
'
note_doesnotexist
'
);
jest
.
spyOn
(
urlUtility
,
'
getLocationHash
'
).
mockReturnValueOnc
e
(
'
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
.
returnValu
e
(
null
);
jest
.
spyOn
(
urlUtility
,
'
getLocationHash
'
).
mockReturnValueOnc
e
(
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
.
returnValu
e
(
true
);
Notes
.
isNewNote
.
mockReturnValueOnc
e
(
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
.
returnValu
e
(
false
);
Notes
.
isNewNote
.
mockReturnValueOnc
e
(
false
);
Notes
.
isUpdatedNote
.
and
.
returnValu
e
(
true
);
Notes
.
isUpdatedNote
.
mockReturnValueOnc
e
(
true
);
const
$note
=
$
(
'
<div>
'
);
const
$note
=
$
(
'
<div>
'
);
$notesList
.
find
.
and
.
returnValu
e
(
$note
);
$notesList
.
find
.
mockReturnValueOnc
e
(
$note
);
const
$newNote
=
$
(
note
.
html
);
const
$newNote
=
$
(
note
.
html
);
Notes
.
animateUpdateNote
.
and
.
returnValu
e
(
$newNote
);
Notes
.
animateUpdateNote
.
mockReturnValueOnc
e
(
$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
.
returnValu
e
(
false
);
Notes
.
isNewNote
.
mockReturnValueOnc
e
(
false
);
Notes
.
isUpdatedNote
.
and
.
returnValu
e
(
true
);
Notes
.
isUpdatedNote
.
mockReturnValueOnc
e
(
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
.
returnValu
e
(
$note
);
$notesList
.
find
.
mockReturnValueOnc
e
(
$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
.
returnValu
e
(
false
);
Notes
.
isNewNote
.
mockReturnValueOnc
e
(
false
);
Notes
.
isUpdatedNote
.
and
.
returnValu
e
(
true
);
Notes
.
isUpdatedNote
.
mockReturnValueOnc
e
(
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
.
returnValu
e
(
$note
);
$notesList
.
find
.
mockReturnValueOnc
e
(
$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
.
r
eturnValue
(
true
);
Notes
.
isNewNote
.
mockR
eturnValue
(
true
);
notes
.
isParallelView
.
and
.
r
eturnValue
(
false
);
notes
.
isParallelView
.
mockR
eturnValue
(
false
);
row
.
prevAll
.
and
.
r
eturnValue
(
row
);
row
.
prevAll
.
mockR
eturnValue
(
row
);
row
.
first
.
and
.
r
eturnValue
(
row
);
row
.
first
.
mockR
eturnValue
(
row
);
row
.
find
.
and
.
r
eturnValue
(
row
);
row
.
find
.
mockR
eturnValue
(
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
.
r
eturnValue
(
''
);
body
.
attr
.
mockR
eturnValue
(
''
);
});
});
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
);
mock
Axios
.
onPost
(
NOTES_POST_PATH
).
reply
(
200
,
note
);
}
}
function
mockNotesPostError
()
{
function
mockNotesPostError
()
{
mock
.
onPost
(
NOTES_POST_PATH
).
networkError
();
mock
Axios
.
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
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
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
(()
=>
{
mock
Axios
.
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
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
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
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
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();
timeout
Promise
()
deferred
Promise()
.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
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
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
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
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
(
'
<script>alert("Boom!");</script>
'
);
expect
(
formContent
).
toEqual
(
'
<script>alert("Boom!");</script>
'
);
...
@@ -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
\n
Merging this
'
;
const
sampleComment
=
'
/wip
\n
/milestone %1.0
\n
/merge
\n
/unassign
\n
Merging 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
();
});
});
});
});
});
});
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