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
1
Merge Requests
1
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
nexedi
gitlab-ce
Commits
346ba81d
Commit
346ba81d
authored
Jun 06, 2020
by
Himanshu Kapoor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate tests in javascripts/* to Jest
Migrate most (except one) tests in javascripts/* to Jest
parent
7b973da3
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
725 additions
and
222 deletions
+725
-222
spec/frontend/awards_handler_spec.js
spec/frontend/awards_handler_spec.js
+57
-54
spec/frontend/collapsed_sidebar_todo_spec.js
spec/frontend/collapsed_sidebar_todo_spec.js
+11
-10
spec/frontend/comment_type_toggle_spec.js
spec/frontend/comment_type_toggle_spec.js
+169
-0
spec/frontend/gl_form_spec.js
spec/frontend/gl_form_spec.js
+34
-29
spec/frontend/global_search_input_spec.js
spec/frontend/global_search_input_spec.js
+24
-24
spec/frontend/labels_issue_sidebar_spec.js
spec/frontend/labels_issue_sidebar_spec.js
+6
-4
spec/frontend/line_highlighter_spec.js
spec/frontend/line_highlighter_spec.js
+268
-0
spec/frontend/merge_request_tabs_spec.js
spec/frontend/merge_request_tabs_spec.js
+80
-81
spec/frontend/right_sidebar_spec.js
spec/frontend/right_sidebar_spec.js
+11
-11
spec/frontend/shortcuts_spec.js
spec/frontend/shortcuts_spec.js
+46
-0
spec/frontend/user_popovers_spec.js
spec/frontend/user_popovers_spec.js
+7
-3
spec/frontend/zen_mode_spec.js
spec/frontend/zen_mode_spec.js
+12
-6
No files found.
spec/
javascripts
/awards_handler_spec.js
→
spec/
frontend
/awards_handler_spec.js
View file @
346ba81d
...
...
@@ -2,6 +2,7 @@ import $ from 'jquery';
import
Cookies
from
'
js-cookie
'
;
import
loadAwardsHandler
from
'
~/awards_handler
'
;
import
'
~/lib/utils/common_utils
'
;
import
waitForPromises
from
'
./helpers/wait_for_promises
'
;
window
.
gl
=
window
.
gl
||
{};
window
.
gon
=
window
.
gon
||
{};
...
...
@@ -10,28 +11,32 @@ let openAndWaitForEmojiMenu;
let
awardsHandler
=
null
;
const
urlRoot
=
gon
.
relative_url_root
;
const
lazyAssert
=
function
(
done
,
assertFn
)
{
setTimeout
(
function
()
{
assertFn
();
done
();
// Maybe jasmine.clock here?
},
333
);
const
lazyAssert
=
(
done
,
assertFn
)
=>
{
jest
.
runOnlyPendingTimers
();
waitForPromises
()
.
then
(()
=>
{
assertFn
();
done
();
})
.
catch
(
e
=>
{
throw
e
;
});
};
describe
(
'
AwardsHandler
'
,
function
()
{
describe
(
'
AwardsHandler
'
,
()
=>
{
preloadFixtures
(
'
snippets/show.html
'
);
beforeEach
(
function
(
done
)
{
beforeEach
(
done
=>
{
loadFixtures
(
'
snippets/show.html
'
);
loadAwardsHandler
(
true
)
.
then
(
obj
=>
{
awardsHandler
=
obj
;
spyOn
(
awardsHandler
,
'
postEmoji
'
).
and
.
callFake
((
button
,
url
,
emoji
,
cb
)
=>
cb
());
jest
.
spyOn
(
awardsHandler
,
'
postEmoji
'
).
mockImplementation
((
button
,
url
,
emoji
,
cb
)
=>
cb
());
done
();
})
.
catch
(
fail
);
.
catch
(
done
.
fail
);
let
isEmojiMenuBuilt
=
false
;
openAndWaitForEmojiMenu
=
function
()
{
openAndWaitForEmojiMenu
=
()
=>
{
return
new
Promise
(
resolve
=>
{
if
(
isEmojiMenuBuilt
)
{
resolve
();
...
...
@@ -49,7 +54,7 @@ describe('AwardsHandler', function() {
};
});
afterEach
(
function
()
{
afterEach
(
()
=>
{
// restore original url root value
gon
.
relative_url_root
=
urlRoot
;
...
...
@@ -59,12 +64,12 @@ describe('AwardsHandler', function() {
awardsHandler
.
destroy
();
});
describe
(
'
::showEmojiMenu
'
,
function
()
{
it
(
'
should show emoji menu when Add emoji button clicked
'
,
function
(
done
)
{
describe
(
'
::showEmojiMenu
'
,
()
=>
{
it
(
'
should show emoji menu when Add emoji button clicked
'
,
done
=>
{
$
(
'
.js-add-award
'
)
.
eq
(
0
)
.
click
();
lazyAssert
(
done
,
function
()
{
lazyAssert
(
done
,
()
=>
{
const
$emojiMenu
=
$
(
'
.emoji-menu
'
);
expect
(
$emojiMenu
.
length
).
toBe
(
1
);
...
...
@@ -74,20 +79,20 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should also show emoji menu for the smiley icon in notes
'
,
function
(
done
)
{
it
(
'
should also show emoji menu for the smiley icon in notes
'
,
done
=>
{
$
(
'
.js-add-award.note-action-button
'
).
click
();
lazyAssert
(
done
,
function
()
{
lazyAssert
(
done
,
()
=>
{
const
$emojiMenu
=
$
(
'
.emoji-menu
'
);
expect
(
$emojiMenu
.
length
).
toBe
(
1
);
});
});
it
(
'
should remove emoji menu when body is clicked
'
,
function
(
done
)
{
it
(
'
should remove emoji menu when body is clicked
'
,
done
=>
{
$
(
'
.js-add-award
'
)
.
eq
(
0
)
.
click
();
lazyAssert
(
done
,
function
()
{
lazyAssert
(
done
,
()
=>
{
const
$emojiMenu
=
$
(
'
.emoji-menu
'
);
$
(
'
body
'
).
click
();
...
...
@@ -97,11 +102,11 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should not remove emoji menu when search is clicked
'
,
function
(
done
)
{
it
(
'
should not remove emoji menu when search is clicked
'
,
done
=>
{
$
(
'
.js-add-award
'
)
.
eq
(
0
)
.
click
();
lazyAssert
(
done
,
function
()
{
lazyAssert
(
done
,
()
=>
{
const
$emojiMenu
=
$
(
'
.emoji-menu
'
);
$
(
'
.emoji-search
'
).
click
();
...
...
@@ -112,8 +117,8 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
::addAwardToEmojiBar
'
,
function
()
{
it
(
'
should add emoji to votes block
'
,
function
()
{
describe
(
'
::addAwardToEmojiBar
'
,
()
=>
{
it
(
'
should add emoji to votes block
'
,
()
=>
{
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
awardsHandler
.
addAwardToEmojiBar
(
$votesBlock
,
'
heart
'
,
false
);
const
$emojiButton
=
$votesBlock
.
find
(
'
[data-name=heart]
'
);
...
...
@@ -123,7 +128,7 @@ describe('AwardsHandler', function() {
expect
(
$votesBlock
.
hasClass
(
'
hidden
'
)).
toBe
(
false
);
});
it
(
'
should remove the emoji when we click again
'
,
function
()
{
it
(
'
should remove the emoji when we click again
'
,
()
=>
{
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
awardsHandler
.
addAwardToEmojiBar
(
$votesBlock
,
'
heart
'
,
false
);
awardsHandler
.
addAwardToEmojiBar
(
$votesBlock
,
'
heart
'
,
false
);
...
...
@@ -132,7 +137,7 @@ describe('AwardsHandler', function() {
expect
(
$emojiButton
.
length
).
toBe
(
0
);
});
it
(
'
should decrement the emoji counter
'
,
function
()
{
it
(
'
should decrement the emoji counter
'
,
()
=>
{
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
awardsHandler
.
addAwardToEmojiBar
(
$votesBlock
,
'
heart
'
,
false
);
const
$emojiButton
=
$votesBlock
.
find
(
'
[data-name=heart]
'
);
...
...
@@ -144,8 +149,8 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
::userAuthored
'
,
function
()
{
it
(
'
should update tooltip to user authored title
'
,
function
()
{
describe
(
'
::userAuthored
'
,
()
=>
{
it
(
'
should update tooltip to user authored title
'
,
()
=>
{
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
$thumbsUpEmoji
.
attr
(
'
data-title
'
,
'
sam
'
);
...
...
@@ -156,27 +161,25 @@ describe('AwardsHandler', function() {
);
});
it
(
'
should restore tooltip back to initial vote list
'
,
function
()
{
jasmine
.
clock
().
install
();
it
(
'
should restore tooltip back to initial vote list
'
,
()
=>
{
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
$thumbsUpEmoji
.
attr
(
'
data-title
'
,
'
sam
'
);
awardsHandler
.
userAuthored
(
$thumbsUpEmoji
);
jasmine
.
clock
().
tick
(
2801
);
jasmine
.
clock
().
uninstall
();
jest
.
advanceTimersByTime
(
2801
);
expect
(
$thumbsUpEmoji
.
data
(
'
originalTitle
'
)).
toBe
(
'
sam
'
);
});
});
describe
(
'
::getAwardUrl
'
,
function
()
{
it
(
'
returns the url for request
'
,
function
()
{
describe
(
'
::getAwardUrl
'
,
()
=>
{
it
(
'
returns the url for request
'
,
()
=>
{
expect
(
awardsHandler
.
getAwardUrl
()).
toBe
(
'
http://test.host/snippets/1/toggle_award_emoji
'
);
});
});
describe
(
'
::addAward and ::checkMutuality
'
,
function
()
{
it
(
'
should handle :+1: and :-1: mutuality
'
,
function
()
{
describe
(
'
::addAward and ::checkMutuality
'
,
()
=>
{
it
(
'
should handle :+1: and :-1: mutuality
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
...
...
@@ -194,8 +197,8 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
::removeEmoji
'
,
function
()
{
it
(
'
should remove emoji
'
,
function
()
{
describe
(
'
::removeEmoji
'
,
()
=>
{
it
(
'
should remove emoji
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
awardsHandler
.
addAward
(
$votesBlock
,
awardUrl
,
'
fire
'
,
false
);
...
...
@@ -207,8 +210,8 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
::addYouToUserList
'
,
function
()
{
it
(
'
should prepend "You" to the award tooltip
'
,
function
()
{
describe
(
'
::addYouToUserList
'
,
()
=>
{
it
(
'
should prepend "You" to the award tooltip
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
...
...
@@ -219,7 +222,7 @@ describe('AwardsHandler', function() {
expect
(
$thumbsUpEmoji
.
data
(
'
originalTitle
'
)).
toBe
(
'
You, sam, jerry, max, and andy
'
);
});
it
(
'
handles the special case where "You" is not cleanly comma separated
'
,
function
()
{
it
(
'
handles the special case where "You" is not cleanly comma separated
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
...
...
@@ -231,8 +234,8 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
::removeYouToUserList
'
,
function
()
{
it
(
'
removes "You" from the front of the tooltip
'
,
function
()
{
describe
(
'
::removeYouToUserList
'
,
()
=>
{
it
(
'
removes "You" from the front of the tooltip
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
...
...
@@ -244,7 +247,7 @@ describe('AwardsHandler', function() {
expect
(
$thumbsUpEmoji
.
data
(
'
originalTitle
'
)).
toBe
(
'
sam, jerry, max, and andy
'
);
});
it
(
'
handles the special case where "You" is not cleanly comma separated
'
,
function
()
{
it
(
'
handles the special case where "You" is not cleanly comma separated
'
,
()
=>
{
const
awardUrl
=
awardsHandler
.
getAwardUrl
();
const
$votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
const
$thumbsUpEmoji
=
$votesBlock
.
find
(
'
[data-name=thumbsup]
'
).
parent
();
...
...
@@ -258,7 +261,7 @@ describe('AwardsHandler', function() {
});
describe
(
'
::searchEmojis
'
,
()
=>
{
it
(
'
should filter the emoji
'
,
function
(
done
)
{
it
(
'
should filter the emoji
'
,
done
=>
{
openAndWaitForEmojiMenu
()
.
then
(()
=>
{
expect
(
$
(
'
[data-name=angel]
'
).
is
(
'
:visible
'
)).
toBe
(
true
);
...
...
@@ -276,7 +279,7 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should clear the search when searching for nothing
'
,
function
(
done
)
{
it
(
'
should clear the search when searching for nothing
'
,
done
=>
{
openAndWaitForEmojiMenu
()
.
then
(()
=>
{
awardsHandler
.
searchEmojis
(
'
ali
'
);
...
...
@@ -298,9 +301,9 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
emoji menu
'
,
function
()
{
describe
(
'
emoji menu
'
,
()
=>
{
const
emojiSelector
=
'
[data-name="sunglasses"]
'
;
const
openEmojiMenuAndAddEmoji
=
function
()
{
const
openEmojiMenuAndAddEmoji
=
()
=>
{
return
openAndWaitForEmojiMenu
().
then
(()
=>
{
const
$menu
=
$
(
'
.emoji-menu
'
);
const
$block
=
$
(
'
.js-awards-block
'
);
...
...
@@ -315,7 +318,7 @@ describe('AwardsHandler', function() {
});
};
it
(
'
should add selected emoji to awards block
'
,
function
(
done
)
{
it
(
'
should add selected emoji to awards block
'
,
done
=>
{
openEmojiMenuAndAddEmoji
()
.
then
(
done
)
.
catch
(
err
=>
{
...
...
@@ -323,7 +326,7 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should remove already selected emoji
'
,
function
(
done
)
{
it
(
'
should remove already selected emoji
'
,
done
=>
{
openEmojiMenuAndAddEmoji
()
.
then
(()
=>
{
$
(
'
.js-add-award
'
)
...
...
@@ -344,13 +347,13 @@ describe('AwardsHandler', function() {
});
});
describe
(
'
frequently used emojis
'
,
function
()
{
describe
(
'
frequently used emojis
'
,
()
=>
{
beforeEach
(()
=>
{
// Clear it out
Cookies
.
set
(
'
frequently_used_emojis
'
,
''
);
});
it
(
'
shouldn
\'
t have any "Frequently used" heading if no frequently used emojis
'
,
function
(
done
)
{
it
(
'
shouldn
\'
t have any "Frequently used" heading if no frequently used emojis
'
,
done
=>
{
return
openAndWaitForEmojiMenu
()
.
then
(()
=>
{
const
emojiMenu
=
document
.
querySelector
(
'
.emoji-menu
'
);
...
...
@@ -364,7 +367,7 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should have any frequently used section when there are frequently used emojis
'
,
function
(
done
)
{
it
(
'
should have any frequently used section when there are frequently used emojis
'
,
done
=>
{
awardsHandler
.
addEmojiToFrequentlyUsedList
(
'
8ball
'
);
return
openAndWaitForEmojiMenu
()
...
...
@@ -383,7 +386,7 @@ describe('AwardsHandler', function() {
});
});
it
(
'
should disregard invalid frequently used emoji that are being attempted to be added
'
,
function
()
{
it
(
'
should disregard invalid frequently used emoji that are being attempted to be added
'
,
()
=>
{
awardsHandler
.
addEmojiToFrequentlyUsedList
(
'
8ball
'
);
awardsHandler
.
addEmojiToFrequentlyUsedList
(
'
invalid_emoji
'
);
awardsHandler
.
addEmojiToFrequentlyUsedList
(
'
grinning
'
);
...
...
@@ -391,7 +394,7 @@ describe('AwardsHandler', function() {
expect
(
awardsHandler
.
getFrequentlyUsedEmojis
()).
toEqual
([
'
8ball
'
,
'
grinning
'
]);
});
it
(
'
should disregard invalid frequently used emoji already set in cookie
'
,
function
()
{
it
(
'
should disregard invalid frequently used emoji already set in cookie
'
,
()
=>
{
Cookies
.
set
(
'
frequently_used_emojis
'
,
'
8ball,invalid_emoji,grinning
'
);
expect
(
awardsHandler
.
getFrequentlyUsedEmojis
()).
toEqual
([
'
8ball
'
,
'
grinning
'
]);
...
...
spec/
javascripts
/collapsed_sidebar_todo_spec.js
→
spec/
frontend
/collapsed_sidebar_todo_spec.js
View file @
346ba81d
...
...
@@ -3,7 +3,8 @@ import { clone } from 'lodash';
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
Sidebar
from
'
~/right_sidebar
'
;
import
timeoutPromise
from
'
./helpers/set_timeout_promise_helper
'
;
import
waitForPromises
from
'
./helpers/wait_for_promises
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
describe
(
'
Issuable right sidebar collapsed todo toggle
'
,
()
=>
{
const
fixtureName
=
'
issues/open-issue.html
'
;
...
...
@@ -23,7 +24,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
mock
=
new
MockAdapter
(
axios
);
mock
.
onPost
(
`
${
gl
.
TEST_HOST
}
/frontend-fixtures/issues-project/todos`
).
reply
(()
=>
{
mock
.
onPost
(
`
${
TEST_HOST
}
/frontend-fixtures/issues-project/todos`
).
reply
(()
=>
{
const
response
=
clone
(
todoData
);
return
[
200
,
response
];
...
...
@@ -64,7 +65,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
toggle todo state
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon .todo-undone
'
),
).
not
.
toBeNull
();
...
...
@@ -82,7 +83,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
toggle todo state of expanded todo toggle
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
expect
(
document
.
querySelector
(
'
.issuable-sidebar-header .js-issuable-todo
'
).
textContent
.
trim
(),
).
toBe
(
'
Mark as done
'
);
...
...
@@ -94,7 +95,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
toggles todo button tooltip
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
)
...
...
@@ -108,7 +109,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
marks todo as done
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
timeoutPromise
()
waitForPromises
()
.
then
(()
=>
{
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon .todo-undone
'
),
...
...
@@ -116,7 +117,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
})
.
then
(
timeoutPromise
)
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon .todo-undone
'
),
...
...
@@ -133,7 +134,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
updates aria-label to Mark as done
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
)
...
...
@@ -147,7 +148,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it
(
'
updates aria-label to add todo
'
,
done
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
timeoutPromise
()
waitForPromises
()
.
then
(()
=>
{
expect
(
document
...
...
@@ -157,7 +158,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
})
.
then
(
timeoutPromise
)
.
then
(
waitForPromises
)
.
then
(()
=>
{
expect
(
document
...
...
spec/
javascripts
/comment_type_toggle_spec.js
→
spec/
frontend
/comment_type_toggle_spec.js
View file @
346ba81d
import
CommentTypeToggle
from
'
~/comment_type_toggle
'
;
import
DropLab
from
'
~/droplab/drop_lab
'
;
import
InputSetter
from
'
~/droplab/plugins/input_setter
'
;
describe
(
'
CommentTypeToggle
'
,
function
()
{
describe
(
'
class constructor
'
,
function
()
{
beforeEach
(
function
()
{
this
.
dropdownTrigger
=
{};
this
.
dropdownList
=
{};
this
.
noteTypeInput
=
{};
this
.
submitButton
=
{};
this
.
closeButton
=
{};
this
.
commentTypeToggle
=
new
CommentTypeToggle
({
dropdownTrigger
:
this
.
dropdownTrigger
,
dropdownList
:
this
.
dropdownList
,
noteTypeInput
:
this
.
noteTypeInput
,
submitButton
:
this
.
submitButton
,
closeButton
:
this
.
closeButton
,
describe
(
'
CommentTypeToggle
'
,
()
=>
{
const
testContext
=
{};
describe
(
'
class constructor
'
,
()
=>
{
beforeEach
(()
=>
{
testContext
.
dropdownTrigger
=
{};
testContext
.
dropdownList
=
{};
testContext
.
noteTypeInput
=
{};
testContext
.
submitButton
=
{};
testContext
.
closeButton
=
{};
testContext
.
commentTypeToggle
=
new
CommentTypeToggle
({
dropdownTrigger
:
testContext
.
dropdownTrigger
,
dropdownList
:
testContext
.
dropdownList
,
noteTypeInput
:
testContext
.
noteTypeInput
,
submitButton
:
testContext
.
submitButton
,
closeButton
:
testContext
.
closeButton
,
});
});
it
(
'
should set .dropdownTrigger
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
dropdownTrigger
).
toBe
(
this
.
dropdownTrigger
);
it
(
'
should set .dropdownTrigger
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
dropdownTrigger
).
toBe
(
testContext
.
dropdownTrigger
);
});
it
(
'
should set .dropdownList
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
dropdownList
).
toBe
(
this
.
dropdownList
);
it
(
'
should set .dropdownList
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
dropdownList
).
toBe
(
testContext
.
dropdownList
);
});
it
(
'
should set .noteTypeInput
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
noteTypeInput
).
toBe
(
this
.
noteTypeInput
);
it
(
'
should set .noteTypeInput
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
noteTypeInput
).
toBe
(
testContext
.
noteTypeInput
);
});
it
(
'
should set .submitButton
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
submitButton
).
toBe
(
this
.
submitButton
);
it
(
'
should set .submitButton
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
submitButton
).
toBe
(
testContext
.
submitButton
);
});
it
(
'
should set .closeButton
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
closeButton
).
toBe
(
this
.
closeButton
);
it
(
'
should set .closeButton
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
closeButton
).
toBe
(
testContext
.
closeButton
);
});
it
(
'
should set .reopenButton
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
reopenButton
).
toBe
(
this
.
reopenButton
);
it
(
'
should set .reopenButton
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
reopenButton
).
toBe
(
testContext
.
reopenButton
);
});
});
describe
(
'
initDroplab
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
commentTypeToggle
=
{
describe
(
'
initDroplab
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
commentTypeToggle
=
{
dropdownTrigger
:
{},
dropdownList
:
{},
noteTypeInput
:
{},
...
...
@@ -54,44 +57,38 @@ describe('CommentTypeToggle', function() {
closeButton
:
{},
setConfig
:
()
=>
{},
};
t
his
.
config
=
{};
t
estContext
.
config
=
{};
this
.
droplab
=
jasmine
.
createSpyObj
(
'
droplab
'
,
[
'
init
'
]);
jest
.
spyOn
(
DropLab
.
prototype
,
'
init
'
).
mockImplementation
();
jest
.
spyOn
(
DropLab
.
prototype
,
'
constructor
'
).
mockImplementation
();
this
.
droplabConstructor
=
spyOnDependency
(
CommentTypeToggle
,
'
DropLab
'
).
and
.
returnValue
(
this
.
droplab
,
);
spyOn
(
this
.
commentTypeToggle
,
'
setConfig
'
).
and
.
returnValue
(
this
.
config
);
CommentTypeToggle
.
prototype
.
initDroplab
.
call
(
this
.
commentTypeToggle
);
});
jest
.
spyOn
(
testContext
.
commentTypeToggle
,
'
setConfig
'
).
mockReturnValue
(
testContext
.
config
);
it
(
'
should instantiate a DropLab instance
'
,
function
()
{
expect
(
this
.
droplabConstructor
).
toHaveBeenCalled
();
CommentTypeToggle
.
prototype
.
initDroplab
.
call
(
testContext
.
commentTypeToggle
);
});
it
(
'
should
set .droplab
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
droplab
).
toBe
(
this
.
droplab
);
it
(
'
should
instantiate a DropLab instance and set .droplab
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
droplab
instanceof
DropLab
).
toBe
(
true
);
});
it
(
'
should call .setConfig
'
,
function
()
{
expect
(
t
his
.
commentTypeToggle
.
setConfig
).
toHaveBeenCalled
();
it
(
'
should call .setConfig
'
,
()
=>
{
expect
(
t
estContext
.
commentTypeToggle
.
setConfig
).
toHaveBeenCalled
();
});
it
(
'
should call DropLab.prototype.init
'
,
function
()
{
expect
(
this
.
droplab
.
init
).
toHaveBeenCalledWith
(
t
his
.
commentTypeToggle
.
dropdownTrigger
,
t
his
.
commentTypeToggle
.
dropdownList
,
it
(
'
should call DropLab.prototype.init
'
,
()
=>
{
expect
(
DropLab
.
prototype
.
init
).
toHaveBeenCalledWith
(
t
estContext
.
commentTypeToggle
.
dropdownTrigger
,
t
estContext
.
commentTypeToggle
.
dropdownList
,
[
InputSetter
],
t
his
.
config
,
t
estContext
.
config
,
);
});
});
describe
(
'
setConfig
'
,
function
()
{
describe
(
'
if no .closeButton is provided
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
commentTypeToggle
=
{
describe
(
'
setConfig
'
,
()
=>
{
describe
(
'
if no .closeButton is provided
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
commentTypeToggle
=
{
dropdownTrigger
:
{},
dropdownList
:
{},
noteTypeInput
:
{},
...
...
@@ -99,26 +96,28 @@ describe('CommentTypeToggle', function() {
reopenButton
:
{},
};
this
.
setConfig
=
CommentTypeToggle
.
prototype
.
setConfig
.
call
(
this
.
commentTypeToggle
);
testContext
.
setConfig
=
CommentTypeToggle
.
prototype
.
setConfig
.
call
(
testContext
.
commentTypeToggle
,
);
});
it
(
'
should not add .closeButton related InputSetter config
'
,
function
()
{
expect
(
t
his
.
setConfig
).
toEqual
({
it
(
'
should not add .closeButton related InputSetter config
'
,
()
=>
{
expect
(
t
estContext
.
setConfig
).
toEqual
({
InputSetter
:
[
{
input
:
t
his
.
commentTypeToggle
.
noteTypeInput
,
input
:
t
estContext
.
commentTypeToggle
.
noteTypeInput
,
valueAttribute
:
'
data-value
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
submitButton
,
input
:
t
estContext
.
commentTypeToggle
.
submitButton
,
valueAttribute
:
'
data-submit-text
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
reopenButton
,
input
:
t
estContext
.
commentTypeToggle
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
reopenButton
,
input
:
t
estContext
.
commentTypeToggle
.
reopenButton
,
valueAttribute
:
'
data-reopen-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
},
...
...
@@ -127,9 +126,9 @@ describe('CommentTypeToggle', function() {
});
});
describe
(
'
if no .reopenButton is provided
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
commentTypeToggle
=
{
describe
(
'
if no .reopenButton is provided
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
commentTypeToggle
=
{
dropdownTrigger
:
{},
dropdownList
:
{},
noteTypeInput
:
{},
...
...
@@ -137,26 +136,28 @@ describe('CommentTypeToggle', function() {
closeButton
:
{},
};
this
.
setConfig
=
CommentTypeToggle
.
prototype
.
setConfig
.
call
(
this
.
commentTypeToggle
);
testContext
.
setConfig
=
CommentTypeToggle
.
prototype
.
setConfig
.
call
(
testContext
.
commentTypeToggle
,
);
});
it
(
'
should not add .reopenButton related InputSetter config
'
,
function
()
{
expect
(
t
his
.
setConfig
).
toEqual
({
it
(
'
should not add .reopenButton related InputSetter config
'
,
()
=>
{
expect
(
t
estContext
.
setConfig
).
toEqual
({
InputSetter
:
[
{
input
:
t
his
.
commentTypeToggle
.
noteTypeInput
,
input
:
t
estContext
.
commentTypeToggle
.
noteTypeInput
,
valueAttribute
:
'
data-value
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
submitButton
,
input
:
t
estContext
.
commentTypeToggle
.
submitButton
,
valueAttribute
:
'
data-submit-text
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
closeButton
,
input
:
t
estContext
.
commentTypeToggle
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
},
{
input
:
t
his
.
commentTypeToggle
.
closeButton
,
input
:
t
estContext
.
commentTypeToggle
.
closeButton
,
valueAttribute
:
'
data-close-text
'
,
inputAttribute
:
'
data-alternative-text
'
,
},
...
...
spec/
javascripts
/gl_form_spec.js
→
spec/
frontend
/gl_form_spec.js
View file @
346ba81d
...
...
@@ -5,51 +5,56 @@ import '~/lib/utils/text_utility';
import
'
~/lib/utils/common_utils
'
;
describe
(
'
GLForm
'
,
()
=>
{
describe
(
'
when instantiated
'
,
function
()
{
const
testContext
=
{};
describe
(
'
when instantiated
'
,
()
=>
{
beforeEach
(
done
=>
{
this
.
form
=
$
(
'
<form class="gfm-form"><textarea class="js-gfm-input"></form>
'
);
this
.
textarea
=
this
.
form
.
find
(
'
textarea
'
);
spyOn
(
$
.
prototype
,
'
off
'
).
and
.
returnValue
(
this
.
textarea
);
spyOn
(
$
.
prototype
,
'
on
'
).
and
.
returnValue
(
this
.
textarea
);
spyOn
(
$
.
prototype
,
'
css
'
);
this
.
glForm
=
new
GLForm
(
this
.
form
,
false
);
setTimeout
(()
=>
{
$
.
prototype
.
off
.
calls
.
reset
();
$
.
prototype
.
on
.
calls
.
reset
();
$
.
prototype
.
css
.
calls
.
reset
();
testContext
.
form
=
$
(
'
<form class="gfm-form"><textarea class="js-gfm-input"></form>
'
);
testContext
.
textarea
=
testContext
.
form
.
find
(
'
textarea
'
);
jest
.
spyOn
(
$
.
prototype
,
'
off
'
).
mockReturnValue
(
testContext
.
textarea
);
jest
.
spyOn
(
$
.
prototype
,
'
on
'
).
mockReturnValue
(
testContext
.
textarea
);
jest
.
spyOn
(
$
.
prototype
,
'
css
'
).
mockImplementation
(()
=>
{});
testContext
.
glForm
=
new
GLForm
(
testContext
.
form
,
false
);
setImmediate
(()
=>
{
$
.
prototype
.
off
.
mockClear
();
$
.
prototype
.
on
.
mockClear
();
$
.
prototype
.
css
.
mockClear
();
done
();
});
});
describe
(
'
setupAutosize
'
,
()
=>
{
beforeEach
(
done
=>
{
this
.
glForm
.
setupAutosize
();
setTimeout
(()
=>
{
testContext
.
glForm
.
setupAutosize
();
setImmediate
(()
=>
{
done
();
});
});
it
(
'
should register an autosize event handler on the textarea
'
,
()
=>
{
expect
(
$
.
prototype
.
off
).
toHaveBeenCalledWith
(
'
autosize:resized
'
);
expect
(
$
.
prototype
.
on
).
toHaveBeenCalledWith
(
'
autosize:resized
'
,
jasmine
.
any
(
Function
));
expect
(
$
.
prototype
.
on
).
toHaveBeenCalledWith
(
'
autosize:resized
'
,
expect
.
any
(
Function
));
});
it
(
'
should register a mouseup event handler on the textarea
'
,
()
=>
{
expect
(
$
.
prototype
.
off
).
toHaveBeenCalledWith
(
'
mouseup.autosize
'
);
expect
(
$
.
prototype
.
on
).
toHaveBeenCalledWith
(
'
mouseup.autosize
'
,
jasmine
.
any
(
Function
));
expect
(
$
.
prototype
.
on
).
toHaveBeenCalledWith
(
'
mouseup.autosize
'
,
expect
.
any
(
Function
));
});
it
(
'
should set the resize css property to vertical
'
,
()
=>
{
jest
.
runOnlyPendingTimers
();
expect
(
$
.
prototype
.
css
).
toHaveBeenCalledWith
(
'
resize
'
,
'
vertical
'
);
});
});
describe
(
'
setHeightData
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
$
.
prototype
,
'
data
'
);
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
and
.
r
eturnValue
(
200
);
t
his
.
glForm
.
setHeightData
();
jest
.
spyOn
(
$
.
prototype
,
'
data
'
).
mockImplementation
(()
=>
{}
);
jest
.
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
mockR
eturnValue
(
200
);
t
estContext
.
glForm
.
setHeightData
();
});
it
(
'
should set the height data attribute
'
,
()
=>
{
...
...
@@ -64,12 +69,12 @@ describe('GLForm', () => {
describe
(
'
destroyAutosize
'
,
()
=>
{
describe
(
'
when called
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
$
.
prototype
,
'
data
'
);
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
and
.
r
eturnValue
(
200
);
spyOn
(
window
,
'
outerHeight
'
).
and
.
returnValue
(
400
)
;
spyOn
(
autosize
,
'
destroy
'
);
jest
.
spyOn
(
$
.
prototype
,
'
data
'
).
mockImplementation
(()
=>
{}
);
jest
.
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
mockR
eturnValue
(
200
);
window
.
outerHeight
=
()
=>
400
;
jest
.
spyOn
(
autosize
,
'
destroy
'
).
mockImplementation
(()
=>
{}
);
t
his
.
glForm
.
destroyAutosize
();
t
estContext
.
glForm
.
destroyAutosize
();
});
it
(
'
should call outerHeight
'
,
()
=>
{
...
...
@@ -81,7 +86,7 @@ describe('GLForm', () => {
});
it
(
'
should call autosize destroy
'
,
()
=>
{
expect
(
autosize
.
destroy
).
toHaveBeenCalledWith
(
t
his
.
textarea
);
expect
(
autosize
.
destroy
).
toHaveBeenCalledWith
(
t
estContext
.
textarea
);
});
it
(
'
should set the data-height attribute
'
,
()
=>
{
...
...
@@ -98,11 +103,11 @@ describe('GLForm', () => {
});
it
(
'
should return undefined if the data-height equals the outerHeight
'
,
()
=>
{
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
and
.
r
eturnValue
(
200
);
spyOn
(
$
.
prototype
,
'
data
'
).
and
.
r
eturnValue
(
200
);
spyOn
(
autosize
,
'
destroy
'
);
jest
.
spyOn
(
$
.
prototype
,
'
outerHeight
'
).
mockR
eturnValue
(
200
);
jest
.
spyOn
(
$
.
prototype
,
'
data
'
).
mockR
eturnValue
(
200
);
jest
.
spyOn
(
autosize
,
'
destroy
'
).
mockImplementation
(()
=>
{}
);
expect
(
t
his
.
glForm
.
destroyAutosize
()).
toBeUndefined
();
expect
(
t
estContext
.
glForm
.
destroyAutosize
()).
toBeUndefined
();
expect
(
autosize
.
destroy
).
not
.
toHaveBeenCalled
();
});
});
...
...
spec/
javascripts
/global_search_input_spec.js
→
spec/
frontend
/global_search_input_spec.js
View file @
346ba81d
...
...
@@ -28,7 +28,7 @@ describe('Global search input dropdown', () => {
const
groupName
=
'
Gitlab Org
'
;
const
removeBodyAttributes
=
function
()
{
const
removeBodyAttributes
=
()
=>
{
const
$body
=
$
(
'
body
'
);
$body
.
removeAttr
(
'
data-page
'
);
...
...
@@ -38,7 +38,7 @@ describe('Global search input dropdown', () => {
// Add required attributes to body before starting the test.
// section would be dashboard|group|project
const
addBodyAttributes
=
function
(
section
)
{
const
addBodyAttributes
=
section
=>
{
if
(
section
==
null
)
{
section
=
'
dashboard
'
;
}
...
...
@@ -57,12 +57,12 @@ describe('Global search input dropdown', () => {
}
};
const
disableProjectIssues
=
function
()
{
const
disableProjectIssues
=
()
=>
{
document
.
querySelector
(
'
.js-search-project-options
'
).
setAttribute
(
'
data-issues-disabled
'
,
true
);
};
// Mock `gl` object in window for dashboard specific page. App code will need it.
const
mockDashboardOptions
=
function
()
{
const
mockDashboardOptions
=
()
=>
{
window
.
gl
||
(
window
.
gl
=
{});
return
(
window
.
gl
.
dashboardOptions
=
{
issuesPath
:
dashboardIssuesPath
,
...
...
@@ -71,7 +71,7 @@ describe('Global search input dropdown', () => {
};
// Mock `gl` object in window for project specific page. App code will need it.
const
mockProjectOptions
=
function
()
{
const
mockProjectOptions
=
()
=>
{
window
.
gl
||
(
window
.
gl
=
{});
return
(
window
.
gl
.
projectOptions
=
{
'
gitlab-ce
'
:
{
...
...
@@ -82,7 +82,7 @@ describe('Global search input dropdown', () => {
});
};
const
mockGroupOptions
=
function
()
{
const
mockGroupOptions
=
()
=>
{
window
.
gl
||
(
window
.
gl
=
{});
return
(
window
.
gl
.
groupOptions
=
{
'
gitlab-org
'
:
{
...
...
@@ -93,7 +93,7 @@ describe('Global search input dropdown', () => {
});
};
const
assertLinks
=
function
(
list
,
issuesPath
,
mrsPath
)
{
const
assertLinks
=
(
list
,
issuesPath
,
mrsPath
)
=>
{
if
(
issuesPath
)
{
const
issuesAssignedToMeLink
=
`a[href="
${
issuesPath
}
/?assignee_username=
${
userName
}
"]`
;
const
issuesIHaveCreatedLink
=
`a[href="
${
issuesPath
}
/?author_username=
${
userName
}
"]`
;
...
...
@@ -113,7 +113,7 @@ describe('Global search input dropdown', () => {
};
preloadFixtures
(
'
static/global_search_input.html
'
);
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
loadFixtures
(
'
static/global_search_input.html
'
);
window
.
gon
=
{};
...
...
@@ -123,13 +123,13 @@ describe('Global search input dropdown', () => {
return
(
widget
=
initGlobalSearchInput
());
});
afterEach
(
function
()
{
afterEach
(
()
=>
{
// Undo what we did to the shared <body>
removeBodyAttributes
();
window
.
gon
=
{};
});
it
(
'
should show Dashboard specific dropdown menu
'
,
function
()
{
it
(
'
should show Dashboard specific dropdown menu
'
,
()
=>
{
addBodyAttributes
();
mockDashboardOptions
();
widget
.
searchInput
.
triggerHandler
(
'
focus
'
);
...
...
@@ -137,7 +137,7 @@ describe('Global search input dropdown', () => {
return
assertLinks
(
list
,
dashboardIssuesPath
,
dashboardMRsPath
);
});
it
(
'
should show Group specific dropdown menu
'
,
function
()
{
it
(
'
should show Group specific dropdown menu
'
,
()
=>
{
addBodyAttributes
(
'
group
'
);
mockGroupOptions
();
widget
.
searchInput
.
triggerHandler
(
'
focus
'
);
...
...
@@ -145,7 +145,7 @@ describe('Global search input dropdown', () => {
return
assertLinks
(
list
,
groupIssuesPath
,
groupMRsPath
);
});
it
(
'
should show Project specific dropdown menu
'
,
function
()
{
it
(
'
should show Project specific dropdown menu
'
,
()
=>
{
addBodyAttributes
(
'
project
'
);
mockProjectOptions
();
widget
.
searchInput
.
triggerHandler
(
'
focus
'
);
...
...
@@ -153,7 +153,7 @@ describe('Global search input dropdown', () => {
return
assertLinks
(
list
,
projectIssuesPath
,
projectMRsPath
);
});
it
(
'
should show only Project mergeRequest dropdown menu items when project issues are disabled
'
,
function
()
{
it
(
'
should show only Project mergeRequest dropdown menu items when project issues are disabled
'
,
()
=>
{
addBodyAttributes
(
'
project
'
);
disableProjectIssues
();
mockProjectOptions
();
...
...
@@ -162,7 +162,7 @@ describe('Global search input dropdown', () => {
assertLinks
(
list
,
null
,
projectMRsPath
);
});
it
(
'
should not show category related menu if there is text in the input
'
,
function
()
{
it
(
'
should not show category related menu if there is text in the input
'
,
()
=>
{
addBodyAttributes
(
'
project
'
);
mockProjectOptions
();
widget
.
searchInput
.
val
(
'
help
'
);
...
...
@@ -173,12 +173,12 @@ describe('Global search input dropdown', () => {
expect
(
list
.
find
(
link
).
length
).
toBe
(
0
);
});
it
(
'
should not submit the search form when selecting an autocomplete row with the keyboard
'
,
function
()
{
it
(
'
should not submit the search form when selecting an autocomplete row with the keyboard
'
,
()
=>
{
const
ENTER
=
13
;
const
DOWN
=
40
;
addBodyAttributes
();
mockDashboardOptions
(
true
);
const
submitSpy
=
spyOnEvent
(
'
form
'
,
'
submit
'
);
const
submitSpy
=
jest
.
spyOn
(
document
.
querySelector
(
'
form
'
)
,
'
submit
'
);
widget
.
searchInput
.
triggerHandler
(
'
focus
'
);
widget
.
wrap
.
trigger
(
$
.
Event
(
'
keydown
'
,
{
which
:
DOWN
}));
const
enterKeyEvent
=
$
.
Event
(
'
keydown
'
,
{
which
:
ENTER
});
...
...
@@ -186,16 +186,16 @@ describe('Global search input dropdown', () => {
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect
(
submitSpy
).
not
.
toHaveBeen
Trigger
ed
();
expect
(
submitSpy
).
not
.
toHaveBeen
Call
ed
();
});
describe
(
'
disableDropdown
'
,
function
()
{
beforeEach
(
function
()
{
describe
(
'
disableDropdown
'
,
()
=>
{
beforeEach
(
()
=>
{
widget
.
enableDropdown
();
});
it
(
'
should close the Dropdown
'
,
function
()
{
const
toggleSpy
=
spyOn
(
widget
.
dropdownToggle
,
'
dropdown
'
);
it
(
'
should close the Dropdown
'
,
()
=>
{
const
toggleSpy
=
jest
.
spyOn
(
widget
.
dropdownToggle
,
'
dropdown
'
);
widget
.
dropdown
.
addClass
(
'
show
'
);
widget
.
disableDropdown
();
...
...
@@ -204,9 +204,9 @@ describe('Global search input dropdown', () => {
});
});
describe
(
'
enableDropdown
'
,
function
()
{
it
(
'
should open the Dropdown
'
,
function
()
{
const
toggleSpy
=
spyOn
(
widget
.
dropdownToggle
,
'
dropdown
'
);
describe
(
'
enableDropdown
'
,
()
=>
{
it
(
'
should open the Dropdown
'
,
()
=>
{
const
toggleSpy
=
jest
.
spyOn
(
widget
.
dropdownToggle
,
'
dropdown
'
);
widget
.
enableDropdown
();
expect
(
toggleSpy
).
toHaveBeenCalledWith
(
'
toggle
'
);
...
...
spec/
javascripts
/labels_issue_sidebar_spec.js
→
spec/
frontend
/labels_issue_sidebar_spec.js
View file @
346ba81d
...
...
@@ -21,7 +21,9 @@ function testLabelClicks(labelOrder, done) {
.
get
(
0
)
.
click
();
setTimeout
(()
=>
{
jest
.
runOnlyPendingTimers
();
setImmediate
(()
=>
{
const
labelsInDropdown
=
$
(
'
.dropdown-content a
'
);
expect
(
labelsInDropdown
.
length
).
toBe
(
10
);
...
...
@@ -38,11 +40,11 @@ function testLabelClicks(labelOrder, done) {
.
get
(
0
)
.
click
();
set
Timeout
(()
=>
{
set
Immediate
(()
=>
{
expect
(
$
(
'
.sidebar-collapsed-icon
'
).
attr
(
'
data-original-title
'
)).
toBe
(
labelOrder
);
done
();
}
,
0
);
}
,
0
);
});
});
}
describe
(
'
Issue dropdown sidebar
'
,
()
=>
{
...
...
spec/
javascripts
/line_highlighter_spec.js
→
spec/
frontend
/line_highlighter_spec.js
View file @
346ba81d
/* eslint-disable
dot-notation,
no-return-assign, no-new, no-underscore-dangle */
/* eslint-disable no-return-assign, no-new, no-underscore-dangle */
import
$
from
'
jquery
'
;
import
LineHighlighter
from
'
~/line_highlighter
'
;
describe
(
'
LineHighlighter
'
,
function
()
{
describe
(
'
LineHighlighter
'
,
()
=>
{
const
testContext
=
{};
preloadFixtures
(
'
static/line_highlighter.html
'
);
const
clickLine
=
function
(
number
,
eventData
=
{})
{
const
clickLine
=
(
number
,
eventData
=
{})
=>
{
if
(
$
.
isEmptyObject
(
eventData
))
{
return
$
(
`#L
${
number
}
`
).
click
();
}
const
e
=
$
.
Event
(
'
click
'
,
eventData
);
return
$
(
`#L
${
number
}
`
).
trigger
(
e
);
};
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
loadFixtures
(
'
static/line_highlighter.html
'
);
this
[
'
class
'
]
=
new
LineHighlighter
();
this
.
css
=
this
[
'
class
'
].
highlightLineClass
;
return
(
this
.
spies
=
{
__setLocationHash__
:
spyOn
(
this
[
'
class
'
],
'
__setLocationHash__
'
).
and
.
callFake
(
function
()
{}),
testContext
.
class
=
new
LineHighlighter
();
testContext
.
css
=
testContext
.
class
.
highlightLineClass
;
return
(
testContext
.
spies
=
{
__setLocationHash__
:
jest
.
spyOn
(
testContext
.
class
,
'
__setLocationHash__
'
)
.
mockImplementation
(()
=>
{}),
});
});
describe
(
'
behavior
'
,
function
()
{
it
(
'
highlights one line given in the URL hash
'
,
function
()
{
describe
(
'
behavior
'
,
()
=>
{
it
(
'
highlights one line given in the URL hash
'
,
()
=>
{
new
LineHighlighter
({
hash
:
'
#L13
'
});
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
});
it
(
'
highlights one line given in the URL hash with given CSS class name
'
,
function
()
{
it
(
'
highlights one line given in the URL hash with given CSS class name
'
,
()
=>
{
const
hiliter
=
new
LineHighlighter
({
hash
:
'
#L13
'
,
highlightLineClass
:
'
hilite
'
});
expect
(
hiliter
.
highlightLineClass
).
toBe
(
'
hilite
'
);
...
...
@@ -36,31 +40,34 @@ describe('LineHighlighter', function() {
expect
(
$
(
'
#LC13
'
)).
not
.
toHaveClass
(
'
hll
'
);
});
it
(
'
highlights a range of lines given in the URL hash
'
,
function
()
{
it
(
'
highlights a range of lines given in the URL hash
'
,
()
=>
{
new
LineHighlighter
({
hash
:
'
#L5-25
'
});
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
21
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
21
);
for
(
let
line
=
5
;
line
<=
25
;
line
+=
1
)
{
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
estContext
.
css
);
}
});
it
(
'
scrolls to the first highlighted line on initial load
'
,
function
()
{
const
spy
=
spyOn
(
$
,
'
scrollTo
'
);
it
(
'
scrolls to the first highlighted line on initial load
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
$
,
'
scrollTo
'
);
new
LineHighlighter
({
hash
:
'
#L5-25
'
});
expect
(
spy
).
toHaveBeenCalledWith
(
'
#L5
'
,
jasmine
.
anything
());
expect
(
spy
).
toHaveBeenCalledWith
(
'
#L5
'
,
expect
.
anything
());
});
it
(
'
discards click events
'
,
function
()
{
const
spy
=
spyOnEvent
(
'
a[data-line-number]
'
,
'
click
'
);
it
(
'
discards click events
'
,
()
=>
{
const
clickSpy
=
jest
.
fn
();
$
(
'
a[data-line-number]
'
).
click
(
clickSpy
);
clickLine
(
13
);
expect
(
spy
).
toHaveBeenPrevented
(
);
expect
(
clickSpy
.
mock
.
calls
[
0
][
0
].
isDefaultPrevented
()).
toEqual
(
true
);
});
it
(
'
handles garbage input from the hash
'
,
function
()
{
const
func
=
function
()
{
it
(
'
handles garbage input from the hash
'
,
()
=>
{
const
func
=
()
=>
{
return
new
LineHighlighter
({
fileHolderSelector
:
'
#blob-content-holder
'
});
};
...
...
@@ -70,7 +77,7 @@ describe('LineHighlighter', function() {
it
(
'
handles hashchange event
'
,
()
=>
{
const
highlighter
=
new
LineHighlighter
();
spyOn
(
highlighter
,
'
highlightHash
'
);
jest
.
spyOn
(
highlighter
,
'
highlightHash
'
).
mockImplementation
(()
=>
{}
);
window
.
dispatchEvent
(
new
Event
(
'
hashchange
'
),
'
L15
'
);
...
...
@@ -78,43 +85,43 @@ describe('LineHighlighter', function() {
});
});
describe
(
'
clickHandler
'
,
function
()
{
it
(
'
handles clicking on a child icon element
'
,
function
()
{
const
spy
=
spyOn
(
this
[
'
class
'
],
'
setHash
'
).
and
.
callThrough
(
);
describe
(
'
clickHandler
'
,
()
=>
{
it
(
'
handles clicking on a child icon element
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
testContext
.
class
,
'
setHash
'
);
$
(
'
#L13 i
'
)
.
mousedown
()
.
click
();
expect
(
spy
).
toHaveBeenCalledWith
(
13
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
});
describe
(
'
without shiftKey
'
,
function
()
{
it
(
'
highlights one line when clicked
'
,
function
()
{
describe
(
'
without shiftKey
'
,
()
=>
{
it
(
'
highlights one line when clicked
'
,
()
=>
{
clickLine
(
13
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
});
it
(
'
unhighlights previously highlighted lines
'
,
function
()
{
it
(
'
unhighlights previously highlighted lines
'
,
()
=>
{
clickLine
(
13
);
clickLine
(
20
);
expect
(
$
(
'
#LC13
'
)).
not
.
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC20
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
not
.
toHaveClass
(
t
estContext
.
css
);
expect
(
$
(
'
#LC20
'
)).
toHaveClass
(
t
estContext
.
css
);
});
it
(
'
sets the hash
'
,
function
()
{
const
spy
=
spyOn
(
this
[
'
class
'
],
'
setHash
'
).
and
.
callThrough
(
);
it
(
'
sets the hash
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
testContext
.
class
,
'
setHash
'
);
clickLine
(
13
);
expect
(
spy
).
toHaveBeenCalledWith
(
13
);
});
});
describe
(
'
with shiftKey
'
,
function
()
{
it
(
'
sets the hash
'
,
function
()
{
const
spy
=
spyOn
(
this
[
'
class
'
],
'
setHash
'
).
and
.
callThrough
(
);
describe
(
'
with shiftKey
'
,
()
=>
{
it
(
'
sets the hash
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
testContext
.
class
,
'
setHash
'
);
clickLine
(
13
);
clickLine
(
20
,
{
shiftKey
:
true
,
...
...
@@ -124,18 +131,18 @@ describe('LineHighlighter', function() {
expect
(
spy
).
toHaveBeenCalledWith
(
13
,
20
);
});
describe
(
'
without existing highlight
'
,
function
()
{
it
(
'
highlights the clicked line
'
,
function
()
{
describe
(
'
without existing highlight
'
,
()
=>
{
it
(
'
highlights the clicked line
'
,
()
=>
{
clickLine
(
13
,
{
shiftKey
:
true
,
});
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
1
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
1
);
});
it
(
'
sets the hash
'
,
function
()
{
const
spy
=
spyOn
(
this
[
'
class
'
]
,
'
setHash
'
);
it
(
'
sets the hash
'
,
()
=>
{
const
spy
=
jest
.
spyOn
(
testContext
.
class
,
'
setHash
'
);
clickLine
(
13
,
{
shiftKey
:
true
,
});
...
...
@@ -144,34 +151,34 @@ describe('LineHighlighter', function() {
});
});
describe
(
'
with existing single-line highlight
'
,
function
()
{
it
(
'
uses existing line as last line when target is lesser
'
,
function
()
{
describe
(
'
with existing single-line highlight
'
,
()
=>
{
it
(
'
uses existing line as last line when target is lesser
'
,
()
=>
{
clickLine
(
20
);
clickLine
(
15
,
{
shiftKey
:
true
,
});
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
6
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
6
);
for
(
let
line
=
15
;
line
<=
20
;
line
+=
1
)
{
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
estContext
.
css
);
}
});
it
(
'
uses existing line as first line when target is greater
'
,
function
()
{
it
(
'
uses existing line as first line when target is greater
'
,
()
=>
{
clickLine
(
5
);
clickLine
(
10
,
{
shiftKey
:
true
,
});
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
6
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
6
);
for
(
let
line
=
5
;
line
<=
10
;
line
+=
1
)
{
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
estContext
.
css
);
}
});
});
describe
(
'
with existing multi-line highlight
'
,
function
()
{
beforeEach
(
function
()
{
describe
(
'
with existing multi-line highlight
'
,
()
=>
{
beforeEach
(
()
=>
{
clickLine
(
10
,
{
shiftKey
:
true
,
});
...
...
@@ -180,82 +187,82 @@ describe('LineHighlighter', function() {
});
});
it
(
'
uses target as first line when it is less than existing first line
'
,
function
()
{
it
(
'
uses target as first line when it is less than existing first line
'
,
()
=>
{
clickLine
(
5
,
{
shiftKey
:
true
,
});
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
6
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
6
);
for
(
let
line
=
5
;
line
<=
10
;
line
+=
1
)
{
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
estContext
.
css
);
}
});
it
(
'
uses target as last line when it is greater than existing first line
'
,
function
()
{
it
(
'
uses target as last line when it is greater than existing first line
'
,
()
=>
{
clickLine
(
15
,
{
shiftKey
:
true
,
});
expect
(
$
(
`.
${
t
his
.
css
}
`
).
length
).
toBe
(
6
);
expect
(
$
(
`.
${
t
estContext
.
css
}
`
).
length
).
toBe
(
6
);
for
(
let
line
=
10
;
line
<=
15
;
line
+=
1
)
{
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
`#LC
${
line
}
`
)).
toHaveClass
(
t
estContext
.
css
);
}
});
});
});
});
describe
(
'
hashToRange
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
subject
=
this
[
'
class
'
]
.
hashToRange
;
describe
(
'
hashToRange
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
subject
=
testContext
.
class
.
hashToRange
;
});
it
(
'
extracts a single line number from the hash
'
,
function
()
{
expect
(
t
his
.
subject
(
'
#L5
'
)).
toEqual
([
5
,
null
]);
it
(
'
extracts a single line number from the hash
'
,
()
=>
{
expect
(
t
estContext
.
subject
(
'
#L5
'
)).
toEqual
([
5
,
null
]);
});
it
(
'
extracts a range of line numbers from the hash
'
,
function
()
{
expect
(
t
his
.
subject
(
'
#L5-15
'
)).
toEqual
([
5
,
15
]);
it
(
'
extracts a range of line numbers from the hash
'
,
()
=>
{
expect
(
t
estContext
.
subject
(
'
#L5-15
'
)).
toEqual
([
5
,
15
]);
});
it
(
'
returns [null, null] when the hash is not a line number
'
,
function
()
{
expect
(
t
his
.
subject
(
'
#foo
'
)).
toEqual
([
null
,
null
]);
it
(
'
returns [null, null] when the hash is not a line number
'
,
()
=>
{
expect
(
t
estContext
.
subject
(
'
#foo
'
)).
toEqual
([
null
,
null
]);
});
});
describe
(
'
highlightLine
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
subject
=
this
[
'
class
'
]
.
highlightLine
;
describe
(
'
highlightLine
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
subject
=
testContext
.
class
.
highlightLine
;
});
it
(
'
highlights the specified line
'
,
function
()
{
t
his
.
subject
(
13
);
it
(
'
highlights the specified line
'
,
()
=>
{
t
estContext
.
subject
(
13
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
});
it
(
'
accepts a String-based number
'
,
function
()
{
t
his
.
subject
(
'
13
'
);
it
(
'
accepts a String-based number
'
,
()
=>
{
t
estContext
.
subject
(
'
13
'
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
his
.
css
);
expect
(
$
(
'
#LC13
'
)).
toHaveClass
(
t
estContext
.
css
);
});
});
describe
(
'
setHash
'
,
function
()
{
beforeEach
(
function
()
{
t
his
.
subject
=
this
[
'
class
'
]
.
setHash
;
describe
(
'
setHash
'
,
()
=>
{
beforeEach
(
()
=>
{
t
estContext
.
subject
=
testContext
.
class
.
setHash
;
});
it
(
'
sets the location hash for a single line
'
,
function
()
{
t
his
.
subject
(
5
);
it
(
'
sets the location hash for a single line
'
,
()
=>
{
t
estContext
.
subject
(
5
);
expect
(
t
his
.
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'
#L5
'
);
expect
(
t
estContext
.
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'
#L5
'
);
});
it
(
'
sets the location hash for a range
'
,
function
()
{
t
his
.
subject
(
5
,
15
);
it
(
'
sets the location hash for a range
'
,
()
=>
{
t
estContext
.
subject
(
5
,
15
);
expect
(
t
his
.
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'
#L5-15
'
);
expect
(
t
estContext
.
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'
#L5-15
'
);
});
});
});
spec/
javascripts
/merge_request_tabs_spec.js
→
spec/
frontend
/merge_request_tabs_spec.js
View file @
346ba81d
...
...
@@ -5,12 +5,16 @@ import MergeRequestTabs from '~/merge_request_tabs';
import
'
~/commit/pipelines/pipelines_bundle
'
;
import
'
~/lib/utils/common_utils
'
;
import
'
vendor/jquery.scrollTo
'
;
import
initMrPage
from
'
./helpers/init_vue_mr_page_helper
'
;
import
initMrPage
from
'
.
./javascripts
/helpers/init_vue_mr_page_helper
'
;
describe
(
'
MergeRequestTabs
'
,
function
()
{
let
mrPageMock
;
jest
.
mock
(
'
~/lib/utils/webpack
'
,
()
=>
({
resetServiceWorkersPublicPath
:
jest
.
fn
(),
}));
describe
(
'
MergeRequestTabs
'
,
()
=>
{
const
testContext
=
{};
const
stubLocation
=
{};
const
setLocation
=
function
(
stubs
)
{
const
setLocation
=
stubs
=>
{
const
defaults
=
{
pathname
:
''
,
search
:
''
,
...
...
@@ -24,29 +28,25 @@ describe('MergeRequestTabs', function() {
'
merge_requests/diff_comment.html
'
,
);
beforeEach
(
function
()
{
mrPageMock
=
initMrPage
();
this
.
class
=
new
MergeRequestTabs
({
stubLocation
});
beforeEach
(()
=>
{
initMrPage
();
testContext
.
class
=
new
MergeRequestTabs
({
stubLocation
});
setLocation
();
t
his
.
spies
=
{
history
:
spyOn
(
window
.
history
,
'
pushState
'
).
and
.
callFake
(
function
()
{}),
t
estContext
.
spies
=
{
history
:
jest
.
spyOn
(
window
.
history
,
'
pushState
'
).
mockImplementation
(()
=>
{}),
};
});
afterEach
(
function
()
{
this
.
class
.
unbindEvents
();
this
.
class
.
destroyPipelinesView
();
mrPageMock
.
restore
();
$
(
'
.js-merge-request-test
'
).
remove
();
gl
.
mrWidget
=
{};
});
describe
(
'
opensInNewTab
'
,
function
()
{
describe
(
'
opensInNewTab
'
,
()
=>
{
const
windowTarget
=
'
_blank
'
;
let
clickTabParams
;
let
tabUrl
;
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
loadFixtures
(
'
merge_requests/merge_request_with_task_list.html
'
);
tabUrl
=
$
(
'
.commits-tab a
'
).
attr
(
'
href
'
);
...
...
@@ -68,76 +68,76 @@ describe('MergeRequestTabs', function() {
describe
(
'
meta click
'
,
()
=>
{
let
metakeyEvent
;
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
metakeyEvent
=
$
.
Event
(
'
click
'
,
{
keyCode
:
91
,
ctrlKey
:
true
});
});
it
(
'
opens page when commits link is clicked
'
,
function
()
{
spyOn
(
window
,
'
open
'
).
and
.
callFake
(
function
(
url
,
name
)
{
it
(
'
opens page when commits link is clicked
'
,
()
=>
{
jest
.
spyOn
(
window
,
'
open
'
).
mockImplementation
((
url
,
name
)
=>
{
expect
(
url
).
toEqual
(
tabUrl
);
expect
(
name
).
toEqual
(
windowTarget
);
});
t
his
.
class
.
bindEvents
();
t
estContext
.
class
.
bindEvents
();
$
(
'
.merge-request-tabs .commits-tab a
'
).
trigger
(
metakeyEvent
);
expect
(
window
.
open
).
toHaveBeenCalled
();
});
it
(
'
opens page when commits badge is clicked
'
,
function
()
{
spyOn
(
window
,
'
open
'
).
and
.
callFake
(
function
(
url
,
name
)
{
it
(
'
opens page when commits badge is clicked
'
,
()
=>
{
jest
.
spyOn
(
window
,
'
open
'
).
mockImplementation
((
url
,
name
)
=>
{
expect
(
url
).
toEqual
(
tabUrl
);
expect
(
name
).
toEqual
(
windowTarget
);
});
t
his
.
class
.
bindEvents
();
t
estContext
.
class
.
bindEvents
();
$
(
'
.merge-request-tabs .commits-tab a .badge
'
).
trigger
(
metakeyEvent
);
expect
(
window
.
open
).
toHaveBeenCalled
();
});
});
it
(
'
opens page tab in a new browser tab with Ctrl+Click - Windows/Linux
'
,
function
()
{
spyOn
(
window
,
'
open
'
).
and
.
callFake
(
function
(
url
,
name
)
{
it
(
'
opens page tab in a new browser tab with Ctrl+Click - Windows/Linux
'
,
()
=>
{
jest
.
spyOn
(
window
,
'
open
'
).
mockImplementation
((
url
,
name
)
=>
{
expect
(
url
).
toEqual
(
tabUrl
);
expect
(
name
).
toEqual
(
windowTarget
);
});
t
his
.
class
.
clickTab
({
...
clickTabParams
,
metaKey
:
true
});
t
estContext
.
class
.
clickTab
({
...
clickTabParams
,
metaKey
:
true
});
expect
(
window
.
open
).
toHaveBeenCalled
();
});
it
(
'
opens page tab in a new browser tab with Cmd+Click - Mac
'
,
function
()
{
spyOn
(
window
,
'
open
'
).
and
.
callFake
(
function
(
url
,
name
)
{
it
(
'
opens page tab in a new browser tab with Cmd+Click - Mac
'
,
()
=>
{
jest
.
spyOn
(
window
,
'
open
'
).
mockImplementation
((
url
,
name
)
=>
{
expect
(
url
).
toEqual
(
tabUrl
);
expect
(
name
).
toEqual
(
windowTarget
);
});
t
his
.
class
.
clickTab
({
...
clickTabParams
,
ctrlKey
:
true
});
t
estContext
.
class
.
clickTab
({
...
clickTabParams
,
ctrlKey
:
true
});
expect
(
window
.
open
).
toHaveBeenCalled
();
});
it
(
'
opens page tab in a new browser tab with Middle-click - Mac/PC
'
,
function
()
{
spyOn
(
window
,
'
open
'
).
and
.
callFake
(
function
(
url
,
name
)
{
it
(
'
opens page tab in a new browser tab with Middle-click - Mac/PC
'
,
()
=>
{
jest
.
spyOn
(
window
,
'
open
'
).
mockImplementation
((
url
,
name
)
=>
{
expect
(
url
).
toEqual
(
tabUrl
);
expect
(
name
).
toEqual
(
windowTarget
);
});
t
his
.
class
.
clickTab
({
...
clickTabParams
,
which
:
2
});
t
estContext
.
class
.
clickTab
({
...
clickTabParams
,
which
:
2
});
expect
(
window
.
open
).
toHaveBeenCalled
();
});
});
describe
(
'
setCurrentAction
'
,
function
()
{
describe
(
'
setCurrentAction
'
,
()
=>
{
let
mock
;
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onAny
().
reply
({
data
:
{}
});
t
his
.
subject
=
this
.
class
.
setCurrentAction
;
t
estContext
.
subject
=
testContext
.
class
.
setCurrentAction
;
});
afterEach
(()
=>
{
...
...
@@ -145,53 +145,53 @@ describe('MergeRequestTabs', function() {
window
.
history
.
replaceState
({},
''
,
'
/
'
);
});
it
(
'
changes from commits
'
,
function
()
{
it
(
'
changes from commits
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1/commits
'
,
});
expect
(
t
his
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
his
.
subject
(
'
diffs
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/diffs
'
);
expect
(
t
estContext
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
estContext
.
subject
(
'
diffs
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/diffs
'
);
});
it
(
'
changes from diffs
'
,
function
()
{
it
(
'
changes from diffs
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1/diffs
'
,
});
expect
(
t
his
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
his
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
expect
(
t
estContext
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
estContext
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
});
it
(
'
changes from diffs.html
'
,
function
()
{
it
(
'
changes from diffs.html
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1/diffs.html
'
,
});
expect
(
t
his
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
his
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
expect
(
t
estContext
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
estContext
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
});
it
(
'
changes from notes
'
,
function
()
{
it
(
'
changes from notes
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1
'
,
});
expect
(
t
his
.
subject
(
'
diffs
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/diffs
'
);
expect
(
t
his
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
expect
(
t
estContext
.
subject
(
'
diffs
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/diffs
'
);
expect
(
t
estContext
.
subject
(
'
commits
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1/commits
'
);
});
it
(
'
includes search parameters and hash string
'
,
function
()
{
it
(
'
includes search parameters and hash string
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1/diffs
'
,
search
:
'
?view=parallel
'
,
hash
:
'
#L15-35
'
,
});
expect
(
t
his
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1?view=parallel#L15-35
'
);
expect
(
t
estContext
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1?view=parallel#L15-35
'
);
});
it
(
'
replaces the current history state
'
,
function
()
{
it
(
'
replaces the current history state
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1
'
,
});
...
...
@@ -204,9 +204,9 @@ describe('MergeRequestTabs', function() {
window
.
location
.
href
,
);
const
newState
=
t
his
.
subject
(
'
commits
'
);
const
newState
=
t
estContext
.
subject
(
'
commits
'
);
expect
(
t
his
.
spies
.
history
).
toHaveBeenCalledWith
(
expect
(
t
estContext
.
spies
.
history
).
toHaveBeenCalledWith
(
{
url
:
newState
,
action
:
'
commits
'
,
...
...
@@ -216,16 +216,16 @@ describe('MergeRequestTabs', function() {
);
});
it
(
'
treats "show" like "notes"
'
,
function
()
{
it
(
'
treats "show" like "notes"
'
,
()
=>
{
setLocation
({
pathname
:
'
/foo/bar/-/merge_requests/1/commits
'
,
});
expect
(
t
his
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
expect
(
t
estContext
.
subject
(
'
show
'
)).
toBe
(
'
/foo/bar/-/merge_requests/1
'
);
});
});
describe
(
'
expandViewContainer
'
,
function
()
{
describe
(
'
expandViewContainer
'
,
()
=>
{
beforeEach
(()
=>
{
$
(
'
body
'
).
append
(
'
<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>
'
,
...
...
@@ -236,59 +236,58 @@ describe('MergeRequestTabs', function() {
$
(
'
.content-wrapper
'
).
remove
();
});
it
(
'
removes container-limited from containers
'
,
function
()
{
t
his
.
class
.
expandViewContainer
();
it
(
'
removes container-limited from containers
'
,
()
=>
{
t
estContext
.
class
.
expandViewContainer
();
expect
(
$
(
'
.content-wrapper
'
)).
not
.
toContainElement
(
'
.container-limited
'
);
expect
(
$
(
'
.content-wrapper
.container-limited
'
)).
toHaveLength
(
0
);
});
it
(
'
does not add container-limited when fluid layout is prefered
'
,
function
()
{
it
(
'
does not add container-limited when fluid layout is prefered
'
,
()
=>
{
$
(
'
.content-wrapper .container-fluid
'
).
removeClass
(
'
container-limited
'
);
t
his
.
class
.
expandViewContainer
(
false
);
t
estContext
.
class
.
expandViewContainer
(
false
);
expect
(
$
(
'
.content-wrapper
'
)).
not
.
toContainElement
(
'
.container-limited
'
);
expect
(
$
(
'
.content-wrapper
.container-limited
'
)).
toHaveLength
(
0
);
});
it
(
'
does remove container-limited from breadcrumbs
'
,
function
()
{
it
(
'
does remove container-limited from breadcrumbs
'
,
()
=>
{
$
(
'
.container-limited
'
).
addClass
(
'
breadcrumbs
'
);
t
his
.
class
.
expandViewContainer
();
t
estContext
.
class
.
expandViewContainer
();
expect
(
$
(
'
.content-wrapper
'
)).
toContainElement
(
'
.container-limited
'
);
expect
(
$
(
'
.content-wrapper
.container-limited
'
)).
toHaveLength
(
1
);
});
});
describe
(
'
tabShown
'
,
function
()
{
describe
(
'
tabShown
'
,
()
=>
{
const
mainContent
=
document
.
createElement
(
'
div
'
);
const
tabContent
=
document
.
createElement
(
'
div
'
);
beforeEach
(
function
()
{
spyOn
(
mainContent
,
'
getBoundingClientRect
'
).
and
.
r
eturnValue
({
top
:
10
});
spyOn
(
tabContent
,
'
getBoundingClientRect
'
).
and
.
r
eturnValue
({
top
:
100
});
spyOn
(
document
,
'
querySelector
'
).
and
.
callFake
(
function
(
selector
)
{
beforeEach
(
()
=>
{
jest
.
spyOn
(
mainContent
,
'
getBoundingClientRect
'
).
mockR
eturnValue
({
top
:
10
});
jest
.
spyOn
(
tabContent
,
'
getBoundingClientRect
'
).
mockR
eturnValue
({
top
:
100
});
jest
.
spyOn
(
document
,
'
querySelector
'
).
mockImplementation
(
selector
=>
{
return
selector
===
'
.content-wrapper
'
?
mainContent
:
tabContent
;
});
t
his
.
class
.
currentAction
=
'
commits
'
;
t
estContext
.
class
.
currentAction
=
'
commits
'
;
});
it
(
'
calls window scrollTo with options if document has scrollBehavior
'
,
function
()
{
it
(
'
calls window scrollTo with options if document has scrollBehavior
'
,
()
=>
{
document
.
documentElement
.
style
.
scrollBehavior
=
''
;
spyOn
(
window
,
'
scrollTo
'
);
jest
.
spyOn
(
window
,
'
scrollTo
'
).
mockImplementation
(()
=>
{}
);
t
his
.
class
.
tabShown
(
'
commits
'
,
'
foobar
'
);
t
estContext
.
class
.
tabShown
(
'
commits
'
,
'
foobar
'
);
expect
(
window
.
scrollTo
.
calls
.
first
().
args
[
0
]).
toEqual
({
top
:
39
,
behavior
:
'
smooth
'
});
expect
(
window
.
scrollTo
.
mock
.
calls
[
0
]
[
0
]).
toEqual
({
top
:
39
,
behavior
:
'
smooth
'
});
});
it
(
'
calls window scrollTo with two args if document does not have scrollBehavior
'
,
function
()
{
spyOnProperty
(
document
.
documentElement
,
'
style
'
,
'
get
'
).
and
.
returnValue
({});
spyOn
(
window
,
'
scrollTo
'
);
it
(
'
calls window scrollTo with two args if document does not have scrollBehavior
'
,
()
=>
{
jest
.
spyOn
(
document
.
documentElement
,
'
style
'
,
'
get
'
).
mockReturnValue
({});
jest
.
spyOn
(
window
,
'
scrollTo
'
).
mockImplementation
(()
=>
{});
t
his
.
class
.
tabShown
(
'
commits
'
,
'
foobar
'
);
t
estContext
.
class
.
tabShown
(
'
commits
'
,
'
foobar
'
);
expect
(
window
.
scrollTo
.
calls
.
first
().
args
).
toEqual
([
0
,
39
]);
expect
(
window
.
scrollTo
.
mock
.
calls
[
0
]
).
toEqual
([
0
,
39
]);
});
});
});
spec/
javascripts
/right_sidebar_spec.js
→
spec/
frontend
/right_sidebar_spec.js
View file @
346ba81d
...
...
@@ -10,7 +10,7 @@ let $icon = null;
let
$page
=
null
;
let
$labelsIcon
=
null
;
const
assertSidebarState
=
function
(
state
)
{
const
assertSidebarState
=
state
=>
{
const
shouldBeExpanded
=
state
===
'
expanded
'
;
const
shouldBeCollapsed
=
state
===
'
collapsed
'
;
expect
(
$aside
.
hasClass
(
'
right-sidebar-expanded
'
)).
toBe
(
shouldBeExpanded
);
...
...
@@ -21,14 +21,13 @@ const assertSidebarState = function(state) {
expect
(
$icon
.
hasClass
(
'
fa-angle-double-left
'
)).
toBe
(
shouldBeCollapsed
);
};
describe
(
'
RightSidebar
'
,
function
()
{
describe
(
'
RightSidebar
'
,
()
=>
{
describe
(
'
fixture tests
'
,
()
=>
{
const
fixtureName
=
'
issues/open-issue.html
'
;
preloadFixtures
(
fixtureName
);
loadJSONFixtures
(
'
todos/todos.json
'
);
let
mock
;
beforeEach
(
function
()
{
beforeEach
(
()
=>
{
loadFixtures
(
fixtureName
);
mock
=
new
MockAdapter
(
axios
);
new
Sidebar
();
// eslint-disable-line no-new
...
...
@@ -43,7 +42,7 @@ describe('RightSidebar', function() {
mock
.
restore
();
});
it
(
'
should expand/collapse the sidebar when arrow is clicked
'
,
function
()
{
it
(
'
should expand/collapse the sidebar when arrow is clicked
'
,
()
=>
{
assertSidebarState
(
'
expanded
'
);
$toggle
.
click
();
assertSidebarState
(
'
collapsed
'
);
...
...
@@ -51,28 +50,29 @@ describe('RightSidebar', function() {
assertSidebarState
(
'
expanded
'
);
});
it
(
'
should float over the page and when sidebar icons clicked
'
,
function
()
{
it
(
'
should float over the page and when sidebar icons clicked
'
,
()
=>
{
$labelsIcon
.
click
();
assertSidebarState
(
'
expanded
'
);
});
it
(
'
should collapse when the icon arrow clicked while it is floating on page
'
,
function
()
{
it
(
'
should collapse when the icon arrow clicked while it is floating on page
'
,
()
=>
{
$labelsIcon
.
click
();
assertSidebarState
(
'
expanded
'
);
$toggle
.
click
();
assertSidebarState
(
'
collapsed
'
);
});
it
(
'
should broadcast todo:toggle event when add todo clicked
'
,
function
(
done
)
{
it
(
'
should broadcast todo:toggle event when add todo clicked
'
,
done
=>
{
const
todos
=
getJSONFixture
(
'
todos/todos.json
'
);
mock
.
onPost
(
/
(
.*
)\/
todos$/
).
reply
(
200
,
todos
);
const
todoToggleSpy
=
spyOnEvent
(
document
,
'
todo:toggle
'
);
const
todoToggleSpy
=
jest
.
fn
();
$
(
document
).
on
(
'
todo:toggle
'
,
todoToggleSpy
);
$
(
'
.issuable-sidebar-header .js-issuable-todo
'
).
click
();
set
Timeout
(()
=>
{
expect
(
todoToggleSpy
.
calls
.
count
()
).
toEqual
(
1
);
set
Immediate
(()
=>
{
expect
(
todoToggleSpy
.
mock
.
calls
.
length
).
toEqual
(
1
);
done
();
});
...
...
spec/
javascripts
/shortcuts_spec.js
→
spec/
frontend
/shortcuts_spec.js
View file @
346ba81d
...
...
@@ -14,8 +14,8 @@ describe('Shortcuts', () => {
beforeEach
(()
=>
{
loadFixtures
(
fixtureName
);
spyOnEvent
(
'
.js-new-note-form .js-md-preview-button
'
,
'
focus
'
);
spyOnEvent
(
'
.edit-note .js-md-preview-button
'
,
'
focus
'
);
jest
.
spyOn
(
document
.
querySelector
(
'
.js-new-note-form .js-md-preview-button
'
)
,
'
focus
'
);
jest
.
spyOn
(
document
.
querySelector
(
'
.edit-note .js-md-preview-button
'
)
,
'
focus
'
);
new
Shortcuts
();
// eslint-disable-line no-new
});
...
...
@@ -25,22 +25,22 @@ describe('Shortcuts', () => {
createEvent
(
'
KeyboardEvent
'
,
document
.
querySelector
(
'
.js-new-note-form .js-note-text
'
)),
);
expect
(
'
focus
'
).
toHaveBeenTriggeredOn
(
'
.js-new-note-form .js-md-preview-button
'
);
expect
(
document
.
querySelector
(
'
.js-new-note-form .js-md-preview-button
'
).
focus
,
).
toHaveBeenCalled
();
});
it
(
'
focues preview button inside edit comment form
'
,
done
=>
{
it
(
'
focues preview button inside edit comment form
'
,
()
=>
{
document
.
querySelector
(
'
.js-note-edit
'
).
click
();
setTimeout
(()
=>
{
Shortcuts
.
toggleMarkdownPreview
(
createEvent
(
'
KeyboardEvent
'
,
document
.
querySelector
(
'
.edit-note .js-note-text
'
)),
);
expect
(
'
focus
'
).
not
.
toHaveBeenTriggeredOn
(
'
.js-new-note-form .js-md-preview-button
'
);
expect
(
'
focus
'
).
toHaveBeenTriggeredOn
(
'
.edit-note .js-md-preview-button
'
);
Shortcuts
.
toggleMarkdownPreview
(
createEvent
(
'
KeyboardEvent
'
,
document
.
querySelector
(
'
.edit-note .js-note-text
'
)),
);
done
();
});
expect
(
document
.
querySelector
(
'
.js-new-note-form .js-md-preview-button
'
).
focus
,
).
not
.
toHaveBeenCalled
();
expect
(
document
.
querySelector
(
'
.edit-note .js-md-preview-button
'
).
focus
).
toHaveBeenCalled
();
});
});
});
spec/
javascripts
/user_popovers_spec.js
→
spec/
frontend
/user_popovers_spec.js
View file @
346ba81d
...
...
@@ -26,10 +26,12 @@ describe('User Popovers', () => {
loadFixtures
(
fixtureTemplate
);
const
usersCacheSpy
=
()
=>
Promise
.
resolve
(
dummyUser
);
spyOn
(
UsersCache
,
'
retrieveById
'
).
and
.
callFake
(
userId
=>
usersCacheSpy
(
userId
));
jest
.
spyOn
(
UsersCache
,
'
retrieveById
'
).
mockImplementation
(
userId
=>
usersCacheSpy
(
userId
));
const
userStatusCacheSpy
=
()
=>
Promise
.
resolve
(
dummyUserStatus
);
spyOn
(
UsersCache
,
'
retrieveStatusById
'
).
and
.
callFake
(
userId
=>
userStatusCacheSpy
(
userId
));
jest
.
spyOn
(
UsersCache
,
'
retrieveStatusById
'
)
.
mockImplementation
(
userId
=>
userStatusCacheSpy
(
userId
));
popovers
=
initUserPopovers
(
document
.
querySelectorAll
(
selector
));
});
...
...
@@ -53,6 +55,8 @@ describe('User Popovers', () => {
let
userLink
;
beforeEach
(()
=>
{
UsersCache
.
retrieveById
.
mockReset
();
userLink
=
document
.
querySelector
(
selector
);
triggerEvent
(
'
mouseenter
'
,
userLink
);
...
...
@@ -68,7 +72,7 @@ describe('User Popovers', () => {
const
[
firstPopover
]
=
popovers
;
expect
(
firstPopover
.
$props
.
user
).
toEqual
(
jasmine
.
objectContaining
({
expect
.
objectContaining
({
name
,
userId
,
username
,
...
...
spec/
javascripts
/zen_mode_spec.js
→
spec/
frontend
/zen_mode_spec.js
View file @
346ba81d
import
$
from
'
jquery
'
;
import
axios
from
'
axios
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
Dropzone
from
'
dropzone
'
;
import
Mousetrap
from
'
mousetrap
'
;
import
ZenMode
from
'
~/zen_mode
'
;
import
initNotes
from
'
~/init_notes
'
;
describe
(
'
ZenMode
'
,
()
=>
{
let
mock
;
let
zen
;
let
dropzoneForElementSpy
;
const
fixtureName
=
'
snippets/show.html
'
;
...
...
@@ -28,10 +31,13 @@ describe('ZenMode', () => {
}
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
().
reply
(
200
);
loadFixtures
(
fixtureName
);
initNotes
();
dropzoneForElementSpy
=
spyOn
(
Dropzone
,
'
forElement
'
).
and
.
callFake
(()
=>
({
dropzoneForElementSpy
=
jest
.
spyOn
(
Dropzone
,
'
forElement
'
).
mockImplementation
(()
=>
({
enable
:
()
=>
true
,
}));
zen
=
new
ZenMode
();
...
...
@@ -49,20 +55,20 @@ describe('ZenMode', () => {
$
(
'
.div-dropzone
'
).
addClass
(
'
js-invalid-dropzone
'
);
exitZen
();
expect
(
dropzoneForElementSpy
.
calls
.
count
()
).
toEqual
(
0
);
expect
(
dropzoneForElementSpy
.
mock
.
calls
.
length
).
toEqual
(
0
);
});
it
(
'
should call dropzone if element is dropzone valid
'
,
()
=>
{
$
(
'
.div-dropzone
'
).
removeClass
(
'
js-invalid-dropzone
'
);
exitZen
();
expect
(
dropzoneForElementSpy
.
calls
.
count
()
).
toEqual
(
2
);
expect
(
dropzoneForElementSpy
.
mock
.
calls
.
length
).
toEqual
(
2
);
});
});
describe
(
'
on enter
'
,
()
=>
{
it
(
'
pauses Mousetrap
'
,
()
=>
{
const
mouseTrapPauseSpy
=
spyOn
(
Mousetrap
,
'
pause
'
);
const
mouseTrapPauseSpy
=
jest
.
spyOn
(
Mousetrap
,
'
pause
'
);
enterZen
();
expect
(
mouseTrapPauseSpy
).
toHaveBeenCalled
();
...
...
@@ -90,14 +96,14 @@ describe('ZenMode', () => {
beforeEach
(
enterZen
);
it
(
'
unpauses Mousetrap
'
,
()
=>
{
const
mouseTrapUnpauseSpy
=
spyOn
(
Mousetrap
,
'
unpause
'
);
const
mouseTrapUnpauseSpy
=
jest
.
spyOn
(
Mousetrap
,
'
unpause
'
);
exitZen
();
expect
(
mouseTrapUnpauseSpy
).
toHaveBeenCalled
();
});
it
(
'
restores the scroll position
'
,
()
=>
{
spyOn
(
zen
,
'
scrollTo
'
);
jest
.
spyOn
(
zen
,
'
scrollTo
'
).
mockImplementation
(()
=>
{}
);
exitZen
();
expect
(
zen
.
scrollTo
).
toHaveBeenCalled
();
...
...
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