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
703f89df
Commit
703f89df
authored
Nov 08, 2021
by
Florie Guibert
Committed by
Simon Knox
Nov 08, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor fetching board scope to GraphQL
Changelog: changed EE: true
parent
bff6c914
Changes
24
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
590 additions
and
255 deletions
+590
-255
app/assets/javascripts/boards/components/board_form.vue
app/assets/javascripts/boards/components/board_form.vue
+23
-20
app/assets/javascripts/boards/components/boards_selector.vue
app/assets/javascripts/boards/components/boards_selector.vue
+49
-14
app/assets/javascripts/boards/graphql/board_scope.fragment.graphql
...s/javascripts/boards/graphql/board_scope.fragment.graphql
+6
-0
app/assets/javascripts/boards/graphql/group_board.query.graphql
...sets/javascripts/boards/graphql/group_board.query.graphql
+9
-0
app/assets/javascripts/boards/graphql/project_board.query.graphql
...ts/javascripts/boards/graphql/project_board.query.graphql
+9
-0
app/assets/javascripts/boards/mount_multiple_boards_switcher.js
...sets/javascripts/boards/mount_multiple_boards_switcher.js
+0
-1
app/assets/javascripts/graphql_shared/fragments/iteration.fragment.graphql
...ripts/graphql_shared/fragments/iteration.fragment.graphql
+4
-0
app/assets/javascripts/graphql_shared/utils.js
app/assets/javascripts/graphql_shared/utils.js
+8
-2
app/views/shared/boards/_switcher.html.haml
app/views/shared/boards/_switcher.html.haml
+1
-2
ee/app/assets/javascripts/boards/components/board_form.vue
ee/app/assets/javascripts/boards/components/board_form.vue
+1
-6
ee/app/assets/javascripts/boards/components/board_scope.vue
ee/app/assets/javascripts/boards/components/board_scope.vue
+5
-1
ee/app/assets/javascripts/boards/components/boards_selector.vue
.../assets/javascripts/boards/components/boards_selector.vue
+4
-0
ee/app/assets/javascripts/boards/components/labels_select.vue
...pp/assets/javascripts/boards/components/labels_select.vue
+2
-6
ee/app/assets/javascripts/boards/graphql/board_scope.fragment.graphql
...s/javascripts/boards/graphql/board_scope.fragment.graphql
+26
-0
ee/app/assets/javascripts/boards/graphql/epic_board.query.graphql
...ssets/javascripts/boards/graphql/epic_board.query.graphql
+17
-0
ee/spec/features/boards/scoped_issue_board_spec.rb
ee/spec/features/boards/scoped_issue_board_spec.rb
+5
-0
ee/spec/frontend/boards/components/board_form_spec.js
ee/spec/frontend/boards/components/board_form_spec.js
+46
-16
ee/spec/frontend/boards/components/boards_selector_spec.js
ee/spec/frontend/boards/components/boards_selector_spec.js
+125
-70
ee/spec/frontend/boards/mock_data.js
ee/spec/frontend/boards/mock_data.js
+12
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/frontend/boards/components/board_form_spec.js
spec/frontend/boards/components/board_form_spec.js
+7
-8
spec/frontend/boards/components/boards_selector_spec.js
spec/frontend/boards/components/boards_selector_spec.js
+151
-96
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+23
-13
spec/frontend/graphql_shared/utils_spec.js
spec/frontend/graphql_shared/utils_spec.js
+54
-0
No files found.
app/assets/javascripts/boards/components/board_form.vue
View file @
703f89df
...
...
@@ -2,10 +2,10 @@
import
{
GlModal
,
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
mapGetters
,
mapActions
,
mapState
}
from
'
vuex
'
;
import
{
TYPE_USER
,
TYPE_ITERATION
,
TYPE_MILESTONE
}
from
'
~/graphql_shared/constants
'
;
import
{
convertToGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
convertToGraphQLId
,
getZeroBasedIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getParameterByName
,
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
{
fullLabelId
,
fullBoardId
}
from
'
../boards_util
'
;
import
{
fullLabelId
}
from
'
../boards_util
'
;
import
{
formType
}
from
'
../constants
'
;
import
createBoardMutation
from
'
../graphql/board_create.mutation.graphql
'
;
...
...
@@ -18,11 +18,11 @@ const boardDefaults = {
name
:
''
,
labels
:
[],
milestone
:
{},
iteration
_id
:
undefined
,
iteration
:
{}
,
assignee
:
{},
weight
:
null
,
hide
_backlog_l
ist
:
false
,
hide
_closed_l
ist
:
false
,
hide
BacklogL
ist
:
false
,
hide
ClosedL
ist
:
false
,
};
export
default
{
...
...
@@ -144,17 +144,16 @@ export default {
return
destroyBoardMutation
;
},
baseMutationVariables
()
{
const
{
board
}
=
this
;
const
variables
=
{
name
:
board
.
name
,
hideBacklogList
:
board
.
hide_backlog_list
,
hideClosedList
:
board
.
hide_closed_list
,
};
const
{
board
:
{
name
,
hideBacklogList
,
hideClosedList
,
id
},
}
=
this
;
const
variables
=
{
name
,
hideBacklogList
,
hideClosedList
};
return
board
.
id
return
id
?
{
...
variables
,
id
:
fullBoardId
(
board
.
id
)
,
id
,
}
:
{
...
variables
,
...
...
@@ -168,11 +167,13 @@ export default {
assigneeId
:
this
.
board
.
assignee
?.
id
?
convertToGraphQLId
(
TYPE_USER
,
this
.
board
.
assignee
.
id
)
:
null
,
// Temporarily converting to milestone ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779
milestoneId
:
this
.
board
.
milestone
?.
id
?
convertToGraphQLId
(
TYPE_MILESTONE
,
this
.
board
.
milestone
.
id
)
?
convertToGraphQLId
(
TYPE_MILESTONE
,
getZeroBasedIdFromGraphQLId
(
this
.
board
.
milestone
.
id
)
)
:
null
,
iterationId
:
this
.
board
.
iteration_id
?
convertToGraphQLId
(
TYPE_ITERATION
,
this
.
board
.
iteration_id
)
// Temporarily converting to iteration ID due to https://gitlab.com/gitlab-org/gitlab/-/issues/344779
iterationId
:
this
.
board
.
iteration
?.
id
?
convertToGraphQLId
(
TYPE_ITERATION
,
getZeroBasedIdFromGraphQLId
(
this
.
board
.
iteration
.
id
))
:
null
,
};
},
...
...
@@ -226,7 +227,7 @@ export default {
await
this
.
$apollo
.
mutate
({
mutation
:
this
.
deleteMutation
,
variables
:
{
id
:
fullBoardId
(
this
.
board
.
id
)
,
id
:
this
.
board
.
id
,
},
});
},
...
...
@@ -262,7 +263,9 @@ export default {
}
},
setIteration
(
iterationId
)
{
this
.
board
.
iteration_id
=
iterationId
;
this
.
$set
(
this
.
board
,
'
iteration
'
,
{
id
:
iterationId
,
});
},
setBoardLabels
(
labels
)
{
this
.
board
.
labels
=
labels
;
...
...
@@ -329,8 +332,8 @@ export default {
</div>
<board-configuration-options
:hide-backlog-list.sync=
"board.hide
_backlog_l
ist"
:hide-closed-list.sync=
"board.hide
_closed_l
ist"
:hide-backlog-list.sync=
"board.hide
BacklogL
ist"
:hide-closed-list.sync=
"board.hide
ClosedL
ist"
:readonly=
"readonly"
/>
...
...
app/assets/javascripts/boards/components/boards_selector.vue
View file @
703f89df
...
...
@@ -9,17 +9,20 @@ import {
GlModalDirective
,
}
from
'
@gitlab/ui
'
;
import
{
throttle
}
from
'
lodash
'
;
import
{
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
map
Actions
,
map
Getters
,
mapState
}
from
'
vuex
'
;
import
BoardForm
from
'
ee_else_ce/boards/components/board_form.vue
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
import
{
s__
}
from
'
~/locale
'
;
import
eventHub
from
'
../eventhub
'
;
import
groupQuery
from
'
../graphql/group_boards.query.graphql
'
;
import
projectQuery
from
'
../graphql/project_boards.query.graphql
'
;
import
groupBoardsQuery
from
'
../graphql/group_boards.query.graphql
'
;
import
projectBoardsQuery
from
'
../graphql/project_boards.query.graphql
'
;
import
groupBoardQuery
from
'
../graphql/group_board.query.graphql
'
;
import
projectBoardQuery
from
'
../graphql/project_board.query.graphql
'
;
const
MIN_BOARDS_TO_VIEW_RECENT
=
10
;
...
...
@@ -39,10 +42,6 @@ export default {
},
inject
:
[
'
fullPath
'
,
'
recentBoardsEndpoint
'
],
props
:
{
currentBoard
:
{
type
:
Object
,
required
:
true
,
},
throttleDuration
:
{
type
:
Number
,
default
:
200
,
...
...
@@ -86,14 +85,47 @@ export default {
maxPosition
:
0
,
filterTerm
:
''
,
currentPage
:
''
,
board
:
{},
};
},
apollo
:
{
board
:
{
query
()
{
return
this
.
currentBoardQuery
;
},
variables
()
{
return
{
fullPath
:
this
.
fullPath
,
boardId
:
this
.
fullBoardId
,
};
},
update
(
data
)
{
const
board
=
data
.
workspace
?.
board
;
return
{
...
board
,
labels
:
board
?.
labels
?.
nodes
,
};
},
error
()
{
this
.
setError
({
message
:
this
.
$options
.
i18n
.
errorFetchingBoard
});
},
},
},
computed
:
{
...
mapState
([
'
boardType
'
]),
...
mapGetters
([
'
isGroupBoard
'
]),
...
mapState
([
'
boardType
'
,
'
fullBoardId
'
]),
...
mapGetters
([
'
isGroupBoard
'
,
'
isProjectBoard
'
]),
parentType
()
{
return
this
.
boardType
;
},
currentBoardQueryCE
()
{
return
this
.
isGroupBoard
?
groupBoardQuery
:
projectBoardQuery
;
},
currentBoardQuery
()
{
return
this
.
currentBoardQueryCE
;
},
isBoardLoading
()
{
return
this
.
$apollo
.
queries
.
board
.
loading
;
},
loading
()
{
return
this
.
loadingRecentBoards
||
Boolean
(
this
.
loadingBoards
);
},
...
...
@@ -102,9 +134,6 @@ export default {
board
.
name
.
toLowerCase
().
includes
(
this
.
filterTerm
.
toLowerCase
()),
);
},
board
()
{
return
this
.
currentBoard
;
},
showCreate
()
{
return
this
.
multipleIssueBoardsAvailable
;
},
...
...
@@ -137,6 +166,7 @@ export default {
eventHub
.
$off
(
'
showBoardModal
'
,
this
.
showPage
);
},
methods
:
{
...
mapActions
([
'
setError
'
]),
showPage
(
page
)
{
this
.
currentPage
=
page
;
},
...
...
@@ -153,7 +183,7 @@ export default {
}));
},
boardQuery
()
{
return
this
.
isGroupBoard
?
group
Query
:
project
Query
;
return
this
.
isGroupBoard
?
group
BoardsQuery
:
projectBoards
Query
;
},
loadBoards
(
toggleDropdown
=
true
)
{
if
(
toggleDropdown
&&
this
.
boards
.
length
>
0
)
{
...
...
@@ -229,13 +259,18 @@ export default {
this
.
hasScrollFade
=
this
.
isScrolledUp
();
},
},
i18n
:
{
errorFetchingBoard
:
s__
(
'
Board|An error occurred while fetching the board, please try again.
'
),
},
};
</
script
>
<
template
>
<div
class=
"boards-switcher js-boards-selector gl-mr-3"
>
<span
class=
"boards-selector-wrapper js-boards-selector-wrapper"
>
<gl-loading-icon
v-if=
"isBoardLoading"
size=
"md"
class=
"gl-mt-2"
/>
<gl-dropdown
v-else
data-qa-selector=
"boards_dropdown"
toggle-class=
"dropdown-menu-toggle js-dropdown-toggle"
menu-class=
"flex-column dropdown-extended-height"
...
...
@@ -336,7 +371,7 @@ export default {
:can-admin-board=
"canAdminBoard"
:scoped-issue-board-feature-enabled=
"scopedIssueBoardFeatureEnabled"
:weights=
"weights"
:current-board=
"
currentB
oard"
:current-board=
"
b
oard"
:current-page=
"currentPage"
@
cancel=
"cancel"
/>
...
...
app/assets/javascripts/boards/graphql/board_scope.fragment.graphql
0 → 100644
View file @
703f89df
fragment
BoardScopeFragment
on
Board
{
id
name
hideBacklogList
hideClosedList
}
app/assets/javascripts/boards/graphql/group_board.query.graphql
0 → 100644
View file @
703f89df
#import "ee_else_ce/boards/graphql/board_scope.fragment.graphql"
query
GroupBoard
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
workspace
:
group
(
fullPath
:
$fullPath
)
{
board
(
id
:
$boardId
)
{
...
BoardScopeFragment
}
}
}
app/assets/javascripts/boards/graphql/project_board.query.graphql
0 → 100644
View file @
703f89df
#import "ee_else_ce/boards/graphql/board_scope.fragment.graphql"
query
ProjectBoard
(
$fullPath
:
ID
!,
$boardId
:
ID
!)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
board
(
id
:
$boardId
)
{
...
BoardScopeFragment
}
}
}
app/assets/javascripts/boards/mount_multiple_boards_switcher.js
View file @
703f89df
...
...
@@ -32,7 +32,6 @@ export default (params = {}) => {
data
()
{
const
boardsSelectorProps
=
{
...
dataset
,
currentBoard
:
JSON
.
parse
(
dataset
.
currentBoard
),
hasMissingBoards
:
parseBoolean
(
dataset
.
hasMissingBoards
),
canAdminBoard
:
parseBoolean
(
dataset
.
canAdminBoard
),
multipleIssueBoardsAvailable
:
parseBoolean
(
dataset
.
multipleIssueBoardsAvailable
),
...
...
app/assets/javascripts/graphql_shared/fragments/iteration.fragment.graphql
0 → 100644
View file @
703f89df
fragment
Iteration
on
Iteration
{
id
title
}
app/assets/javascripts/graphql_shared/utils.js
View file @
703f89df
...
...
@@ -15,6 +15,8 @@ export const isGid = (id) => {
return
false
;
};
const
parseGid
=
(
gid
)
=>
parseInt
(
`
${
gid
}
`
.
replace
(
/gid:
\/\/
gitlab
\/
.*
\/
/g
,
''
),
10
);
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Environments/123. This method extracts Id number
...
...
@@ -23,8 +25,12 @@ export const isGid = (id) => {
* @param {String} gid GraphQL global ID
* @returns {Number}
*/
export
const
getIdFromGraphQLId
=
(
gid
=
''
)
=>
parseInt
(
`
${
gid
}
`
.
replace
(
/gid:
\/\/
gitlab
\/
.*
\/
/g
,
''
),
10
)
||
null
;
export
const
getIdFromGraphQLId
=
(
gid
=
''
)
=>
parseGid
(
gid
)
||
null
;
export
const
getZeroBasedIdFromGraphQLId
=
(
gid
=
''
)
=>
{
const
parsedGid
=
parseGid
(
gid
);
return
Number
.
isInteger
(
parsedGid
)
?
parsedGid
:
null
;
};
export
const
MutationOperationMode
=
{
Append
:
'
APPEND
'
,
...
...
app/views/shared/boards/_switcher.html.haml
View file @
703f89df
...
...
@@ -3,8 +3,7 @@
-
milestone_filter_opts
=
milestone_filter_opts
.
merge
(
only_group_milestones:
true
)
if
board
.
group_board?
-
weights
=
Gitlab
.
ee?
?
([
Issue
::
WEIGHT_ANY
]
+
Issue
.
weight_options
)
:
[]
#js-multiple-boards-switcher
.inline.boards-switcher
{
data:
{
current_board:
current_board_json
.
to_json
,
milestone_path:
milestones_filter_path
(
milestone_filter_opts
),
#js-multiple-boards-switcher
.inline.boards-switcher
{
data:
{
milestone_path:
milestones_filter_path
(
milestone_filter_opts
),
board_base_url:
board_base_url
,
has_missing_boards:
(
!
multiple_boards_available?
&&
current_board_parent
.
boards
.
size
>
1
).
to_s
,
can_admin_board:
can?
(
current_user
,
:admin_issue_board
,
parent
).
to_s
,
...
...
ee/app/assets/javascripts/boards/components/board_form.vue
View file @
703f89df
...
...
@@ -3,9 +3,7 @@
// extends a valid Vue single file component.
/* eslint-disable @gitlab/no-runtime-template-compiler */
import
{
mapGetters
}
from
'
vuex
'
;
import
{
fullBoardId
}
from
'
~/boards/boards_util
'
;
import
BoardFormFoss
from
'
~/boards/components/board_form.vue
'
;
import
{
fullEpicBoardId
}
from
'
../boards_util
'
;
import
createEpicBoardMutation
from
'
../graphql/epic_board_create.mutation.graphql
'
;
import
destroyEpicBoardMutation
from
'
../graphql/epic_board_destroy.mutation.graphql
'
;
import
updateEpicBoardMutation
from
'
../graphql/epic_board_update.mutation.graphql
'
;
...
...
@@ -18,11 +16,8 @@ export default {
return
this
.
board
.
id
?
updateEpicBoardMutation
:
createEpicBoardMutation
;
},
mutationVariables
()
{
const
{
board
}
=
this
;
return
{
...
this
.
baseMutationVariables
,
...(
board
.
id
&&
this
.
isEpicBoard
&&
{
id
:
fullEpicBoardId
(
board
.
id
)
}),
...(
this
.
scopedIssueBoardFeatureEnabled
||
this
.
isEpicBoard
?
this
.
boardScopeMutationVariables
:
{}),
...
...
@@ -56,7 +51,7 @@ export default {
await
this
.
$apollo
.
mutate
({
mutation
:
this
.
isEpicBoard
?
destroyEpicBoardMutation
:
this
.
deleteMutation
,
variables
:
{
id
:
this
.
isEpicBoard
?
fullEpicBoardId
(
this
.
board
.
id
)
:
fullBoardId
(
this
.
board
.
id
)
,
id
:
this
.
board
.
id
,
},
});
},
...
...
ee/app/assets/javascripts/boards/components/board_scope.vue
View file @
703f89df
<
script
>
import
{
mapGetters
}
from
'
vuex
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
AssigneeSelect
from
'
./assignee_select.vue
'
;
import
BoardScopeCurrentIteration
from
'
./board_scope_current_iteration.vue
'
;
import
BoardLabelsSelect
from
'
./labels_select.vue
'
;
...
...
@@ -51,6 +52,9 @@ export default {
?
__
(
'
Board scope affects which issues are displayed for anyone who visits this board
'
)
:
__
(
'
Board scope affects which epics are displayed for anyone who visits this board
'
);
},
iterationId
()
{
return
getIdFromGraphQLId
(
this
.
board
.
iteration
?.
id
)
||
null
;
},
},
methods
:
{
...
...
@@ -87,7 +91,7 @@ export default {
<board-scope-current-iteration
v-if=
"isIssueBoard"
:can-admin-board=
"canAdminBoard"
:iteration-id=
"
board.iteration_i
d"
:iteration-id=
"
iterationI
d"
@
set-iteration=
"$emit('set-iteration', $event)"
/>
...
...
ee/app/assets/javascripts/boards/components/boards_selector.vue
View file @
703f89df
...
...
@@ -7,6 +7,7 @@ import BoardsSelectorFoss from '~/boards/components/boards_selector.vue';
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
Tracking
from
'
~/tracking
'
;
import
epicBoardsQuery
from
'
../graphql/epic_boards.query.graphql
'
;
import
epicBoardQuery
from
'
../graphql/epic_board.query.graphql
'
;
export
default
{
extends
:
BoardsSelectorFoss
,
...
...
@@ -19,6 +20,9 @@ export default {
showDelete
()
{
return
this
.
boards
.
length
>
1
;
},
currentBoardQuery
()
{
return
this
.
isEpicBoard
?
epicBoardQuery
:
this
.
currentBoardQueryCE
;
},
},
methods
:
{
epicBoardUpdate
(
data
)
{
...
...
ee/app/assets/javascripts/boards/components/labels_select.vue
View file @
703f89df
...
...
@@ -2,7 +2,6 @@
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
debounce
}
from
'
lodash
'
;
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
LabelItem
from
'
~/vue_shared/components/sidebar/labels_select_widget/label_item.vue
'
;
import
searchGroupLabels
from
'
~/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql
'
;
import
searchProjectLabels
from
'
~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql
'
;
...
...
@@ -60,10 +59,7 @@ export default {
return
!
this
.
isEditing
;
},
update
(
data
)
{
return
data
.
workspace
?.
labels
?.
nodes
.
map
((
label
)
=>
({
...
label
,
id
:
getIdFromGraphQLId
(
label
.
id
),
}));
return
data
.
workspace
?.
labels
?.
nodes
;
},
error
()
{
this
.
setError
({
message
:
this
.
$options
.
i18n
.
errorSearchingLabels
});
...
...
@@ -123,7 +119,7 @@ export default {
this
.
$emit
(
'
set-labels
'
,
labels
);
},
onLabelRemove
(
labelId
)
{
const
labels
=
this
.
selected
.
filter
(({
id
})
=>
getIdFromGraphQLId
(
id
)
!==
labelId
);
const
labels
=
this
.
selected
.
filter
(({
id
})
=>
id
!==
labelId
);
this
.
selected
=
labels
;
this
.
$emit
(
'
set-labels
'
,
labels
);
},
...
...
ee/app/assets/javascripts/boards/graphql/board_scope.fragment.graphql
0 → 100644
View file @
703f89df
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/label.fragment.graphql"
#import "~/graphql_shared/fragments/iteration.fragment.graphql"
fragment
BoardScopeFragment
on
Board
{
id
name
hideBacklogList
hideClosedList
assignee
{
...
User
}
milestone
{
id
title
}
labels
{
nodes
{
...
Label
}
}
iteration
{
...
Iteration
}
weight
}
ee/app/assets/javascripts/boards/graphql/epic_board.query.graphql
0 → 100644
View file @
703f89df
#import "~/graphql_shared/fragments/label.fragment.graphql"
query
EpicBoard
(
$fullPath
:
ID
!,
$boardId
:
BoardsEpicBoardID
!)
{
workspace
:
group
(
fullPath
:
$fullPath
)
{
board
:
epicBoard
(
id
:
$boardId
)
{
id
name
hideBacklogList
hideClosedList
labels
{
nodes
{
...
Label
}
}
}
}
}
ee/spec/features/boards/scoped_issue_board_spec.rb
View file @
703f89df
...
...
@@ -225,6 +225,7 @@ RSpec.describe 'Scoped issue boards', :js do
it
'prefills fields'
do
visit
project_boards_path
(
project_2
)
wait_for_all_requests
edit_board
.
click
...
...
@@ -332,6 +333,7 @@ RSpec.describe 'Scoped issue boards', :js do
it
'adds label to board'
do
label_title
=
issue
.
labels
.
first
.
title
visit
project_boards_path
(
project
)
wait_for_all_requests
update_board_label
(
label_title
)
...
...
@@ -346,6 +348,7 @@ RSpec.describe 'Scoped issue boards', :js do
label_2_title
=
issue_2
.
labels
.
first
.
title
visit
project_boards_path
(
project
)
wait_for_all_requests
update_board_label
(
label_title
)
...
...
@@ -365,6 +368,7 @@ RSpec.describe 'Scoped issue boards', :js do
label_2_title
=
issue_2
.
labels
.
first
.
title
visit
project_boards_path
(
project
)
wait_for_all_requests
update_board_label
(
label_title
)
...
...
@@ -378,6 +382,7 @@ RSpec.describe 'Scoped issue boards', :js do
stub_licensed_features
(
multiple_group_issue_boards:
true
)
visit
group_boards_path
(
group
)
wait_for_all_requests
edit_board
.
click
page
.
within
(
'.labels'
)
do
...
...
ee/spec/frontend/boards/components/board_form_spec.js
View file @
703f89df
...
...
@@ -23,15 +23,19 @@ jest.mock('~/lib/utils/url_utility', () => ({
Vue
.
use
(
Vuex
);
const
currentBoard
=
{
id
:
1
,
id
:
'
gid://gitlab/Board/1
'
,
name
:
'
test
'
,
labels
:
[],
milestone
_id
:
undefined
,
milestone
:
{}
,
assignee
:
{},
assignee_id
:
undefined
,
weight
:
null
,
hide_backlog_list
:
false
,
hide_closed_list
:
false
,
hideBacklogList
:
false
,
hideClosedList
:
false
,
};
const
currentEpicBoard
=
{
...
currentBoard
,
id
:
'
gid://gitlab/Boards::EpicBoard/321
'
,
};
const
defaultProps
=
{
...
...
@@ -206,7 +210,9 @@ describe('BoardForm', () => {
milestone
:
{
id
:
2
,
},
iteration_id
:
3
,
iteration
:
{
id
:
'
gid://gitlab/Iteration/3
'
,
},
},
canAdminBoard
:
true
,
currentPage
:
formType
.
edit
,
...
...
@@ -221,7 +227,7 @@ describe('BoardForm', () => {
mutation
:
updateBoardMutation
,
variables
:
{
input
:
expect
.
objectContaining
({
id
:
`gid://gitlab/Board/
${
currentBoard
.
id
}
`
,
id
:
currentBoard
.
id
,
assigneeId
:
'
gid://gitlab/User/1
'
,
milestoneId
:
'
gid://gitlab/Milestone/2
'
,
iterationId
:
'
gid://gitlab/Iteration/3
'
,
...
...
@@ -240,11 +246,15 @@ describe('BoardForm', () => {
mutate
=
jest
.
fn
().
mockResolvedValue
({
data
:
{
epicBoardUpdate
:
{
epicBoard
:
{
id
:
'
gid://gitlab/Boards::EpicBoard/321
'
,
webPath
:
'
test-path
'
},
epicBoard
:
{
id
:
currentEpicBoard
.
id
,
webPath
:
'
test-path
'
},
},
},
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
edit
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
edit
,
currentBoard
:
currentEpicBoard
,
});
findInput
().
trigger
(
'
keyup.enter
'
,
{
metaKey
:
true
});
...
...
@@ -254,7 +264,7 @@ describe('BoardForm', () => {
mutation
:
updateEpicBoardMutation
,
variables
:
{
input
:
expect
.
objectContaining
({
id
:
`gid://gitlab/Boards::EpicBoard/
${
currentBoard
.
id
}
`
,
id
:
currentEpicBoard
.
id
,
}),
},
});
...
...
@@ -265,7 +275,11 @@ describe('BoardForm', () => {
it
(
'
shows a GlAlert if GraphQL mutation fails
'
,
async
()
=>
{
mutate
=
jest
.
fn
().
mockRejectedValue
(
'
Houston, we have a problem
'
);
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
edit
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
edit
,
currentBoard
:
currentEpicBoard
,
});
jest
.
spyOn
(
wrapper
.
vm
,
'
setError
'
).
mockImplementation
(()
=>
{});
findInput
().
trigger
(
'
keyup.enter
'
,
{
metaKey
:
true
});
...
...
@@ -285,19 +299,31 @@ describe('BoardForm', () => {
});
it
(
'
passes correct primary action text and variant
'
,
()
=>
{
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
,
currentBoard
:
currentEpicBoard
,
});
expect
(
findModalActionPrimary
().
text
).
toBe
(
'
Delete
'
);
expect
(
findModalActionPrimary
().
attributes
[
0
].
variant
).
toBe
(
'
danger
'
);
});
it
(
'
renders delete confirmation message
'
,
()
=>
{
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
,
currentBoard
:
currentEpicBoard
,
});
expect
(
findDeleteConfirmation
().
exists
()).
toBe
(
true
);
});
it
(
'
calls a correct GraphQL mutation and redirects to correct page after deleting board
'
,
async
()
=>
{
mutate
=
jest
.
fn
().
mockResolvedValue
({});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
,
currentBoard
:
currentEpicBoard
,
});
findModal
().
vm
.
$emit
(
'
primary
'
);
await
waitForPromises
();
...
...
@@ -305,7 +331,7 @@ describe('BoardForm', () => {
expect
(
mutate
).
toHaveBeenCalledWith
({
mutation
:
destroyEpicBoardMutation
,
variables
:
{
id
:
'
gid://gitlab/Boards::EpicBoard/1
'
,
id
:
currentEpicBoard
.
id
,
},
});
...
...
@@ -315,7 +341,11 @@ describe('BoardForm', () => {
it
(
'
shows a GlAlert if GraphQL mutation fails
'
,
async
()
=>
{
mutate
=
jest
.
fn
().
mockRejectedValue
(
'
Houston, we have a problem
'
);
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
});
createComponent
({
canAdminBoard
:
true
,
currentPage
:
formType
.
delete
,
currentBoard
:
currentEpicBoard
,
});
jest
.
spyOn
(
wrapper
.
vm
,
'
setError
'
).
mockImplementation
(()
=>
{});
findModal
().
vm
.
$emit
(
'
primary
'
);
...
...
ee/spec/frontend/boards/components/boards_selector_spec.js
View file @
703f89df
import
{
GlDropdown
,
GlLoadingIcon
,
GlDropdownSectionHeader
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
mount
}
from
'
@vue/test-utils
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
nextTick
}
from
'
vue
'
;
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
Vuex
from
'
vuex
'
;
import
BoardsSelector
from
'
ee/boards/components/boards_selector.vue
'
;
import
{
BoardType
}
from
'
~/boards/constants
'
;
import
epicBoardQuery
from
'
ee/boards/graphql/epic_board.query.graphql
'
;
import
groupBoardQuery
from
'
~/boards/graphql/group_board.query.graphql
'
;
import
projectBoardQuery
from
'
~/boards/graphql/project_board.query.graphql
'
;
import
defaultStore
from
'
~/boards/stores
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
{
mockGroupBoardResponse
,
mockProjectBoardResponse
}
from
'
jest/boards/mock_data
'
;
import
{
mockEpicBoardResponse
}
from
'
../mock_data
'
;
const
throttleDuration
=
1
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
Vue
.
use
(
VueApollo
);
function
boardGenerator
(
n
)
{
return
new
Array
(
n
).
fill
().
map
((
board
,
index
)
=>
{
...
...
@@ -29,13 +37,28 @@ describe('BoardsSelector', () => {
let
allBoardsResponse
;
let
recentBoardsResponse
;
let
mock
;
let
fakeApollo
;
let
store
;
const
boards
=
boardGenerator
(
20
);
const
recentBoards
=
boardGenerator
(
5
);
const
createStore
=
()
=>
{
return
new
Vuex
.
Store
({
const
createStore
=
({
isGroupBoard
=
false
,
isProjectBoard
=
false
,
isEpicBoard
=
false
,
}
=
{})
=>
{
store
=
new
Vuex
.
Store
({
...
defaultStore
,
actions
:
{
setError
:
jest
.
fn
(),
},
getters
:
{
isEpicBoard
:
()
=>
false
,
isEpicBoard
:
()
=>
isEpicBoard
,
isGroupBoard
:
()
=>
isGroupBoard
,
isProjectBoard
:
()
=>
isProjectBoard
,
},
state
:
{
boardType
:
isGroupBoard
?
BoardType
.
group
:
BoardType
.
project
,
},
});
};
...
...
@@ -45,43 +68,22 @@ describe('BoardsSelector', () => {
const
getLoadingIcon
=
()
=>
wrapper
.
findComponent
(
GlLoadingIcon
);
const
findDropdown
=
()
=>
wrapper
.
findComponent
(
GlDropdown
);
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
const
$apollo
=
{
queries
:
{
boards
:
{
loading
:
false
,
},
},
};
allBoardsResponse
=
Promise
.
resolve
({
data
:
{
group
:
{
boards
:
{
edges
:
boards
.
map
((
board
)
=>
({
node
:
board
})),
},
},
},
});
recentBoardsResponse
=
Promise
.
resolve
({
data
:
recentBoards
,
});
const
projectBoardQueryHandlerSuccess
=
jest
.
fn
().
mockResolvedValue
(
mockProjectBoardResponse
);
const
groupBoardQueryHandlerSuccess
=
jest
.
fn
().
mockResolvedValue
(
mockGroupBoardResponse
);
const
epicBoardQueryHandlerSuccess
=
jest
.
fn
().
mockResolvedValue
(
mockEpicBoardResponse
);
const
store
=
createStore
();
const
createComponent
=
()
=>
{
fakeApollo
=
createMockApollo
([
[
projectBoardQuery
,
projectBoardQueryHandlerSuccess
],
[
groupBoardQuery
,
groupBoardQueryHandlerSuccess
],
[
epicBoardQuery
,
epicBoardQueryHandlerSuccess
],
]);
wrapper
=
mount
(
BoardsSelector
,
{
localVue
,
store
,
apolloProvider
:
fakeApollo
,
propsData
:
{
throttleDuration
,
currentBoard
:
{
id
:
1
,
name
:
'
Development
'
,
milestone_id
:
null
,
weight
:
null
,
assignee_id
:
null
,
labels
:
[],
},
boardBaseUrl
:
`
${
TEST_HOST
}
/board/base/url`
,
hasMissingBoards
:
false
,
canAdminBoard
:
true
,
...
...
@@ -89,13 +91,11 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled
:
true
,
weights
:
[],
},
mocks
:
{
$apollo
},
attachTo
:
document
.
body
,
provide
:
{
fullPath
:
''
,
recentBoardsEndpoint
:
`
${
TEST_HOST
}
/recent`
,
},
store
,
});
wrapper
.
vm
.
$apollo
.
addSmartQuery
=
jest
.
fn
((
_
,
options
)
=>
{
...
...
@@ -103,48 +103,103 @@ describe('BoardsSelector', () => {
[
options
.
loadingKey
]:
true
,
});
});
mock
.
onGet
(
`
${
TEST_HOST
}
/recent`
).
replyOnce
(
200
,
recentBoards
);
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
fakeApollo
=
null
;
store
=
null
;
mock
.
restore
();
});
describe
(
'
loading
'
,
()
=>
{
// we are testing loading state, so don't resolve responses until after the tests
afterEach
(
async
()
=>
{
await
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]);
return
nextTick
();
});
describe
(
'
fetching all board
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
it
(
'
shows loading spinner
'
,
()
=>
{
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
expect
(
getDropdownItems
()).
toHaveLength
(
0
);
expect
(
getLoadingIcon
().
exists
()).
toBe
(
true
);
allBoardsResponse
=
Promise
.
resolve
({
data
:
{
group
:
{
boards
:
{
edges
:
boards
.
map
((
board
)
=>
({
node
:
board
})),
},
},
},
});
recentBoardsResponse
=
Promise
.
resolve
({
data
:
recentBoards
,
});
createStore
();
createComponent
();
mock
.
onGet
(
`
${
TEST_HOST
}
/recent`
).
replyOnce
(
200
,
recentBoards
);
});
});
describe
(
'
loaded
'
,
()
=>
{
beforeEach
(
async
()
=>
{
await
wrapper
.
setData
({
loadingBoards
:
false
,
describe
(
'
loading
'
,
()
=>
{
beforeEach
(
async
()
=>
{
// Wait for current board to be loaded
await
nextTick
();
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
});
// we are testing loading state, so don't resolve responses until after the tests
afterEach
(
async
()
=>
{
await
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]);
await
nextTick
();
});
it
(
'
shows loading spinner
'
,
()
=>
{
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
expect
(
getDropdownItems
()).
toHaveLength
(
0
);
expect
(
getLoadingIcon
().
exists
()).
toBe
(
true
);
});
// NOTE: Due to timing issues, this `return` of `Promise.all` is required because
// `app/assets/javascripts/boards/components/boards_selector.vue` does a `$nextTick()` in
// loadRecentBoards. For some unknown reason it doesn't work with `await`, the `return`
// is required.
return
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]).
then
(()
=>
nextTick
());
});
it
(
'
hides loading spinner
'
,
async
()
=>
{
await
wrapper
.
vm
.
$nextTick
();
expect
(
getLoadingIcon
().
exists
()).
toBe
(
false
);
describe
(
'
loaded
'
,
()
=>
{
beforeEach
(
async
()
=>
{
// Wait for current board to be loaded
await
nextTick
();
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
await
wrapper
.
setData
({
loadingBoards
:
false
,
loadingRecentBoards
:
false
,
});
});
it
(
'
hides loading spinner
'
,
async
()
=>
{
await
nextTick
();
expect
(
getLoadingIcon
().
exists
()).
toBe
(
false
);
});
});
});
describe
(
'
fetching current board
'
,
()
=>
{
it
.
each
`
boardType | isEpicBoard | queryHandler | notCalledHandler
${
BoardType
.
group
}
|
${
false
}
|
${
groupBoardQueryHandlerSuccess
}
|
${
projectBoardQueryHandlerSuccess
}
${
BoardType
.
project
}
|
${
false
}
|
${
projectBoardQueryHandlerSuccess
}
|
${
groupBoardQueryHandlerSuccess
}
${
BoardType
.
group
}
|
${
true
}
|
${
epicBoardQueryHandlerSuccess
}
|
${
groupBoardQueryHandlerSuccess
}
`
(
'
fetches $boardType board when isEpicBoard is $isEpicBoard
'
,
async
({
boardType
,
isEpicBoard
,
queryHandler
,
notCalledHandler
})
=>
{
createStore
({
isProjectBoard
:
boardType
===
BoardType
.
project
,
isGroupBoard
:
boardType
===
BoardType
.
group
,
isEpicBoard
,
});
createComponent
();
await
nextTick
();
expect
(
queryHandler
).
toHaveBeenCalled
();
expect
(
notCalledHandler
).
not
.
toHaveBeenCalled
();
},
);
});
});
ee/spec/frontend/boards/mock_data.js
View file @
703f89df
...
...
@@ -7,6 +7,18 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
import
MilestoneToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue
'
;
import
WeightToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue
'
;
export
const
mockEpicBoardResponse
=
{
data
:
{
workspace
:
{
epicBoard
:
{
id
:
'
gid://gitlab/Boards::EpicBoard/1
'
,
name
:
'
Development
'
,
},
__typename
:
'
Group
'
,
},
},
};
export
const
mockLabel
=
{
id
:
'
gid://gitlab/GroupLabel/121
'
,
title
:
'
To Do
'
,
...
...
locale/gitlab.pot
View file @
703f89df
...
...
@@ -5630,6 +5630,9 @@ msgstr ""
msgid "Boards|View scope"
msgstr ""
msgid "Board|An error occurred while fetching the board, please try again."
msgstr ""
msgid "Board|Are you sure you want to delete this board?"
msgstr ""
...
...
spec/frontend/boards/components/board_form_spec.js
View file @
703f89df
...
...
@@ -17,15 +17,14 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
const
currentBoard
=
{
id
:
1
,
id
:
'
gid://gitlab/Board/1
'
,
name
:
'
test
'
,
labels
:
[],
milestone
_id
:
undefined
,
milestone
:
{}
,
assignee
:
{},
assignee_id
:
undefined
,
weight
:
null
,
hide
_backlog_l
ist
:
false
,
hide
_closed_l
ist
:
false
,
hide
BacklogL
ist
:
false
,
hide
ClosedL
ist
:
false
,
};
const
defaultProps
=
{
...
...
@@ -249,7 +248,7 @@ describe('BoardForm', () => {
mutation
:
updateBoardMutation
,
variables
:
{
input
:
expect
.
objectContaining
({
id
:
`gid://gitlab/Board/
${
currentBoard
.
id
}
`
,
id
:
currentBoard
.
id
,
}),
},
});
...
...
@@ -275,7 +274,7 @@ describe('BoardForm', () => {
mutation
:
updateBoardMutation
,
variables
:
{
input
:
expect
.
objectContaining
({
id
:
`gid://gitlab/Board/
${
currentBoard
.
id
}
`
,
id
:
currentBoard
.
id
,
}),
},
});
...
...
@@ -323,7 +322,7 @@ describe('BoardForm', () => {
expect
(
mutate
).
toHaveBeenCalledWith
({
mutation
:
destroyBoardMutation
,
variables
:
{
id
:
'
gid://gitlab/Board/1
'
,
id
:
currentBoard
.
id
,
},
});
...
...
spec/frontend/boards/components/boards_selector_spec.js
View file @
703f89df
import
{
GlDropdown
,
GlLoadingIcon
,
GlDropdownSectionHeader
}
from
'
@gitlab/ui
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
nextTick
}
from
'
vue
'
;
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
Vuex
from
'
vuex
'
;
import
{
TEST_HOST
}
from
'
spec/test_constants
'
;
import
BoardsSelector
from
'
~/boards/components/boards_selector.vue
'
;
import
groupBoardQuery
from
'
~/boards/graphql/group_board.query.graphql
'
;
import
projectBoardQuery
from
'
~/boards/graphql/project_board.query.graphql
'
;
import
defaultStore
from
'
~/boards/stores
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
{
mockGroupBoardResponse
,
mockProjectBoardResponse
}
from
'
../mock_data
'
;
const
throttleDuration
=
1
;
Vue
.
use
(
VueApollo
);
function
boardGenerator
(
n
)
{
return
new
Array
(
n
).
fill
().
map
((
board
,
index
)
=>
{
const
id
=
`
${
index
}
`
;
...
...
@@ -25,9 +34,27 @@ describe('BoardsSelector', () => {
let
allBoardsResponse
;
let
recentBoardsResponse
;
let
mock
;
let
fakeApollo
;
let
store
;
const
boards
=
boardGenerator
(
20
);
const
recentBoards
=
boardGenerator
(
5
);
const
createStore
=
({
isGroupBoard
=
false
,
isProjectBoard
=
false
}
=
{})
=>
{
store
=
new
Vuex
.
Store
({
...
defaultStore
,
actions
:
{
setError
:
jest
.
fn
(),
},
getters
:
{
isGroupBoard
:
()
=>
isGroupBoard
,
isProjectBoard
:
()
=>
isProjectBoard
,
},
state
:
{
boardType
:
isGroupBoard
?
'
group
'
:
'
project
'
,
},
});
};
const
fillSearchBox
=
(
filterTerm
)
=>
{
const
searchBox
=
wrapper
.
find
({
ref
:
'
searchBox
'
});
const
searchBoxInput
=
searchBox
.
find
(
'
input
'
);
...
...
@@ -40,40 +67,20 @@ describe('BoardsSelector', () => {
const
getLoadingIcon
=
()
=>
wrapper
.
find
(
GlLoadingIcon
);
const
findDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
const
$apollo
=
{
queries
:
{
boards
:
{
loading
:
false
,
},
},
};
const
projectBoardQueryHandlerSuccess
=
jest
.
fn
().
mockResolvedValue
(
mockProjectBoardResponse
);
const
groupBoardQueryHandlerSuccess
=
jest
.
fn
().
mockResolvedValue
(
mockGroupBoardResponse
);
allBoardsResponse
=
Promise
.
resolve
({
data
:
{
group
:
{
boards
:
{
edges
:
boards
.
map
((
board
)
=>
({
node
:
board
})),
},
},
},
});
recentBoardsResponse
=
Promise
.
resolve
({
data
:
recentBoards
,
});
const
createComponent
=
()
=>
{
fakeApollo
=
createMockApollo
([
[
projectBoardQuery
,
projectBoardQueryHandlerSuccess
],
[
groupBoardQuery
,
groupBoardQueryHandlerSuccess
],
]);
wrapper
=
mount
(
BoardsSelector
,
{
store
,
apolloProvider
:
fakeApollo
,
propsData
:
{
throttleDuration
,
currentBoard
:
{
id
:
1
,
name
:
'
Development
'
,
milestone_id
:
null
,
weight
:
null
,
assignee_id
:
null
,
labels
:
[],
},
boardBaseUrl
:
`
${
TEST_HOST
}
/board/base/url`
,
hasMissingBoards
:
false
,
canAdminBoard
:
true
,
...
...
@@ -81,7 +88,6 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled
:
true
,
weights
:
[],
},
mocks
:
{
$apollo
},
attachTo
:
document
.
body
,
provide
:
{
fullPath
:
''
,
...
...
@@ -94,12 +100,7 @@ describe('BoardsSelector', () => {
[
options
.
loadingKey
]:
true
,
});
});
mock
.
onGet
(
`
${
TEST_HOST
}
/recent`
).
replyOnce
(
200
,
recentBoards
);
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
...
...
@@ -107,104 +108,158 @@ describe('BoardsSelector', () => {
mock
.
restore
();
});
describe
(
'
loading
'
,
()
=>
{
// we are testing loading state, so don't resolve responses until after the tests
afterEach
(()
=>
{
return
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]).
then
(()
=>
nextTick
());
});
describe
(
'
fetching all boards
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
it
(
'
shows loading spinner
'
,
()
=>
{
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
expect
(
getDropdownItems
()).
toHaveLength
(
0
);
expect
(
getLoadingIcon
().
exists
()).
toBe
(
true
);
allBoardsResponse
=
Promise
.
resolve
({
data
:
{
group
:
{
boards
:
{
edges
:
boards
.
map
((
board
)
=>
({
node
:
board
})),
},
},
},
});
recentBoardsResponse
=
Promise
.
resolve
({
data
:
recentBoards
,
});
createStore
();
createComponent
();
mock
.
onGet
(
`
${
TEST_HOST
}
/recent`
).
replyOnce
(
200
,
recentBoards
);
});
});
describe
(
'
loaded
'
,
()
=>
{
beforeEach
(
async
()
=>
{
await
wrapper
.
setData
({
loadingBoards
:
false
,
describe
(
'
loading
'
,
()
=>
{
beforeEach
(
async
()
=>
{
// Wait for current board to be loaded
await
nextTick
();
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
});
// we are testing loading state, so don't resolve responses until after the tests
afterEach
(
async
()
=>
{
await
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]);
await
nextTick
();
});
return
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]).
then
(()
=>
nextTick
());
});
it
(
'
hides loading spinner
'
,
async
()
=>
{
await
wrapper
.
vm
.
$nextTick
();
expect
(
getLoadingIcon
().
exists
()).
toBe
(
false
);
it
(
'
shows loading spinner
'
,
()
=>
{
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
expect
(
getDropdownItems
()).
toHaveLength
(
0
);
expect
(
getLoadingIcon
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
filtering
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setData
({
boards
,
});
describe
(
'
loaded
'
,
()
=>
{
beforeEach
(
async
()
=>
{
// Wait for current board to be loaded
await
nextTick
();
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown
().
vm
.
$emit
(
'
show
'
);
return
nextTick
();
await
wrapper
.
setData
({
loadingBoards
:
false
,
loadingRecentBoards
:
false
,
});
await
Promise
.
all
([
allBoardsResponse
,
recentBoardsResponse
]);
await
nextTick
();
});
it
(
'
shows all boards without filtering
'
,
()
=>
{
expect
(
getDropdownItems
()).
toHaveLength
(
boards
.
length
+
recentBoards
.
length
);
it
(
'
hides loading spinner
'
,
async
()
=>
{
await
nextTick
();
expect
(
getLoadingIcon
().
exists
()).
toBe
(
false
);
});
it
(
'
shows only matching boards when filtering
'
,
()
=>
{
const
filterTerm
=
'
board1
'
;
const
expectedCount
=
boards
.
filter
((
board
)
=>
board
.
name
.
includes
(
filterTerm
)).
length
;
describe
(
'
filtering
'
,
()
=>
{
beforeEach
(
async
()
=>
{
wrapper
.
setData
({
boards
,
});
await
nextTick
();
});
fillSearchBox
(
filterTerm
);
it
(
'
shows all boards without filtering
'
,
()
=>
{
expect
(
getDropdownItems
()).
toHaveLength
(
boards
.
length
+
recentBoards
.
length
);
});
return
nextTick
().
then
(()
=>
{
it
(
'
shows only matching boards when filtering
'
,
async
()
=>
{
const
filterTerm
=
'
board1
'
;
const
expectedCount
=
boards
.
filter
((
board
)
=>
board
.
name
.
includes
(
filterTerm
)).
length
;
fillSearchBox
(
filterTerm
);
await
nextTick
();
expect
(
getDropdownItems
()).
toHaveLength
(
expectedCount
);
});
});
it
(
'
shows message if there are no matching boards
'
,
()
=>
{
fillSearchBox
(
'
does not exist
'
);
it
(
'
shows message if there are no matching boards
'
,
async
()
=>
{
fillSearchBox
(
'
does not exist
'
);
return
nextTick
().
then
(()
=>
{
await
nextTick
();
expect
(
getDropdownItems
()).
toHaveLength
(
0
);
expect
(
wrapper
.
text
().
includes
(
'
No matching boards found
'
)).
toBe
(
true
);
});
});
});
describe
(
'
recent boards section
'
,
()
=>
{
it
(
'
shows only when boards are greater than 10
'
,
()
=>
{
wrapper
.
setData
({
boards
,
});
describe
(
'
recent boards section
'
,
()
=>
{
it
(
'
shows only when boards are greater than 10
'
,
async
()
=>
{
wrapper
.
setData
({
boards
,
});
return
nextTick
().
then
(()
=>
{
await
nextTick
();
expect
(
getDropdownHeaders
()).
toHaveLength
(
2
);
});
});
it
(
'
does not show when boards are less than 10
'
,
()
=>
{
wrapper
.
setData
({
boards
:
boards
.
slice
(
0
,
5
),
});
it
(
'
does not show when boards are less than 10
'
,
async
()
=>
{
wrapper
.
setData
({
boards
:
boards
.
slice
(
0
,
5
),
});
return
nextTick
().
then
(()
=>
{
await
nextTick
();
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
});
});
it
(
'
does not show when recentBoards api returns empty array
'
,
()
=>
{
wrapper
.
setData
({
recentBoards
:
[],
});
it
(
'
does not show when recentBoards api returns empty array
'
,
async
()
=>
{
wrapper
.
setData
({
recentBoards
:
[],
});
return
nextTick
().
then
(()
=>
{
await
nextTick
();
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
});
});
it
(
'
does not show when search is active
'
,
()
=>
{
fillSearchBox
(
'
Random string
'
);
it
(
'
does not show when search is active
'
,
async
()
=>
{
fillSearchBox
(
'
Random string
'
);
return
nextTick
().
then
(()
=>
{
await
nextTick
();
expect
(
getDropdownHeaders
()).
toHaveLength
(
0
);
});
});
});
});
describe
(
'
fetching current board
'
,
()
=>
{
it
.
each
`
boardType | queryHandler | notCalledHandler
${
'
group
'
}
|
${
groupBoardQueryHandlerSuccess
}
|
${
projectBoardQueryHandlerSuccess
}
${
'
project
'
}
|
${
projectBoardQueryHandlerSuccess
}
|
${
groupBoardQueryHandlerSuccess
}
`
(
'
fetches $boardType board
'
,
async
({
boardType
,
queryHandler
,
notCalledHandler
})
=>
{
createStore
({
isProjectBoard
:
boardType
===
'
project
'
,
isGroupBoard
:
boardType
===
'
group
'
,
});
createComponent
();
await
nextTick
();
expect
(
queryHandler
).
toHaveBeenCalled
();
expect
(
notCalledHandler
).
not
.
toHaveBeenCalled
();
});
});
});
spec/frontend/boards/mock_data.js
View file @
703f89df
...
...
@@ -30,17 +30,27 @@ export const listObj = {
},
};
export
const
listObjDuplicate
=
{
id
:
listObj
.
id
,
position
:
1
,
title
:
'
Test
'
,
list_type
:
'
label
'
,
weight
:
3
,
label
:
{
id
:
listObj
.
label
.
id
,
title
:
'
Test
'
,
color
:
'
#ff0000
'
,
description
:
'
testing;
'
,
export
const
mockGroupBoardResponse
=
{
data
:
{
workspace
:
{
board
:
{
id
:
'
gid://gitlab/Board/1
'
,
name
:
'
Development
'
,
},
__typename
:
'
Group
'
,
},
},
};
export
const
mockProjectBoardResponse
=
{
data
:
{
workspace
:
{
board
:
{
id
:
'
gid://gitlab/Board/2
'
,
name
:
'
Development
'
,
},
__typename
:
'
Project
'
,
},
},
};
...
...
@@ -634,8 +644,8 @@ export const mockProjectLabelsResponse = {
labels
:
{
nodes
:
[
mockLabel1
,
mockLabel2
],
},
__typename
:
'
Project
'
,
},
__typename
:
'
Project
'
,
},
};
...
...
@@ -646,7 +656,7 @@ export const mockGroupLabelsResponse = {
labels
:
{
nodes
:
[
mockLabel1
,
mockLabel2
],
},
__typename
:
'
Group
'
,
},
__typename
:
'
Group
'
,
},
};
spec/frontend/graphql_shared/utils_spec.js
View file @
703f89df
import
{
isGid
,
getIdFromGraphQLId
,
getZeroBasedIdFromGraphQLId
,
convertToGraphQLId
,
convertToGraphQLIds
,
convertFromGraphQLIds
,
...
...
@@ -51,6 +52,10 @@ describe('getIdFromGraphQLId', () => {
input
:
'
gid://gitlab/Environments/
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/Environments/0
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/Environments/123
'
,
output
:
123
,
...
...
@@ -66,6 +71,55 @@ describe('getIdFromGraphQLId', () => {
});
});
describe
(
'
getZeroBasedIdFromGraphQLId
'
,
()
=>
{
[
{
input
:
''
,
output
:
null
,
},
{
input
:
null
,
output
:
null
,
},
{
input
:
2
,
output
:
2
,
},
{
input
:
'
gid://
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/Environments
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/Environments/
'
,
output
:
null
,
},
{
input
:
'
gid://gitlab/Environments/0
'
,
output
:
0
,
},
{
input
:
'
gid://gitlab/Environments/123
'
,
output
:
123
,
},
{
input
:
'
gid://gitlab/DesignManagement::Version/2
'
,
output
:
2
,
},
].
forEach
(({
input
,
output
})
=>
{
it
(
`getZeroBasedIdFromGraphQLId returns
${
output
}
when passed
${
input
}
`
,
()
=>
{
expect
(
getZeroBasedIdFromGraphQLId
(
input
)).
toBe
(
output
);
});
});
});
describe
(
'
convertToGraphQLId
'
,
()
=>
{
it
(
'
combines $type and $id into $result
'
,
()
=>
{
expect
(
convertToGraphQLId
(
mockType
,
mockId
)).
toBe
(
mockGid
);
...
...
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