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
64f4952d
Commit
64f4952d
authored
Jan 14, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
4d4d0403
d89d8429
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
796 additions
and
219 deletions
+796
-219
GITALY_SERVER_VERSION
GITALY_SERVER_VERSION
+1
-1
app/assets/javascripts/boards/components/board_new_issue.vue
app/assets/javascripts/boards/components/board_new_issue.vue
+1
-1
app/assets/javascripts/boards/components/board_new_issue_new.vue
...ets/javascripts/boards/components/board_new_issue_new.vue
+3
-7
app/assets/javascripts/boards/components/project_select.vue
app/assets/javascripts/boards/components/project_select.vue
+31
-39
app/assets/javascripts/boards/components/project_select_deprecated.vue
...vascripts/boards/components/project_select_deprecated.vue
+145
-0
app/assets/javascripts/boards/graphql/group_projects.query.graphql
...s/javascripts/boards/graphql/group_projects.query.graphql
+17
-0
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+32
-0
app/assets/javascripts/boards/stores/mutation_types.js
app/assets/javascripts/boards/stores/mutation_types.js
+4
-0
app/assets/javascripts/boards/stores/mutations.js
app/assets/javascripts/boards/stores/mutations.js
+21
-0
app/assets/javascripts/boards/stores/state.js
app/assets/javascripts/boards/stores/state.js
+7
-1
app/services/packages/maven/find_or_create_package_service.rb
...services/packages/maven/find_or_create_package_service.rb
+14
-1
changelogs/unreleased/276882-maven-dupe-fix.yml
changelogs/unreleased/276882-maven-dupe-fix.yml
+6
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb
...e_request/user_closes_reopens_merge_request_state_spec.rb
+1
-1
spec/frontend/boards/components/board_new_issue_new_spec.js
spec/frontend/boards/components/board_new_issue_new_spec.js
+2
-2
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+15
-0
spec/frontend/boards/project_select_deprecated_spec.js
spec/frontend/boards/project_select_deprecated_spec.js
+261
-0
spec/frontend/boards/project_select_spec.js
spec/frontend/boards/project_select_spec.js
+52
-159
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+89
-0
spec/frontend/boards/stores/mutations_spec.js
spec/frontend/boards/stores/mutations_spec.js
+61
-1
spec/requests/api/maven_packages_spec.rb
spec/requests/api/maven_packages_spec.rb
+21
-6
spec/services/packages/maven/find_or_create_package_service_spec.rb
...ces/packages/maven/find_or_create_package_service_spec.rb
+9
-0
No files found.
GITALY_SERVER_VERSION
View file @
64f4952d
04496625daa52aaa82fe4140d898258e7e6fda22
a6674b359a02a4bf0549dcaa77ac05b1f4850831
app/assets/javascripts/boards/components/board_new_issue.vue
View file @
64f4952d
...
...
@@ -3,7 +3,7 @@ import { GlButton } from '@gitlab/ui';
import
{
getMilestone
}
from
'
ee_else_ce/boards/boards_util
'
;
import
ListIssue
from
'
ee_else_ce/boards/models/issue
'
;
import
eventHub
from
'
../eventhub
'
;
import
ProjectSelect
from
'
./project_select.vue
'
;
import
ProjectSelect
from
'
./project_select
_deprecated
.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
...
...
app/assets/javascripts/boards/components/board_new_issue_new.vue
View file @
64f4952d
<
script
>
import
{
mapActions
}
from
'
vuex
'
;
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
getMilestone
}
from
'
ee_else_ce/boards/boards_util
'
;
import
eventHub
from
'
../eventhub
'
;
...
...
@@ -28,10 +28,10 @@ export default {
data
()
{
return
{
title
:
''
,
selectedProject
:
{},
};
},
computed
:
{
...
mapState
([
'
selectedProject
'
]),
disabled
()
{
if
(
this
.
groupId
)
{
return
this
.
title
===
''
||
!
this
.
selectedProject
.
name
;
...
...
@@ -45,7 +45,6 @@ export default {
},
mounted
()
{
this
.
$refs
.
input
.
focus
();
eventHub
.
$on
(
'
setSelectedProject
'
,
this
.
setSelectedProject
);
},
methods
:
{
...
mapActions
([
'
addListNewIssue
'
]),
...
...
@@ -68,7 +67,7 @@ export default {
labelIds
:
labels
?.
map
((
l
)
=>
l
.
id
),
assigneeIds
:
assignees
?.
map
((
a
)
=>
a
?.
id
),
milestoneId
:
milestone
?.
id
,
projectPath
:
this
.
selectedProject
.
p
ath
,
projectPath
:
this
.
selectedProject
.
fullP
ath
,
weight
:
weight
>=
0
?
weight
:
null
,
},
list
:
this
.
list
,
...
...
@@ -80,9 +79,6 @@ export default {
this
.
title
=
''
;
eventHub
.
$emit
(
`toggle-issue-form-
${
this
.
list
.
id
}
`
);
},
setSelectedProject
(
selectedProject
)
{
this
.
selectedProject
=
selectedProject
;
},
},
};
</
script
>
...
...
app/assets/javascripts/boards/components/project_select.vue
View file @
64f4952d
<
script
>
import
{
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlSearchBoxByType
,
GlIntersectionObserver
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
eventHub
from
'
../eventhub
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Api
from
'
../../api
'
;
import
{
featureAccessLevel
}
from
'
~/pages/projects/shared/permissions/constants
'
;
import
{
ListType
}
from
'
../constants
'
;
...
...
@@ -27,6 +27,7 @@ export default {
order_by
:
'
similarity
'
,
},
components
:
{
GlIntersectionObserver
,
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
,
...
...
@@ -43,13 +44,12 @@ export default {
data
()
{
return
{
initialLoading
:
true
,
isFetching
:
false
,
projects
:
[],
selectedProject
:
{},
searchTerm
:
''
,
};
},
computed
:
{
...
mapState
([
'
groupProjects
'
,
'
groupProjectsFlags
'
]),
selectedProjectName
()
{
return
this
.
selectedProject
.
name
||
this
.
$options
.
i18n
.
dropdownText
;
},
...
...
@@ -65,47 +65,30 @@ export default {
};
},
isFetchResultEmpty
()
{
return
this
.
projects
.
length
===
0
;
return
this
.
groupProjects
.
length
===
0
;
},
hasNextPage
()
{
return
this
.
groupProjectsFlags
.
pageInfo
?.
hasNextPage
;
},
},
watch
:
{
searchTerm
()
{
this
.
fetch
Projects
(
);
this
.
fetch
GroupProjects
({
search
:
this
.
searchTerm
}
);
},
},
async
mounted
()
{
await
this
.
fetchProjects
(
);
mounted
()
{
this
.
fetchGroupProjects
({}
);
this
.
initialLoading
=
false
;
},
methods
:
{
async
fetchProjects
()
{
this
.
isFetching
=
true
;
try
{
const
projects
=
await
Api
.
groupProjects
(
this
.
groupId
,
this
.
searchTerm
,
this
.
fetchOptions
);
this
.
projects
=
projects
.
map
((
project
)
=>
{
return
{
id
:
project
.
id
,
name
:
project
.
name
,
namespacedName
:
project
.
name_with_namespace
,
path
:
project
.
path_with_namespace
,
};
});
}
catch
(
err
)
{
/* Handled in Api.groupProjects */
}
finally
{
this
.
isFetching
=
false
;
}
},
...
mapActions
([
'
fetchGroupProjects
'
,
'
setSelectedProject
'
]),
selectProject
(
projectId
)
{
this
.
selectedProject
=
this
.
projects
.
find
((
project
)
=>
project
.
id
===
projectId
);
/*
TODO Remove eventhub, use Vuex for BoardNewIssue and GraphQL for BoardNewIssueNew
https://gitlab.com/gitlab-org/gitlab/-/issues/276173
*/
eventHub
.
$emit
(
'
setSelectedProject
'
,
this
.
selectedProject
);
this
.
selectedProject
=
this
.
groupProjects
.
find
((
project
)
=>
project
.
id
===
projectId
);
this
.
setSelectedProject
(
this
.
selectedProject
);
},
loadMoreProjects
()
{
this
.
fetchGroupProjects
({
search
:
this
.
searchTerm
,
fetchNext
:
true
});
},
},
};
...
...
@@ -130,20 +113,29 @@ export default {
:placeholder=
"$options.i18n.searchPlaceholder"
/>
<gl-dropdown-item
v-for=
"project in
p
rojects"
v-show=
"!
isFetch
ing"
v-for=
"project in
groupP
rojects"
v-show=
"!
groupProjectsFlags.isLoad
ing"
:key=
"project.id"
:name=
"project.name"
@
click=
"selectProject(project.id)"
>
{{
project
.
name
spacedNam
e
}}
{{
project
.
name
WithNamespac
e
}}
</gl-dropdown-item>
<gl-dropdown-text
v-show=
"isFetching"
data-testid=
"dropdown-text-loading-icon"
>
<gl-dropdown-text
v-show=
"groupProjectsFlags.isLoading"
data-testid=
"dropdown-text-loading-icon"
>
<gl-loading-icon
class=
"gl-mx-auto"
/>
</gl-dropdown-text>
<gl-dropdown-text
v-if=
"isFetchResultEmpty && !isFetching"
data-testid=
"empty-result-message"
>
<gl-dropdown-text
v-if=
"isFetchResultEmpty && !groupProjectsFlags.isLoading"
data-testid=
"empty-result-message"
>
<span
class=
"gl-text-gray-500"
>
{{
$options
.
i18n
.
emptySearchResult
}}
</span>
</gl-dropdown-text>
<gl-intersection-observer
v-if=
"hasNextPage"
@
appear=
"loadMoreProjects"
>
<gl-loading-icon
v-if=
"groupProjectsFlags.isLoadingMore"
size=
"md"
/>
</gl-intersection-observer>
</gl-dropdown>
</div>
</
template
>
app/assets/javascripts/boards/components/project_select_deprecated.vue
0 → 100644
View file @
64f4952d
<
script
>
import
{
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlSearchBoxByType
,
GlLoadingIcon
,
}
from
'
@gitlab/ui
'
;
import
eventHub
from
'
../eventhub
'
;
import
{
s__
}
from
'
~/locale
'
;
import
Api
from
'
../../api
'
;
import
{
featureAccessLevel
}
from
'
~/pages/projects/shared/permissions/constants
'
;
import
{
ListType
}
from
'
../constants
'
;
export
default
{
name
:
'
ProjectSelect
'
,
i18n
:
{
headerTitle
:
s__
(
`BoardNewIssue|Projects`
),
dropdownText
:
s__
(
`BoardNewIssue|Select a project`
),
searchPlaceholder
:
s__
(
`BoardNewIssue|Search projects`
),
emptySearchResult
:
s__
(
`BoardNewIssue|No matching results`
),
},
defaultFetchOptions
:
{
with_issues_enabled
:
true
,
with_shared
:
false
,
include_subgroups
:
true
,
order_by
:
'
similarity
'
,
},
components
:
{
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
,
GlDropdownText
,
GlSearchBoxByType
,
},
props
:
{
list
:
{
type
:
Object
,
required
:
true
,
},
},
inject
:
[
'
groupId
'
],
data
()
{
return
{
initialLoading
:
true
,
isFetching
:
false
,
projects
:
[],
selectedProject
:
{},
searchTerm
:
''
,
};
},
computed
:
{
selectedProjectName
()
{
return
this
.
selectedProject
.
name
||
this
.
$options
.
i18n
.
dropdownText
;
},
fetchOptions
()
{
const
additionalAttrs
=
{};
if
(
this
.
list
.
type
&&
this
.
list
.
type
!==
ListType
.
backlog
)
{
additionalAttrs
.
min_access_level
=
featureAccessLevel
.
EVERYONE
;
}
return
{
...
this
.
$options
.
defaultFetchOptions
,
...
additionalAttrs
,
};
},
isFetchResultEmpty
()
{
return
this
.
projects
.
length
===
0
;
},
},
watch
:
{
searchTerm
()
{
this
.
fetchProjects
();
},
},
async
mounted
()
{
await
this
.
fetchProjects
();
this
.
initialLoading
=
false
;
},
methods
:
{
async
fetchProjects
()
{
this
.
isFetching
=
true
;
try
{
const
projects
=
await
Api
.
groupProjects
(
this
.
groupId
,
this
.
searchTerm
,
this
.
fetchOptions
);
this
.
projects
=
projects
.
map
((
project
)
=>
{
return
{
id
:
project
.
id
,
name
:
project
.
name
,
namespacedName
:
project
.
name_with_namespace
,
path
:
project
.
path_with_namespace
,
};
});
}
catch
(
err
)
{
/* Handled in Api.groupProjects */
}
finally
{
this
.
isFetching
=
false
;
}
},
selectProject
(
projectId
)
{
this
.
selectedProject
=
this
.
projects
.
find
((
project
)
=>
project
.
id
===
projectId
);
eventHub
.
$emit
(
'
setSelectedProject
'
,
this
.
selectedProject
);
},
},
};
</
script
>
<
template
>
<div>
<label
class=
"gl-font-weight-bold gl-mt-3"
data-testid=
"header-label"
>
{{
$options
.
i18n
.
headerTitle
}}
</label>
<gl-dropdown
data-testid=
"project-select-dropdown"
:text=
"selectedProjectName"
:header-text=
"$options.i18n.headerTitle"
block
menu-class=
"gl-w-full!"
:loading=
"initialLoading"
>
<gl-search-box-by-type
v-model.trim=
"searchTerm"
debounce=
"250"
:placeholder=
"$options.i18n.searchPlaceholder"
/>
<gl-dropdown-item
v-for=
"project in projects"
v-show=
"!isFetching"
:key=
"project.id"
:name=
"project.name"
@
click=
"selectProject(project.id)"
>
{{
project
.
namespacedName
}}
</gl-dropdown-item>
<gl-dropdown-text
v-show=
"isFetching"
data-testid=
"dropdown-text-loading-icon"
>
<gl-loading-icon
class=
"gl-mx-auto"
/>
</gl-dropdown-text>
<gl-dropdown-text
v-if=
"isFetchResultEmpty && !isFetching"
data-testid=
"empty-result-message"
>
<span
class=
"gl-text-gray-500"
>
{{
$options
.
i18n
.
emptySearchResult
}}
</span>
</gl-dropdown-text>
</gl-dropdown>
</div>
</
template
>
app/assets/javascripts/boards/graphql/group_projects.query.graphql
0 → 100644
View file @
64f4952d
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query
getGroupProjects
(
$fullPath
:
ID
!,
$search
:
String
,
$after
:
String
)
{
group
(
fullPath
:
$fullPath
)
{
projects
(
search
:
$search
,
after
:
$after
,
first
:
100
)
{
nodes
{
id
name
fullPath
nameWithNamespace
}
pageInfo
{
...
PageInfo
}
}
}
}
app/assets/javascripts/boards/stores/actions.js
View file @
64f4952d
...
...
@@ -29,6 +29,7 @@ import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.grap
import
issueSetSubscriptionMutation
from
'
../graphql/issue_set_subscription.mutation.graphql
'
;
import
issueSetMilestoneMutation
from
'
../graphql/issue_set_milestone.mutation.graphql
'
;
import
issueSetTitleMutation
from
'
../graphql/issue_set_title.mutation.graphql
'
;
import
groupProjectsQuery
from
'
../graphql/group_projects.query.graphql
'
;
const
notImplemented
=
()
=>
{
/* eslint-disable-next-line @gitlab/require-i18n-strings */
...
...
@@ -498,6 +499,37 @@ export default {
});
},
fetchGroupProjects
:
({
commit
,
state
},
{
search
=
''
,
fetchNext
=
false
})
=>
{
commit
(
types
.
REQUEST_GROUP_PROJECTS
,
fetchNext
);
const
{
fullPath
}
=
state
;
const
variables
=
{
fullPath
,
search
:
search
!==
''
?
search
:
undefined
,
after
:
fetchNext
?
state
.
groupProjectsFlags
.
pageInfo
.
endCursor
:
undefined
,
};
return
gqlClient
.
query
({
query
:
groupProjectsQuery
,
variables
,
})
.
then
(({
data
})
=>
{
const
{
projects
}
=
data
.
group
;
commit
(
types
.
RECEIVE_GROUP_PROJECTS_SUCCESS
,
{
projects
:
projects
.
nodes
,
pageInfo
:
projects
.
pageInfo
,
fetchNext
,
});
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_GROUP_PROJECTS_FAILURE
));
},
setSelectedProject
:
({
commit
},
project
)
=>
{
commit
(
types
.
SET_SELECTED_PROJECT
,
project
);
},
fetchBacklog
:
()
=>
{
notImplemented
();
},
...
...
app/assets/javascripts/boards/stores/mutation_types.js
View file @
64f4952d
...
...
@@ -36,3 +36,7 @@ export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
export
const
UPDATE_ISSUE_BY_ID
=
'
UPDATE_ISSUE_BY_ID
'
;
export
const
SET_ASSIGNEE_LOADING
=
'
SET_ASSIGNEE_LOADING
'
;
export
const
RESET_ISSUES
=
'
RESET_ISSUES
'
;
export
const
REQUEST_GROUP_PROJECTS
=
'
REQUEST_GROUP_PROJECTS
'
;
export
const
RECEIVE_GROUP_PROJECTS_SUCCESS
=
'
RECEIVE_GROUP_PROJECTS_SUCCESS
'
;
export
const
RECEIVE_GROUP_PROJECTS_FAILURE
=
'
RECEIVE_GROUP_PROJECTS_FAILURE
'
;
export
const
SET_SELECTED_PROJECT
=
'
SET_SELECTED_PROJECT
'
;
app/assets/javascripts/boards/stores/mutations.js
View file @
64f4952d
...
...
@@ -237,4 +237,25 @@ export default {
[
mutationTypes
.
TOGGLE_EMPTY_STATE
]:
()
=>
{
notImplemented
();
},
[
mutationTypes
.
REQUEST_GROUP_PROJECTS
]:
(
state
,
fetchNext
)
=>
{
Vue
.
set
(
state
,
'
groupProjectsFlags
'
,
{
[
fetchNext
?
'
isLoadingMore
'
:
'
isLoading
'
]:
true
,
pageInfo
:
state
.
groupProjectsFlags
.
pageInfo
,
});
},
[
mutationTypes
.
RECEIVE_GROUP_PROJECTS_SUCCESS
]:
(
state
,
{
projects
,
pageInfo
,
fetchNext
})
=>
{
Vue
.
set
(
state
,
'
groupProjects
'
,
fetchNext
?
[...
state
.
groupProjects
,
...
projects
]
:
projects
);
Vue
.
set
(
state
,
'
groupProjectsFlags
'
,
{
isLoading
:
false
,
isLoadingMore
:
false
,
pageInfo
});
},
[
mutationTypes
.
RECEIVE_GROUP_PROJECTS_FAILURE
]:
(
state
)
=>
{
state
.
error
=
s__
(
'
Boards|An error occurred while fetching group projects. Please try again.
'
);
Vue
.
set
(
state
,
'
groupProjectsFlags
'
,
{
isLoading
:
false
,
isLoadingMore
:
false
});
},
[
mutationTypes
.
SET_SELECTED_PROJECT
]:
(
state
,
project
)
=>
{
state
.
selectedProject
=
project
;
},
};
app/assets/javascripts/boards/stores/state.js
View file @
64f4952d
import
{
inactiveId
}
from
'
~/boards/constants
'
;
export
default
()
=>
({
endpoints
:
{},
boardType
:
null
,
disabled
:
false
,
isShowingLabels
:
true
,
...
...
@@ -15,6 +14,13 @@ export default () => ({
issues
:
{},
filterParams
:
{},
boardConfig
:
{},
groupProjects
:
[],
groupProjectsFlags
:
{
isLoading
:
false
,
isLoadingMore
:
false
,
pageInfo
:
{},
},
selectedProject
:
{},
error
:
undefined
,
// TODO: remove after ce/ee split of board_content.vue
isShowingEpicsSwimlanes
:
false
,
...
...
app/services/packages/maven/find_or_create_package_service.rb
View file @
64f4952d
...
...
@@ -11,7 +11,12 @@ module Packages
.
execute
unless
Namespace
::
PackageSetting
.
duplicates_allowed?
(
package
)
return
ServiceResponse
.
error
(
message:
'Duplicate package is not allowed'
)
files
=
package
&
.
package_files
||
[]
current_maven_files
=
files
.
map
{
|
file
|
extname
(
file
.
file_name
)
}
if
current_maven_files
.
compact
.
include?
(
extname
(
params
[
:file_name
]))
return
ServiceResponse
.
error
(
message:
'Duplicate package is not allowed'
)
end
end
unless
package
...
...
@@ -54,6 +59,14 @@ module Packages
ServiceResponse
.
success
(
payload:
{
package:
package
})
end
private
def
extname
(
filename
)
return
if
filename
.
blank?
File
.
extname
(
filename
)
end
end
end
end
changelogs/unreleased/276882-maven-dupe-fix.yml
0 → 100644
View file @
64f4952d
---
title
:
Fix behavior of maven_duplicates_allowed setting so new Maven packages can
be uploaded
merge_request
:
51524
author
:
type
:
fixed
locale/gitlab.pot
View file @
64f4952d
...
...
@@ -4582,6 +4582,9 @@ msgstr ""
msgid "Boards|An error occurred while creating the list. Please try again."
msgstr ""
msgid "Boards|An error occurred while fetching group projects. Please try again."
msgstr ""
msgid "Boards|An error occurred while fetching issues. Please reload the page."
msgstr ""
...
...
spec/features/merge_request/user_closes_reopens_merge_request_state_spec.rb
View file @
64f4952d
...
...
@@ -55,7 +55,7 @@ RSpec.describe 'User closes/reopens a merge request', :js do
end
end
describe
'when closed'
do
describe
'when closed'
,
quarantine:
'https://gitlab.com/gitlab-org/gitlab/-/issues/297500'
do
context
'when clicking the top `Reopen merge request` link'
,
:aggregate_failures
do
let
(
:closed_merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
state:
'closed'
)
}
...
...
spec/frontend/boards/components/board_new_issue_new_spec.js
View file @
64f4952d
...
...
@@ -3,7 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import
BoardNewIssue
from
'
~/boards/components/board_new_issue_new.vue
'
;
import
'
~/boards/models/list
'
;
import
{
mockList
}
from
'
../mock_data
'
;
import
{
mockList
,
mockGroupProjects
}
from
'
../mock_data
'
;
const
localVue
=
createLocalVue
();
...
...
@@ -29,7 +29,7 @@ describe('Issue boards new issue form', () => {
beforeEach
(()
=>
{
const
store
=
new
Vuex
.
Store
({
state
:
{},
state
:
{
selectedProject
:
mockGroupProjects
[
0
]
},
actions
:
{
addListNewIssue
:
addListNewIssuesSpy
},
getters
:
{},
});
...
...
spec/frontend/boards/mock_data.js
View file @
64f4952d
...
...
@@ -365,3 +365,18 @@ export const mockRawGroupProjects = [
path_with_namespace
:
'
awesome-group/foobar-project
'
,
},
];
export
const
mockGroupProjects
=
[
{
id
:
0
,
name
:
'
Example Project
'
,
nameWithNamespace
:
'
Awesome Group / Example Project
'
,
fullPath
:
'
awesome-group/example-project
'
,
},
{
id
:
1
,
name
:
'
Foobar Project
'
,
nameWithNamespace
:
'
Awesome Group / Foobar Project
'
,
fullPath
:
'
awesome-group/foobar-project
'
,
},
];
spec/frontend/boards/project_select_deprecated_spec.js
0 → 100644
View file @
64f4952d
import
{
mount
}
from
'
@vue/test-utils
'
;
import
axios
from
'
axios
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
import
{
featureAccessLevel
}
from
'
~/pages/projects/shared/permissions/constants
'
;
import
{
ListType
}
from
'
~/boards/constants
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
deprecatedCreateFlash
as
flash
}
from
'
~/flash
'
;
import
ProjectSelect
from
'
~/boards/components/project_select_deprecated.vue
'
;
import
{
listObj
,
mockRawGroupProjects
}
from
'
./mock_data
'
;
jest
.
mock
(
'
~/boards/eventhub
'
);
jest
.
mock
(
'
~/flash
'
);
const
dummyGon
=
{
api_version
:
'
v4
'
,
relative_url_root
:
'
/gitlab
'
,
};
const
mockGroupId
=
1
;
const
mockProjectsList1
=
mockRawGroupProjects
.
slice
(
0
,
1
);
const
mockProjectsList2
=
mockRawGroupProjects
.
slice
(
1
);
const
mockDefaultFetchOptions
=
{
with_issues_enabled
:
true
,
with_shared
:
false
,
include_subgroups
:
true
,
order_by
:
'
similarity
'
,
};
const
itemsPerPage
=
20
;
describe
(
'
ProjectSelect component
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
const
findLabel
=
()
=>
wrapper
.
find
(
"
[data-testid='header-label']
"
);
const
findGlDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findGlDropdownLoadingIcon
=
()
=>
findGlDropdown
().
find
(
'
button:first-child
'
).
find
(
GlLoadingIcon
);
const
findGlSearchBoxByType
=
()
=>
wrapper
.
find
(
GlSearchBoxByType
);
const
findGlDropdownItems
=
()
=>
wrapper
.
findAll
(
GlDropdownItem
);
const
findFirstGlDropdownItem
=
()
=>
findGlDropdownItems
().
at
(
0
);
const
findInMenuLoadingIcon
=
()
=>
wrapper
.
find
(
"
[data-testid='dropdown-text-loading-icon']
"
);
const
findEmptySearchMessage
=
()
=>
wrapper
.
find
(
"
[data-testid='empty-result-message']
"
);
const
mockGetRequest
=
(
data
=
[],
statusCode
=
httpStatus
.
OK
)
=>
{
axiosMock
.
onGet
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
)
.
replyOnce
(
statusCode
,
data
);
};
const
searchForProject
=
async
(
keyword
,
waitForAll
=
true
)
=>
{
findGlSearchBoxByType
().
vm
.
$emit
(
'
input
'
,
keyword
);
if
(
waitForAll
)
{
await
axios
.
waitForAll
();
}
};
const
createWrapper
=
async
({
list
=
listObj
}
=
{},
waitForAll
=
true
)
=>
{
wrapper
=
mount
(
ProjectSelect
,
{
propsData
:
{
list
,
},
provide
:
{
groupId
:
1
,
},
});
if
(
waitForAll
)
{
await
axios
.
waitForAll
();
}
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
window
.
gon
=
dummyGon
;
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
axiosMock
.
restore
();
jest
.
clearAllMocks
();
});
it
(
'
displays a header title
'
,
async
()
=>
{
createWrapper
({});
expect
(
findLabel
().
text
()).
toBe
(
'
Projects
'
);
});
it
(
'
renders a default dropdown text
'
,
async
()
=>
{
createWrapper
({});
expect
(
findGlDropdown
().
exists
()).
toBe
(
true
);
expect
(
findGlDropdown
().
text
()).
toContain
(
'
Select a project
'
);
});
describe
(
'
when mounted
'
,
()
=>
{
it
(
'
displays a loading icon while projects are being fetched
'
,
async
()
=>
{
mockGetRequest
([]);
createWrapper
({},
false
);
expect
(
findGlDropdownLoadingIcon
().
exists
()).
toBe
(
true
);
await
axios
.
waitForAll
();
expect
(
axiosMock
.
history
.
get
[
0
].
params
).
toMatchObject
({
search
:
''
});
expect
(
axiosMock
.
history
.
get
[
0
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
expect
(
findGlDropdownLoadingIcon
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when dropdown menu is open
'
,
()
=>
{
describe
(
'
by default
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
});
it
(
'
shows GlSearchBoxByType with default attributes
'
,
()
=>
{
expect
(
findGlSearchBoxByType
().
exists
()).
toBe
(
true
);
expect
(
findGlSearchBoxByType
().
vm
.
$attrs
).
toMatchObject
({
placeholder
:
'
Search projects
'
,
debounce
:
'
250
'
,
});
});
it
(
"
displays the fetched project's name
"
,
()
=>
{
expect
(
findFirstGlDropdownItem
().
exists
()).
toBe
(
true
);
expect
(
findFirstGlDropdownItem
().
text
()).
toContain
(
mockProjectsList1
[
0
].
name
);
});
it
(
"
doesn't render loading icon in the menu
"
,
()
=>
{
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
false
);
});
it
(
'
renders empty search result message
'
,
async
()
=>
{
await
createWrapper
();
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when a project is selected
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
await
findFirstGlDropdownItem
().
find
(
'
button
'
).
trigger
(
'
click
'
);
});
it
(
'
emits setSelectedProject with correct project metadata
'
,
()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
setSelectedProject
'
,
{
id
:
mockProjectsList1
[
0
].
id
,
path
:
mockProjectsList1
[
0
].
path_with_namespace
,
name
:
mockProjectsList1
[
0
].
name
,
namespacedName
:
mockProjectsList1
[
0
].
name_with_namespace
,
});
});
it
(
'
renders the name of the selected project
'
,
()
=>
{
expect
(
findGlDropdown
().
find
(
'
.gl-new-dropdown-button-text
'
).
text
()).
toBe
(
mockProjectsList1
[
0
].
name
,
);
});
});
describe
(
'
when user searches for a project
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
});
it
(
'
calls API with correct parameters with default fetch options
'
,
async
()
=>
{
await
searchForProject
(
'
foobar
'
);
const
expectedApiParams
=
{
search
:
'
foobar
'
,
per_page
:
itemsPerPage
,
...
mockDefaultFetchOptions
,
};
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
(
expectedApiParams
);
expect
(
axiosMock
.
history
.
get
[
1
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
});
describe
(
"
when list type is defined and isn't backlog
"
,
()
=>
{
it
(
'
calls API with an additional fetch option (min_access_level)
'
,
async
()
=>
{
axiosMock
.
reset
();
await
createWrapper
({
list
:
{
...
listObj
,
type
:
ListType
.
label
}
});
await
searchForProject
(
'
foobar
'
);
const
expectedApiParams
=
{
search
:
'
foobar
'
,
per_page
:
itemsPerPage
,
...
mockDefaultFetchOptions
,
min_access_level
:
featureAccessLevel
.
EVERYONE
,
};
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
(
expectedApiParams
);
expect
(
axiosMock
.
history
.
get
[
1
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
});
});
it
(
'
displays and hides gl-loading-icon while and after fetching data
'
,
async
()
=>
{
await
searchForProject
(
'
some keyword
'
,
false
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
true
);
await
axios
.
waitForAll
();
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
false
);
});
it
(
'
flashes an error message when fetching fails
'
,
async
()
=>
{
mockGetRequest
([],
httpStatus
.
INTERNAL_SERVER_ERROR
);
await
searchForProject
(
'
foobar
'
);
expect
(
flash
).
toHaveBeenCalledTimes
(
1
);
expect
(
flash
).
toHaveBeenCalledWith
(
'
Something went wrong while fetching projects
'
);
});
describe
(
'
with non-empty search result
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList2
);
await
searchForProject
(
'
foobar
'
);
});
it
(
'
displays the retrieved list of projects
'
,
async
()
=>
{
expect
(
findFirstGlDropdownItem
().
text
()).
toContain
(
mockProjectsList2
[
0
].
name
);
});
it
(
'
does not render empty search result message
'
,
async
()
=>
{
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
false
);
});
});
});
});
});
spec/frontend/boards/project_select_spec.js
View file @
64f4952d
import
{
mount
}
from
'
@vue/test-utils
'
;
import
axios
from
'
axios
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
Vuex
from
'
vuex
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
{
GlDropdown
,
GlDropdownItem
,
GlSearchBoxByType
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
import
{
featureAccessLevel
}
from
'
~/pages/projects/shared/permissions/constants
'
;
import
{
ListType
}
from
'
~/boards/constants
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
deprecatedCreateFlash
as
flash
}
from
'
~/flash
'
;
import
defaultState
from
'
~/boards/stores/state
'
;
import
ProjectSelect
from
'
~/boards/components/project_select.vue
'
;
import
{
listObj
,
mockRaw
GroupProjects
}
from
'
./mock_data
'
;
import
{
mockList
,
mock
GroupProjects
}
from
'
./mock_data
'
;
jest
.
mock
(
'
~/boards/eventhub
'
);
jest
.
mock
(
'
~/flash
'
);
const
localVue
=
createLocalVue
(
);
localVue
.
use
(
Vuex
);
const
dummyGon
=
{
api_version
:
'
v4
'
,
relative_url_root
:
'
/gitlab
'
,
const
actions
=
{
fetchGroupProjects
:
jest
.
fn
()
,
setSelectedProject
:
jest
.
fn
()
,
};
const
mockGroupId
=
1
;
const
mockProjectsList1
=
mockRawGroupProjects
.
slice
(
0
,
1
);
const
mockProjectsList2
=
mockRawGroupProjects
.
slice
(
1
);
const
mockDefaultFetchOptions
=
{
with_issues_enabled
:
true
,
with_shared
:
false
,
include_subgroups
:
true
,
order_by
:
'
similarity
'
,
const
createStore
=
(
state
=
defaultState
)
=>
{
return
new
Vuex
.
Store
({
state
,
actions
,
});
};
const
itemsPerPage
=
20
;
const
mockProjectsList1
=
mockGroupProjects
.
slice
(
0
,
1
)
;
describe
(
'
ProjectSelect component
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
const
findLabel
=
()
=>
wrapper
.
find
(
"
[data-testid='header-label']
"
);
const
findGlDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
...
...
@@ -46,55 +37,43 @@ describe('ProjectSelect component', () => {
const
findInMenuLoadingIcon
=
()
=>
wrapper
.
find
(
"
[data-testid='dropdown-text-loading-icon']
"
);
const
findEmptySearchMessage
=
()
=>
wrapper
.
find
(
"
[data-testid='empty-result-message']
"
);
const
mockGetRequest
=
(
data
=
[],
statusCode
=
httpStatus
.
OK
)
=>
{
axiosMock
.
onGet
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
)
.
replyOnce
(
statusCode
,
data
);
};
const
searchForProject
=
async
(
keyword
,
waitForAll
=
true
)
=>
{
findGlSearchBoxByType
().
vm
.
$emit
(
'
input
'
,
keyword
);
if
(
waitForAll
)
{
await
axios
.
waitForAll
();
}
};
const
createWrapper
=
(
state
=
{})
=>
{
const
store
=
createStore
({
groupProjects
:
[],
groupProjectsFlags
:
{
isLoading
:
false
,
pageInfo
:
{
hasNextPage
:
false
,
},
},
...
state
,
});
const
createWrapper
=
async
({
list
=
listObj
}
=
{},
waitForAll
=
true
)
=>
{
wrapper
=
mount
(
ProjectSelect
,
{
localVue
,
propsData
:
{
list
,
list
:
mockList
,
},
store
,
provide
:
{
groupId
:
1
,
},
});
if
(
waitForAll
)
{
await
axios
.
waitForAll
();
}
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
window
.
gon
=
dummyGon
;
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
axiosMock
.
restore
();
jest
.
clearAllMocks
();
});
it
(
'
displays a header title
'
,
async
()
=>
{
createWrapper
(
{}
);
it
(
'
displays a header title
'
,
()
=>
{
createWrapper
();
expect
(
findLabel
().
text
()).
toBe
(
'
Projects
'
);
});
it
(
'
renders a default dropdown text
'
,
async
()
=>
{
createWrapper
(
{}
);
it
(
'
renders a default dropdown text
'
,
()
=>
{
createWrapper
();
expect
(
findGlDropdown
().
exists
()).
toBe
(
true
);
expect
(
findGlDropdown
().
text
()).
toContain
(
'
Select a project
'
);
...
...
@@ -102,18 +81,11 @@ describe('ProjectSelect component', () => {
describe
(
'
when mounted
'
,
()
=>
{
it
(
'
displays a loading icon while projects are being fetched
'
,
async
()
=>
{
mockGetRequest
([]);
createWrapper
({},
false
);
createWrapper
();
expect
(
findGlDropdownLoadingIcon
().
exists
()).
toBe
(
true
);
await
axios
.
waitForAll
();
expect
(
axiosMock
.
history
.
get
[
0
].
params
).
toMatchObject
({
search
:
''
});
expect
(
axiosMock
.
history
.
get
[
0
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
findGlDropdownLoadingIcon
().
exists
()).
toBe
(
false
);
});
...
...
@@ -121,10 +93,8 @@ describe('ProjectSelect component', () => {
describe
(
'
when dropdown menu is open
'
,
()
=>
{
describe
(
'
by default
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
beforeEach
(()
=>
{
createWrapper
({
groupProjects
:
mockGroupProjects
});
});
it
(
'
shows GlSearchBoxByType with default attributes
'
,
()
=>
{
...
...
@@ -144,29 +114,24 @@ describe('ProjectSelect component', () => {
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
false
);
});
it
(
'
renders empty search result message
'
,
async
()
=>
{
await
createWrapper
();
it
(
'
does not render empty search result message
'
,
()
=>
{
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when no projects are being returned
'
,
()
=>
{
it
(
'
renders empty search result message
'
,
()
=>
{
createWrapper
();
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when a project is selected
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
await
findFirstGlDropdownItem
().
find
(
'
button
'
).
trigger
(
'
click
'
);
});
beforeEach
(()
=>
{
createWrapper
({
groupProjects
:
mockProjectsList1
});
it
(
'
emits setSelectedProject with correct project metadata
'
,
()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
setSelectedProject
'
,
{
id
:
mockProjectsList1
[
0
].
id
,
path
:
mockProjectsList1
[
0
].
path_with_namespace
,
name
:
mockProjectsList1
[
0
].
name
,
namespacedName
:
mockProjectsList1
[
0
].
name_with_namespace
,
});
findFirstGlDropdownItem
().
find
(
'
button
'
).
trigger
(
'
click
'
);
});
it
(
'
renders the name of the selected project
'
,
()
=>
{
...
...
@@ -176,85 +141,13 @@ describe('ProjectSelect component', () => {
});
});
describe
(
'
when user searches for a project
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList1
);
await
createWrapper
();
});
it
(
'
calls API with correct parameters with default fetch options
'
,
async
()
=>
{
await
searchForProject
(
'
foobar
'
);
const
expectedApiParams
=
{
search
:
'
foobar
'
,
per_page
:
itemsPerPage
,
...
mockDefaultFetchOptions
,
};
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
(
expectedApiParams
);
expect
(
axiosMock
.
history
.
get
[
1
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
describe
(
'
when projects are loading
'
,
()
=>
{
beforeEach
(()
=>
{
createWrapper
({
groupProjectsFlags
:
{
isLoading
:
true
}
});
});
describe
(
"
when list type is defined and isn't backlog
"
,
()
=>
{
it
(
'
calls API with an additional fetch option (min_access_level)
'
,
async
()
=>
{
axiosMock
.
reset
();
await
createWrapper
({
list
:
{
...
listObj
,
type
:
ListType
.
label
}
});
await
searchForProject
(
'
foobar
'
);
const
expectedApiParams
=
{
search
:
'
foobar
'
,
per_page
:
itemsPerPage
,
...
mockDefaultFetchOptions
,
min_access_level
:
featureAccessLevel
.
EVERYONE
,
};
expect
(
axiosMock
.
history
.
get
[
1
].
params
).
toMatchObject
(
expectedApiParams
);
expect
(
axiosMock
.
history
.
get
[
1
].
url
).
toBe
(
`/gitlab/api/v4/groups/
${
mockGroupId
}
/projects.json`
,
);
});
});
it
(
'
displays and hides gl-loading-icon while and after fetching data
'
,
async
()
=>
{
await
searchForProject
(
'
some keyword
'
,
false
);
await
wrapper
.
vm
.
$nextTick
();
it
(
'
displays and hides gl-loading-icon while and after fetching data
'
,
()
=>
{
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
true
);
await
axios
.
waitForAll
();
expect
(
findInMenuLoadingIcon
().
isVisible
()).
toBe
(
false
);
});
it
(
'
flashes an error message when fetching fails
'
,
async
()
=>
{
mockGetRequest
([],
httpStatus
.
INTERNAL_SERVER_ERROR
);
await
searchForProject
(
'
foobar
'
);
expect
(
flash
).
toHaveBeenCalledTimes
(
1
);
expect
(
flash
).
toHaveBeenCalledWith
(
'
Something went wrong while fetching projects
'
);
});
describe
(
'
with non-empty search result
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockGetRequest
(
mockProjectsList2
);
await
searchForProject
(
'
foobar
'
);
});
it
(
'
displays the retrieved list of projects
'
,
async
()
=>
{
expect
(
findFirstGlDropdownItem
().
text
()).
toContain
(
mockProjectsList2
[
0
].
name
);
});
it
(
'
does not render empty search result message
'
,
async
()
=>
{
expect
(
findEmptySearchMessage
().
exists
()).
toBe
(
false
);
});
});
});
});
...
...
spec/frontend/boards/stores/actions_spec.js
View file @
64f4952d
...
...
@@ -9,6 +9,7 @@ import {
mockMilestone
,
labels
,
mockActiveIssue
,
mockGroupProjects
,
}
from
'
../mock_data
'
;
import
actions
,
{
gqlClient
}
from
'
~/boards/stores/actions
'
;
import
*
as
types
from
'
~/boards/stores/mutation_types
'
;
...
...
@@ -1037,6 +1038,94 @@ describe('setActiveIssueTitle', () => {
});
});
describe
(
'
fetchGroupProjects
'
,
()
=>
{
const
state
=
{
fullPath
:
'
gitlab-org
'
,
};
const
pageInfo
=
{
endCursor
:
''
,
hasNextPage
:
false
,
};
const
queryResponse
=
{
data
:
{
group
:
{
projects
:
{
nodes
:
mockGroupProjects
,
pageInfo
:
{
endCursor
:
''
,
hasNextPage
:
false
,
},
},
},
},
};
it
(
'
should commit mutations REQUEST_GROUP_PROJECTS and RECEIVE_GROUP_PROJECTS_SUCCESS on success
'
,
(
done
)
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockResolvedValue
(
queryResponse
);
testAction
(
actions
.
fetchGroupProjects
,
{},
state
,
[
{
type
:
types
.
REQUEST_GROUP_PROJECTS
,
payload
:
false
,
},
{
type
:
types
.
RECEIVE_GROUP_PROJECTS_SUCCESS
,
payload
:
{
projects
:
mockGroupProjects
,
pageInfo
,
fetchNext
:
false
},
},
],
[],
done
,
);
});
it
(
'
should commit mutations REQUEST_GROUP_PROJECTS and RECEIVE_GROUP_PROJECTS_FAILURE on failure
'
,
(
done
)
=>
{
jest
.
spyOn
(
gqlClient
,
'
query
'
).
mockRejectedValue
();
testAction
(
actions
.
fetchGroupProjects
,
{},
state
,
[
{
type
:
types
.
REQUEST_GROUP_PROJECTS
,
payload
:
false
,
},
{
type
:
types
.
RECEIVE_GROUP_PROJECTS_FAILURE
,
},
],
[],
done
,
);
});
});
describe
(
'
setSelectedProject
'
,
()
=>
{
it
(
'
should commit mutation SET_SELECTED_PROJECT
'
,
(
done
)
=>
{
const
project
=
mockGroupProjects
[
0
];
testAction
(
actions
.
setSelectedProject
,
project
,
{},
[
{
type
:
types
.
SET_SELECTED_PROJECT
,
payload
:
project
,
},
],
[],
done
,
);
});
});
describe
(
'
fetchBacklog
'
,
()
=>
{
expectNotImplemented
(
actions
.
fetchBacklog
);
});
...
...
spec/frontend/boards/stores/mutations_spec.js
View file @
64f4952d
import
mutations
from
'
~/boards/stores/mutations
'
;
import
*
as
types
from
'
~/boards/stores/mutation_types
'
;
import
defaultState
from
'
~/boards/stores/state
'
;
import
{
mockLists
,
rawIssue
,
mockIssue
,
mockIssue2
}
from
'
../mock_data
'
;
import
{
mockLists
,
rawIssue
,
mockIssue
,
mockIssue2
,
mockGroupProjects
}
from
'
../mock_data
'
;
const
expectNotImplemented
=
(
action
)
=>
{
it
(
'
is not implemented
'
,
()
=>
{
...
...
@@ -529,4 +529,64 @@ describe('Board Store Mutations', () => {
describe
(
'
TOGGLE_EMPTY_STATE
'
,
()
=>
{
expectNotImplemented
(
mutations
.
TOGGLE_EMPTY_STATE
);
});
describe
(
'
REQUEST_GROUP_PROJECTS
'
,
()
=>
{
it
(
'
Should set isLoading in groupProjectsFlags to true in state when fetchNext is false
'
,
()
=>
{
mutations
[
types
.
REQUEST_GROUP_PROJECTS
](
state
,
false
);
expect
(
state
.
groupProjectsFlags
.
isLoading
).
toBe
(
true
);
});
it
(
'
Should set isLoading in groupProjectsFlags to true in state when fetchNext is true
'
,
()
=>
{
mutations
[
types
.
REQUEST_GROUP_PROJECTS
](
state
,
true
);
expect
(
state
.
groupProjectsFlags
.
isLoadingMore
).
toBe
(
true
);
});
});
describe
(
'
RECEIVE_GROUP_PROJECTS_SUCCESS
'
,
()
=>
{
it
(
'
Should set groupProjects and pageInfo to state and isLoading in groupProjectsFlags to false
'
,
()
=>
{
mutations
[
types
.
RECEIVE_GROUP_PROJECTS_SUCCESS
](
state
,
{
projects
:
mockGroupProjects
,
pageInfo
:
{
hasNextPage
:
false
},
});
expect
(
state
.
groupProjects
).
toEqual
(
mockGroupProjects
);
expect
(
state
.
groupProjectsFlags
.
isLoading
).
toBe
(
false
);
expect
(
state
.
groupProjectsFlags
.
pageInfo
).
toEqual
({
hasNextPage
:
false
});
});
it
(
'
Should merge projects in groupProjects in state when fetchNext is true
'
,
()
=>
{
state
=
{
...
state
,
groupProjects
:
[
mockGroupProjects
[
0
]],
};
mutations
[
types
.
RECEIVE_GROUP_PROJECTS_SUCCESS
](
state
,
{
projects
:
[
mockGroupProjects
[
1
]],
fetchNext
:
true
,
});
expect
(
state
.
groupProjects
).
toEqual
(
mockGroupProjects
);
});
});
describe
(
'
RECEIVE_GROUP_PROJECTS_FAILURE
'
,
()
=>
{
it
(
'
Should set error in state and isLoading in groupProjectsFlags to false
'
,
()
=>
{
mutations
[
types
.
RECEIVE_GROUP_PROJECTS_FAILURE
](
state
);
expect
(
state
.
error
).
toEqual
(
'
An error occurred while fetching group projects. Please try again.
'
,
);
expect
(
state
.
groupProjectsFlags
.
isLoading
).
toBe
(
false
);
});
});
describe
(
'
SET_SELECTED_PROJECT
'
,
()
=>
{
it
(
'
Should set selectedProject to state
'
,
()
=>
{
mutations
[
types
.
SET_SELECTED_PROJECT
](
state
,
mockGroupProjects
[
0
]);
expect
(
state
.
selectedProject
).
toEqual
(
mockGroupProjects
[
0
]);
});
});
});
spec/requests/api/maven_packages_spec.rb
View file @
64f4952d
...
...
@@ -679,6 +679,15 @@ RSpec.describe API::MavenPackages do
package_settings
.
update!
(
maven_duplicates_allowed:
false
)
end
shared_examples
'storing the package file'
do
it
'stores the file'
,
:aggregate_failures
do
expect
{
upload_file_with_token
(
params:
params
)
}.
to
change
{
package
.
package_files
.
count
}.
by
(
1
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
jar_file
.
file_name
).
to
eq
(
file_upload
.
original_filename
)
end
end
it
'rejects the request'
,
:aggregate_failures
do
expect
{
upload_file_with_token
(
params:
params
)
}.
not_to
change
{
package
.
package_files
.
count
}
...
...
@@ -686,17 +695,23 @@ RSpec.describe API::MavenPackages do
expect
(
json_response
[
'message'
]).
to
include
(
'Duplicate package is not allowed'
)
end
context
'when the package name matches the exception regex'
do
context
'when uploading different non-duplicate files to the same package'
do
let!
(
:package
)
{
create
(
:maven_package
,
project:
project
,
name:
project
.
full_path
)
}
before
do
package_settings
.
update!
(
maven_duplicate_exception_regex:
'.*'
)
package_file
=
package
.
package_files
.
find_by
(
file_name:
'my-app-1.0-20180724.124855-1.jar'
)
package_file
.
destroy!
end
it
'stores the package file'
,
:aggregate_failures
do
expect
{
upload_file_with_token
(
params:
params
)
}.
to
change
{
package
.
package_files
.
count
}.
by
(
1
)
it
_behaves_like
'storing the package file'
end
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
jar_file
.
file_name
).
to
eq
(
file_upload
.
original_filename
)
context
'when the package name matches the exception regex'
do
before
do
package_settings
.
update!
(
maven_duplicate_exception_regex:
'.*'
)
end
it_behaves_like
'storing the package file'
end
end
...
...
spec/services/packages/maven/find_or_create_package_service_spec.rb
View file @
64f4952d
...
...
@@ -111,6 +111,15 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do
expect
(
subject
.
errors
).
to
include
(
'Duplicate package is not allowed'
)
end
context
'when uploading different non-duplicate files to the same package'
do
before
do
package_file
=
existing_package
.
package_files
.
find_by
(
file_name:
'my-app-1.0-20180724.124855-1.jar'
)
package_file
.
destroy!
end
it_behaves_like
'reuse existing package'
end
context
'when the package name matches the exception regex'
do
before
do
package_settings
.
update!
(
maven_duplicate_exception_regex:
'.*'
)
...
...
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