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
db04e3ee
Commit
db04e3ee
authored
Feb 13, 2018
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-02-13
parents
7afd29db
41285af4
Changes
27
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
399 additions
and
202 deletions
+399
-202
app/assets/javascripts/create_merge_request_dropdown.js
app/assets/javascripts/create_merge_request_dropdown.js
+1
-0
app/assets/javascripts/droplab/constants.js
app/assets/javascripts/droplab/constants.js
+0
-2
app/assets/javascripts/droplab/drop_down.js
app/assets/javascripts/droplab/drop_down.js
+8
-5
app/assets/javascripts/issue.js
app/assets/javascripts/issue.js
+47
-28
app/assets/javascripts/notes/components/comment_form.vue
app/assets/javascripts/notes/components/comment_form.vue
+41
-20
app/assets/javascripts/notes/index.js
app/assets/javascripts/notes/index.js
+2
-0
app/assets/javascripts/notes/services/notes_service.js
app/assets/javascripts/notes/services/notes_service.js
+3
-0
app/assets/javascripts/notes/stores/actions.js
app/assets/javascripts/notes/stores/actions.js
+33
-0
app/assets/javascripts/notes/stores/getters.js
app/assets/javascripts/notes/stores/getters.js
+1
-0
app/assets/javascripts/notes/stores/mutation_types.js
app/assets/javascripts/notes/stores/mutation_types.js
+4
-0
app/assets/javascripts/notes/stores/mutations.js
app/assets/javascripts/notes/stores/mutations.js
+8
-0
app/assets/javascripts/vue_shared/components/loading_button.vue
...sets/javascripts/vue_shared/components/loading_button.vue
+1
-1
app/assets/stylesheets/framework/common.scss
app/assets/stylesheets/framework/common.scss
+2
-0
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+0
-4
app/assets/stylesheets/framework/forms.scss
app/assets/stylesheets/framework/forms.scss
+1
-0
app/assets/stylesheets/pages/issues.scss
app/assets/stylesheets/pages/issues.scss
+11
-65
app/services/merge_requests/build_service.rb
app/services/merge_requests/build_service.rb
+5
-3
app/views/projects/issues/_discussion.html.haml
app/views/projects/issues/_discussion.html.haml
+2
-0
app/views/projects/issues/_new_branch.html.haml
app/views/projects/issues/_new_branch.html.haml
+30
-27
changelogs/unreleased/42923-close-issue.yml
changelogs/unreleased/42923-close-issue.yml
+5
-0
changelogs/unreleased/winh-new-branch-dropdown-style.yml
changelogs/unreleased/winh-new-branch-dropdown-style.yml
+5
-0
spec/javascripts/droplab/drop_down_spec.js
spec/javascripts/droplab/drop_down_spec.js
+77
-36
spec/javascripts/notes/helpers.js
spec/javascripts/notes/helpers.js
+12
-0
spec/javascripts/notes/mock_data.js
spec/javascripts/notes/mock_data.js
+2
-0
spec/javascripts/notes/stores/actions_spec.js
spec/javascripts/notes/stores/actions_spec.js
+71
-0
spec/javascripts/notes/stores/getters_spec.js
spec/javascripts/notes/stores/getters_spec.js
+6
-0
spec/services/merge_requests/build_service_spec.rb
spec/services/merge_requests/build_service_spec.rb
+21
-11
No files found.
app/assets/javascripts/create_merge_request_dropdown.js
View file @
db04e3ee
...
@@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
...
@@ -180,6 +180,7 @@ export default class CreateMergeRequestDropdown {
valueAttribute
:
'
data-text
'
,
valueAttribute
:
'
data-text
'
,
},
},
],
],
hideOnClick
:
false
,
};
};
}
}
...
...
app/assets/javascripts/droplab/constants.js
View file @
db04e3ee
...
@@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
...
@@ -3,7 +3,6 @@ const DATA_DROPDOWN = 'data-dropdown';
const
SELECTED_CLASS
=
'
droplab-item-selected
'
;
const
SELECTED_CLASS
=
'
droplab-item-selected
'
;
const
ACTIVE_CLASS
=
'
droplab-item-active
'
;
const
ACTIVE_CLASS
=
'
droplab-item-active
'
;
const
IGNORE_CLASS
=
'
droplab-item-ignore
'
;
const
IGNORE_CLASS
=
'
droplab-item-ignore
'
;
const
IGNORE_HIDING_CLASS
=
'
droplab-item-ignore-hiding
'
;
// Matches `{{anything}}` and `{{ everything }}`.
// Matches `{{anything}}` and `{{ everything }}`.
const
TEMPLATE_REGEX
=
/
\{\{(
.+
?)\}\}
/g
;
const
TEMPLATE_REGEX
=
/
\{\{(
.+
?)\}\}
/g
;
...
@@ -14,5 +13,4 @@ export {
...
@@ -14,5 +13,4 @@ export {
ACTIVE_CLASS
,
ACTIVE_CLASS
,
TEMPLATE_REGEX
,
TEMPLATE_REGEX
,
IGNORE_CLASS
,
IGNORE_CLASS
,
IGNORE_HIDING_CLASS
,
};
};
app/assets/javascripts/droplab/drop_down.js
View file @
db04e3ee
import
utils
from
'
./utils
'
;
import
utils
from
'
./utils
'
;
import
{
SELECTED_CLASS
,
IGNORE_CLASS
,
IGNORE_HIDING_CLASS
}
from
'
./constants
'
;
import
{
SELECTED_CLASS
,
IGNORE_CLASS
}
from
'
./constants
'
;
class
DropDown
{
class
DropDown
{
constructor
(
list
,
config
=
{})
{
constructor
(
list
,
config
=
{
})
{
this
.
currentIndex
=
0
;
this
.
currentIndex
=
0
;
this
.
hidden
=
true
;
this
.
hidden
=
true
;
this
.
list
=
typeof
list
===
'
string
'
?
document
.
querySelector
(
list
)
:
list
;
this
.
list
=
typeof
list
===
'
string
'
?
document
.
querySelector
(
list
)
:
list
;
this
.
items
=
[];
this
.
items
=
[];
this
.
eventWrapper
=
{};
this
.
eventWrapper
=
{};
this
.
hideOnClick
=
config
.
hideOnClick
!==
false
;
if
(
config
.
addActiveClassToDropdownButton
)
{
if
(
config
.
addActiveClassToDropdownButton
)
{
this
.
dropdownToggle
=
this
.
list
.
parentNode
.
querySelector
(
'
.js-dropdown-toggle
'
);
this
.
dropdownToggle
=
this
.
list
.
parentNode
.
querySelector
(
'
.js-dropdown-toggle
'
);
...
@@ -37,15 +38,17 @@ class DropDown {
...
@@ -37,15 +38,17 @@ class DropDown {
clickEvent
(
e
)
{
clickEvent
(
e
)
{
if
(
e
.
target
.
tagName
===
'
UL
'
)
return
;
if
(
e
.
target
.
tagName
===
'
UL
'
)
return
;
if
(
e
.
target
.
cl
assList
.
contains
(
IGNORE_CLASS
))
return
;
if
(
e
.
target
.
cl
osest
(
`.
${
IGNORE_CLASS
}
`
))
return
;
const
selected
=
utils
.
closest
(
e
.
target
,
'
LI
'
);
const
selected
=
e
.
target
.
closest
(
'
li
'
);
if
(
!
selected
)
return
;
if
(
!
selected
)
return
;
this
.
addSelectedClass
(
selected
);
this
.
addSelectedClass
(
selected
);
e
.
preventDefault
();
e
.
preventDefault
();
if
(
!
e
.
target
.
classList
.
contains
(
IGNORE_HIDING_CLASS
))
this
.
hide
();
if
(
this
.
hideOnClick
)
{
this
.
hide
();
}
const
listEvent
=
new
CustomEvent
(
'
click.dl
'
,
{
const
listEvent
=
new
CustomEvent
(
'
click.dl
'
,
{
detail
:
{
detail
:
{
...
...
app/assets/javascripts/issue.js
View file @
db04e3ee
...
@@ -24,32 +24,29 @@ export default class Issue {
...
@@ -24,32 +24,29 @@ export default class Issue {
if
(
Issue
.
createMrDropdownWrap
)
{
if
(
Issue
.
createMrDropdownWrap
)
{
this
.
createMergeRequestDropdown
=
new
CreateMergeRequestDropdown
(
Issue
.
createMrDropdownWrap
);
this
.
createMergeRequestDropdown
=
new
CreateMergeRequestDropdown
(
Issue
.
createMrDropdownWrap
);
}
}
}
initIssueBtnEventListeners
()
{
const
issueFailMessage
=
'
Unable to update this issue at this time.
'
;
return
$
(
document
).
on
(
'
click
'
,
'
.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen
'
,
(
e
)
=>
{
// Listen to state changes in the Vue app
var
$button
,
shouldSubmit
,
url
;
document
.
addEventListener
(
'
issuable_vue_app:change
'
,
(
event
)
=>
{
e
.
preventDefault
();
this
.
updateTopState
(
event
.
detail
.
isClosed
,
event
.
detail
.
data
);
e
.
stopImmediatePropagation
();
});
$button
=
$
(
e
.
currentTarget
);
shouldSubmit
=
$button
.
hasClass
(
'
btn-comment
'
);
if
(
shouldSubmit
)
{
Issue
.
submitNoteForm
(
$button
.
closest
(
'
form
'
));
}
}
this
.
disableCloseReopenButton
(
$button
);
/**
* This method updates the top area of the issue.
url
=
$button
.
attr
(
'
href
'
);
*
return
axios
.
put
(
url
)
* Once the issue state changes, either through a click on the top area (jquery)
.
then
(({
data
})
=>
{
* or a click on the bottom area (Vue) we need to update the top area.
*
* @param {Boolean} isClosed
* @param {Array} data
* @param {String} issueFailMessage
*/
updateTopState
(
isClosed
,
data
,
issueFailMessage
=
'
Unable to update this issue at this time.
'
)
{
if
(
'
id
'
in
data
)
{
const
isClosedBadge
=
$
(
'
div.status-box-issue-closed
'
);
const
isClosedBadge
=
$
(
'
div.status-box-issue-closed
'
);
const
isOpenBadge
=
$
(
'
div.status-box-open
'
);
const
isOpenBadge
=
$
(
'
div.status-box-open
'
);
const
projectIssuesCounter
=
$
(
'
.issue_counter
'
);
const
projectIssuesCounter
=
$
(
'
.issue_counter
'
);
if
(
'
id
'
in
data
)
{
const
isClosed
=
$button
.
hasClass
(
'
btn-close
'
);
isClosedBadge
.
toggleClass
(
'
hidden
'
,
!
isClosed
);
isClosedBadge
.
toggleClass
(
'
hidden
'
,
!
isClosed
);
isOpenBadge
.
toggleClass
(
'
hidden
'
,
isClosed
);
isOpenBadge
.
toggleClass
(
'
hidden
'
,
isClosed
);
...
@@ -72,6 +69,28 @@ export default class Issue {
...
@@ -72,6 +69,28 @@ export default class Issue {
}
else
{
}
else
{
flash
(
issueFailMessage
);
flash
(
issueFailMessage
);
}
}
}
initIssueBtnEventListeners
()
{
const
issueFailMessage
=
'
Unable to update this issue at this time.
'
;
return
$
(
document
).
on
(
'
click
'
,
'
.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen
'
,
(
e
)
=>
{
var
$button
,
shouldSubmit
,
url
;
e
.
preventDefault
();
e
.
stopImmediatePropagation
();
$button
=
$
(
e
.
currentTarget
);
shouldSubmit
=
$button
.
hasClass
(
'
btn-comment
'
);
if
(
shouldSubmit
)
{
Issue
.
submitNoteForm
(
$button
.
closest
(
'
form
'
));
}
this
.
disableCloseReopenButton
(
$button
);
url
=
$button
.
attr
(
'
href
'
);
return
axios
.
put
(
url
)
.
then
(({
data
})
=>
{
const
isClosed
=
$button
.
hasClass
(
'
btn-close
'
);
this
.
updateTopState
(
isClosed
,
data
);
})
})
.
catch
(()
=>
flash
(
issueFailMessage
))
.
catch
(()
=>
flash
(
issueFailMessage
))
.
then
(()
=>
{
.
then
(()
=>
{
...
...
app/assets/javascripts/notes/components/comment_form.vue
View file @
db04e3ee
...
@@ -2,16 +2,18 @@
...
@@ -2,16 +2,18 @@
import
{
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
{
mapActions
,
mapGetters
}
from
'
vuex
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
Autosize
from
'
autosize
'
;
import
Autosize
from
'
autosize
'
;
import
{
__
}
from
'
~/locale
'
;
import
Flash
from
'
../../flash
'
;
import
Flash
from
'
../../flash
'
;
import
Autosave
from
'
../../autosave
'
;
import
Autosave
from
'
../../autosave
'
;
import
TaskList
from
'
../../task_list
'
;
import
TaskList
from
'
../../task_list
'
;
import
*
as
constants
from
'
../constants
'
;
import
*
as
constants
from
'
../constants
'
;
import
eventHub
from
'
../event_hub
'
;
import
eventHub
from
'
../event_hub
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
import
noteSignedOutWidget
from
'
./note_signed_out_widget.vue
'
;
import
discussionLockedWidget
from
'
./discussion_locked_widget.vue
'
;
import
markdownField
from
'
../../vue_shared/components/markdown/field.vue
'
;
import
markdownField
from
'
../../vue_shared/components/markdown/field.vue
'
;
import
userAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
userAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
loadingButton
from
'
../../vue_shared/components/loading_button.vue
'
;
import
noteSignedOutWidget
from
'
./note_signed_out_widget.vue
'
;
import
discussionLockedWidget
from
'
./discussion_locked_widget.vue
'
;
import
issuableStateMixin
from
'
../mixins/issuable_state
'
;
import
issuableStateMixin
from
'
../mixins/issuable_state
'
;
export
default
{
export
default
{
...
@@ -22,6 +24,7 @@
...
@@ -22,6 +24,7 @@
discussionLockedWidget
,
discussionLockedWidget
,
markdownField
,
markdownField
,
userAvatarLink
,
userAvatarLink
,
loadingButton
,
},
},
mixins
:
[
mixins
:
[
issuableStateMixin
,
issuableStateMixin
,
...
@@ -30,9 +33,6 @@
...
@@ -30,9 +33,6 @@
return
{
return
{
note
:
''
,
note
:
''
,
noteType
:
constants
.
COMMENT
,
noteType
:
constants
.
COMMENT
,
// Can't use mapGetters,
// this needs to be in the data object because it belongs to the state
issueState
:
this
.
$store
.
getters
.
getNoteableData
.
state
,
isSubmitting
:
false
,
isSubmitting
:
false
,
isSubmitButtonDisabled
:
true
,
isSubmitButtonDisabled
:
true
,
};
};
...
@@ -43,6 +43,7 @@
...
@@ -43,6 +43,7 @@
'
getUserData
'
,
'
getUserData
'
,
'
getNoteableData
'
,
'
getNoteableData
'
,
'
getNotesData
'
,
'
getNotesData
'
,
'
issueState
'
,
]),
]),
isLoggedIn
()
{
isLoggedIn
()
{
return
this
.
getUserData
.
id
;
return
this
.
getUserData
.
id
;
...
@@ -105,7 +106,7 @@
...
@@ -105,7 +106,7 @@
mounted
()
{
mounted
()
{
// jQuery is needed here because it is a custom event being dispatched with jQuery.
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$
(
document
).
on
(
'
issuable:change
'
,
(
e
,
isClosed
)
=>
{
$
(
document
).
on
(
'
issuable:change
'
,
(
e
,
isClosed
)
=>
{
this
.
issueState
=
isClosed
?
constants
.
CLOSED
:
constants
.
REOPENED
;
this
.
toggleIssueLocalState
(
isClosed
?
constants
.
CLOSED
:
constants
.
REOPENED
)
;
});
});
this
.
initAutoSave
();
this
.
initAutoSave
();
...
@@ -117,6 +118,9 @@
...
@@ -117,6 +118,9 @@
'
stopPolling
'
,
'
stopPolling
'
,
'
restartPolling
'
,
'
restartPolling
'
,
'
removePlaceholderNotes
'
,
'
removePlaceholderNotes
'
,
'
closeIssue
'
,
'
reopenIssue
'
,
'
toggleIssueLocalState
'
,
]),
]),
setIsSubmitButtonDisabled
(
note
,
isSubmitting
)
{
setIsSubmitButtonDisabled
(
note
,
isSubmitting
)
{
if
(
!
_
.
isEmpty
(
note
)
&&
!
isSubmitting
)
{
if
(
!
_
.
isEmpty
(
note
)
&&
!
isSubmitting
)
{
...
@@ -126,6 +130,8 @@
...
@@ -126,6 +130,8 @@
}
}
},
},
handleSave
(
withIssueAction
)
{
handleSave
(
withIssueAction
)
{
this
.
isSubmitting
=
true
;
if
(
this
.
note
.
length
)
{
if
(
this
.
note
.
length
)
{
const
noteData
=
{
const
noteData
=
{
endpoint
:
this
.
endpoint
,
endpoint
:
this
.
endpoint
,
...
@@ -142,7 +148,6 @@
...
@@ -142,7 +148,6 @@
if
(
this
.
noteType
===
constants
.
DISCUSSION
)
{
if
(
this
.
noteType
===
constants
.
DISCUSSION
)
{
noteData
.
data
.
note
.
type
=
constants
.
DISCUSSION_NOTE
;
noteData
.
data
.
note
.
type
=
constants
.
DISCUSSION_NOTE
;
}
}
this
.
isSubmitting
=
true
;
this
.
note
=
''
;
// Empty textarea while being requested. Repopulate in catch
this
.
note
=
''
;
// Empty textarea while being requested. Repopulate in catch
this
.
resizeTextarea
();
this
.
resizeTextarea
();
this
.
stopPolling
();
this
.
stopPolling
();
...
@@ -184,13 +189,25 @@ Please check your network connection and try again.`;
...
@@ -184,13 +189,25 @@ Please check your network connection and try again.`;
this
.
toggleIssueState
();
this
.
toggleIssueState
();
}
}
},
},
enableButton
()
{
this
.
isSubmitting
=
false
;
},
toggleIssueState
()
{
toggleIssueState
()
{
this
.
issueState
=
this
.
isIssueOpen
?
constants
.
CLOSED
:
constants
.
REOPENED
;
if
(
this
.
isIssueOpen
)
{
this
.
closeIssue
()
// This is out of scope for the Notes Vue component.
.
then
(()
=>
this
.
enableButton
())
// It was the shortest path to update the issue state and relevant places.
.
catch
(()
=>
{
const
btnClass
=
this
.
isIssueOpen
?
'
btn-reopen
'
:
'
btn-close
'
;
this
.
enableButton
();
$
(
`.js-btn-issue-action.
${
btnClass
}
:visible`
).
trigger
(
'
click
'
);
Flash
(
__
(
'
Something went wrong while closing the issue. Please try again later
'
));
});
}
else
{
this
.
reopenIssue
()
.
then
(()
=>
this
.
enableButton
())
.
catch
(()
=>
{
this
.
enableButton
();
Flash
(
__
(
'
Something went wrong while reopening the issue. Please try again later
'
));
});
}
},
},
discard
(
shouldClear
=
true
)
{
discard
(
shouldClear
=
true
)
{
// `blur` is needed to clear slash commands autocomplete cache if event fired.
// `blur` is needed to clear slash commands autocomplete cache if event fired.
...
@@ -368,15 +385,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
...
@@ -368,15 +385,19 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
</li>
</li>
</ul>
</ul>
</div>
</div>
<button
type=
"button"
<loading-button
@
click=
"handleSave(true)"
v-if=
"canUpdateIssue"
v-if=
"canUpdateIssue"
:class=
"actionButtonClassNames"
:loading=
"isSubmitting"
@
click=
"handleSave(true)"
:container-class=
"[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button'
]"
:disabled=
"isSubmitting"
:disabled=
"isSubmitting"
class=
"btn btn-comment btn-comment-and-close js-action-button"
>
:label=
"issueActionButtonTitle"
{{
issueActionButtonTitle
}}
/>
</button>
<button
<button
type=
"button"
type=
"button"
v-if=
"note.length"
v-if=
"note.length"
...
...
app/assets/javascripts/notes/index.js
View file @
db04e3ee
...
@@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
...
@@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
notesPath
:
notesDataset
.
notesPath
,
notesPath
:
notesDataset
.
notesPath
,
markdownDocsPath
:
notesDataset
.
markdownDocsPath
,
markdownDocsPath
:
notesDataset
.
markdownDocsPath
,
quickActionsDocsPath
:
notesDataset
.
quickActionsDocsPath
,
quickActionsDocsPath
:
notesDataset
.
quickActionsDocsPath
,
closeIssuePath
:
notesDataset
.
closeIssuePath
,
reopenIssuePath
:
notesDataset
.
reopenIssuePath
,
},
},
};
};
},
},
...
...
app/assets/javascripts/notes/services/notes_service.js
View file @
db04e3ee
...
@@ -32,4 +32,7 @@ export default {
...
@@ -32,4 +32,7 @@ export default {
toggleAward
(
endpoint
,
data
)
{
toggleAward
(
endpoint
,
data
)
{
return
Vue
.
http
.
post
(
endpoint
,
data
,
{
emulateJSON
:
true
});
return
Vue
.
http
.
post
(
endpoint
,
data
,
{
emulateJSON
:
true
});
},
},
toggleIssueState
(
endpoint
,
data
)
{
return
Vue
.
http
.
put
(
endpoint
,
data
);
},
};
};
app/assets/javascripts/notes/stores/actions.js
View file @
db04e3ee
...
@@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
...
@@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service
export
const
removePlaceholderNotes
=
({
commit
})
=>
export
const
removePlaceholderNotes
=
({
commit
})
=>
commit
(
types
.
REMOVE_PLACEHOLDER_NOTES
);
commit
(
types
.
REMOVE_PLACEHOLDER_NOTES
);
export
const
closeIssue
=
({
commit
,
dispatch
,
state
})
=>
service
.
toggleIssueState
(
state
.
notesData
.
closeIssuePath
)
.
then
(
res
=>
res
.
json
())
.
then
((
data
)
=>
{
commit
(
types
.
CLOSE_ISSUE
);
dispatch
(
'
emitStateChangedEvent
'
,
data
);
});
export
const
reopenIssue
=
({
commit
,
dispatch
,
state
})
=>
service
.
toggleIssueState
(
state
.
notesData
.
reopenIssuePath
)
.
then
(
res
=>
res
.
json
())
.
then
((
data
)
=>
{
commit
(
types
.
REOPEN_ISSUE
);
dispatch
(
'
emitStateChangedEvent
'
,
data
);
});
export
const
emitStateChangedEvent
=
({
commit
,
getters
},
data
)
=>
{
const
event
=
new
CustomEvent
(
'
issuable_vue_app:change
'
,
{
detail
:
{
data
,
isClosed
:
getters
.
issueState
===
constants
.
CLOSED
,
}
});
document
.
dispatchEvent
(
event
);
};
export
const
toggleIssueLocalState
=
({
commit
},
newState
)
=>
{
if
(
newState
===
constants
.
CLOSED
)
{
commit
(
types
.
CLOSE_ISSUE
);
}
else
if
(
newState
===
constants
.
REOPENED
)
{
commit
(
types
.
REOPEN_ISSUE
);
}
};
export
const
saveNote
=
({
commit
,
dispatch
},
noteData
)
=>
{
export
const
saveNote
=
({
commit
,
dispatch
},
noteData
)
=>
{
const
{
note
}
=
noteData
.
data
.
note
;
const
{
note
}
=
noteData
.
data
.
note
;
let
placeholderText
=
note
;
let
placeholderText
=
note
;
...
...
app/assets/javascripts/notes/stores/getters.js
View file @
db04e3ee
...
@@ -8,6 +8,7 @@ export const getNotesDataByProp = state => prop => state.notesData[prop];
...
@@ -8,6 +8,7 @@ export const getNotesDataByProp = state => prop => state.notesData[prop];
export
const
getNoteableData
=
state
=>
state
.
noteableData
;
export
const
getNoteableData
=
state
=>
state
.
noteableData
;
export
const
getNoteableDataByProp
=
state
=>
prop
=>
state
.
noteableData
[
prop
];
export
const
getNoteableDataByProp
=
state
=>
prop
=>
state
.
noteableData
[
prop
];
export
const
issueState
=
state
=>
state
.
noteableData
.
state
;
export
const
getUserData
=
state
=>
state
.
userData
||
{};
export
const
getUserData
=
state
=>
state
.
userData
||
{};
export
const
getUserDataByProp
=
state
=>
prop
=>
state
.
userData
&&
state
.
userData
[
prop
];
export
const
getUserDataByProp
=
state
=>
prop
=>
state
.
userData
&&
state
.
userData
[
prop
];
...
...
app/assets/javascripts/notes/stores/mutation_types.js
View file @
db04e3ee
...
@@ -12,3 +12,7 @@ export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE';
...
@@ -12,3 +12,7 @@ export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE';
export
const
TOGGLE_AWARD
=
'
TOGGLE_AWARD
'
;
export
const
TOGGLE_AWARD
=
'
TOGGLE_AWARD
'
;
export
const
TOGGLE_DISCUSSION
=
'
TOGGLE_DISCUSSION
'
;
export
const
TOGGLE_DISCUSSION
=
'
TOGGLE_DISCUSSION
'
;
export
const
UPDATE_NOTE
=
'
UPDATE_NOTE
'
;
export
const
UPDATE_NOTE
=
'
UPDATE_NOTE
'
;
// Issue
export
const
CLOSE_ISSUE
=
'
CLOSE_ISSUE
'
;
export
const
REOPEN_ISSUE
=
'
REOPEN_ISSUE
'
;
app/assets/javascripts/notes/stores/mutations.js
View file @
db04e3ee
...
@@ -152,4 +152,12 @@ export default {
...
@@ -152,4 +152,12 @@ export default {
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
,
note
);
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
,
note
);
}
}
},
},
[
types
.
CLOSE_ISSUE
](
state
)
{
Object
.
assign
(
state
.
noteableData
,
{
state
:
constants
.
CLOSED
});
},
[
types
.
REOPEN_ISSUE
](
state
)
{
Object
.
assign
(
state
.
noteableData
,
{
state
:
constants
.
REOPENED
});
},
};
};
app/assets/javascripts/vue_shared/components/loading_button.vue
View file @
db04e3ee
...
@@ -39,7 +39,7 @@
...
@@ -39,7 +39,7 @@
required
:
false
,
required
:
false
,
},
},
containerClass
:
{
containerClass
:
{
type
:
String
,
type
:
[
String
,
Array
,
Object
]
,
required
:
false
,
required
:
false
,
default
:
'
btn btn-align-content
'
,
default
:
'
btn btn-align-content
'
,
},
},
...
...
app/assets/stylesheets/framework/common.scss
View file @
db04e3ee
...
@@ -457,9 +457,11 @@ img.emoji {
...
@@ -457,9 +457,11 @@ img.emoji {
.prepend-top-10
{
margin-top
:
10px
;
}
.prepend-top-10
{
margin-top
:
10px
;
}
.prepend-top-15
{
margin-top
:
15px
;
}
.prepend-top-15
{
margin-top
:
15px
;
}
.prepend-top-default
{
margin-top
:
$gl-padding
!
important
;
}
.prepend-top-default
{
margin-top
:
$gl-padding
!
important
;
}
.prepend-top-16
{
margin-top
:
16px
;
}
.prepend-top-20
{
margin-top
:
20px
;
}
.prepend-top-20
{
margin-top
:
20px
;
}
.prepend-left-4
{
margin-left
:
4px
;
}
.prepend-left-4
{
margin-left
:
4px
;
}
.prepend-left-5
{
margin-left
:
5px
;
}
.prepend-left-5
{
margin-left
:
5px
;
}
.prepend-left-8
{
margin-left
:
8px
;
}
.prepend-left-10
{
margin-left
:
10px
;
}
.prepend-left-10
{
margin-left
:
10px
;
}
.prepend-left-15
{
margin-left
:
15px
;
}
.prepend-left-15
{
margin-left
:
15px
;
}
.prepend-left-default
{
margin-left
:
$gl-padding
;
}
.prepend-left-default
{
margin-left
:
$gl-padding
;
}
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
db04e3ee
...
@@ -736,10 +736,6 @@
...
@@ -736,10 +736,6 @@
}
}
}
}
.droplab-item-ignore
{
pointer-events
:
none
;
}
.pika-single.animate-picker.is-bound
,
.pika-single.animate-picker.is-bound
,
.pika-single.animate-picker.is-bound.is-hidden
{
.pika-single.animate-picker.is-bound.is-hidden
{
/*
/*
...
...
app/assets/stylesheets/framework/forms.scss
View file @
db04e3ee
...
@@ -182,6 +182,7 @@ label {
...
@@ -182,6 +182,7 @@ label {
.help-block
{
.help-block
{
margin-bottom
:
0
;
margin-bottom
:
0
;
margin-top
:
#{
$grid-size
/
2
}
;
}
}
.gl-field-error
{
.gl-field-error
{
...
...
app/assets/stylesheets/pages/issues.scss
View file @
db04e3ee
...
@@ -201,11 +201,6 @@ ul.related-merge-requests > li {
...
@@ -201,11 +201,6 @@ ul.related-merge-requests > li {
}
}
.create-mr-dropdown-wrap
{
.create-mr-dropdown-wrap
{
.branch-message
,
.ref-message
{
display
:
none
;
}
.
ref
:
:
selection
{
.
ref
:
:
selection
{
color
:
$placeholder-text-color
;
color
:
$placeholder-text-color
;
}
}
...
@@ -236,6 +231,17 @@ ul.related-merge-requests > li {
...
@@ -236,6 +231,17 @@ ul.related-merge-requests > li {
transform
:
translateY
(
0
);
transform
:
translateY
(
0
);
display
:
none
;
display
:
none
;
margin-top
:
4px
;
margin-top
:
4px
;
// override dropdown item styles
.btn.btn-success
{
@include
btn-default
;
@include
btn-green
;
border-style
:
solid
;
border-width
:
1px
;
line-height
:
$line-height-base
;
width
:
auto
;
}
}
}
.create-merge-request-dropdown-toggle
{
.create-merge-request-dropdown-toggle
{
...
@@ -245,66 +251,6 @@ ul.related-merge-requests > li {
...
@@ -245,66 +251,6 @@ ul.related-merge-requests > li {
margin-left
:
0
;
margin-left
:
0
;
}
}
}
}
.droplab-item-ignore
{
pointer-events
:
auto
;
}
.create-item
{
cursor
:
pointer
;
margin
:
0
1px
;
&
:hover
,
&
:focus
{
background-color
:
$dropdown-item-hover-bg
;
color
:
$gl-text-color
;
}
}
li
.divider
{
margin
:
8px
10px
;
}
li
:not
(
.divider
)
{
padding
:
8px
9px
;
&
:last-child
{
padding-bottom
:
8px
;
}
&
.droplab-item-selected
{
.icon-container
{
i
{
visibility
:
visible
;
}
}
.description
{
display
:
block
;
}
}
&
.droplab-item-ignore
{
padding-top
:
8px
;
}
.icon-container
{
float
:
left
;
i
{
visibility
:
hidden
;
}
}
.description
{
padding-left
:
22px
;
}
input
,
span
{
margin
:
4px
0
0
;
}
}
}
}
.discussion-reply-holder
.note-edit-form
{
.discussion-reply-holder
.note-edit-form
{
...
...
app/services/merge_requests/build_service.rb
View file @
db04e3ee
...
@@ -161,10 +161,12 @@ module MergeRequests
...
@@ -161,10 +161,12 @@ module MergeRequests
merge_request
.
title
=
"Resolve
\"
#{
issue
.
title
}
\"
"
if
issue
.
is_a?
(
Issue
)
merge_request
.
title
=
"Resolve
\"
#{
issue
.
title
}
\"
"
if
issue
.
is_a?
(
Issue
)
unless
merge_request
.
title
return
if
merge_request
.
title
.
present?
branch_title
=
source_branch
.
downcase
.
remove
(
issue_iid
.
downcase
).
titleize
.
humanize
if
issue_iid
.
present?
merge_request
.
title
=
"Resolve
#{
issue_iid
}
"
merge_request
.
title
=
"Resolve
#{
issue_iid
}
"
merge_request
.
title
+=
"
\"
#{
branch_title
}
\"
"
unless
branch_title
.
empty?
branch_title
=
source_branch
.
downcase
.
remove
(
issue_iid
.
downcase
).
titleize
.
humanize
merge_request
.
title
+=
"
\"
#{
branch_title
}
\"
"
if
branch_title
.
present?
end
end
end
end
...
...
app/views/projects/issues/_discussion.html.haml
View file @
db04e3ee
...
@@ -12,6 +12,8 @@
...
@@ -12,6 +12,8 @@
markdown_docs_path:
help_page_path
(
'user/markdown'
),
markdown_docs_path:
help_page_path
(
'user/markdown'
),
quick_actions_docs_path:
help_page_path
(
'user/project/quick_actions'
),
quick_actions_docs_path:
help_page_path
(
'user/project/quick_actions'
),
notes_path:
notes_url
,
notes_path:
notes_url
,
close_issue_path:
issue_path
(
@issue
,
issue:
{
state_event: :close
},
format:
'json'
),
reopen_issue_path:
issue_path
(
@issue
,
issue:
{
state_event: :reopen
},
format:
'json'
),
last_fetched_at:
Time
.
now
.
to_i
,
last_fetched_at:
Time
.
now
.
to_i
,
noteable_data:
serialize_issuable
(
@issue
),
noteable_data:
serialize_issuable
(
@issue
),
current_user_data:
UserSerializer
.
new
.
represent
(
current_user
,
only_path:
true
).
to_json
}
}
current_user_data:
UserSerializer
.
new
.
represent
(
current_user
,
only_path:
true
).
to_json
}
}
app/views/projects/issues/_new_branch.html.haml
View file @
db04e3ee
...
@@ -21,30 +21,33 @@
...
@@ -21,30 +21,33 @@
%button
.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle
{
type:
'button'
,
data:
{
dropdown:
{
trigger:
'#create-merge-request-dropdown'
}
}
}
%button
.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle
{
type:
'button'
,
data:
{
dropdown:
{
trigger:
'#create-merge-request-dropdown'
}
}
}
=
icon
(
'caret-down'
)
=
icon
(
'caret-down'
)
.droplab-dropdown
%ul
#create-merge-request-dropdown
.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors
{
data:
{
dropdown:
true
}
}
%ul
#create-merge-request-dropdown
.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors
{
data:
{
dropdown:
true
}
}
-
if
can_create_merge_request
-
if
can_create_merge_request
%li
.create-item.droplab-item-selected.droplab-item-ignore-hiding
{
role:
'button'
,
data:
{
value:
'create-mr'
,
text:
'Create merge request'
}
}
%li
.droplab-item-selected
{
role:
'button'
,
data:
{
value:
'create-mr'
,
text:
_
(
'Create merge request'
)
}
}
.menu-item.droplab-item-ignore-hiding
.menu-item
.icon-container.droplab-item-ignore-hiding
=
icon
(
'check'
)
=
icon
(
'check'
,
class:
'icon'
)
.description.droplab-item-ignore-hiding
Create merge request and branch
=
_
(
'Create merge request and branch'
)
%li
.create-item.droplab-item-ignore-hiding
{
class:
[
!
can_create_merge_request
&&
'droplab-item-selected'
],
role:
'button'
,
data:
{
value:
'create-branch'
,
text:
'Create branch'
}
}
%li
{
class:
[
!
can_create_merge_request
&&
'droplab-item-selected'
],
role:
'button'
,
data:
{
value:
'create-branch'
,
text:
_
(
'Create branch'
)
}
}
.menu-item.droplab-item-ignore-hiding
.menu-item
.icon-container.droplab-item-ignore-hiding
=
icon
(
'check'
)
=
icon
(
'check'
,
class:
'icon'
)
.description.droplab-item-ignore-hiding
Create branch
=
_
(
'Create branch'
)
%li
.divider
%li
.divider.droplab-item-ignore
%li
.droplab-item-ignore
%li
.droplab-item-ignore.prepend-left-8.append-right-8.prepend-top-16
Branch name
.form-group
%input
.js-branch-name.form-control.droplab-item-ignore
{
type:
'text'
,
placeholder:
"#{@issue.to_branch_name}"
,
value:
"#{@issue.to_branch_name}"
}
%label
{
for:
'new-branch-name'
}
%span
.js-branch-message.branch-message.droplab-item-ignore
=
_
(
'Branch name'
)
%input
#new-branch-name
.js-branch-name.form-control
{
type:
'text'
,
placeholder:
"#{@issue.to_branch_name}"
,
value:
"#{@issue.to_branch_name}"
}
%li
.droplab-item-ignore
%span
.js-branch-message.help-block
Source (branch or tag)
%input
.js-ref.ref.form-control.droplab-item-ignore
{
type:
'text'
,
placeholder:
"#{@project.default_branch}"
,
value:
"#{@project.default_branch}"
,
data:
{
value:
"#{@project.default_branch}"
}
}
.form-group
%span
.js-ref-message.ref-message.droplab-item-ignore
%label
{
for:
'source-name'
}
=
_
(
'Source (branch or tag)'
)
%li
.droplab-item-ignore
%input
#source-name
.js-ref.ref.form-control
{
type:
'text'
,
placeholder:
"#{@project.default_branch}"
,
value:
"#{@project.default_branch}"
,
data:
{
value:
"#{@project.default_branch}"
}
}
%button
.btn.btn-success.js-create-target.droplab-item-ignore
{
type:
'button'
,
data:
{
action:
'create-mr'
}
}
%span
.js-ref-message.help-block
Create merge request
.form-group
%button
.btn.btn-success.js-create-target
{
type:
'button'
,
data:
{
action:
'create-mr'
}
}
=
_
(
'Create merge request'
)
changelogs/unreleased/42923-close-issue.yml
0 → 100644
View file @
db04e3ee
---
title
:
Fix close button on issues not working on mobile
merge_request
:
author
:
type
:
fixed
changelogs/unreleased/winh-new-branch-dropdown-style.yml
0 → 100644
View file @
db04e3ee
---
title
:
Cleanup new branch/merge request form in issues
merge_request
:
16854
author
:
type
:
fixed
spec/javascripts/droplab/drop_down_spec.js
View file @
db04e3ee
import
DropDown
from
'
~/droplab/drop_down
'
;
import
DropDown
from
'
~/droplab/drop_down
'
;
import
utils
from
'
~/droplab/utils
'
;
import
utils
from
'
~/droplab/utils
'
;
import
{
SELECTED_CLASS
,
IGNORE_CLASS
}
from
'
~/droplab/constants
'
;
import
{
SELECTED_CLASS
}
from
'
~/droplab/constants
'
;
describe
(
'
DropDown
'
,
function
()
{
describe
(
'
Drop
Lab Drop
Down
'
,
function
()
{
describe
(
'
class constructor
'
,
function
()
{
describe
(
'
class constructor
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
spyOn
(
DropDown
.
prototype
,
'
getItems
'
);
spyOn
(
DropDown
.
prototype
,
'
getItems
'
);
...
@@ -128,93 +128,131 @@ describe('DropDown', function () {
...
@@ -128,93 +128,131 @@ describe('DropDown', function () {
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
classList
=
jasmine
.
createSpyObj
(
'
classList
'
,
[
'
contains
'
]);
this
.
classList
=
jasmine
.
createSpyObj
(
'
classList
'
,
[
'
contains
'
]);
this
.
list
=
{
dispatchEvent
:
()
=>
{}
};
this
.
list
=
{
dispatchEvent
:
()
=>
{}
};
this
.
dropdown
=
{
hide
:
()
=>
{},
list
:
this
.
list
,
addSelectedClass
:
()
=>
{}
};
this
.
dropdown
=
{
this
.
event
=
{
preventDefault
:
()
=>
{},
target
:
{
classList
:
this
.
classList
}
};
hideOnClick
:
true
,
hide
:
()
=>
{},
list
:
this
.
list
,
addSelectedClass
:
()
=>
{},
};
this
.
event
=
{
preventDefault
:
()
=>
{},
target
:
{
classList
:
this
.
classList
,
closest
:
()
=>
null
,
},
};
this
.
customEvent
=
{};
this
.
customEvent
=
{};
this
.
closestElement
=
{};
this
.
dummyListItem
=
document
.
createElement
(
'
li
'
);
spyOn
(
this
.
event
.
target
,
'
closest
'
).
and
.
callFake
((
selector
)
=>
{
if
(
selector
===
'
li
'
)
{
return
this
.
dummyListItem
;
}
return
null
;
});
spyOn
(
this
.
dropdown
,
'
hide
'
);
spyOn
(
this
.
dropdown
,
'
hide
'
);
spyOn
(
this
.
dropdown
,
'
addSelectedClass
'
);
spyOn
(
this
.
dropdown
,
'
addSelectedClass
'
);
spyOn
(
this
.
list
,
'
dispatchEvent
'
);
spyOn
(
this
.
list
,
'
dispatchEvent
'
);
spyOn
(
this
.
event
,
'
preventDefault
'
);
spyOn
(
this
.
event
,
'
preventDefault
'
);
spyOn
(
window
,
'
CustomEvent
'
).
and
.
returnValue
(
this
.
customEvent
);
spyOn
(
window
,
'
CustomEvent
'
).
and
.
returnValue
(
this
.
customEvent
);
spyOn
(
utils
,
'
closest
'
).
and
.
returnValues
(
this
.
closestElement
,
undefined
);
this
.
classList
.
contains
.
and
.
returnValue
(
false
);
this
.
classList
.
contains
.
and
.
returnValue
(
false
);
});
it
(
'
should call event.target.closest
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
});
it
(
'
should call utils.closest
'
,
function
()
{
expect
(
this
.
event
.
target
.
closest
).
toHaveBeenCalledWith
(
'
.droplab-item-ignore
'
);
expect
(
utils
.
closest
).
toHaveBeenCalledWith
(
this
.
event
.
target
,
'
LI
'
);
expect
(
this
.
event
.
target
.
closest
).
toHaveBeenCalledWith
(
'
li
'
);
});
});
it
(
'
should call addSelectedClass
'
,
function
()
{
it
(
'
should call addSelectedClass
'
,
function
()
{
expect
(
this
.
dropdown
.
addSelectedClass
).
toHaveBeenCalledWith
(
this
.
closestElement
);
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
dropdown
.
addSelectedClass
).
toHaveBeenCalledWith
(
this
.
dummyListItem
);
});
});
it
(
'
should call .preventDefault
'
,
function
()
{
it
(
'
should call .preventDefault
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
event
.
preventDefault
).
toHaveBeenCalled
();
expect
(
this
.
event
.
preventDefault
).
toHaveBeenCalled
();
});
});
it
(
'
should call .hide
'
,
function
()
{
it
(
'
should call .hide
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
dropdown
.
hide
).
toHaveBeenCalled
();
expect
(
this
.
dropdown
.
hide
).
toHaveBeenCalled
();
});
});
it
(
'
should construct CustomEvent
'
,
function
()
{
it
(
'
should construct CustomEvent
'
,
function
()
{
expect
(
window
.
CustomEvent
).
toHaveBeenCalledWith
(
'
click.dl
'
,
jasmine
.
any
(
Object
));
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
});
it
(
'
should call .classList.contains checking for IGNORE_CLASS
'
,
function
()
{
expect
(
window
.
CustomEvent
).
toHaveBeenCalledWith
(
'
click.dl
'
,
jasmine
.
any
(
Object
));
expect
(
this
.
classList
.
contains
).
toHaveBeenCalledWith
(
IGNORE_CLASS
);
});
});
it
(
'
should call .dispatchEvent with the customEvent
'
,
function
()
{
it
(
'
should call .dispatchEvent with the customEvent
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
list
.
dispatchEvent
).
toHaveBeenCalledWith
(
this
.
customEvent
);
expect
(
this
.
list
.
dispatchEvent
).
toHaveBeenCalledWith
(
this
.
customEvent
);
});
});
describe
(
'
if the target is a UL element
'
,
function
()
{
describe
(
'
if the target is a UL element
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
event
=
{
preventDefault
:
()
=>
{},
target
:
{
tagName
:
'
UL
'
,
classList
:
this
.
classList
}
};
this
.
event
.
target
=
document
.
createElement
(
'
ul
'
);
spyOn
(
this
.
event
,
'
preventDefault
'
);
utils
.
closest
.
calls
.
reset
();
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
spyOn
(
this
.
event
.
target
,
'
closest
'
);
});
});
it
(
'
should return immediately
'
,
function
()
{
it
(
'
should return immediately
'
,
function
()
{
expect
(
utils
.
closest
).
not
.
toHaveBeenCalled
();
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
event
.
target
.
closest
).
not
.
toHaveBeenCalled
();
expect
(
this
.
dropdown
.
addSelectedClass
).
not
.
toHaveBeenCalled
();
});
});
});
});
describe
(
'
if the target has the
IGNORE_CLASS
class
'
,
function
()
{
describe
(
'
if the target has the
droplab-item-ignore
class
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
event
=
{
preventDefault
:
()
=>
{},
target
:
{
tagName
:
'
LI
'
,
classList
:
this
.
classList
}
};
this
.
ignoredButton
=
document
.
createElement
(
'
button
'
);
this
.
ignoredButton
.
classList
.
add
(
'
droplab-item-ignore
'
);
this
.
event
.
target
=
this
.
ignoredButton
;
spyOn
(
this
.
event
,
'
preventDefault
'
);
spyOn
(
this
.
ignoredButton
,
'
closest
'
).
and
.
callThrough
();
this
.
classList
.
contains
.
and
.
returnValue
(
true
);
});
utils
.
closest
.
calls
.
reset
();
it
(
'
does not select element
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
});
it
(
'
should return immediately
'
,
function
()
{
expect
(
this
.
ignoredButton
.
closest
.
calls
.
count
()).
toBe
(
1
);
expect
(
utils
.
closest
).
not
.
toHaveBeenCalled
();
expect
(
this
.
ignoredButton
.
closest
).
toHaveBeenCalledWith
(
'
.droplab-item-ignore
'
);
expect
(
this
.
dropdown
.
addSelectedClass
).
not
.
toHaveBeenCalled
();
});
});
});
});
describe
(
'
if no selected element exists
'
,
function
()
{
describe
(
'
if no selected element exists
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
event
.
preventDefault
.
calls
.
reset
();
this
.
event
.
preventDefault
.
calls
.
reset
();
this
.
clickEvent
=
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
this
.
dummyListItem
=
null
;
});
it
(
'
should return undefined
'
,
function
()
{
expect
(
this
.
clickEvent
).
toBe
(
undefined
);
});
});
it
(
'
should return before .preventDefault is called
'
,
function
()
{
it
(
'
should return before .preventDefault is called
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
event
.
preventDefault
).
not
.
toHaveBeenCalled
();
expect
(
this
.
event
.
preventDefault
).
not
.
toHaveBeenCalled
();
expect
(
this
.
dropdown
.
addSelectedClass
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
if hideOnClick is false
'
,
()
=>
{
beforeEach
(
function
()
{
this
.
dropdown
.
hideOnClick
=
false
;
this
.
dropdown
.
hide
.
calls
.
reset
();
});
it
(
'
should not call .hide
'
,
function
()
{
DropDown
.
prototype
.
clickEvent
.
call
(
this
.
dropdown
,
this
.
event
);
expect
(
this
.
dropdown
.
hide
).
not
.
toHaveBeenCalled
();
});
});
});
});
});
});
...
@@ -278,20 +316,23 @@ describe('DropDown', function () {
...
@@ -278,20 +316,23 @@ describe('DropDown', function () {
describe
(
'
addEvents
'
,
function
()
{
describe
(
'
addEvents
'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
this
.
list
=
{
addEventListener
:
()
=>
{}
};
this
.
list
=
{
addEventListener
:
()
=>
{},
querySelectorAll
:
()
=>
[],
};
this
.
dropdown
=
{
this
.
dropdown
=
{
list
:
this
.
list
,
list
:
this
.
list
,
clickEvent
:
()
=>
{},
clickEvent
:
()
=>
{},
closeDropdown
:
()
=>
{},
closeDropdown
:
()
=>
{},
eventWrapper
:
{},
eventWrapper
:
{},
};
};
});
it
(
'
should call .addEventListener
'
,
function
()
{
spyOn
(
this
.
list
,
'
addEventListener
'
);
spyOn
(
this
.
list
,
'
addEventListener
'
);
DropDown
.
prototype
.
addEvents
.
call
(
this
.
dropdown
);
DropDown
.
prototype
.
addEvents
.
call
(
this
.
dropdown
);
});
it
(
'
should call .addEventListener
'
,
function
()
{
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
click
'
,
jasmine
.
any
(
Function
));
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
click
'
,
jasmine
.
any
(
Function
));
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
keyup
'
,
jasmine
.
any
(
Function
));
expect
(
this
.
list
.
addEventListener
).
toHaveBeenCalledWith
(
'
keyup
'
,
jasmine
.
any
(
Function
));
});
});
...
...
spec/javascripts/notes/helpers.js
0 → 100644
View file @
db04e3ee
// eslint-disable-next-line import/prefer-default-export
export
const
resetStore
=
(
store
)
=>
{
store
.
replaceState
({
notes
:
[],
targetNoteHash
:
null
,
lastFetchedAt
:
null
,
notesData
:
{},
userData
:
{},
noteableData
:
{},
});
};
spec/javascripts/notes/mock_data.js
View file @
db04e3ee
...
@@ -7,6 +7,8 @@ export const notesDataMock = {
...
@@ -7,6 +7,8 @@ export const notesDataMock = {
notesPath
:
'
/gitlab-org/gitlab-ce/noteable/issue/98/notes
'
,
notesPath
:
'
/gitlab-org/gitlab-ce/noteable/issue/98/notes
'
,
quickActionsDocsPath
:
'
/help/user/project/quick_actions
'
,
quickActionsDocsPath
:
'
/help/user/project/quick_actions
'
,
registerPath
:
'
/users/sign_in?redirect_to_referer=yes#register-pane
'
,
registerPath
:
'
/users/sign_in?redirect_to_referer=yes#register-pane
'
,
closeIssuePath
:
'
/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close
'
,
reopenIssuePath
:
'
/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen
'
,
};
};
export
const
userDataMock
=
{
export
const
userDataMock
=
{
...
...
spec/javascripts/notes/stores/actions_spec.js
View file @
db04e3ee
import
Vue
from
'
vue
'
;
import
_
from
'
underscore
'
;
import
*
as
actions
from
'
~/notes/stores/actions
'
;
import
*
as
actions
from
'
~/notes/stores/actions
'
;
import
store
from
'
~/notes/stores
'
;
import
testAction
from
'
../../helpers/vuex_action_helper
'
;
import
testAction
from
'
../../helpers/vuex_action_helper
'
;
import
{
resetStore
}
from
'
../helpers
'
;
import
{
discussionMock
,
notesDataMock
,
userDataMock
,
noteableDataMock
,
individualNote
}
from
'
../mock_data
'
;
import
{
discussionMock
,
notesDataMock
,
userDataMock
,
noteableDataMock
,
individualNote
}
from
'
../mock_data
'
;
describe
(
'
Actions Notes Store
'
,
()
=>
{
describe
(
'
Actions Notes Store
'
,
()
=>
{
afterEach
(()
=>
{
resetStore
(
store
);
});
describe
(
'
setNotesData
'
,
()
=>
{
describe
(
'
setNotesData
'
,
()
=>
{
it
(
'
should set received notes data
'
,
(
done
)
=>
{
it
(
'
should set received notes data
'
,
(
done
)
=>
{
testAction
(
actions
.
setNotesData
,
null
,
{
notesData
:
{}
},
[
testAction
(
actions
.
setNotesData
,
null
,
{
notesData
:
{}
},
[
...
@@ -58,4 +66,67 @@ describe('Actions Notes Store', () => {
...
@@ -58,4 +66,67 @@ describe('Actions Notes Store', () => {
],
done
);
],
done
);
});
});
});
});
describe
(
'
async methods
'
,
()
=>
{
const
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
({}),
{
status
:
200
,
}));
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
interceptor
);
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
);
});
describe
(
'
closeIssue
'
,
()
=>
{
it
(
'
sets state as closed
'
,
(
done
)
=>
{
store
.
dispatch
(
'
closeIssue
'
,
{
notesData
:
{
closeIssuePath
:
''
}
})
.
then
(()
=>
{
expect
(
store
.
state
.
noteableData
.
state
).
toEqual
(
'
closed
'
);
done
();
})
.
catch
(
done
.
fail
);
});
});
describe
(
'
reopenIssue
'
,
()
=>
{
it
(
'
sets state as reopened
'
,
(
done
)
=>
{
store
.
dispatch
(
'
reopenIssue
'
,
{
notesData
:
{
reopenIssuePath
:
''
}
})
.
then
(()
=>
{
expect
(
store
.
state
.
noteableData
.
state
).
toEqual
(
'
reopened
'
);
done
();
})
.
catch
(
done
.
fail
);
});
});
});
describe
(
'
emitStateChangedEvent
'
,
()
=>
{
it
(
'
emits an event on the document
'
,
()
=>
{
document
.
addEventListener
(
'
issuable_vue_app:change
'
,
(
event
)
=>
{
expect
(
event
.
detail
.
data
).
toEqual
({
id
:
'
1
'
,
state
:
'
closed
'
});
expect
(
event
.
detail
.
isClosed
).
toEqual
(
false
);
});
store
.
dispatch
(
'
emitStateChangedEvent
'
,
{
id
:
'
1
'
,
state
:
'
closed
'
});
});
});
describe
(
'
toggleIssueLocalState
'
,
()
=>
{
it
(
'
sets issue state as closed
'
,
(
done
)
=>
{
testAction
(
actions
.
toggleIssueLocalState
,
'
closed
'
,
{},
[
{
type
:
'
CLOSE_ISSUE
'
,
payload
:
'
closed
'
},
],
done
);
});
it
(
'
sets issue state as reopened
'
,
(
done
)
=>
{
testAction
(
actions
.
toggleIssueLocalState
,
'
reopened
'
,
{},
[
{
type
:
'
REOPEN_ISSUE
'
,
payload
:
'
reopened
'
},
],
done
);
});
});
});
});
spec/javascripts/notes/stores/getters_spec.js
View file @
db04e3ee
...
@@ -55,4 +55,10 @@ describe('Getters Notes Store', () => {
...
@@ -55,4 +55,10 @@ describe('Getters Notes Store', () => {
expect
(
getters
.
getCurrentUserLastNote
(
state
)).
toEqual
(
individualNote
.
notes
[
0
]);
expect
(
getters
.
getCurrentUserLastNote
(
state
)).
toEqual
(
individualNote
.
notes
[
0
]);
});
});
});
});
describe
(
'
issueState
'
,
()
=>
{
it
(
'
should return the issue state
'
,
()
=>
{
expect
(
getters
.
issueState
(
state
)).
toEqual
(
noteableDataMock
.
state
);
});
});
});
});
spec/services/merge_requests/build_service_spec.rb
View file @
db04e3ee
...
@@ -286,15 +286,24 @@ describe MergeRequests::BuildService do
...
@@ -286,15 +286,24 @@ describe MergeRequests::BuildService do
end
end
end
end
context
'branch starts with JIRA-formatted external issue IID'
do
describe
'with JIRA enabled'
do
let
(
:source_branch
)
{
'EXMPL-12345'
}
before
do
before
do
allow
(
project
).
to
receive
(
:external_issue_tracker
).
and_return
(
true
)
allow
(
project
).
to
receive
(
:external_issue_tracker
).
and_return
(
true
)
allow
(
project
).
to
receive
(
:issues_enabled?
).
and_return
(
false
)
allow
(
project
).
to
receive
(
:issues_enabled?
).
and_return
(
false
)
allow
(
project
).
to
receive
(
:external_issue_reference_pattern
).
and_return
(
IssueTrackerService
.
reference_pattern
)
allow
(
project
).
to
receive
(
:external_issue_reference_pattern
).
and_return
(
IssueTrackerService
.
reference_pattern
)
end
end
context
'branch does not start with JIRA-formatted external issue IID'
do
let
(
:source_branch
)
{
'test-branch'
}
it
'sets the title to the humanized branch title'
do
expect
(
merge_request
.
title
).
to
eq
(
'Test branch'
)
end
end
context
'branch starts with JIRA-formatted external issue IID'
do
let
(
:source_branch
)
{
'EXMPL-12345'
}
it
'sets the title to the humanized branch title'
do
it
'sets the title to the humanized branch title'
do
expect
(
merge_request
.
title
).
to
eq
(
'Resolve EXMPL-12345'
)
expect
(
merge_request
.
title
).
to
eq
(
'Resolve EXMPL-12345'
)
end
end
...
@@ -316,6 +325,7 @@ describe MergeRequests::BuildService do
...
@@ -316,6 +325,7 @@ describe MergeRequests::BuildService do
end
end
end
end
end
end
end
context
'source branch does not exist'
do
context
'source branch does not exist'
do
before
do
before
do
...
...
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