Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
0d6e50d5
Commit
0d6e50d5
authored
Aug 07, 2018
by
Paul Slaughter
Committed by
Phil Hughes
Aug 07, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create Web IDE MR and branch picker
parent
0e90f27f
Changes
50
Hide whitespace changes
Inline
Side-by-side
Showing
50 changed files
with
1532 additions
and
402 deletions
+1532
-402
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+12
-0
app/assets/javascripts/ide/components/branches/item.vue
app/assets/javascripts/ide/components/branches/item.vue
+60
-0
app/assets/javascripts/ide/components/branches/search_list.vue
...ssets/javascripts/ide/components/branches/search_list.vue
+111
-0
app/assets/javascripts/ide/components/ide_tree.vue
app/assets/javascripts/ide/components/ide_tree.vue
+1
-1
app/assets/javascripts/ide/components/ide_tree_list.vue
app/assets/javascripts/ide/components/ide_tree_list.vue
+3
-2
app/assets/javascripts/ide/components/merge_requests/dropdown.vue
...ts/javascripts/ide/components/merge_requests/dropdown.vue
+0
-63
app/assets/javascripts/ide/components/merge_requests/item.vue
...assets/javascripts/ide/components/merge_requests/item.vue
+9
-9
app/assets/javascripts/ide/components/merge_requests/list.vue
...assets/javascripts/ide/components/merge_requests/list.vue
+97
-75
app/assets/javascripts/ide/components/nav_dropdown.vue
app/assets/javascripts/ide/components/nav_dropdown.vue
+59
-0
app/assets/javascripts/ide/components/nav_dropdown_button.vue
...assets/javascripts/ide/components/nav_dropdown_button.vue
+54
-0
app/assets/javascripts/ide/components/nav_form.vue
app/assets/javascripts/ide/components/nav_form.vue
+40
-0
app/assets/javascripts/ide/components/shared/tokened_input.vue
...ssets/javascripts/ide/components/shared/tokened_input.vue
+121
-0
app/assets/javascripts/ide/stores/index.js
app/assets/javascripts/ide/stores/index.js
+2
-0
app/assets/javascripts/ide/stores/modules/branches/actions.js
...assets/javascripts/ide/stores/modules/branches/actions.js
+39
-0
app/assets/javascripts/ide/stores/modules/branches/index.js
app/assets/javascripts/ide/stores/modules/branches/index.js
+10
-0
app/assets/javascripts/ide/stores/modules/branches/mutation_types.js
...javascripts/ide/stores/modules/branches/mutation_types.js
+5
-0
app/assets/javascripts/ide/stores/modules/branches/mutations.js
...sets/javascripts/ide/stores/modules/branches/mutations.js
+21
-0
app/assets/javascripts/ide/stores/modules/branches/state.js
app/assets/javascripts/ide/stores/modules/branches/state.js
+4
-0
app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
.../javascripts/ide/stores/modules/merge_requests/actions.js
+11
-30
app/assets/javascripts/ide/stores/modules/merge_requests/getters.js
.../javascripts/ide/stores/modules/merge_requests/getters.js
+0
-4
app/assets/javascripts/ide/stores/modules/merge_requests/index.js
...ts/javascripts/ide/stores/modules/merge_requests/index.js
+0
-2
app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
...avascripts/ide/stores/modules/merge_requests/mutations.js
+9
-9
app/assets/javascripts/ide/stores/modules/merge_requests/state.js
...ts/javascripts/ide/stores/modules/merge_requests/state.js
+2
-8
app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
...cripts/vue_shared/components/dropdown/dropdown_button.vue
+11
-3
app/assets/javascripts/vue_shared/components/icon.vue
app/assets/javascripts/vue_shared/components/icon.vue
+1
-1
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+2
-1
app/assets/stylesheets/framework/images.scss
app/assets/stylesheets/framework/images.scss
+1
-1
app/assets/stylesheets/page_bundles/ide.scss
app/assets/stylesheets/page_bundles/ide.scss
+51
-9
changelogs/unreleased/46165-web-ide-branch-picker.yml
changelogs/unreleased/46165-web-ide-branch-picker.yml
+5
-0
doc/user/project/web_ide/index.md
doc/user/project/web_ide/index.md
+11
-2
lib/api/branches.rb
lib/api/branches.rb
+1
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-6
spec/features/projects/tree/create_directory_spec.rb
spec/features/projects/tree/create_directory_spec.rb
+2
-2
spec/features/projects/tree/create_file_spec.rb
spec/features/projects/tree/create_file_spec.rb
+1
-1
spec/javascripts/helpers/vuex_action_helper.js
spec/javascripts/helpers/vuex_action_helper.js
+1
-1
spec/javascripts/ide/components/branches/item_spec.js
spec/javascripts/ide/components/branches/item_spec.js
+53
-0
spec/javascripts/ide/components/branches/search_list_spec.js
spec/javascripts/ide/components/branches/search_list_spec.js
+79
-0
spec/javascripts/ide/components/merge_requests/dropdown_spec.js
...avascripts/ide/components/merge_requests/dropdown_spec.js
+0
-47
spec/javascripts/ide/components/merge_requests/item_spec.js
spec/javascripts/ide/components/merge_requests/item_spec.js
+7
-8
spec/javascripts/ide/components/merge_requests/list_spec.js
spec/javascripts/ide/components/merge_requests/list_spec.js
+72
-40
spec/javascripts/ide/components/nav_dropdown_button_spec.js
spec/javascripts/ide/components/nav_dropdown_button_spec.js
+63
-0
spec/javascripts/ide/components/nav_dropdown_spec.js
spec/javascripts/ide/components/nav_dropdown_spec.js
+50
-0
spec/javascripts/ide/components/shared/tokened_input_spec.js
spec/javascripts/ide/components/shared/tokened_input_spec.js
+132
-0
spec/javascripts/ide/helpers.js
spec/javascripts/ide/helpers.js
+2
-0
spec/javascripts/ide/mock_data.js
spec/javascripts/ide/mock_data.js
+30
-0
spec/javascripts/ide/stores/modules/branches/actions_spec.js
spec/javascripts/ide/stores/modules/branches/actions_spec.js
+193
-0
spec/javascripts/ide/stores/modules/branches/mutations_spec.js
...javascripts/ide/stores/modules/branches/mutations_spec.js
+51
-0
spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
...scripts/ide/stores/modules/merge_requests/actions_spec.js
+14
-63
spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
...ripts/ide/stores/modules/merge_requests/mutations_spec.js
+8
-11
spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
...ts/vue_shared/components/dropdown/dropdown_button_spec.js
+12
-3
No files found.
app/assets/javascripts/api.js
View file @
0d6e50d5
...
...
@@ -244,6 +244,18 @@ const Api = {
});
},
branches
(
id
,
query
=
''
,
options
=
{})
{
const
url
=
Api
.
buildUrl
(
this
.
createBranchPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
id
));
return
axios
.
get
(
url
,
{
params
:
{
search
:
query
,
per_page
:
20
,
...
options
,
},
});
},
createBranch
(
id
,
{
ref
,
branch
})
{
const
url
=
Api
.
buildUrl
(
this
.
createBranchPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
id
));
...
...
app/assets/javascripts/ide/components/branches/item.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Timeago
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
router
from
'
../../ide_router
'
;
export
default
{
components
:
{
Icon
,
Timeago
,
},
props
:
{
item
:
{
type
:
Object
,
required
:
true
,
},
projectId
:
{
type
:
String
,
required
:
true
,
},
isActive
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
computed
:
{
branchHref
()
{
return
router
.
resolve
(
`/project/
${
this
.
projectId
}
/edit/
${
this
.
item
.
name
}
`
).
href
;
},
},
};
</
script
>
<
template
>
<a
:href=
"branchHref"
class=
"btn-link d-flex align-items-center"
>
<span
class=
"d-flex append-right-default ide-search-list-current-icon"
>
<icon
v-if=
"isActive"
:size=
"18"
name=
"mobile-issue-close"
/>
</span>
<span>
<strong>
{{
item
.
name
}}
</strong>
<span
class=
"ide-merge-request-project-path d-block mt-1"
>
Updated
<timeago
:time=
"item.committedDate || ''"
/>
</span>
</span>
</a>
</
template
>
app/assets/javascripts/ide/components/branches/search_list.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
_
from
'
underscore
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Item
from
'
./item.vue
'
;
export
default
{
components
:
{
LoadingIcon
,
Item
,
Icon
,
},
data
()
{
return
{
search
:
''
,
};
},
computed
:
{
...
mapState
(
'
branches
'
,
[
'
branches
'
,
'
isLoading
'
]),
...
mapState
([
'
currentBranchId
'
,
'
currentProjectId
'
]),
hasBranches
()
{
return
this
.
branches
.
length
!==
0
;
},
hasNoSearchResults
()
{
return
this
.
search
!==
''
&&
!
this
.
hasBranches
;
},
},
watch
:
{
isLoading
:
{
handler
:
'
focusSearch
'
,
},
},
mounted
()
{
this
.
loadBranches
();
},
methods
:
{
...
mapActions
(
'
branches
'
,
[
'
fetchBranches
'
]),
loadBranches
()
{
this
.
fetchBranches
({
search
:
this
.
search
});
},
searchBranches
:
_
.
debounce
(
function
debounceSearch
()
{
this
.
loadBranches
();
},
250
),
focusSearch
()
{
if
(
!
this
.
isLoading
)
{
this
.
$nextTick
(()
=>
{
this
.
$refs
.
searchInput
.
focus
();
});
}
},
isActiveBranch
(
item
)
{
return
item
.
name
===
this
.
currentBranchId
;
},
},
};
</
script
>
<
template
>
<div>
<div
class=
"dropdown-input mt-3 pb-3 mb-0 border-bottom"
>
<div
class=
"position-relative"
>
<input
ref=
"searchInput"
:placeholder=
"__('Search branches')"
v-model=
"search"
type=
"search"
class=
"form-control dropdown-input-field"
@
input=
"searchBranches"
/>
<icon
:size=
"18"
name=
"search"
class=
"input-icon"
/>
</div>
</div>
<div
class=
"dropdown-content ide-merge-requests-dropdown-content d-flex"
>
<loading-icon
v-if=
"isLoading"
class=
"mt-3 mb-3 align-self-center ml-auto mr-auto"
size=
"2"
/>
<ul
v-else
class=
"mb-3 w-100"
>
<template
v-if=
"hasBranches"
>
<li
v-for=
"item in branches"
:key=
"item.name"
>
<item
:item=
"item"
:project-id=
"currentProjectId"
:is-active=
"isActiveBranch(item)"
/>
</li>
</
template
>
<li
v-else
class=
"ide-search-list-empty d-flex align-items-center justify-content-center"
>
<
template
v-if=
"hasNoSearchResults"
>
{{
__
(
'
No branches found
'
)
}}
</
template
>
</li>
</ul>
</div>
</div>
</template>
app/assets/javascripts/ide/components/ide_tree.vue
View file @
0d6e50d5
...
...
@@ -41,7 +41,7 @@ export default {
slot=
"header"
>
{{
__
(
'
Edit
'
)
}}
<div
class=
"ml-auto d-flex"
>
<div
class=
"
ide-tree-actions
ml-auto d-flex"
>
<new-entry-button
:label=
"__('New file')"
:show-label=
"false"
...
...
app/assets/javascripts/ide/components/ide_tree_list.vue
View file @
0d6e50d5
...
...
@@ -3,14 +3,14 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
SkeletonLoadingContainer
from
'
~/vue_shared/components/skeleton_loading_container.vue
'
;
import
RepoFile
from
'
./repo_file.vue
'
;
import
N
ewDropdown
from
'
./new_dropdown/index
.vue
'
;
import
N
avDropdown
from
'
./nav_dropdown
.vue
'
;
export
default
{
components
:
{
Icon
,
RepoFile
,
SkeletonLoadingContainer
,
N
ew
Dropdown
,
N
av
Dropdown
,
},
props
:
{
viewerType
:
{
...
...
@@ -57,6 +57,7 @@ export default {
:class=
"headerClass"
class=
"ide-tree-header"
>
<nav-dropdown
/>
<slot
name=
"header"
></slot>
</header>
<div
...
...
app/assets/javascripts/ide/components/merge_requests/dropdown.vue
deleted
100644 → 0
View file @
0e90f27f
<
script
>
import
{
mapGetters
}
from
'
vuex
'
;
import
Tabs
from
'
../../../vue_shared/components/tabs/tabs
'
;
import
Tab
from
'
../../../vue_shared/components/tabs/tab.vue
'
;
import
List
from
'
./list.vue
'
;
export
default
{
components
:
{
Tabs
,
Tab
,
List
,
},
props
:
{
show
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
...
mapGetters
(
'
mergeRequests
'
,
[
'
assignedData
'
,
'
createdData
'
]),
createdMergeRequestLength
()
{
return
this
.
createdData
.
mergeRequests
.
length
;
},
assignedMergeRequestLength
()
{
return
this
.
assignedData
.
mergeRequests
.
length
;
},
},
};
</
script
>
<
template
>
<div
class=
"dropdown-menu ide-merge-requests-dropdown p-0"
>
<tabs
v-if=
"show"
stop-propagation
>
<tab
active
>
<template
slot=
"title"
>
{{
__
(
'
Created by me
'
)
}}
<span
class=
"badge badge-pill"
>
{{
createdMergeRequestLength
}}
</span>
</
template
>
<list
:empty-text=
"__('You have not created any merge requests')"
type=
"created"
/>
</tab>
<tab>
<
template
slot=
"title"
>
{{
__
(
'
Assigned to me
'
)
}}
<span
class=
"badge badge-pill"
>
{{
assignedMergeRequestLength
}}
</span>
</
template
>
<list
:empty-text=
"__('You do not have any assigned merge requests')"
type=
"assigned"
/>
</tab>
</tabs>
</div>
</template>
app/assets/javascripts/ide/components/merge_requests/item.vue
View file @
0d6e50d5
<
script
>
import
Icon
from
'
../../../vue_shared/components/icon.vue
'
;
import
router
from
'
../../ide_router
'
;
export
default
{
components
:
{
...
...
@@ -29,22 +30,21 @@ export default {
pathWithID
()
{
return
`
${
this
.
item
.
projectPathWithNamespace
}
!
${
this
.
item
.
iid
}
`
;
},
},
methods
:
{
clickItem
()
{
this
.
$emit
(
'
click
'
,
this
.
item
)
;
mergeRequestHref
()
{
const
path
=
`/project/
${
this
.
item
.
projectPathWithNamespace
}
/merge_requests/
${
this
.
item
.
iid
}
`
;
return
router
.
resolve
(
path
).
href
;
},
},
};
</
script
>
<
template
>
<
button
type=
"button
"
<
a
:href=
"mergeRequestHref
"
class=
"btn-link d-flex align-items-center"
@
click=
"clickItem"
>
<span
class=
"d-flex append-right-default ide-
merge-reque
st-current-icon"
>
<span
class=
"d-flex append-right-default ide-
search-li
st-current-icon"
>
<icon
v-if=
"isActive"
:size=
"18"
...
...
@@ -59,5 +59,5 @@ export default {
{{
pathWithID
}}
</span>
</span>
</
button
>
</
a
>
</
template
>
app/assets/javascripts/ide/components/merge_requests/list.vue
View file @
0d6e50d5
<
script
>
import
{
mapActions
,
map
Getters
,
map
State
}
from
'
vuex
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
_
from
'
underscore
'
;
import
LoadingIcon
from
'
../../../vue_shared/components/loading_icon.vue
'
;
import
{
__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
LoadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
Item
from
'
./item.vue
'
;
import
TokenedInput
from
'
../shared/tokened_input.vue
'
;
const
SEARCH_TYPES
=
[
{
type
:
'
created
'
,
label
:
__
(
'
Created by me
'
)
},
{
type
:
'
assigned
'
,
label
:
__
(
'
Assigned to me
'
)
},
];
export
default
{
components
:
{
LoadingIcon
,
TokenedInput
,
Item
,
},
props
:
{
type
:
{
type
:
String
,
required
:
true
,
},
emptyText
:
{
type
:
String
,
required
:
true
,
},
Icon
,
},
data
()
{
return
{
search
:
''
,
currentSearchType
:
null
,
hasSearchFocus
:
false
,
};
},
computed
:
{
...
map
Getters
(
'
mergeRequests
'
,
[
'
getData
'
]),
...
map
State
(
'
mergeRequests
'
,
[
'
mergeRequests
'
,
'
isLoading
'
]),
...
mapState
([
'
currentMergeRequestId
'
,
'
currentProjectId
'
]),
data
()
{
return
this
.
getData
(
this
.
type
);
},
isLoading
()
{
return
this
.
data
.
isLoading
;
},
mergeRequests
()
{
return
this
.
data
.
mergeRequests
;
},
hasMergeRequests
()
{
return
this
.
mergeRequests
.
length
!==
0
;
},
hasNoSearchResults
()
{
return
this
.
search
!==
''
&&
!
this
.
hasMergeRequests
;
},
showSearchTypes
()
{
return
this
.
hasSearchFocus
&&
!
this
.
search
&&
!
this
.
currentSearchType
;
},
type
()
{
return
this
.
currentSearchType
?
this
.
currentSearchType
.
type
:
''
;
},
searchTokens
()
{
return
this
.
currentSearchType
?
[
this
.
currentSearchType
]
:
[];
},
},
watch
:
{
isLoading
:
{
handler
:
'
focusSearch
'
,
search
()
{
// When the search is updated, let's turn off this flag to hide the search types
this
.
hasSearchFocus
=
false
;
},
},
mounted
()
{
this
.
loadMergeRequests
();
},
methods
:
{
...
mapActions
(
'
mergeRequests
'
,
[
'
fetchMergeRequests
'
,
'
openMergeRequest
'
]),
...
mapActions
(
'
mergeRequests
'
,
[
'
fetchMergeRequests
'
]),
loadMergeRequests
()
{
this
.
fetchMergeRequests
({
type
:
this
.
type
,
search
:
this
.
search
});
},
viewMergeRequest
(
item
)
{
this
.
openMergeRequest
({
projectPath
:
item
.
projectPathWithNamespace
,
id
:
item
.
iid
,
});
},
searchMergeRequests
:
_
.
debounce
(
function
debounceSearch
()
{
this
.
loadMergeRequests
();
},
250
),
focusSearch
()
{
if
(
!
this
.
isLoading
)
{
this
.
$nextTick
(()
=>
{
this
.
$refs
.
searchInput
.
focus
();
})
;
}
onSearchFocus
()
{
this
.
hasSearchFocus
=
true
;
},
setSearchType
(
searchType
)
{
this
.
currentSearchType
=
searchType
;
this
.
loadMergeRequests
();
},
},
searchTypes
:
SEARCH_TYPES
,
};
</
script
>
<
template
>
<div>
<div
class=
"dropdown-input mt-3 pb-3 mb-0 border-bottom"
>
<input
ref=
"searchInput"
:placeholder=
"__('Search merge requests')"
v-model=
"search"
type=
"search"
class=
"dropdown-input-field"
@
input=
"searchMergeRequests"
/>
<i
aria-hidden=
"true"
class=
"fa fa-search dropdown-input-search"
></i>
<div
class=
"position-relative"
>
<tokened-input
v-model=
"search"
:tokens=
"searchTokens"
:placeholder=
"__('Search merge requests')"
@
focus=
"onSearchFocus"
@
input=
"searchMergeRequests"
@
removeToken=
"setSearchType(null)"
/>
<icon
:size=
"18"
name=
"search"
class=
"input-icon"
/>
</div>
</div>
<div
class=
"dropdown-content ide-merge-requests-dropdown-content d-flex"
>
<loading-icon
...
...
@@ -98,35 +103,52 @@ export default {
class=
"mt-3 mb-3 align-self-center ml-auto mr-auto"
size=
"2"
/>
<ul
v-else
class=
"mb-3 w-100"
>
<template
v-if=
"hasMergeRequests"
>
<li
v-for=
"item in mergeRequests"
:key=
"item.id"
>
<item
:item=
"item"
:current-id=
"currentMergeRequestId"
:current-project-id=
"currentProjectId"
@
click=
"viewMergeRequest"
/>
</li>
</
template
>
<li
v-else
class=
"ide-merge-requests-empty d-flex align-items-center justify-content-center"
<template
v-else
>
<ul
class=
"mb-3 w-100"
>
<
template
v-if=
"hasNoSearchResults"
>
{{
__
(
'
No merge requests found
'
)
}}
<template
v-if=
"showSearchTypes"
>
<li
v-for=
"searchType in $options.searchTypes"
:key=
"searchType.type"
>
<button
type=
"button"
class=
"btn-link d-flex align-items-center"
@
click.stop=
"setSearchType(searchType)"
>
<span
class=
"d-flex append-right-default ide-search-list-current-icon"
>
<icon
:size=
"18"
name=
"search"
/>
</span>
<span>
{{
searchType
.
label
}}
</span>
</button>
</li>
</
template
>
<
template
v-else
>
{{
emptyText
}}
<
template
v-else-if=
"hasMergeRequests"
>
<li
v-for=
"item in mergeRequests"
:key=
"item.id"
>
<item
:item=
"item"
:current-id=
"currentMergeRequestId"
:current-project-id=
"currentProjectId"
/>
</li>
</
template
>
</li>
</ul>
<li
v-else
class=
"ide-search-list-empty d-flex align-items-center justify-content-center"
>
{{ __('No merge requests found') }}
</li>
</ul>
</template>
</div>
</div>
</template>
app/assets/javascripts/ide/components/nav_dropdown.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
$
from
'
jquery
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
NavForm
from
'
./nav_form.vue
'
;
import
NavDropdownButton
from
'
./nav_dropdown_button.vue
'
;
export
default
{
components
:
{
Icon
,
NavDropdownButton
,
NavForm
,
},
data
()
{
return
{
isVisibleDropdown
:
false
,
};
},
mounted
()
{
this
.
addDropdownListeners
();
},
beforeDestroy
()
{
this
.
removeDropdownListeners
();
},
methods
:
{
addDropdownListeners
()
{
$
(
this
.
$refs
.
dropdown
)
.
on
(
'
show.bs.dropdown
'
,
()
=>
this
.
showDropdown
())
.
on
(
'
hide.bs.dropdown
'
,
()
=>
this
.
hideDropdown
());
},
removeDropdownListeners
()
{
$
(
this
.
$refs
.
dropdown
)
.
off
(
'
show.bs.dropdown
'
)
.
off
(
'
hide.bs.dropdown
'
);
},
showDropdown
()
{
this
.
isVisibleDropdown
=
true
;
},
hideDropdown
()
{
this
.
isVisibleDropdown
=
false
;
},
},
};
</
script
>
<
template
>
<div
ref=
"dropdown"
class=
"btn-group ide-nav-dropdown dropdown"
>
<nav-dropdown-button
/>
<div
class=
"dropdown-menu dropdown-menu-left p-0"
>
<nav-form
v-if=
"isVisibleDropdown"
/>
</div>
</div>
</
template
>
app/assets/javascripts/ide/components/nav_dropdown_button.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
{
mapState
}
from
'
vuex
'
;
import
DropdownButton
from
'
~/vue_shared/components/dropdown/dropdown_button.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
const
EMPTY_LABEL
=
'
-
'
;
export
default
{
components
:
{
Icon
,
DropdownButton
,
},
computed
:
{
...
mapState
([
'
currentBranchId
'
,
'
currentMergeRequestId
'
]),
mergeRequestLabel
()
{
return
this
.
currentMergeRequestId
?
`!
${
this
.
currentMergeRequestId
}
`
:
EMPTY_LABEL
;
},
branchLabel
()
{
return
this
.
currentBranchId
||
EMPTY_LABEL
;
},
},
};
</
script
>
<
template
>
<dropdown-button>
<span
class=
"row"
>
<span
class=
"col-7 text-truncate"
>
<icon
:size=
"16"
:aria-label=
"__('Current Branch')"
name=
"branch"
/>
{{
branchLabel
}}
</span>
<span
class=
"col-5 pl-0 text-truncate"
>
<icon
:size=
"16"
:aria-label=
"__('Merge Request')"
name=
"merge-request"
/>
{{
mergeRequestLabel
}}
</span>
</span>
</dropdown-button>
</
template
>
app/assets/javascripts/ide/components/nav_form.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
Tabs
from
'
~/vue_shared/components/tabs/tabs
'
;
import
Tab
from
'
~/vue_shared/components/tabs/tab.vue
'
;
import
BranchesSearchList
from
'
./branches/search_list.vue
'
;
import
MergeRequestSearchList
from
'
./merge_requests/list.vue
'
;
export
default
{
components
:
{
Tabs
,
Tab
,
BranchesSearchList
,
MergeRequestSearchList
,
},
};
</
script
>
<
template
>
<div
class=
"ide-nav-form p-0"
>
<tabs
stop-propagation
>
<tab
active
>
<template
slot=
"title"
>
{{
__
(
'
Merge Requests
'
)
}}
</
template
>
<merge-request-search-list
/>
</tab>
<tab>
<
template
slot=
"title"
>
{{
__
(
'
Branches
'
)
}}
</
template
>
<branches-search-list
/>
</tab>
</tabs>
</div>
</template>
app/assets/javascripts/ide/components/shared/tokened_input.vue
0 → 100644
View file @
0d6e50d5
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
components
:
{
Icon
,
},
props
:
{
placeholder
:
{
type
:
String
,
required
:
false
,
default
:
__
(
'
Search
'
),
},
tokens
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
value
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
backspaceCount
:
0
,
};
},
computed
:
{
placeholderText
()
{
return
this
.
tokens
.
length
?
''
:
this
.
placeholder
;
},
},
watch
:
{
tokens
()
{
this
.
$refs
.
input
.
focus
();
},
},
methods
:
{
onFocus
()
{
this
.
$emit
(
'
focus
'
);
},
onBlur
()
{
this
.
$emit
(
'
blur
'
);
},
onInput
(
evt
)
{
this
.
$emit
(
'
input
'
,
evt
.
target
.
value
);
},
onBackspace
()
{
if
(
!
this
.
value
&&
this
.
tokens
.
length
)
{
this
.
backspaceCount
+=
1
;
}
else
{
this
.
backspaceCount
=
0
;
return
;
}
if
(
this
.
backspaceCount
>
1
)
{
this
.
removeToken
(
this
.
tokens
[
this
.
tokens
.
length
-
1
]);
this
.
backspaceCount
=
0
;
}
},
removeToken
(
token
)
{
this
.
$emit
(
'
removeToken
'
,
token
);
},
},
};
</
script
>
<
template
>
<div
class=
"filtered-search-wrapper"
>
<div
class=
"filtered-search-box"
>
<div
class=
"tokens-container list-unstyled"
>
<div
v-for=
"token in tokens"
:key=
"token.label"
class=
"filtered-search-token"
>
<button
class=
"selectable btn-blank"
type=
"button"
@
click.stop=
"removeToken(token)"
@
keyup.delete=
"removeToken(token)"
>
<div
class=
"value-container rounded"
>
<div
class=
"value"
>
{{
token
.
label
}}
</div>
<div
class=
"remove-token inverted"
>
<icon
:size=
"10"
name=
"close"
/>
</div>
</div>
</button>
</div>
<div
class=
"input-token"
>
<input
ref=
"input"
:placeholder=
"placeholderText"
:value=
"value"
type=
"search"
class=
"form-control filtered-search"
@
input=
"onInput"
@
focus=
"onFocus"
@
blur=
"onBlur"
@
keyup.delete=
"onBackspace"
/>
</div>
</div>
</div>
</div>
</
template
>
app/assets/javascripts/ide/stores/index.js
View file @
0d6e50d5
...
...
@@ -7,6 +7,7 @@ import mutations from './mutations';
import
commitModule
from
'
./modules/commit
'
;
import
pipelines
from
'
./modules/pipelines
'
;
import
mergeRequests
from
'
./modules/merge_requests
'
;
import
branches
from
'
./modules/branches
'
;
Vue
.
use
(
Vuex
);
...
...
@@ -20,6 +21,7 @@ export const createStore = () =>
commit
:
commitModule
,
pipelines
,
mergeRequests
,
branches
,
},
});
...
...
app/assets/javascripts/ide/stores/modules/branches/actions.js
0 → 100644
View file @
0d6e50d5
import
{
__
}
from
'
~/locale
'
;
import
Api
from
'
~/api
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
const
requestBranches
=
({
commit
})
=>
commit
(
types
.
REQUEST_BRANCHES
);
export
const
receiveBranchesError
=
({
commit
,
dispatch
},
{
search
})
=>
{
dispatch
(
'
setErrorMessage
'
,
{
text
:
__
(
'
Error loading branches.
'
),
action
:
payload
=>
dispatch
(
'
fetchBranches
'
,
payload
).
then
(()
=>
dispatch
(
'
setErrorMessage
'
,
null
,
{
root
:
true
}),
),
actionText
:
__
(
'
Please try again
'
),
actionPayload
:
{
search
},
},
{
root
:
true
},
);
commit
(
types
.
RECEIVE_BRANCHES_ERROR
);
};
export
const
receiveBranchesSuccess
=
({
commit
},
data
)
=>
commit
(
types
.
RECEIVE_BRANCHES_SUCCESS
,
data
);
export
const
fetchBranches
=
({
dispatch
,
rootGetters
},
{
search
=
''
})
=>
{
dispatch
(
'
requestBranches
'
);
dispatch
(
'
resetBranches
'
);
return
Api
.
branches
(
rootGetters
.
currentProject
.
id
,
search
,
{
sort
:
'
updated_desc
'
})
.
then
(({
data
})
=>
dispatch
(
'
receiveBranchesSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
receiveBranchesError
'
,
{
search
}));
};
export
const
resetBranches
=
({
commit
})
=>
commit
(
types
.
RESET_BRANCHES
);
export
const
openBranch
=
({
rootState
,
dispatch
},
id
)
=>
dispatch
(
'
goToRoute
'
,
`/project/
${
rootState
.
currentProjectId
}
/edit/
${
id
}
`
,
{
root
:
true
});
export
default
()
=>
{};
app/assets/javascripts/ide/stores/modules/branches/index.js
0 → 100644
View file @
0d6e50d5
import
state
from
'
./state
'
;
import
*
as
actions
from
'
./actions
'
;
import
mutations
from
'
./mutations
'
;
export
default
{
namespaced
:
true
,
state
:
state
(),
actions
,
mutations
,
};
app/assets/javascripts/ide/stores/modules/branches/mutation_types.js
0 → 100644
View file @
0d6e50d5
export
const
REQUEST_BRANCHES
=
'
REQUEST_BRANCHES
'
;
export
const
RECEIVE_BRANCHES_ERROR
=
'
RECEIVE_BRANCHES_ERROR
'
;
export
const
RECEIVE_BRANCHES_SUCCESS
=
'
RECEIVE_BRANCHES_SUCCESS
'
;
export
const
RESET_BRANCHES
=
'
RESET_BRANCHES
'
;
app/assets/javascripts/ide/stores/modules/branches/mutations.js
0 → 100644
View file @
0d6e50d5
/* eslint-disable no-param-reassign */
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
REQUEST_BRANCHES
](
state
)
{
state
.
isLoading
=
true
;
},
[
types
.
RECEIVE_BRANCHES_ERROR
](
state
)
{
state
.
isLoading
=
false
;
},
[
types
.
RECEIVE_BRANCHES_SUCCESS
](
state
,
data
)
{
state
.
isLoading
=
false
;
state
.
branches
=
data
.
map
(
branch
=>
({
name
:
branch
.
name
,
committedDate
:
branch
.
commit
.
committed_date
,
}));
},
[
types
.
RESET_BRANCHES
](
state
)
{
state
.
branches
=
[];
},
};
app/assets/javascripts/ide/stores/modules/branches/state.js
0 → 100644
View file @
0d6e50d5
export
default
()
=>
({
isLoading
:
false
,
branches
:
[],
});
app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
View file @
0d6e50d5
import
{
__
}
from
'
../../../../locale
'
;
import
Api
from
'
../../../../api
'
;
import
router
from
'
../../../ide_router
'
;
import
{
scopes
}
from
'
./constants
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
rootTypes
from
'
../../mutation_types
'
;
export
const
requestMergeRequests
=
({
commit
}
,
type
)
=>
commit
(
types
.
REQUEST_MERGE_REQUESTS
,
type
);
export
const
requestMergeRequests
=
({
commit
})
=>
commit
(
types
.
REQUEST_MERGE_REQUESTS
);
export
const
receiveMergeRequestsError
=
({
commit
,
dispatch
},
{
type
,
search
})
=>
{
dispatch
(
'
setErrorMessage
'
,
...
...
@@ -21,39 +19,22 @@ export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }
},
{
root
:
true
},
);
commit
(
types
.
RECEIVE_MERGE_REQUESTS_ERROR
,
type
);
commit
(
types
.
RECEIVE_MERGE_REQUESTS_ERROR
);
};
export
const
receiveMergeRequestsSuccess
=
({
commit
},
{
type
,
data
}
)
=>
commit
(
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
,
{
type
,
data
}
);
export
const
receiveMergeRequestsSuccess
=
({
commit
},
data
)
=>
commit
(
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
,
data
);
export
const
fetchMergeRequests
=
({
dispatch
,
state
:
{
state
}
},
{
type
,
search
=
''
})
=>
{
const
scope
=
scopes
[
type
];
dispatch
(
'
requestMergeRequests
'
,
type
);
dispatch
(
'
resetMergeRequests
'
,
type
);
dispatch
(
'
requestMergeRequests
'
);
dispatch
(
'
resetMergeRequests
'
);
const
scope
=
type
?
scopes
[
type
]
:
'
all
'
;
return
Api
.
mergeRequests
({
scope
,
state
,
search
})
.
then
(({
data
})
=>
dispatch
(
'
receiveMergeRequestsSuccess
'
,
{
type
,
data
}
))
.
then
(({
data
})
=>
dispatch
(
'
receiveMergeRequestsSuccess
'
,
data
))
.
catch
(()
=>
dispatch
(
'
receiveMergeRequestsError
'
,
{
type
,
search
}));
};
export
const
resetMergeRequests
=
({
commit
},
type
)
=>
commit
(
types
.
RESET_MERGE_REQUESTS
,
type
);
export
const
openMergeRequest
=
({
commit
,
dispatch
},
{
projectPath
,
id
})
=>
{
commit
(
rootTypes
.
CLEAR_PROJECTS
,
null
,
{
root
:
true
});
commit
(
rootTypes
.
SET_CURRENT_MERGE_REQUEST
,
`
${
id
}
`
,
{
root
:
true
});
commit
(
rootTypes
.
RESET_OPEN_FILES
,
null
,
{
root
:
true
});
dispatch
(
'
setCurrentBranchId
'
,
''
,
{
root
:
true
});
dispatch
(
'
pipelines/stopPipelinePolling
'
,
null
,
{
root
:
true
})
.
then
(()
=>
{
dispatch
(
'
pipelines/resetLatestPipeline
'
,
null
,
{
root
:
true
});
dispatch
(
'
pipelines/clearEtagPoll
'
,
null
,
{
root
:
true
});
})
.
catch
(
e
=>
{
throw
e
;
});
dispatch
(
'
setRightPane
'
,
null
,
{
root
:
true
});
router
.
push
(
`/project/
${
projectPath
}
/merge_requests/
${
id
}
`
);
};
export
const
resetMergeRequests
=
({
commit
})
=>
commit
(
types
.
RESET_MERGE_REQUESTS
);
export
default
()
=>
{};
app/assets/javascripts/ide/stores/modules/merge_requests/getters.js
deleted
100644 → 0
View file @
0e90f27f
export
const
getData
=
state
=>
type
=>
state
[
type
];
export
const
assignedData
=
state
=>
state
.
assigned
;
export
const
createdData
=
state
=>
state
.
created
;
app/assets/javascripts/ide/stores/modules/merge_requests/index.js
View file @
0d6e50d5
import
state
from
'
./state
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
export
default
{
...
...
@@ -8,5 +7,4 @@ export default {
state
:
state
(),
actions
,
mutations
,
getters
,
};
app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
View file @
0d6e50d5
...
...
@@ -2,15 +2,15 @@
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
REQUEST_MERGE_REQUESTS
](
state
,
type
)
{
state
[
type
]
.
isLoading
=
true
;
[
types
.
REQUEST_MERGE_REQUESTS
](
state
)
{
state
.
isLoading
=
true
;
},
[
types
.
RECEIVE_MERGE_REQUESTS_ERROR
](
state
,
type
)
{
state
[
type
]
.
isLoading
=
false
;
[
types
.
RECEIVE_MERGE_REQUESTS_ERROR
](
state
)
{
state
.
isLoading
=
false
;
},
[
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
](
state
,
{
type
,
data
}
)
{
state
[
type
]
.
isLoading
=
false
;
state
[
type
]
.
mergeRequests
=
data
.
map
(
mergeRequest
=>
({
[
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
](
state
,
data
)
{
state
.
isLoading
=
false
;
state
.
mergeRequests
=
data
.
map
(
mergeRequest
=>
({
id
:
mergeRequest
.
id
,
iid
:
mergeRequest
.
iid
,
title
:
mergeRequest
.
title
,
...
...
@@ -20,7 +20,7 @@ export default {
.
replace
(
`/merge_requests/
${
mergeRequest
.
iid
}
`
,
''
),
}));
},
[
types
.
RESET_MERGE_REQUESTS
](
state
,
type
)
{
state
[
type
]
.
mergeRequests
=
[];
[
types
.
RESET_MERGE_REQUESTS
](
state
)
{
state
.
mergeRequests
=
[];
},
};
app/assets/javascripts/ide/stores/modules/merge_requests/state.js
View file @
0d6e50d5
import
{
states
}
from
'
./constants
'
;
export
default
()
=>
({
created
:
{
isLoading
:
false
,
mergeRequests
:
[],
},
assigned
:
{
isLoading
:
false
,
mergeRequests
:
[],
},
isLoading
:
false
,
mergeRequests
:
[],
state
:
states
.
opened
,
});
app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
View file @
0d6e50d5
...
...
@@ -38,9 +38,17 @@ export default {
v-show=
"isLoading"
:inline=
"true"
/>
<span
class=
"dropdown-toggle-text"
>
{{
toggleText
}}
</span>
<template>
<slot
v-if=
"$slots.default"
></slot>
<span
v-else
class=
"dropdown-toggle-text"
>
{{
toggleText
}}
</span>
</
template
>
<span
v-show=
"!isLoading"
class=
"dropdown-toggle-icon"
...
...
app/assets/javascripts/vue_shared/components/icon.vue
View file @
0d6e50d5
<
script
>
// only allow classes in images.scss e.g. s12
const
validSizes
=
[
8
,
12
,
16
,
18
,
24
,
32
,
48
,
72
];
const
validSizes
=
[
8
,
1
0
,
1
2
,
16
,
18
,
24
,
32
,
48
,
72
];
let
iconValidator
=
()
=>
true
;
/*
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
0d6e50d5
...
...
@@ -571,7 +571,8 @@
margin-bottom
:
10px
;
padding
:
0
10px
;
.fa
{
.fa
,
.input-icon
{
position
:
absolute
;
top
:
10px
;
right
:
20px
;
...
...
app/assets/stylesheets/framework/images.scss
View file @
0d6e50d5
...
...
@@ -39,7 +39,7 @@
svg
{
fill
:
currentColor
;
$svg-sizes
:
8
12
16
18
24
32
48
72
;
$svg-sizes
:
8
1
0
1
2
16
18
24
32
48
72
;
@each
$svg-size
in
$svg-sizes
{
&
.s
#{
$svg-size
}
{
@include
svg-size
(
#{
$svg-size
}
px
);
...
...
app/assets/stylesheets/page_bundles/ide.scss
View file @
0d6e50d5
@import
'framework/variables'
;
@import
'framework/mixins'
;
$search-list-icon-width
:
18px
;
$ide-activity-bar-width
:
60px
;
$ide-context-header-padding
:
10px
;
$ide-project-avatar-end
:
$ide-context-header-padding
+
48px
;
...
...
@@ -49,7 +50,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display
:
flex
;
flex-direction
:
column
;
flex
:
1
;
overflow
:
hidden
;
min-height
:
0
;
.file
{
height
:
32px
;
...
...
@@ -541,11 +542,11 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display
:
flex
;
flex
:
1
;
flex-direction
:
column
;
overflow
:
hidden
;
background-color
:
$white-light
;
border-left
:
1px
solid
$white-dark
;
border-top
:
1px
solid
$white-dark
;
border-top-left-radius
:
$border-radius-small
;
min-height
:
0
;
}
}
...
...
@@ -1057,6 +1058,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
flex
:
0
0
auto
;
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
padding
:
12px
0
;
margin-left
:
$ide-tree-padding
;
margin-right
:
$ide-tree-padding
;
...
...
@@ -1066,6 +1068,32 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
margin-left
:
auto
;
}
.ide-nav-dropdown
{
width
:
100%
;
margin-bottom
:
12px
;
.dropdown-menu
{
width
:
385px
;
max-height
:
initial
;
}
.dropdown-menu-toggle
{
svg
{
vertical-align
:
middle
;
}
&
:hover
{
background-color
:
$white-normal
;
}
}
&
.show
{
.dropdown-menu-toggle
{
background-color
:
$white-dark
;
}
}
}
button
{
color
:
$gl-text-color
;
}
...
...
@@ -1181,7 +1209,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-context-body
{
overflow
:
hidden
;
min-height
:
0
;
}
.ide-sidebar-project-title
{
...
...
@@ -1331,7 +1359,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
min-height
:
60px
;
}
.ide-
merge-requests-dropdown
{
.ide-
nav-form
{
.nav-links
li
{
width
:
50%
;
padding-left
:
0
;
...
...
@@ -1350,22 +1378,36 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
padding-left
:
$gl-padding
;
padding-right
:
$gl-padding
;
.fa
{
right
:
26px
;
.input-icon
{
right
:
auto
;
left
:
10px
;
top
:
50%
;
transform
:
translateY
(
-50%
);
}
}
.dropdown-input-field
{
padding-left
:
$search-list-icon-width
+
$gl-padding
;
padding-top
:
2px
;
padding-bottom
:
2px
;
}
.tokens-container
{
padding-left
:
$search-list-icon-width
+
$gl-padding
;
overflow-x
:
hidden
;
}
.btn-link
{
padding-top
:
$gl-padding
;
padding-bottom
:
$gl-padding
;
}
}
.ide-
merge-reque
st-current-icon
{
min-width
:
18px
;
.ide-
search-li
st-current-icon
{
min-width
:
$search-list-icon-width
;
}
.ide-
merge-requests
-empty
{
.ide-
search-list
-empty
{
height
:
230px
;
}
...
...
changelogs/unreleased/46165-web-ide-branch-picker.yml
0 → 100644
View file @
0d6e50d5
---
title
:
Create branch and MR picker for Web IDE
merge_request
:
20978
author
:
type
:
changed
doc/user/project/web_ide/index.md
View file @
0d6e50d5
...
...
@@ -59,9 +59,18 @@ left.
> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) [GitLab Core][ce] 11.0.
Switching between your authored and assigned merge requests can be done without
leaving the Web IDE. Click the
project name in the top left to open a list of
merge requests. You will need to commit or discard all your changes before
leaving the Web IDE. Click the
dropdown in the top of the sidebar to open a list
of
merge requests. You will need to commit or discard all your changes before
switching to a different merge request.
## Switching branches
> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) [GitLab Core][ce] 11.2.
Switching between branches of the current project repository can be done without
leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
of branches. You will need to commit or discard all your changes before
switching to a different branch.
[
ce
]:
https://about.gitlab.com/pricing/
[
ee
]:
https://about.gitlab.com/pricing/
lib/api/branches.rb
View file @
0d6e50d5
...
...
@@ -19,6 +19,7 @@ module API
params
:filter_params
do
optional
:search
,
type:
String
,
desc:
'Return list of branches matching the search criteria'
optional
:sort
,
type:
String
,
desc:
'Return list of branches sorted by the given field'
end
end
...
...
locale/gitlab.pot
View file @
0d6e50d5
...
...
@@ -1930,6 +1930,9 @@ msgstr ""
msgid "Cron syntax"
msgstr ""
msgid "Current Branch"
msgstr ""
msgid "CurrentUser|Profile"
msgstr ""
...
...
@@ -2409,6 +2412,9 @@ msgstr ""
msgid "Error loading branch data. Please try again."
msgstr ""
msgid "Error loading branches."
msgstr ""
msgid "Error loading last commit."
msgstr ""
...
...
@@ -3605,6 +3611,9 @@ msgstr ""
msgid "No assignee"
msgstr ""
msgid "No branches found"
msgstr ""
msgid "No changes"
msgstr ""
...
...
@@ -6045,9 +6054,6 @@ msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
msgid "You do not have any assigned merge requests"
msgstr ""
msgid "You don't have any applications"
msgstr ""
...
...
@@ -6057,9 +6063,6 @@ msgstr ""
msgid "You have no permissions"
msgstr ""
msgid "You have not created any merge requests"
msgstr ""
msgid "You have reached your project limit"
msgstr ""
...
...
spec/features/projects/tree/create_directory_spec.rb
View file @
0d6e50d5
...
...
@@ -22,7 +22,7 @@ describe 'Multi-file editor new directory', :js do
end
it
'creates directory in current directory'
do
all
(
'.ide-tree-
header
button'
).
last
.
click
all
(
'.ide-tree-
actions
button'
).
last
.
click
page
.
within
(
'.modal'
)
do
find
(
'.form-control'
).
set
(
'folder name'
)
...
...
@@ -30,7 +30,7 @@ describe 'Multi-file editor new directory', :js do
click_button
(
'Create directory'
)
end
first
(
'.ide-tree-
header
button'
).
click
first
(
'.ide-tree-
actions
button'
).
click
page
.
within
(
'.modal-dialog'
)
do
find
(
'.form-control'
).
set
(
'file name'
)
...
...
spec/features/projects/tree/create_file_spec.rb
View file @
0d6e50d5
...
...
@@ -22,7 +22,7 @@ describe 'Multi-file editor new file', :js do
end
it
'creates file in current directory'
do
first
(
'.ide-tree-
header
button'
).
click
first
(
'.ide-tree-
actions
button'
).
click
page
.
within
(
'.modal'
)
do
find
(
'.form-control'
).
set
(
'file name'
)
...
...
spec/javascripts/helpers/vuex_action_helper.js
View file @
0d6e50d5
...
...
@@ -84,7 +84,7 @@ export default (
done
();
};
const
result
=
action
({
commit
,
state
,
dispatch
,
rootState
:
state
},
payload
);
const
result
=
action
({
commit
,
state
,
dispatch
,
rootState
:
state
,
rootGetters
:
state
},
payload
);
return
new
Promise
(
resolve
=>
{
setImmediate
(
resolve
);
...
...
spec/javascripts/ide/components/branches/item_spec.js
0 → 100644
View file @
0d6e50d5
import
Vue
from
'
vue
'
;
import
mountCompontent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
router
from
'
~/ide/ide_router
'
;
import
Item
from
'
~/ide/components/branches/item.vue
'
;
import
{
getTimeago
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
projectData
}
from
'
../../mock_data
'
;
const
TEST_BRANCH
=
{
name
:
'
master
'
,
committedDate
:
'
2018-01-05T05:50Z
'
,
};
const
TEST_PROJECT_ID
=
projectData
.
name_with_namespace
;
describe
(
'
IDE branch item
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
Item
);
let
vm
;
beforeEach
(()
=>
{
vm
=
mountCompontent
(
Component
,
{
item
:
{
...
TEST_BRANCH
},
projectId
:
TEST_PROJECT_ID
,
isActive
:
false
,
});
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
renders branch name and timeago
'
,
()
=>
{
const
timeText
=
getTimeago
().
format
(
TEST_BRANCH
.
committedDate
);
expect
(
vm
.
$el
).
toContainText
(
TEST_BRANCH
.
name
);
expect
(
vm
.
$el
.
querySelector
(
'
time
'
)).
toHaveText
(
timeText
);
expect
(
vm
.
$el
.
querySelector
(
'
.ic-mobile-issue-close
'
)).
toBe
(
null
);
});
it
(
'
renders link to branch
'
,
()
=>
{
const
expectedHref
=
router
.
resolve
(
`/project/
${
TEST_PROJECT_ID
}
/edit/
${
TEST_BRANCH
.
name
}
`
).
href
;
expect
(
vm
.
$el
).
toMatch
(
'
a
'
);
expect
(
vm
.
$el
).
toHaveAttr
(
'
href
'
,
expectedHref
);
});
it
(
'
renders icon if isActive
'
,
done
=>
{
vm
.
isActive
=
true
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.ic-mobile-issue-close
'
)).
not
.
toBe
(
null
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
spec/javascripts/ide/components/branches/search_list_spec.js
0 → 100644
View file @
0d6e50d5
import
Vue
from
'
vue
'
;
import
store
from
'
~/ide/stores
'
;
import
*
as
types
from
'
~/ide/stores/modules/branches/mutation_types
'
;
import
List
from
'
~/ide/components/branches/search_list.vue
'
;
import
{
createComponentWithStore
}
from
'
../../../helpers/vue_mount_component_helper
'
;
import
{
branches
as
testBranches
}
from
'
../../mock_data
'
;
import
{
resetStore
}
from
'
../../helpers
'
;
describe
(
'
IDE branches search list
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
List
);
let
vm
;
beforeEach
(()
=>
{
vm
=
createComponentWithStore
(
Component
,
store
,
{});
spyOn
(
vm
,
'
fetchBranches
'
);
vm
.
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
resetStore
(
store
);
});
it
(
'
calls fetch on mounted
'
,
()
=>
{
expect
(
vm
.
fetchBranches
).
toHaveBeenCalledWith
({
search
:
''
,
});
});
it
(
'
renders loading icon
'
,
done
=>
{
vm
.
$store
.
state
.
branches
.
isLoading
=
true
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
).
toContainElement
(
'
.loading-container
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders branches not found when search is not empty
'
,
done
=>
{
vm
.
search
=
'
testing
'
;
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
$el
).
toContainText
(
'
No branches found
'
);
done
();
});
});
describe
(
'
with branches
'
,
()
=>
{
const
currentBranch
=
testBranches
[
1
];
beforeEach
(
done
=>
{
vm
.
$store
.
state
.
currentBranchId
=
currentBranch
.
name
;
vm
.
$store
.
commit
(
`branches/
${
types
.
RECEIVE_BRANCHES_SUCCESS
}
`
,
testBranches
);
vm
.
$nextTick
(
done
);
});
it
(
'
renders list
'
,
()
=>
{
const
elementText
=
Array
.
from
(
vm
.
$el
.
querySelectorAll
(
'
li strong
'
))
.
map
(
x
=>
x
.
textContent
.
trim
());
expect
(
elementText
).
toEqual
(
testBranches
.
map
(
x
=>
x
.
name
));
});
it
(
'
renders check next to active branch
'
,
()
=>
{
const
checkedText
=
Array
.
from
(
vm
.
$el
.
querySelectorAll
(
'
li
'
))
.
filter
(
x
=>
x
.
querySelector
(
'
.ide-search-list-current-icon svg
'
))
.
map
(
x
=>
x
.
querySelector
(
'
strong
'
).
textContent
.
trim
());
expect
(
checkedText
).
toEqual
([
currentBranch
.
name
]);
});
});
});
spec/javascripts/ide/components/merge_requests/dropdown_spec.js
deleted
100644 → 0
View file @
0e90f27f
import
Vue
from
'
vue
'
;
import
{
createStore
}
from
'
~/ide/stores
'
;
import
Dropdown
from
'
~/ide/components/merge_requests/dropdown.vue
'
;
import
{
createComponentWithStore
}
from
'
../../../helpers/vue_mount_component_helper
'
;
import
{
mergeRequests
}
from
'
../../mock_data
'
;
describe
(
'
IDE merge requests dropdown
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
Dropdown
);
let
vm
;
beforeEach
(()
=>
{
const
store
=
createStore
();
vm
=
createComponentWithStore
(
Component
,
store
,
{
show
:
false
}).
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
does not render tabs when show is false
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.nav-links
'
)).
toBe
(
null
);
});
describe
(
'
when show is true
'
,
()
=>
{
beforeEach
(
done
=>
{
vm
.
show
=
true
;
vm
.
$store
.
state
.
mergeRequests
.
assigned
.
mergeRequests
.
push
(
mergeRequests
[
0
]);
vm
.
$nextTick
(
done
);
});
it
(
'
renders tabs
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.nav-links
'
)).
not
.
toBe
(
null
);
});
it
(
'
renders count for assigned & created data
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.nav-links a
'
).
textContent
).
toContain
(
'
Created by me
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.nav-links a .badge
'
).
textContent
).
toContain
(
'
0
'
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.nav-links a
'
)[
1
].
textContent
).
toContain
(
'
Assigned to me
'
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.nav-links a
'
)[
1
].
querySelector
(
'
.badge
'
).
textContent
,
).
toContain
(
'
1
'
);
});
});
});
spec/javascripts/ide/components/merge_requests/item_spec.js
View file @
0d6e50d5
import
Vue
from
'
vue
'
;
import
router
from
'
~/ide/ide_router
'
;
import
Item
from
'
~/ide/components/merge_requests/item.vue
'
;
import
mountCompontent
from
'
../../../helpers/vue_mount_component_helper
'
;
...
...
@@ -27,6 +28,12 @@ describe('IDE merge request item', () => {
expect
(
vm
.
$el
.
textContent
).
toContain
(
'
gitlab-org/gitlab-ce!1
'
);
});
it
(
'
renders link with href
'
,
()
=>
{
const
expectedHref
=
router
.
resolve
(
`/project/
${
vm
.
item
.
projectPathWithNamespace
}
/merge_requests/
${
vm
.
item
.
iid
}
`
).
href
;
expect
(
vm
.
$el
).
toMatch
(
'
a
'
);
expect
(
vm
.
$el
).
toHaveAttr
(
'
href
'
,
expectedHref
);
});
it
(
'
renders icon if ID matches currentId
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.ic-mobile-issue-close
'
)).
not
.
toBe
(
null
);
});
...
...
@@ -50,12 +57,4 @@ describe('IDE merge request item', () => {
done
();
});
});
it
(
'
emits click event on click
'
,
()
=>
{
spyOn
(
vm
,
'
$emit
'
);
vm
.
$el
.
click
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
click
'
,
vm
.
item
);
});
});
spec/javascripts/ide/components/merge_requests/list_spec.js
View file @
0d6e50d5
...
...
@@ -10,10 +10,7 @@ describe('IDE merge requests list', () => {
let
vm
;
beforeEach
(()
=>
{
vm
=
createComponentWithStore
(
Component
,
store
,
{
type
:
'
created
'
,
emptyText
:
'
empty text
'
,
});
vm
=
createComponentWithStore
(
Component
,
store
,
{});
spyOn
(
vm
,
'
fetchMergeRequests
'
);
...
...
@@ -28,13 +25,13 @@ describe('IDE merge requests list', () => {
it
(
'
calls fetch on mounted
'
,
()
=>
{
expect
(
vm
.
fetchMergeRequests
).
toHaveBeenCalledWith
({
type
:
'
created
'
,
search
:
''
,
type
:
''
,
});
});
it
(
'
renders loading icon
'
,
done
=>
{
vm
.
$store
.
state
.
mergeRequests
.
created
.
isLoading
=
true
;
vm
.
$store
.
state
.
mergeRequests
.
isLoading
=
true
;
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.loading-container
'
)).
not
.
toBe
(
null
);
...
...
@@ -43,10 +40,6 @@ describe('IDE merge requests list', () => {
});
});
it
(
'
renders empty text when no merge requests exist
'
,
()
=>
{
expect
(
vm
.
$el
.
textContent
).
toContain
(
'
empty text
'
);
});
it
(
'
renders no search results text when search is not empty
'
,
done
=>
{
vm
.
search
=
'
testing
'
;
...
...
@@ -57,9 +50,29 @@ describe('IDE merge requests list', () => {
});
});
it
(
'
clicking on search type, sets currentSearchType and loads merge requests
'
,
done
=>
{
vm
.
onSearchFocus
();
vm
.
$nextTick
()
.
then
(()
=>
{
vm
.
$el
.
querySelector
(
'
li button
'
).
click
();
return
vm
.
$nextTick
();
})
.
then
(()
=>
{
expect
(
vm
.
currentSearchType
).
toEqual
(
vm
.
$options
.
searchTypes
[
0
]);
expect
(
vm
.
fetchMergeRequests
).
toHaveBeenCalledWith
({
type
:
vm
.
currentSearchType
.
type
,
search
:
''
,
});
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
describe
(
'
with merge requests
'
,
()
=>
{
beforeEach
(
done
=>
{
vm
.
$store
.
state
.
mergeRequests
.
created
.
mergeRequests
.
push
({
vm
.
$store
.
state
.
mergeRequests
.
mergeRequests
.
push
({
...
mergeRequests
[
0
],
projectPathWithNamespace
:
'
gitlab-org/gitlab-ce
'
,
});
...
...
@@ -71,35 +84,6 @@ describe('IDE merge requests list', () => {
expect
(
vm
.
$el
.
querySelectorAll
(
'
li
'
).
length
).
toBe
(
1
);
expect
(
vm
.
$el
.
querySelector
(
'
li
'
).
textContent
).
toContain
(
mergeRequests
[
0
].
title
);
});
it
(
'
calls openMergeRequest when clicking merge request
'
,
done
=>
{
spyOn
(
vm
,
'
openMergeRequest
'
);
vm
.
$el
.
querySelector
(
'
li button
'
).
click
();
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
openMergeRequest
).
toHaveBeenCalledWith
({
projectPath
:
'
gitlab-org/gitlab-ce
'
,
id
:
1
,
});
done
();
});
});
});
describe
(
'
focusSearch
'
,
()
=>
{
it
(
'
focuses search input when loading is false
'
,
done
=>
{
spyOn
(
vm
.
$refs
.
searchInput
,
'
focus
'
);
vm
.
$store
.
state
.
mergeRequests
.
created
.
isLoading
=
false
;
vm
.
focusSearch
();
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
$refs
.
searchInput
.
focus
).
toHaveBeenCalled
();
done
();
});
});
});
describe
(
'
searchMergeRequests
'
,
()
=>
{
...
...
@@ -123,4 +107,52 @@ describe('IDE merge requests list', () => {
expect
(
vm
.
loadMergeRequests
).
toHaveBeenCalled
();
});
});
describe
(
'
onSearchFocus
'
,
()
=>
{
it
(
'
shows search types
'
,
done
=>
{
vm
.
$el
.
querySelector
(
'
input
'
).
dispatchEvent
(
new
Event
(
'
focus
'
));
expect
(
vm
.
hasSearchFocus
).
toBe
(
true
);
expect
(
vm
.
showSearchTypes
).
toBe
(
true
);
vm
.
$nextTick
()
.
then
(()
=>
{
const
expectedSearchTypes
=
vm
.
$options
.
searchTypes
.
map
(
x
=>
x
.
label
);
const
renderedSearchTypes
=
Array
.
from
(
vm
.
$el
.
querySelectorAll
(
'
li
'
))
.
map
(
x
=>
x
.
textContent
.
trim
());
expect
(
renderedSearchTypes
).
toEqual
(
expectedSearchTypes
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
does not show search types, if already has search value
'
,
()
=>
{
vm
.
search
=
'
lorem ipsum
'
;
vm
.
$el
.
querySelector
(
'
input
'
).
dispatchEvent
(
new
Event
(
'
focus
'
));
expect
(
vm
.
hasSearchFocus
).
toBe
(
true
);
expect
(
vm
.
showSearchTypes
).
toBe
(
false
);
});
it
(
'
does not show search types, if already has a search type
'
,
()
=>
{
vm
.
currentSearchType
=
{};
vm
.
$el
.
querySelector
(
'
input
'
).
dispatchEvent
(
new
Event
(
'
focus
'
));
expect
(
vm
.
hasSearchFocus
).
toBe
(
true
);
expect
(
vm
.
showSearchTypes
).
toBe
(
false
);
});
it
(
'
resets hasSearchFocus when search changes
'
,
done
=>
{
vm
.
hasSearchFocus
=
true
;
vm
.
search
=
'
something else
'
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
hasSearchFocus
).
toBe
(
false
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
});
spec/javascripts/ide/components/nav_dropdown_button_spec.js
0 → 100644
View file @
0d6e50d5
import
Vue
from
'
vue
'
;
import
NavDropdownButton
from
'
~/ide/components/nav_dropdown_button.vue
'
;
import
store
from
'
~/ide/stores
'
;
import
{
trimText
}
from
'
spec/helpers/vue_component_helper
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
resetStore
}
from
'
../helpers
'
;
describe
(
'
NavDropdown
'
,
()
=>
{
const
TEST_BRANCH_ID
=
'
lorem-ipsum-dolar
'
;
const
TEST_MR_ID
=
'
12345
'
;
const
Component
=
Vue
.
extend
(
NavDropdownButton
);
let
vm
;
beforeEach
(()
=>
{
vm
=
mountComponentWithStore
(
Component
,
{
store
});
vm
.
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
resetStore
(
store
);
});
it
(
'
renders empty placeholders, if state is falsey
'
,
()
=>
{
expect
(
trimText
(
vm
.
$el
.
textContent
)).
toEqual
(
'
- -
'
);
});
it
(
'
renders branch name, if state has currentBranchId
'
,
done
=>
{
vm
.
$store
.
state
.
currentBranchId
=
TEST_BRANCH_ID
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
trimText
(
vm
.
$el
.
textContent
)).
toEqual
(
`
${
TEST_BRANCH_ID
}
-`
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders mr id, if state has currentMergeRequestId
'
,
done
=>
{
vm
.
$store
.
state
.
currentMergeRequestId
=
TEST_MR_ID
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
trimText
(
vm
.
$el
.
textContent
)).
toEqual
(
`- !
${
TEST_MR_ID
}
`
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
renders branch and mr, if state has both
'
,
done
=>
{
vm
.
$store
.
state
.
currentBranchId
=
TEST_BRANCH_ID
;
vm
.
$store
.
state
.
currentMergeRequestId
=
TEST_MR_ID
;
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
trimText
(
vm
.
$el
.
textContent
)).
toEqual
(
`
${
TEST_BRANCH_ID
}
!
${
TEST_MR_ID
}
`
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
spec/javascripts/ide/components/nav_dropdown_spec.js
0 → 100644
View file @
0d6e50d5
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
store
from
'
~/ide/stores
'
;
import
NavDropdown
from
'
~/ide/components/nav_dropdown.vue
'
;
import
{
mountComponentWithStore
}
from
'
spec/helpers/vue_mount_component_helper
'
;
describe
(
'
IDE NavDropdown
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
NavDropdown
);
let
vm
;
let
$dropdown
;
beforeEach
(()
=>
{
vm
=
mountComponentWithStore
(
Component
,
{
store
});
$dropdown
=
$
(
vm
.
$el
);
// block dispatch from doing anything
spyOn
(
vm
.
$store
,
'
dispatch
'
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
renders nothing initially
'
,
()
=>
{
expect
(
vm
.
$el
).
not
.
toContainElement
(
'
.ide-nav-form
'
);
});
it
(
'
renders nav form when show.bs.dropdown
'
,
done
=>
{
$dropdown
.
trigger
(
'
show.bs.dropdown
'
);
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
).
toContainElement
(
'
.ide-nav-form
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
destroys nav form when closed
'
,
done
=>
{
$dropdown
.
trigger
(
'
show.bs.dropdown
'
);
$dropdown
.
trigger
(
'
hide.bs.dropdown
'
);
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
).
not
.
toContainElement
(
'
.ide-nav-form
'
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
spec/javascripts/ide/components/shared/tokened_input_spec.js
0 → 100644
View file @
0d6e50d5
import
Vue
from
'
vue
'
;
import
TokenedInput
from
'
~/ide/components/shared/tokened_input.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
const
TEST_PLACEHOLDER
=
'
Searching in test
'
;
const
TEST_TOKENS
=
[
{
label
:
'
lorem
'
,
id
:
1
},
{
label
:
'
ipsum
'
,
id
:
2
},
{
label
:
'
dolar
'
,
id
:
3
},
];
const
TEST_VALUE
=
'
lorem
'
;
function
getTokenElements
(
vm
)
{
return
Array
.
from
(
vm
.
$el
.
querySelectorAll
(
'
.filtered-search-token button
'
));
}
function
createBackspaceEvent
()
{
const
e
=
new
Event
(
'
keyup
'
);
e
.
keyCode
=
8
;
e
.
which
=
e
.
keyCode
;
e
.
altKey
=
false
;
e
.
ctrlKey
=
true
;
e
.
shiftKey
=
false
;
e
.
metaKey
=
false
;
return
e
;
}
describe
(
'
IDE shared/TokenedInput
'
,
()
=>
{
const
Component
=
Vue
.
extend
(
TokenedInput
);
let
vm
;
beforeEach
(()
=>
{
vm
=
mountComponent
(
Component
,
{
tokens
:
TEST_TOKENS
,
placeholder
:
TEST_PLACEHOLDER
,
value
:
TEST_VALUE
,
});
spyOn
(
vm
,
'
$emit
'
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
renders tokens
'
,
()
=>
{
const
renderedTokens
=
getTokenElements
(
vm
)
.
map
(
x
=>
x
.
textContent
.
trim
());
expect
(
renderedTokens
).
toEqual
(
TEST_TOKENS
.
map
(
x
=>
x
.
label
));
});
it
(
'
renders input
'
,
()
=>
{
expect
(
vm
.
$refs
.
input
).
toBeTruthy
();
expect
(
vm
.
$refs
.
input
).
toHaveValue
(
TEST_VALUE
);
});
it
(
'
renders placeholder, when tokens are empty
'
,
done
=>
{
vm
.
tokens
=
[];
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$refs
.
input
).
toHaveAttr
(
'
placeholder
'
,
TEST_PLACEHOLDER
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
triggers "removeToken" on token click
'
,
()
=>
{
getTokenElements
(
vm
)[
0
].
click
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
removeToken
'
,
TEST_TOKENS
[
0
]);
});
it
(
'
when input triggers backspace event, it calls "onBackspace"
'
,
()
=>
{
spyOn
(
vm
,
'
onBackspace
'
);
vm
.
$refs
.
input
.
dispatchEvent
(
createBackspaceEvent
());
vm
.
$refs
.
input
.
dispatchEvent
(
createBackspaceEvent
());
expect
(
vm
.
onBackspace
).
toHaveBeenCalledTimes
(
2
);
});
it
(
'
triggers "removeToken" on backspaces when value is empty
'
,
()
=>
{
vm
.
value
=
''
;
vm
.
onBackspace
();
expect
(
vm
.
$emit
).
not
.
toHaveBeenCalled
();
expect
(
vm
.
backspaceCount
).
toEqual
(
1
);
vm
.
onBackspace
();
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
removeToken
'
,
TEST_TOKENS
[
TEST_TOKENS
.
length
-
1
]);
expect
(
vm
.
backspaceCount
).
toEqual
(
0
);
});
it
(
'
does not trigger "removeToken" on backspaces when value is not empty
'
,
()
=>
{
vm
.
onBackspace
();
vm
.
onBackspace
();
expect
(
vm
.
backspaceCount
).
toEqual
(
0
);
expect
(
vm
.
$emit
).
not
.
toHaveBeenCalled
();
});
it
(
'
does not trigger "removeToken" on backspaces when tokens are empty
'
,
()
=>
{
vm
.
tokens
=
[];
vm
.
onBackspace
();
vm
.
onBackspace
();
expect
(
vm
.
backspaceCount
).
toEqual
(
0
);
expect
(
vm
.
$emit
).
not
.
toHaveBeenCalled
();
});
it
(
'
triggers "focus" on input focus
'
,
()
=>
{
vm
.
$refs
.
input
.
dispatchEvent
(
new
Event
(
'
focus
'
));
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
focus
'
);
});
it
(
'
triggers "blur" on input blur
'
,
()
=>
{
vm
.
$refs
.
input
.
dispatchEvent
(
new
Event
(
'
blur
'
));
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
blur
'
);
});
it
(
'
triggers "input" with value on input change
'
,
()
=>
{
vm
.
$refs
.
input
.
value
=
'
something-else
'
;
vm
.
$refs
.
input
.
dispatchEvent
(
new
Event
(
'
input
'
));
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
input
'
,
'
something-else
'
);
});
});
spec/javascripts/ide/helpers.js
View file @
0d6e50d5
...
...
@@ -4,6 +4,7 @@ import state from '~/ide/stores/state';
import
commitState
from
'
~/ide/stores/modules/commit/state
'
;
import
mergeRequestsState
from
'
~/ide/stores/modules/merge_requests/state
'
;
import
pipelinesState
from
'
~/ide/stores/modules/pipelines/state
'
;
import
branchesState
from
'
~/ide/stores/modules/branches/state
'
;
export
const
resetStore
=
store
=>
{
const
newState
=
{
...
...
@@ -11,6 +12,7 @@ export const resetStore = store => {
commit
:
commitState
(),
mergeRequests
:
mergeRequestsState
(),
pipelines
:
pipelinesState
(),
branches
:
branchesState
(),
};
store
.
replaceState
(
newState
);
};
...
...
spec/javascripts/ide/mock_data.js
View file @
0d6e50d5
...
...
@@ -165,3 +165,33 @@ export const mergeRequests = [
web_url
:
`
${
gl
.
TEST_HOST
}
/namespace/project-path/merge_requests/1`
,
},
];
export
const
branches
=
[
{
id
:
1
,
name
:
'
master
'
,
commit
:
{
message
:
'
Update master branch
'
,
committed_date
:
'
2018-08-01T00:20:05Z
'
,
},
can_push
:
true
,
},
{
id
:
2
,
name
:
'
feature/lorem-ipsum
'
,
commit
:
{
message
:
'
Update some stuff
'
,
committed_date
:
'
2018-08-02T00:00:05Z
'
,
},
can_push
:
true
,
},
{
id
:
3
,
name
:
'
feature/dolar-amit
'
,
commit
:
{
message
:
'
Update some more stuff
'
,
committed_date
:
'
2018-06-30T00:20:05Z
'
,
},
can_push
:
true
,
},
];
spec/javascripts/ide/stores/modules/branches/actions_spec.js
0 → 100644
View file @
0d6e50d5
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
state
from
'
~/ide/stores/modules/branches/state
'
;
import
*
as
types
from
'
~/ide/stores/modules/branches/mutation_types
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
import
{
requestBranches
,
receiveBranchesError
,
receiveBranchesSuccess
,
fetchBranches
,
resetBranches
,
openBranch
,
}
from
'
~/ide/stores/modules/branches/actions
'
;
import
{
branches
,
projectData
}
from
'
../../../mock_data
'
;
describe
(
'
IDE branches actions
'
,
()
=>
{
const
TEST_SEARCH
=
'
foosearch
'
;
let
mockedContext
;
let
mockedState
;
let
mock
;
beforeEach
(()
=>
{
mockedContext
=
{
dispatch
()
{},
rootState
:
{
currentProjectId
:
projectData
.
name_with_namespace
,
},
rootGetters
:
{
currentProject
:
projectData
,
},
state
:
state
(),
};
// testAction looks for rootGetters in state,
// so they need to be concatenated here.
mockedState
=
{
...
mockedContext
.
state
,
...
mockedContext
.
rootGetters
,
...
mockedContext
.
rootState
,
};
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
requestBranches
'
,
()
=>
{
it
(
'
should commit request
'
,
done
=>
{
testAction
(
requestBranches
,
null
,
mockedContext
.
state
,
[{
type
:
types
.
REQUEST_BRANCHES
}],
[],
done
,
);
});
});
describe
(
'
receiveBranchesError
'
,
()
=>
{
it
(
'
should should commit error
'
,
done
=>
{
testAction
(
receiveBranchesError
,
{
search
:
TEST_SEARCH
},
mockedContext
.
state
,
[{
type
:
types
.
RECEIVE_BRANCHES_ERROR
}],
[
{
type
:
'
setErrorMessage
'
,
payload
:
{
text
:
'
Error loading branches.
'
,
action
:
jasmine
.
any
(
Function
),
actionText
:
'
Please try again
'
,
actionPayload
:
{
search
:
TEST_SEARCH
},
},
},
],
done
,
);
});
});
describe
(
'
receiveBranchesSuccess
'
,
()
=>
{
it
(
'
should commit received data
'
,
done
=>
{
testAction
(
receiveBranchesSuccess
,
branches
,
mockedContext
.
state
,
[{
type
:
types
.
RECEIVE_BRANCHES_SUCCESS
,
payload
:
branches
}],
[],
done
,
);
});
});
describe
(
'
fetchBranches
'
,
()
=>
{
beforeEach
(()
=>
{
gon
.
api_version
=
'
v4
'
;
});
describe
(
'
success
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/
\/
api
\/
v4
\/
projects
\/\d
+
\/
repository
\/
branches
(
.*
)
$/
).
replyOnce
(
200
,
branches
);
});
it
(
'
calls API with params
'
,
()
=>
{
const
apiSpy
=
spyOn
(
axios
,
'
get
'
).
and
.
callThrough
();
fetchBranches
(
mockedContext
,
{
search
:
TEST_SEARCH
});
expect
(
apiSpy
).
toHaveBeenCalledWith
(
jasmine
.
anything
(),
{
params
:
jasmine
.
objectContaining
({
search
:
TEST_SEARCH
,
sort
:
'
updated_desc
'
,
}),
});
});
it
(
'
dispatches success with received data
'
,
done
=>
{
testAction
(
fetchBranches
,
{
search
:
TEST_SEARCH
},
mockedState
,
[],
[
{
type
:
'
requestBranches
'
},
{
type
:
'
resetBranches
'
},
{
type
:
'
receiveBranchesSuccess
'
,
payload
:
branches
,
},
],
done
,
);
});
});
describe
(
'
error
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
/
\/
api
\/
v4
\/
projects
\/\d
+
\/
repository
\/
branches
(
.*
)
$/
).
replyOnce
(
500
);
});
it
(
'
dispatches error
'
,
done
=>
{
testAction
(
fetchBranches
,
{
search
:
TEST_SEARCH
},
mockedState
,
[],
[
{
type
:
'
requestBranches
'
},
{
type
:
'
resetBranches
'
},
{
type
:
'
receiveBranchesError
'
,
payload
:
{
search
:
TEST_SEARCH
},
},
],
done
,
);
});
});
describe
(
'
resetBranches
'
,
()
=>
{
it
(
'
commits reset
'
,
done
=>
{
testAction
(
resetBranches
,
null
,
mockedContext
.
state
,
[{
type
:
types
.
RESET_BRANCHES
}],
[],
done
,
);
});
});
describe
(
'
openBranch
'
,
()
=>
{
it
(
'
dispatches goToRoute action with path
'
,
done
=>
{
const
branchId
=
branches
[
0
].
name
;
const
expectedPath
=
`/project/
${
projectData
.
name_with_namespace
}
/edit/
${
branchId
}
`
;
testAction
(
openBranch
,
branchId
,
mockedState
,
[],
[{
type
:
'
goToRoute
'
,
payload
:
expectedPath
}],
done
,
);
});
});
});
});
spec/javascripts/ide/stores/modules/branches/mutations_spec.js
0 → 100644
View file @
0d6e50d5
import
state
from
'
~/ide/stores/modules/branches/state
'
;
import
mutations
from
'
~/ide/stores/modules/branches/mutations
'
;
import
*
as
types
from
'
~/ide/stores/modules/branches/mutation_types
'
;
import
{
branches
}
from
'
../../../mock_data
'
;
describe
(
'
IDE branches mutations
'
,
()
=>
{
let
mockedState
;
beforeEach
(()
=>
{
mockedState
=
state
();
});
describe
(
types
.
REQUEST_BRANCHES
,
()
=>
{
it
(
'
sets loading to true
'
,
()
=>
{
mutations
[
types
.
REQUEST_BRANCHES
](
mockedState
);
expect
(
mockedState
.
isLoading
).
toBe
(
true
);
});
});
describe
(
types
.
RECEIVE_BRANCHES_ERROR
,
()
=>
{
it
(
'
sets loading to false
'
,
()
=>
{
mutations
[
types
.
RECEIVE_BRANCHES_ERROR
](
mockedState
);
expect
(
mockedState
.
isLoading
).
toBe
(
false
);
});
});
describe
(
types
.
RECEIVE_BRANCHES_SUCCESS
,
()
=>
{
it
(
'
sets branches
'
,
()
=>
{
const
expectedBranches
=
branches
.
map
(
branch
=>
({
name
:
branch
.
name
,
committedDate
:
branch
.
commit
.
committed_date
,
}));
mutations
[
types
.
RECEIVE_BRANCHES_SUCCESS
](
mockedState
,
branches
);
expect
(
mockedState
.
branches
).
toEqual
(
expectedBranches
);
});
});
describe
(
types
.
RESET_BRANCHES
,
()
=>
{
it
(
'
clears branches array
'
,
()
=>
{
mockedState
.
branches
=
[
'
test
'
];
mutations
[
types
.
RESET_BRANCHES
](
mockedState
);
expect
(
mockedState
.
branches
).
toEqual
([]);
});
});
});
spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
View file @
0d6e50d5
...
...
@@ -8,9 +8,7 @@ import {
receiveMergeRequestsSuccess
,
fetchMergeRequests
,
resetMergeRequests
,
openMergeRequest
,
}
from
'
~/ide/stores/modules/merge_requests/actions
'
;
import
router
from
'
~/ide/ide_router
'
;
import
{
mergeRequests
}
from
'
../../../mock_data
'
;
import
testAction
from
'
../../../../helpers/vuex_action_helper
'
;
...
...
@@ -28,12 +26,12 @@ describe('IDE merge requests actions', () => {
});
describe
(
'
requestMergeRequests
'
,
()
=>
{
it
(
'
should
should
commit request
'
,
done
=>
{
it
(
'
should commit request
'
,
done
=>
{
testAction
(
requestMergeRequests
,
'
created
'
,
null
,
mockedState
,
[{
type
:
types
.
REQUEST_MERGE_REQUESTS
,
payload
:
'
created
'
}],
[{
type
:
types
.
REQUEST_MERGE_REQUESTS
}],
[],
done
,
);
...
...
@@ -46,7 +44,7 @@ describe('IDE merge requests actions', () => {
receiveMergeRequestsError
,
{
type
:
'
created
'
,
search
:
''
},
mockedState
,
[{
type
:
types
.
RECEIVE_MERGE_REQUESTS_ERROR
,
payload
:
'
created
'
}],
[{
type
:
types
.
RECEIVE_MERGE_REQUESTS_ERROR
}],
[
{
type
:
'
setErrorMessage
'
,
...
...
@@ -67,12 +65,12 @@ describe('IDE merge requests actions', () => {
it
(
'
should commit received data
'
,
done
=>
{
testAction
(
receiveMergeRequestsSuccess
,
{
type
:
'
created
'
,
data
:
'
data
'
}
,
mergeRequests
,
mockedState
,
[
{
type
:
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
,
payload
:
{
type
:
'
created
'
,
data
:
'
data
'
}
,
payload
:
mergeRequests
,
},
],
[],
...
...
@@ -129,11 +127,11 @@ describe('IDE merge requests actions', () => {
mockedState
,
[],
[
{
type
:
'
requestMergeRequests
'
,
payload
:
'
created
'
},
{
type
:
'
resetMergeRequests
'
,
payload
:
'
created
'
},
{
type
:
'
requestMergeRequests
'
},
{
type
:
'
resetMergeRequests
'
},
{
type
:
'
receiveMergeRequestsSuccess
'
,
payload
:
{
type
:
'
created
'
,
data
:
mergeRequests
}
,
payload
:
mergeRequests
,
},
],
done
,
...
...
@@ -149,12 +147,12 @@ describe('IDE merge requests actions', () => {
it
(
'
dispatches error
'
,
done
=>
{
testAction
(
fetchMergeRequests
,
{
type
:
'
created
'
},
{
type
:
'
created
'
,
search
:
''
},
mockedState
,
[],
[
{
type
:
'
requestMergeRequests
'
,
payload
:
'
created
'
},
{
type
:
'
resetMergeRequests
'
,
payload
:
'
created
'
},
{
type
:
'
requestMergeRequests
'
},
{
type
:
'
resetMergeRequests
'
},
{
type
:
'
receiveMergeRequestsError
'
,
payload
:
{
type
:
'
created
'
,
search
:
''
}
},
],
done
,
...
...
@@ -167,59 +165,12 @@ describe('IDE merge requests actions', () => {
it
(
'
commits reset
'
,
done
=>
{
testAction
(
resetMergeRequests
,
'
created
'
,
null
,
mockedState
,
[{
type
:
types
.
RESET_MERGE_REQUESTS
,
payload
:
'
created
'
}],
[{
type
:
types
.
RESET_MERGE_REQUESTS
}],
[],
done
,
);
});
});
describe
(
'
openMergeRequest
'
,
()
=>
{
beforeEach
(()
=>
{
spyOn
(
router
,
'
push
'
);
});
it
(
'
commits reset mutations and actions
'
,
done
=>
{
const
commit
=
jasmine
.
createSpy
();
const
dispatch
=
jasmine
.
createSpy
().
and
.
returnValue
(
Promise
.
resolve
());
openMergeRequest
({
commit
,
dispatch
},
{
projectPath
:
'
gitlab-org/gitlab-ce
'
,
id
:
'
1
'
});
setTimeout
(()
=>
{
expect
(
commit
.
calls
.
argsFor
(
0
)).
toEqual
([
'
CLEAR_PROJECTS
'
,
null
,
{
root
:
true
}]);
expect
(
commit
.
calls
.
argsFor
(
1
)).
toEqual
([
'
SET_CURRENT_MERGE_REQUEST
'
,
'
1
'
,
{
root
:
true
}]);
expect
(
commit
.
calls
.
argsFor
(
2
)).
toEqual
([
'
RESET_OPEN_FILES
'
,
null
,
{
root
:
true
}]);
expect
(
dispatch
.
calls
.
argsFor
(
0
)).
toEqual
([
'
setCurrentBranchId
'
,
''
,
{
root
:
true
}]);
expect
(
dispatch
.
calls
.
argsFor
(
1
)).
toEqual
([
'
pipelines/stopPipelinePolling
'
,
null
,
{
root
:
true
},
]);
expect
(
dispatch
.
calls
.
argsFor
(
2
)).
toEqual
([
'
setRightPane
'
,
null
,
{
root
:
true
}]);
expect
(
dispatch
.
calls
.
argsFor
(
3
)).
toEqual
([
'
pipelines/resetLatestPipeline
'
,
null
,
{
root
:
true
},
]);
expect
(
dispatch
.
calls
.
argsFor
(
4
)).
toEqual
([
'
pipelines/clearEtagPoll
'
,
null
,
{
root
:
true
},
]);
done
();
});
});
it
(
'
pushes new route
'
,
()
=>
{
openMergeRequest
(
{
commit
()
{},
dispatch
:
()
=>
Promise
.
resolve
()
},
{
projectPath
:
'
gitlab-org/gitlab-ce
'
,
id
:
'
1
'
},
);
expect
(
router
.
push
).
toHaveBeenCalledWith
(
'
/project/gitlab-org/gitlab-ce/merge_requests/1
'
);
});
});
});
spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
View file @
0d6e50d5
...
...
@@ -12,29 +12,26 @@ describe('IDE merge requests mutations', () => {
describe
(
types
.
REQUEST_MERGE_REQUESTS
,
()
=>
{
it
(
'
sets loading to true
'
,
()
=>
{
mutations
[
types
.
REQUEST_MERGE_REQUESTS
](
mockedState
,
'
created
'
);
mutations
[
types
.
REQUEST_MERGE_REQUESTS
](
mockedState
);
expect
(
mockedState
.
created
.
isLoading
).
toBe
(
true
);
expect
(
mockedState
.
isLoading
).
toBe
(
true
);
});
});
describe
(
types
.
RECEIVE_MERGE_REQUESTS_ERROR
,
()
=>
{
it
(
'
sets loading to false
'
,
()
=>
{
mutations
[
types
.
RECEIVE_MERGE_REQUESTS_ERROR
](
mockedState
,
'
created
'
);
mutations
[
types
.
RECEIVE_MERGE_REQUESTS_ERROR
](
mockedState
);
expect
(
mockedState
.
created
.
isLoading
).
toBe
(
false
);
expect
(
mockedState
.
isLoading
).
toBe
(
false
);
});
});
describe
(
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
,
()
=>
{
it
(
'
sets merge requests
'
,
()
=>
{
gon
.
gitlab_url
=
gl
.
TEST_HOST
;
mutations
[
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
](
mockedState
,
{
type
:
'
created
'
,
data
:
mergeRequests
,
});
mutations
[
types
.
RECEIVE_MERGE_REQUESTS_SUCCESS
](
mockedState
,
mergeRequests
);
expect
(
mockedState
.
created
.
mergeRequests
).
toEqual
([
expect
(
mockedState
.
mergeRequests
).
toEqual
([
{
id
:
1
,
iid
:
1
,
...
...
@@ -50,9 +47,9 @@ describe('IDE merge requests mutations', () => {
it
(
'
clears merge request array
'
,
()
=>
{
mockedState
.
mergeRequests
=
[
'
test
'
];
mutations
[
types
.
RESET_MERGE_REQUESTS
](
mockedState
,
'
created
'
);
mutations
[
types
.
RESET_MERGE_REQUESTS
](
mockedState
);
expect
(
mockedState
.
created
.
mergeRequests
).
toEqual
([]);
expect
(
mockedState
.
mergeRequests
).
toEqual
([]);
});
});
});
spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
View file @
0d6e50d5
...
...
@@ -2,15 +2,15 @@ import Vue from 'vue';
import
dropdownButtonComponent
from
'
~/vue_shared/components/dropdown/dropdown_button.vue
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
mountComponentWithSlots
}
from
'
spec/helpers/vue_mount_component_helper
'
;
const
defaultLabel
=
'
Select
'
;
const
customLabel
=
'
Select project
'
;
const
createComponent
=
config
=>
{
const
createComponent
=
(
props
,
slots
=
{})
=>
{
const
Component
=
Vue
.
extend
(
dropdownButtonComponent
);
return
mountComponent
(
Component
,
config
);
return
mountComponent
WithSlots
(
Component
,
{
props
,
slots
}
);
};
describe
(
'
DropdownButtonComponent
'
,
()
=>
{
...
...
@@ -65,5 +65,14 @@ describe('DropdownButtonComponent', () => {
expect
(
dropdownIconEl
).
not
.
toBeNull
();
expect
(
dropdownIconEl
.
classList
.
contains
(
'
fa-chevron-down
'
)).
toBe
(
true
);
});
it
(
'
renders slot, if default slot exists
'
,
()
=>
{
vm
=
createComponent
({},
{
default
:
[
'
Lorem Ipsum Dolar
'
],
});
expect
(
vm
.
$el
).
not
.
toContainElement
(
'
.dropdown-toggle-text
'
);
expect
(
vm
.
$el
).
toHaveText
(
'
Lorem Ipsum Dolar
'
);
});
});
});
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