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
4eeb6b0d
Commit
4eeb6b0d
authored
Dec 13, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/gitlab@master
parent
8cc5f279
Changes
31
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
957 additions
and
664 deletions
+957
-664
app/controllers/concerns/membership_actions.rb
app/controllers/concerns/membership_actions.rb
+11
-0
app/controllers/groups/group_members_controller.rb
app/controllers/groups/group_members_controller.rb
+7
-2
app/controllers/projects/project_members_controller.rb
app/controllers/projects/project_members_controller.rb
+1
-1
app/finders/group_members_finder.rb
app/finders/group_members_finder.rb
+5
-5
app/finders/members_finder.rb
app/finders/members_finder.rb
+13
-7
app/views/shared/members/_sort_dropdown.html.haml
app/views/shared/members/_sort_dropdown.html.haml
+10
-0
changelogs/unreleased/xanf-improve-members-filtering.yml
changelogs/unreleased/xanf-improve-members-filtering.yml
+5
-0
doc/ci/img/collapsible_log.png
doc/ci/img/collapsible_log.png
+0
-0
doc/ci/img/collapsible_log_v12_6.png
doc/ci/img/collapsible_log_v12_6.png
+0
-0
doc/ci/pipelines.md
doc/ci/pipelines.md
+1
-1
doc/development/background_migrations.md
doc/development/background_migrations.md
+6
-1
doc/development/database_review.md
doc/development/database_review.md
+6
-3
doc/user/group/subgroups/img/group_members_filter_v12_6.png
doc/user/group/subgroups/img/group_members_filter_v12_6.png
+0
-0
doc/user/group/subgroups/index.md
doc/user/group/subgroups/index.md
+12
-2
doc/user/project/members/img/project_members.png
doc/user/project/members/img/project_members.png
+0
-0
doc/user/project/members/img/project_members_filter_v12_6.png
...user/project/members/img/project_members_filter_v12_6.png
+0
-0
doc/user/project/members/index.md
doc/user/project/members/index.md
+25
-0
lib/api/helpers/members_helpers.rb
lib/api/helpers/members_helpers.rb
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-0
qa/qa.rb
qa/qa.rb
+1
-0
qa/qa/runtime/search.rb
qa/qa/runtime/search.rb
+36
-0
spec/controllers/groups/group_members_controller_spec.rb
spec/controllers/groups/group_members_controller_spec.rb
+30
-1
spec/controllers/projects/project_members_controller_spec.rb
spec/controllers/projects/project_members_controller_spec.rb
+30
-0
spec/features/groups/members/filter_members_spec.rb
spec/features/groups/members/filter_members_spec.rb
+42
-17
spec/features/projects/members/group_members_spec.rb
spec/features/projects/members/group_members_spec.rb
+35
-0
spec/finders/group_members_finder_spec.rb
spec/finders/group_members_finder_spec.rb
+31
-7
spec/finders/members_finder_spec.rb
spec/finders/members_finder_spec.rb
+46
-4
spec/frontend/boards/boards_store_spec.js
spec/frontend/boards/boards_store_spec.js
+490
-1
spec/frontend/boards/mock_data.js
spec/frontend/boards/mock_data.js
+91
-0
spec/javascripts/boards/boards_store_spec.js
spec/javascripts/boards/boards_store_spec.js
+0
-509
spec/javascripts/boards/mock_data.js
spec/javascripts/boards/mock_data.js
+13
-102
No files found.
app/controllers/concerns/membership_actions.rb
View file @
4eeb6b0d
...
...
@@ -144,4 +144,15 @@ module MembershipActions
end
end
end
def
requested_relations
case
params
[
:with_inherited_permissions
].
presence
when
'exclude'
[
:direct
]
when
'only'
[
:inherited
]
else
[
:inherited
,
:direct
]
end
end
end
app/controllers/groups/group_members_controller.rb
View file @
4eeb6b0d
...
...
@@ -24,8 +24,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@sort
=
params
[
:sort
].
presence
||
sort_value_name
@project
=
@group
.
projects
.
find
(
params
[
:project_id
])
if
params
[
:project_id
]
@members
=
GroupMembersFinder
.
new
(
@group
).
execute
@members
=
find_members
if
can_manage_members
@invited_members
=
@members
.
invite
...
...
@@ -52,6 +51,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
# MembershipActions concern
alias_method
:membershipable
,
:group
private
def
find_members
GroupMembersFinder
.
new
(
@group
).
execute
(
include_relations:
requested_relations
)
end
end
Groups
::
GroupMembersController
.
prepend_if_ee
(
'EE::Groups::GroupMembersController'
)
app/controllers/projects/project_members_controller.rb
View file @
4eeb6b0d
...
...
@@ -17,7 +17,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@skip_groups
<<
@project
.
namespace_id
unless
@project
.
personal?
@skip_groups
+=
@project
.
group
.
ancestors
.
pluck
(
:id
)
if
@project
.
group
@project_members
=
MembersFinder
.
new
(
@project
,
current_user
).
execute
@project_members
=
MembersFinder
.
new
(
@project
,
current_user
).
execute
(
include_relations:
requested_relations
)
if
params
[
:search
].
present?
@project_members
=
@project_members
.
joins
(
:user
).
merge
(
User
.
search
(
params
[
:search
]))
...
...
app/finders/group_members_finder.rb
View file @
4eeb6b0d
...
...
@@ -6,15 +6,15 @@ class GroupMembersFinder < UnionFinder
end
# rubocop: disable CodeReuse/ActiveRecord
def
execute
(
include_
descendants:
false
)
def
execute
(
include_
relations:
[
:inherited
,
:direct
]
)
group_members
=
@group
.
members
relations
=
[]
return
group_members
unless
@group
.
parent
||
include_descendants
return
group_members
if
include_relations
==
[
:direct
]
relations
<<
group_members
relations
<<
group_members
if
include_relations
.
include?
(
:direct
)
if
@group
.
parent
if
include_relations
.
include?
(
:inherited
)
&&
@group
.
parent
parents_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
ancestors
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
...
...
@@ -22,7 +22,7 @@ class GroupMembersFinder < UnionFinder
relations
<<
parents_members
end
if
include_
descendants
if
include_
relations
.
include?
(
:descendants
)
descendant_members
=
GroupMember
.
non_request
.
where
(
source_id:
@group
.
descendants
.
select
(
:id
))
.
where
.
not
(
user_id:
@group
.
users
.
select
(
:id
))
...
...
app/finders/members_finder.rb
View file @
4eeb6b0d
...
...
@@ -9,14 +9,18 @@ class MembersFinder
@group
=
project
.
group
end
def
execute
(
include_
descendants:
false
,
include_invited_groups_members:
false
)
def
execute
(
include_
relations:
[
:inherited
,
:direct
]
)
project_members
=
project
.
project_members
project_members
=
project_members
.
non_invite
unless
can?
(
current_user
,
:admin_project
,
project
)
union_members
=
group_union_members
(
include_descendants
,
include_invited_groups_members
)
return
project_members
if
include_relations
==
[
:direct
]
union_members
=
group_union_members
(
include_relations
)
union_members
<<
project_members
if
include_relations
.
include?
(
:direct
)
if
union_members
.
any?
distinct_union_of_members
(
union_members
<<
project_members
)
distinct_union_of_members
(
union_members
)
else
project_members
end
...
...
@@ -28,15 +32,17 @@ class MembersFinder
private
def
group_union_members
(
include_
descendants
,
include_invited_groups_member
s
)
def
group_union_members
(
include_
relation
s
)
[].
tap
do
|
members
|
members
<<
direct_group_members
(
include_
descendants
)
if
group
members
<<
project_invited_groups_members
if
include_
invited_groups_members
members
<<
direct_group_members
(
include_
relations
.
include?
(
:descendants
)
)
if
group
members
<<
project_invited_groups_members
if
include_
relations
.
include?
(
:invited_groups_members
)
end
end
def
direct_group_members
(
include_descendants
)
GroupMembersFinder
.
new
(
group
).
execute
(
include_descendants:
include_descendants
).
non_invite
# rubocop: disable CodeReuse/Finder
requested_relations
=
[
:inherited
,
:direct
]
requested_relations
<<
:descendants
if
include_descendants
GroupMembersFinder
.
new
(
group
).
execute
(
include_relations:
requested_relations
).
non_invite
# rubocop: disable CodeReuse/Finder
end
def
project_invited_groups_members
...
...
app/views/shared/members/_sort_dropdown.html.haml
View file @
4eeb6b0d
...
...
@@ -8,3 +8,13 @@
%li
=
link_to
filter_group_project_member_path
(
sort:
value
),
class:
(
"is-active"
if
@sort
==
value
)
do
=
title
%li
.divider
%li
{
data:
{
'qa-selector'
:
'filter-members-with-inherited-permissions'
}
}
=
link_to
filter_group_project_member_path
(
with_inherited_permissions:
nil
),
class:
(
"is-active"
unless
params
[
:with_inherited_permissions
].
present?
)
do
=
_
(
"Show all members"
)
%li
{
data:
{
'qa-selector'
:
'filter-members-with-inherited-permissions'
}
}
=
link_to
filter_group_project_member_path
(
with_inherited_permissions:
'exclude'
),
class:
(
"is-active"
if
params
[
:with_inherited_permissions
]
==
'exclude'
)
do
=
_
(
"Show only direct members"
)
%li
{
data:
{
'qa-selector'
:
'filter-members-with-inherited-permissions'
}
}
=
link_to
filter_group_project_member_path
(
with_inherited_permissions:
'only'
),
class:
(
"is-active"
if
params
[
:with_inherited_permissions
]
==
'only'
)
do
=
_
(
"Show only inherited members"
)
changelogs/unreleased/xanf-improve-members-filtering.yml
0 → 100644
View file @
4eeb6b0d
---
title
:
Added filtering of inherited members for subgroups
merge_request
:
18842
author
:
type
:
added
doc/ci/img/collapsible_log.png
deleted
100644 → 0
View file @
8cc5f279
272 KB
doc/ci/img/collapsible_log_v12_6.png
0 → 100644
View file @
4eeb6b0d
94.3 KB
doc/ci/pipelines.md
View file @
4eeb6b0d
...
...
@@ -157,7 +157,7 @@ In the following example:
-
Two sections are collapsed and can be expanded.
-
Three sections are expanded and can be collapsed.
![
Collapsible sections
](
img/collapsible_log.png
)
![
Collapsible sections
](
img/collapsible_log
_v12_6
.png
)
## Configuring pipelines
...
...
doc/development/background_migrations.md
View file @
4eeb6b0d
...
...
@@ -16,10 +16,15 @@ migrations automatically reschedule themselves for a later point in time.
> the migrations.
In the vast majority of cases you will want to use a regular Rails migration
instead. Background migrations should
_only_
be used when migrating _data_ in
instead. Background migrations should be used when migrating _data_ in
tables that have so many rows this process would take hours when performed in a
regular Rails migration.
Background migrations _may_ also be used when executing numerous single-row queries
for every item on a large dataset. Typically, for single-record patterns, runtime is
largely dependent on the size of the dataset, hence it should be split accordingly
and put into background migrations.
Background migrations _may not_ be used to perform schema migrations, they
should only be used for data migrations.
...
...
doc/development/database_review.md
View file @
4eeb6b0d
...
...
@@ -103,10 +103,13 @@ and details for a database reviewer:
need to fit comfortably within
`15s`
- preferably much less than that - on GitLab.com.
-
For column removals, make sure the column has been
[
ignored in a previous release
](
what_requires_downtime.md#dropping-columns
)
-
Check
[
background migrations
](
background_migrations.md
)
:
-
Establish a time estimate for execution on GitLab.com.
-
They should only be used when migrating data in larger tables
.
-
If a single
`update`
is below than
`1s`
the query can be placed
-
Establish a time estimate for execution on GitLab.com.
For historical purposes,
it's highly recommended to include this estimation on the merge request description
.
-
If a single
`update`
is below than
`1s`
the query can be placed
directly in a regular migration (inside
`db/migrate`
).
-
Background migrations are normally used, but not limited to:
-
Migrating data in larger tables.
-
Making numerous SQL queries per record in a dataset.
-
Review queries (for example, make sure batch sizes are fine)
-
Because execution time can be longer than for a regular migration,
it's suggested to treat background migrations as post migrations:
...
...
doc/user/group/subgroups/img/group_members_filter_v12_6.png
0 → 100644
View file @
4eeb6b0d
19.4 KB
doc/user/group/subgroups/index.md
View file @
4eeb6b0d
...
...
@@ -4,7 +4,7 @@ type: reference, howto, concepts
# Subgroups
>[Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/2772) in GitLab 9.0.
>
[Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/2772) in GitLab 9.0.
Subgroups, also known as nested groups or hierarchical groups, allow you to have up to 20
levels of groups.
...
...
@@ -142,6 +142,16 @@ From the image above, we can deduce the following things:
-
Administrator is the Owner and member of
**all**
subgroups and for that reason,
as with User3, there is no indication of an ancestor group.
[
From
](
https://gitlab.com/gitlab-org/gitlab/issues/21727
)
GitLab 12.6, you can filter
this list using dropdown on the right side:
![
Group members filter
](
img/group_members_filter_v12_6.png
)
-
**Show only direct members**
displays only Administrator and User3, since these are
the only users that belong to group
`four`
, which is the one we're inspecting.
-
**Show only inherited members**
displays User0, User1 and User2, no matter which group
above the hierarchy is the source of inherited permissions.
### Overriding the ancestor group membership
NOTE:
**Note:**
...
...
@@ -186,7 +196,7 @@ Here's a list of what you can't do with subgroups:
[
ce-2772
]:
https://gitlab.com/gitlab-org/gitlab-foss/issues/2772
[
permissions
]:
../../permissions.md#group-members-permissions
[
reserved
]:
../../reserved_names.md
[
reserved
]:
../../reserved_names.md
[
issue
]:
https://gitlab.com/gitlab-org/gitlab-foss/issues/30472#note_27747600
<!-- ## Troubleshooting
...
...
doc/user/project/members/img/project_members.png
0 → 100644
View file @
4eeb6b0d
106 KB
doc/user/project/members/img/project_members_filter_v12_6.png
0 → 100644
View file @
4eeb6b0d
19.4 KB
doc/user/project/members/index.md
View file @
4eeb6b0d
...
...
@@ -10,6 +10,31 @@ or import a new user to your project.
To view, edit, add, and remove project's members, go to your
project's
**Settings > Members**
.
## Inherited membership
When your project belongs to the group, group members inherit the membership and permission
level for the project from the group.
![
Project members page
](
img/project_members.png
)
From the image above, we can deduce the following things:
-
There are 3 members that have access to the project.
-
User0 is a Reporter and has inherited their permissions from group
`demo`
which contains current project.
-
For User1 there is no indication of a group, therefore they belong directly
to the project we're inspecting.
-
Administrator is the Owner and member of
**all**
groups and for that reason,
there is an indication of an ancestor group and inherited Owner permissions.
[
From
](
https://gitlab.com/gitlab-org/gitlab/issues/21727
)
, you can filter this list
using dropdown on the right side:
![
Project members filter
](
img/project_members_filter_v12_6.png
)
-
**Show only direct members**
displays only User1.
-
**Show only inherited members**
displays User0 and Administrator.
## Add a user
Right next to
**People**
, start typing the name or username of the user you
...
...
lib/api/helpers/members_helpers.rb
View file @
4eeb6b0d
...
...
@@ -29,7 +29,7 @@ module API
end
def
find_all_members_for_project
(
project
)
MembersFinder
.
new
(
project
,
current_user
).
execute
(
include_
invited_groups_members:
true
)
MembersFinder
.
new
(
project
,
current_user
).
execute
(
include_
relations:
[
:inherited
,
:direct
,
:invited_groups_members
]
)
end
def
find_all_members_for_group
(
group
)
...
...
locale/gitlab.pot
View file @
4eeb6b0d
...
...
@@ -16189,6 +16189,9 @@ msgstr ""
msgid "Show all activity"
msgstr ""
msgid "Show all members"
msgstr ""
msgid "Show archived projects"
msgstr ""
...
...
@@ -16216,6 +16219,12 @@ msgstr ""
msgid "Show latest version"
msgstr ""
msgid "Show only direct members"
msgstr ""
msgid "Show only inherited members"
msgstr ""
msgid "Show parent pages"
msgstr ""
...
...
qa/qa.rb
View file @
4eeb6b0d
...
...
@@ -36,6 +36,7 @@ module QA
autoload
:GPG
,
'qa/runtime/gpg'
autoload
:MailHog
,
'qa/runtime/mail_hog'
autoload
:IPAddress
,
'qa/runtime/ip_address'
autoload
:Search
,
'qa/runtime/search'
module
API
autoload
:Client
,
'qa/runtime/api/client'
...
...
qa/qa/runtime/search.rb
0 → 100644
View file @
4eeb6b0d
# frozen_string_literal: true
module
QA
module
Runtime
module
Search
extend
self
extend
Support
::
Api
ElasticSearchServerError
=
Class
.
new
(
RuntimeError
)
def
elasticsearch_responding?
QA
::
Runtime
::
Logger
.
debug
(
"Attempting to search via Elasticsearch..."
)
QA
::
Support
::
Retrier
.
retry_on_exception
do
# We don't care about the results of the search, we just need
# any search that uses Elasticsearch, not the native search
# The Elasticsearch-only scopes are blobs, wiki_blobs, and commits.
request
=
Runtime
::
API
::
Request
.
new
(
api_client
,
"/search?scope=blobs&search=foo"
)
response
=
get
(
request
.
url
)
unless
response
.
code
==
singleton_class
::
HTTP_STATUS_OK
raise
ElasticSearchServerError
,
"Search attempt failed. Request returned (
#{
response
.
code
}
): `
#{
response
}
`."
end
true
end
end
private
def
api_client
@api_client
||=
Runtime
::
API
::
Client
.
new
(
:gitlab
)
end
end
end
end
spec/controllers/groups/group_members_controller_spec.rb
View file @
4eeb6b0d
...
...
@@ -5,7 +5,7 @@ require 'spec_helper'
describe
Groups
::
GroupMembersController
do
include
ExternalAuthorizationServiceHelpers
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
,
:public
)
}
let
(
:membership
)
{
create
(
:group_member
,
group:
group
)
}
...
...
@@ -49,6 +49,35 @@ describe Groups::GroupMembersController do
expect
(
assigns
(
:invited_members
).
count
).
to
eq
(
1
)
end
end
context
'when user has owner access to subgroup'
do
let
(
:nested_group
)
{
create
(
:group
,
parent:
group
)
}
let
(
:nested_group_user
)
{
create
(
:user
)
}
before
do
group
.
add_owner
(
user
)
nested_group
.
add_owner
(
nested_group_user
)
sign_in
(
user
)
end
it
'lists inherited group members by default'
do
get
:index
,
params:
{
group_id:
nested_group
}
expect
(
assigns
(
:members
).
map
(
&
:user_id
)).
to
contain_exactly
(
user
.
id
,
nested_group_user
.
id
)
end
it
'lists direct group members only'
do
get
:index
,
params:
{
group_id:
nested_group
,
with_inherited_permissions:
'exclude'
}
expect
(
assigns
(
:members
).
map
(
&
:user_id
)).
to
contain_exactly
(
nested_group_user
.
id
)
end
it
'lists inherited group members only'
do
get
:index
,
params:
{
group_id:
nested_group
,
with_inherited_permissions:
'only'
}
expect
(
assigns
(
:members
).
map
(
&
:user_id
)).
to
contain_exactly
(
user
.
id
)
end
end
end
describe
'POST create'
do
...
...
spec/controllers/projects/project_members_controller_spec.rb
View file @
4eeb6b0d
...
...
@@ -4,6 +4,7 @@ require('spec_helper')
describe
Projects
::
ProjectMembersController
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
,
:public
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
describe
'GET index'
do
...
...
@@ -12,6 +13,35 @@ describe Projects::ProjectMembersController do
expect
(
response
).
to
have_gitlab_http_status
(
200
)
end
context
'when project belongs to group'
do
let
(
:user_in_group
)
{
create
(
:user
)
}
let
(
:project_in_group
)
{
create
(
:project
,
:public
,
group:
group
)
}
before
do
group
.
add_owner
(
user_in_group
)
project_in_group
.
add_maintainer
(
user
)
sign_in
(
user
)
end
it
'lists inherited project members by default'
do
get
:index
,
params:
{
namespace_id:
project_in_group
.
namespace
,
project_id:
project_in_group
}
expect
(
assigns
(
:project_members
).
map
(
&
:user_id
)).
to
contain_exactly
(
user
.
id
,
user_in_group
.
id
)
end
it
'lists direct project members only'
do
get
:index
,
params:
{
namespace_id:
project_in_group
.
namespace
,
project_id:
project_in_group
,
with_inherited_permissions:
'exclude'
}
expect
(
assigns
(
:project_members
).
map
(
&
:user_id
)).
to
contain_exactly
(
user
.
id
)
end
it
'lists inherited project members only'
do
get
:index
,
params:
{
namespace_id:
project_in_group
.
namespace
,
project_id:
project_in_group
,
with_inherited_permissions:
'only'
}
expect
(
assigns
(
:project_members
).
map
(
&
:user_id
)).
to
contain_exactly
(
user_in_group
.
id
)
end
end
end
describe
'POST create'
do
...
...
spec/features/groups/members/filter_members_spec.rb
View file @
4eeb6b0d
...
...
@@ -3,42 +3,71 @@
require
'spec_helper'
describe
'Groups > Members > Filter members'
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user_with_2fa
)
{
create
(
:user
,
:two_factor_via_otp
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:nested_group_user
)
{
create
(
:user
)
}
let
(
:user_with_2fa
)
{
create
(
:user
,
:two_factor_via_otp
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:nested_group
)
{
create
(
:group
,
parent:
group
)
}
before
do
group
.
add_owner
(
user
)
group
.
add_maintainer
(
user_with_2fa
)
nested_group
.
add_maintainer
(
nested_group_user
)
sign_in
(
user
)
end
it
'shows all members'
do
visit_members_list
visit_members_list
(
group
)
expect
(
first_member
).
to
include
(
user
.
name
)
expect
(
second_member
).
to
include
(
user_with_2fa
.
name
)
expect
(
member
(
0
)
).
to
include
(
user
.
name
)
expect
(
member
(
1
)
).
to
include
(
user_with_2fa
.
name
)
expect
(
page
).
to
have_css
(
'.member-filter-2fa-dropdown .dropdown-toggle-text'
,
text:
'Everyone'
)
end
it
'shows only 2FA members'
do
visit_members_list
(
two_factor:
'enabled'
)
visit_members_list
(
group
,
two_factor:
'enabled'
)
expect
(
first_member
).
to
include
(
user_with_2fa
.
name
)
expect
(
member
(
0
)
).
to
include
(
user_with_2fa
.
name
)
expect
(
members_list
.
size
).
to
eq
(
1
)
expect
(
page
).
to
have_css
(
'.member-filter-2fa-dropdown .dropdown-toggle-text'
,
text:
'Enabled'
)
end
it
'shows only non 2FA members'
do
visit_members_list
(
two_factor:
'disabled'
)
visit_members_list
(
group
,
two_factor:
'disabled'
)
expect
(
first_member
).
to
include
(
user
.
name
)
expect
(
member
(
0
)
).
to
include
(
user
.
name
)
expect
(
members_list
.
size
).
to
eq
(
1
)
expect
(
page
).
to
have_css
(
'.member-filter-2fa-dropdown .dropdown-toggle-text'
,
text:
'Disabled'
)
end
def
visit_members_list
(
options
=
{})
it
'shows inherited members by default'
do
visit_members_list
(
nested_group
)
expect
(
member
(
0
)).
to
include
(
user
.
name
)
expect
(
member
(
1
)).
to
include
(
user_with_2fa
.
name
)
expect
(
member
(
2
)).
to
include
(
nested_group_user
.
name
)
expect
(
members_list
.
size
).
to
eq
(
3
)
expect
(
page
).
to
have_css
(
'[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active'
,
text:
'Show all members'
)
end
it
'shows only group members'
do
visit_members_list
(
nested_group
,
with_inherited_permissions:
'exclude'
)
expect
(
member
(
0
)).
to
include
(
nested_group_user
.
name
)
expect
(
members_list
.
size
).
to
eq
(
1
)
expect
(
page
).
to
have_css
(
'[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active'
,
text:
'Show only direct members'
)
end
it
'shows only inherited members'
do
visit_members_list
(
nested_group
,
with_inherited_permissions:
'only'
)
expect
(
member
(
0
)).
to
include
(
user
.
name
)
expect
(
member
(
1
)).
to
include
(
user_with_2fa
.
name
)
expect
(
members_list
.
size
).
to
eq
(
2
)
expect
(
page
).
to
have_css
(
'[data-qa-selector="filter-members-with-inherited-permissions"] a.is-active'
,
text:
'Show only inherited members'
)
end
def
visit_members_list
(
group
,
options
=
{})
visit
group_group_members_path
(
group
.
to_param
,
options
)
end
...
...
@@ -46,11 +75,7 @@ describe 'Groups > Members > Filter members' do
page
.
all
(
'ul.content-list > li'
)
end
def
first_member
members_list
.
first
.
text
end
def
second_member
members_list
.
last
.
text
def
member
(
number
)
members_list
[
number
].
text
end
end
spec/features/projects/members/group_members_spec.rb
View file @
4eeb6b0d
...
...
@@ -31,6 +31,41 @@ describe 'Projects members' do
end
end
context
'with a group'
do
it
'shows group and project members by default'
do
visit
project_project_members_path
(
project
)
page
.
within
first
(
'.content-list'
)
do
expect
(
page
).
to
have_content
(
developer
.
name
)
expect
(
page
).
to
have_content
(
user
.
name
)
expect
(
page
).
to
have_content
(
group
.
name
)
end
end
it
'shows project members only if requested'
do
visit
project_project_members_path
(
project
,
with_inherited_permissions:
'exclude'
)
page
.
within
first
(
'.content-list'
)
do
expect
(
page
).
to
have_content
(
developer
.
name
)
expect
(
page
).
not_to
have_content
(
user
.
name
)
expect
(
page
).
not_to
have_content
(
group
.
name
)
end
end
it
'shows group members only if requested'
do
visit
project_project_members_path
(
project
,
with_inherited_permissions:
'only'
)
page
.
within
first
(
'.content-list'
)
do
expect
(
page
).
not_to
have_content
(
developer
.
name
)
expect
(
page
).
to
have_content
(
user
.
name
)
expect
(
page
).
to
have_content
(
group
.
name
)
end
end
end
context
'with a group and a project invitee'
do
before
do
group_invitee
...
...
spec/finders/group_members_finder_spec.rb
View file @
4eeb6b0d
...
...
@@ -3,12 +3,13 @@
require
'spec_helper'
describe
GroupMembersFinder
,
'#execute'
do
let
(
:group
)
{
create
(
:group
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:nested_group
)
{
create
(
:group
,
parent:
group
)
}
let
(
:user1
)
{
create
(
:user
)
}
let
(
:user2
)
{
create
(
:user
)
}
let
(
:user3
)
{
create
(
:user
)
}
let
(
:user4
)
{
create
(
:user
)
}
let
(
:deeper_nested_group
)
{
create
(
:group
,
parent:
nested_group
)
}
let
(
:user1
)
{
create
(
:user
)
}
let
(
:user2
)
{
create
(
:user
)
}
let
(
:user3
)
{
create
(
:user
)
}
let
(
:user4
)
{
create
(
:user
)
}
it
'returns members for top-level group'
do
member1
=
group
.
add_maintainer
(
user1
)
...
...
@@ -20,7 +21,7 @@ describe GroupMembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member3
,
member2
,
member1
])
end
it
'returns members
for nested group
'
do
it
'returns members
& inherited members for nested group by default
'
do
group
.
add_developer
(
user2
)
nested_group
.
request_access
(
user4
)
member1
=
group
.
add_maintainer
(
user1
)
...
...
@@ -32,6 +33,29 @@ describe GroupMembersFinder, '#execute' do
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member3
,
member4
])
end
it
'does not return inherited members for nested group if requested'
do
group
.
add_maintainer
(
user1
)
group
.
add_developer
(
user2
)
member2
=
nested_group
.
add_maintainer
(
user2
)
member3
=
nested_group
.
add_maintainer
(
user3
)
result
=
described_class
.
new
(
nested_group
).
execute
(
include_relations:
[
:direct
])
expect
(
result
.
to_a
).
to
match_array
([
member2
,
member3
])
end
it
'returns only inherited members for nested group if requested'
do
group
.
add_developer
(
user2
)
nested_group
.
request_access
(
user4
)
member1
=
group
.
add_maintainer
(
user1
)
nested_group
.
add_maintainer
(
user2
)
nested_group
.
add_maintainer
(
user3
)
result
=
described_class
.
new
(
nested_group
).
execute
(
include_relations:
[
:inherited
])
expect
(
result
.
to_a
).
to
match_array
([
member1
])
end
it
'returns members for descendant groups if requested'
do
member1
=
group
.
add_maintainer
(
user2
)
member2
=
group
.
add_maintainer
(
user1
)
...
...
@@ -39,7 +63,7 @@ describe GroupMembersFinder, '#execute' do
member3
=
nested_group
.
add_maintainer
(
user3
)
member4
=
nested_group
.
add_maintainer
(
user4
)
result
=
described_class
.
new
(
group
).
execute
(
include_
descendants:
true
)
result
=
described_class
.
new
(
group
).
execute
(
include_
relations:
[
:direct
,
:descendants
]
)
expect
(
result
.
to_a
).
to
match_array
([
member1
,
member2
,
member3
,
member4
])
end
...
...
spec/finders/members_finder_spec.rb
View file @
4eeb6b0d
...
...
@@ -22,22 +22,64 @@ describe MembersFinder, '#execute' do
expect
(
result
).
to
contain_exactly
(
member1
,
member2
,
member3
)
end
it
'includes only non-invite members if user do not have amdin permissions on project'
do
create
(
:project_member
,
:invited
,
project:
project
,
invite_email:
create
(
:user
).
email
)
member1
=
project
.
add_maintainer
(
user1
)
member2
=
project
.
add_developer
(
user2
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_relations:
[
:direct
])
expect
(
result
).
to
contain_exactly
(
member1
,
member2
)
end
it
'includes invited members if user have admin permissions on project'
do
member_invite
=
create
(
:project_member
,
:invited
,
project:
project
,
invite_email:
create
(
:user
).
email
)
member1
=
project
.
add_maintainer
(
user1
)
member2
=
project
.
add_maintainer
(
user2
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_relations:
[
:direct
])
expect
(
result
).
to
contain_exactly
(
member1
,
member2
,
member_invite
)
end
it
'includes nested group members if asked'
,
:nested_groups
do
nested_group
.
request_access
(
user1
)
member1
=
group
.
add_maintainer
(
user2
)
member2
=
nested_group
.
add_maintainer
(
user3
)
member3
=
project
.
add_maintainer
(
user4
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_
descendants:
true
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_
relations:
[
:direct
,
:descendants
]
)
expect
(
result
).
to
contain_exactly
(
member1
,
member2
,
member3
)
end
it
'returns only members of project if asked'
do
nested_group
.
request_access
(
user1
)
group
.
add_maintainer
(
user2
)
nested_group
.
add_maintainer
(
user3
)
member4
=
project
.
add_maintainer
(
user4
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_relations:
[
:direct
])
expect
(
result
).
to
contain_exactly
(
member4
)
end
it
'returns only inherited members of project if asked'
do
nested_group
.
request_access
(
user1
)
member2
=
group
.
add_maintainer
(
user2
)
member3
=
nested_group
.
add_maintainer
(
user3
)
project
.
add_maintainer
(
user4
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_relations:
[
:inherited
])
expect
(
result
).
to
contain_exactly
(
member2
,
member3
)
end
it
'returns the members.access_level when the user is invited'
,
:nested_groups
do
member_invite
=
create
(
:project_member
,
:invited
,
project:
project
,
invite_email:
create
(
:user
).
email
)
member1
=
group
.
add_maintainer
(
user2
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_
descendants:
true
)
result
=
described_class
.
new
(
project
,
user2
).
execute
(
include_
relations:
[
:direct
,
:descendants
]
)
expect
(
result
).
to
contain_exactly
(
member1
,
member_invite
)
expect
(
result
.
last
.
access_level
).
to
eq
(
member_invite
.
access_level
)
...
...
@@ -48,14 +90,14 @@ describe MembersFinder, '#execute' do
group
.
add_developer
(
user1
)
nested_group
.
add_reporter
(
user1
)
result
=
described_class
.
new
(
project
,
user1
).
execute
(
include_
descendants:
true
)
result
=
described_class
.
new
(
project
,
user1
).
execute
(
include_
relations:
[
:direct
,
:descendants
]
)
expect
(
result
).
to
contain_exactly
(
member1
)
expect
(
result
.
first
.
access_level
).
to
eq
(
Gitlab
::
Access
::
DEVELOPER
)
end
context
'when include_invited_groups_members == true'
do
subject
{
described_class
.
new
(
project
,
user2
).
execute
(
include_
invited_groups_members:
true
)
}
subject
{
described_class
.
new
(
project
,
user2
).
execute
(
include_
relations:
[
:inherited
,
:direct
,
:invited_groups_members
]
)
}
set
(
:linked_group
)
{
create
(
:group
,
:public
)
}
set
(
:nested_linked_group
)
{
create
(
:group
,
parent:
linked_group
)
}
...
...
spec/frontend/boards/boards_store_spec.js
View file @
4eeb6b0d
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
listObj
,
listObjDuplicate
}
from
'
./mock_data
'
;
import
ListIssue
from
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
jest
.
mock
(
'
js-cookie
'
);
const
createTestIssue
=
()
=>
({
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
});
describe
(
'
boardsStore
'
,
()
=>
{
const
dummyResponse
=
"
without type checking this doesn't matter
"
;
...
...
@@ -23,6 +39,19 @@ describe('boardsStore', () => {
});
});
afterEach
(()
=>
{
axiosMock
.
restore
();
jest
.
clearAllMocks
();
});
const
setupDefaultResponses
=
()
=>
{
axiosMock
.
onGet
(
`
${
endpoints
.
listsEndpoint
}
/
${
listObj
.
id
}
/issues?id=
${
listObj
.
id
}
&page=1`
)
.
reply
(
200
,
{
issues
:
[
createTestIssue
()]
});
axiosMock
.
onPost
(
endpoints
.
listsEndpoint
).
reply
(
200
,
listObj
);
axiosMock
.
onPut
();
};
describe
(
'
all
'
,
()
=>
{
it
(
'
makes a request to fetch lists
'
,
()
=>
{
axiosMock
.
onGet
(
endpoints
.
listsEndpoint
).
replyOnce
(
200
,
dummyResponse
);
...
...
@@ -546,4 +575,464 @@ describe('boardsStore', () => {
return
expect
(
boardsStore
.
deleteBoard
({
id
})).
rejects
.
toThrow
();
});
});
describe
(
'
when created
'
,
()
=>
{
beforeEach
(()
=>
{
setupDefaultResponses
();
jest
.
spyOn
(
boardsStore
,
'
moveIssue
'
).
mockReturnValue
(
Promise
.
resolve
());
jest
.
spyOn
(
boardsStore
,
'
moveMultipleIssues
'
).
mockReturnValue
(
Promise
.
resolve
());
boardsStore
.
create
();
});
it
(
'
starts with a blank state
'
,
()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
describe
(
'
addList
'
,
()
=>
{
it
(
'
sorts by position
'
,
()
=>
{
boardsStore
.
addList
({
position
:
2
});
boardsStore
.
addList
({
position
:
1
});
expect
(
boardsStore
.
state
.
lists
.
map
(({
position
})
=>
position
)).
toEqual
([
1
,
2
]);
});
});
describe
(
'
toggleFilter
'
,
()
=>
{
const
dummyFilter
=
'
x=42
'
;
let
updateTokensSpy
;
beforeEach
(()
=>
{
updateTokensSpy
=
jest
.
fn
();
eventHub
.
$once
(
'
updateTokens
'
,
updateTokensSpy
);
// prevent using window.history
jest
.
spyOn
(
boardsStore
,
'
updateFiltersUrl
'
).
mockReturnValue
();
});
it
(
'
adds the filter if it is not present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
'
something
'
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
`something&
${
dummyFilter
}
`
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
it
(
'
removes the filter if it is present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
`something&
${
dummyFilter
}
`
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
'
something
'
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
});
describe
(
'
lists
'
,
()
=>
{
it
(
'
creates new list without persisting to DB
'
,
()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
});
it
(
'
finds list by ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
finds list by type
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
type
'
,
'
label
'
);
expect
(
list
).
toBeDefined
();
});
it
(
'
finds list by label ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findListByLabelId
(
listObj
.
label
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
gets issue when new list added
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
list
.
issues
.
length
).
toBe
(
1
);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
1
);
});
});
it
(
'
persists new list
'
,
()
=>
{
boardsStore
.
new
({
title
:
'
Test
'
,
list_type
:
'
label
'
,
label
:
{
id
:
1
,
title
:
'
Testing
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
},
});
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
return
axios
.
waitForAll
().
then
(()
=>
{
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
).
toEqual
(
expect
.
objectContaining
({
id
:
listObj
.
id
,
position
:
0
,
}),
);
});
});
it
(
'
check for blank state adding
'
,
()
=>
{
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
true
);
});
it
(
'
check for blank state not adding
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
false
);
});
it
(
'
check for blank state adding when closed list exist
'
,
()
=>
{
boardsStore
.
addList
({
list_type
:
'
closed
'
,
});
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
true
);
});
it
(
'
adds the blank state
'
,
()
=>
{
boardsStore
.
addBlankState
();
const
list
=
boardsStore
.
findList
(
'
type
'
,
'
blank
'
,
'
blank
'
);
expect
(
list
).
toBeDefined
();
});
it
(
'
removes list from state
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
boardsStore
.
removeList
(
listObj
.
id
,
'
label
'
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
it
(
'
moves the position of lists
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
boardsStore
.
moveList
(
listOne
,
[
listObjDuplicate
.
id
,
listObj
.
id
]);
expect
(
listOne
.
position
).
toBe
(
1
);
});
it
(
'
moves an issue from one list to another
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
1
));
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
it
(
'
moves an issue from backlog to a list
'
,
()
=>
{
const
backlog
=
boardsStore
.
addList
({
...
listObj
,
list_type
:
'
backlog
'
,
});
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
backlog
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
backlog
,
listTwo
,
backlog
.
findIssue
(
1
));
expect
(
backlog
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
it
(
'
moves issue to top of another list
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
0
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
null
,
1
);
});
});
it
(
'
moves issue to bottom of another list
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
1
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
1
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
1
,
null
);
});
});
it
(
'
moves issue in list
'
,
()
=>
{
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
});
const
list
=
boardsStore
.
addList
(
listObj
);
return
axios
.
waitForAll
().
then
(()
=>
{
list
.
addIssue
(
issue
);
expect
(
list
.
issues
.
length
).
toBe
(
2
);
boardsStore
.
moveIssueInList
(
list
,
issue
,
0
,
1
,
[
1
,
2
]);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
null
,
null
,
1
,
null
);
});
});
});
describe
(
'
setListDetail
'
,
()
=>
{
it
(
'
sets the list detail
'
,
()
=>
{
boardsStore
.
detail
.
list
=
'
not a list
'
;
const
dummyValue
=
'
new list
'
;
boardsStore
.
setListDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
list
).
toEqual
(
dummyValue
);
});
});
describe
(
'
clearDetailIssue
'
,
()
=>
{
it
(
'
resets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
something
'
;
boardsStore
.
clearDetailIssue
();
expect
(
boardsStore
.
detail
.
issue
).
toEqual
({});
});
});
describe
(
'
setIssueDetail
'
,
()
=>
{
it
(
'
sets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
some details
'
;
const
dummyValue
=
'
new details
'
;
boardsStore
.
setIssueDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
issue
).
toEqual
(
dummyValue
);
});
});
describe
(
'
startMoving
'
,
()
=>
{
it
(
'
stores list and issue
'
,
()
=>
{
const
dummyIssue
=
'
some issue
'
;
const
dummyList
=
'
some list
'
;
boardsStore
.
startMoving
(
dummyList
,
dummyIssue
);
expect
(
boardsStore
.
moving
.
issue
).
toEqual
(
dummyIssue
);
expect
(
boardsStore
.
moving
.
list
).
toEqual
(
dummyList
);
});
});
describe
(
'
setTimeTrackingLimitToHours
'
,
()
=>
{
it
(
'
sets the timeTracking.LimitToHours option
'
,
()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
false
;
boardsStore
.
setTimeTrackingLimitToHours
(
'
true
'
);
expect
(
boardsStore
.
timeTracking
.
limitToHours
).
toEqual
(
true
);
});
});
describe
(
'
setCurrentBoard
'
,
()
=>
{
const
dummyBoard
=
'
hoverboard
'
;
it
(
'
sets the current board
'
,
()
=>
{
const
{
state
}
=
boardsStore
;
state
.
currentBoard
=
null
;
boardsStore
.
setCurrentBoard
(
dummyBoard
);
expect
(
state
.
currentBoard
).
toEqual
(
dummyBoard
);
});
});
describe
(
'
toggleMultiSelect
'
,
()
=>
{
let
basicIssueObj
;
beforeAll
(()
=>
{
basicIssueObj
=
{
id
:
987654
};
});
afterEach
(()
=>
{
boardsStore
.
clearMultiSelect
();
});
it
(
'
adds issue when not present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
const
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
});
it
(
'
removes issue when issue is present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
let
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(({
id
})
=>
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
false
);
});
});
describe
(
'
clearMultiSelect
'
,
()
=>
{
it
(
'
clears all the multi selected issues
'
,
()
=>
{
const
issue1
=
{
id
:
12345
};
const
issue2
=
{
id
:
12346
};
boardsStore
.
toggleMultiSelect
(
issue1
);
boardsStore
.
toggleMultiSelect
(
issue2
);
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
2
);
boardsStore
.
clearMultiSelect
();
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
0
);
});
});
describe
(
'
moveMultipleIssuesToList
'
,
()
=>
{
it
(
'
move issues on the new index
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
return
axios
.
waitForAll
().
then
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveMultipleIssuesToList
({
listFrom
:
listOne
,
listTo
:
listTwo
,
issues
:
listOne
.
issues
,
newIndex
:
0
,
});
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
});
});
});
describe
(
'
moveMultipleIssuesInList
'
,
()
=>
{
it
(
'
moves multiple issues in list
'
,
()
=>
{
const
issueObj
=
{
title
:
'
Issue #1
'
,
id
:
12345
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
};
const
issue1
=
new
ListIssue
(
issueObj
);
const
issue2
=
new
ListIssue
({
...
issueObj
,
title
:
'
Issue #2
'
,
id
:
12346
,
});
const
list
=
boardsStore
.
addList
(
listObj
);
return
axios
.
waitForAll
().
then
(()
=>
{
list
.
addIssue
(
issue1
);
list
.
addIssue
(
issue2
);
expect
(
list
.
issues
.
length
).
toBe
(
3
);
expect
(
list
.
issues
[
0
].
id
).
not
.
toBe
(
issue2
.
id
);
boardsStore
.
moveMultipleIssuesInList
({
list
,
issues
:
[
issue1
,
issue2
],
oldIndicies
:
[
0
],
newIndex
:
1
,
idArray
:
[
1
,
12345
,
12346
],
});
expect
(
list
.
issues
[
0
].
id
).
toBe
(
issue1
.
id
);
expect
(
boardsStore
.
moveMultipleIssues
).
toHaveBeenCalledWith
({
ids
:
[
issue1
.
id
,
issue2
.
id
],
fromListId
:
null
,
toListId
:
null
,
moveBeforeId
:
1
,
moveAfterId
:
null
,
});
});
});
});
});
});
spec/frontend/boards/mock_data.js
0 → 100644
View file @
4eeb6b0d
export
const
boardObj
=
{
id
:
1
,
name
:
'
test
'
,
milestone_id
:
null
,
};
export
const
listObj
=
{
id
:
300
,
position
:
0
,
title
:
'
Test
'
,
list_type
:
'
label
'
,
weight
:
3
,
label
:
{
id
:
5000
,
title
:
'
Test
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
textColor
:
'
white
'
,
},
};
export
const
listObjDuplicate
=
{
id
:
listObj
.
id
,
position
:
1
,
title
:
'
Test
'
,
list_type
:
'
label
'
,
weight
:
3
,
label
:
{
id
:
listObj
.
label
.
id
,
title
:
'
Test
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
},
};
export
const
mockAssigneesList
=
[
{
id
:
2
,
name
:
'
Terrell Graham
'
,
username
:
'
monserrate.gleichner
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/598fd02741ac58b88854a99d16704309?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/monserrate.gleichner
'
,
path
:
'
/monserrate.gleichner
'
,
},
{
id
:
12
,
name
:
'
Susy Johnson
'
,
username
:
'
tana_harvey
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e021a7b0f3e4ae53b5068d487e68c031?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/tana_harvey
'
,
path
:
'
/tana_harvey
'
,
},
{
id
:
20
,
name
:
'
Conchita Eichmann
'
,
username
:
'
juliana_gulgowski
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/c43c506cb6fd7b37017d3b54b94aa937?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/juliana_gulgowski
'
,
path
:
'
/juliana_gulgowski
'
,
},
{
id
:
6
,
name
:
'
Bryce Turcotte
'
,
username
:
'
melynda
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/cc2518f2c6f19f8fac49e1a5ee092a9b?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/melynda
'
,
path
:
'
/melynda
'
,
},
{
id
:
1
,
name
:
'
Administrator
'
,
username
:
'
root
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/root
'
,
path
:
'
/root
'
,
},
];
export
const
mockMilestone
=
{
id
:
1
,
state
:
'
active
'
,
title
:
'
Milestone title
'
,
description
:
'
Harum corporis aut consequatur quae dolorem error sequi quia.
'
,
start_date
:
'
2018-01-01
'
,
due_date
:
'
2019-12-31
'
,
};
spec/javascripts/boards/boards_store_spec.js
deleted
100644 → 0
View file @
8cc5f279
/* global ListIssue */
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
Cookies
from
'
js-cookie
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
'
~/boards/models/label
'
;
import
'
~/boards/models/assignee
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
listObj
,
listObjDuplicate
,
boardsMockInterceptor
}
from
'
./mock_data
'
;
import
waitForPromises
from
'
../../frontend/helpers/wait_for_promises
'
;
describe
(
'
Store
'
,
()
=>
{
let
mock
;
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onAny
().
reply
(
boardsMockInterceptor
);
boardsStore
.
create
();
spyOn
(
boardsStore
,
'
moveIssue
'
).
and
.
callFake
(
()
=>
new
Promise
(
resolve
=>
{
resolve
();
}),
);
spyOn
(
boardsStore
,
'
moveMultipleIssues
'
).
and
.
callFake
(
()
=>
new
Promise
(
resolve
=>
{
resolve
();
}),
);
Cookies
.
set
(
'
issue_board_welcome_hidden
'
,
'
false
'
,
{
expires
:
365
*
10
,
path
:
''
,
});
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
starts with a blank state
'
,
()
=>
{
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
describe
(
'
addList
'
,
()
=>
{
it
(
'
sorts by position
'
,
()
=>
{
boardsStore
.
addList
({
position
:
2
});
boardsStore
.
addList
({
position
:
1
});
expect
(
boardsStore
.
state
.
lists
[
0
].
position
).
toBe
(
1
);
});
});
describe
(
'
toggleFilter
'
,
()
=>
{
const
dummyFilter
=
'
x=42
'
;
let
updateTokensSpy
;
beforeEach
(()
=>
{
updateTokensSpy
=
jasmine
.
createSpy
(
'
updateTokens
'
);
eventHub
.
$once
(
'
updateTokens
'
,
updateTokensSpy
);
// prevent using window.history
spyOn
(
boardsStore
,
'
updateFiltersUrl
'
).
and
.
callFake
(()
=>
{});
});
it
(
'
adds the filter if it is not present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
'
something
'
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
`something&
${
dummyFilter
}
`
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
it
(
'
removes the filter if it is present
'
,
()
=>
{
boardsStore
.
filter
.
path
=
`something&
${
dummyFilter
}
`
;
boardsStore
.
toggleFilter
(
dummyFilter
);
expect
(
boardsStore
.
filter
.
path
).
toEqual
(
'
something
'
);
expect
(
updateTokensSpy
).
toHaveBeenCalled
();
expect
(
boardsStore
.
updateFiltersUrl
).
toHaveBeenCalled
();
});
});
describe
(
'
lists
'
,
()
=>
{
it
(
'
creates new list without persisting to DB
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
});
it
(
'
finds list by ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
finds list by type
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
type
'
,
'
label
'
);
expect
(
list
).
toBeDefined
();
});
it
(
'
finds list by label ID
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findListByLabelId
(
listObj
.
label
.
id
);
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
});
it
(
'
gets issue when new list added
'
,
done
=>
{
boardsStore
.
addList
(
listObj
);
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
setTimeout
(()
=>
{
expect
(
list
.
issues
.
length
).
toBe
(
1
);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
1
);
done
();
},
0
);
});
it
(
'
persists new list
'
,
done
=>
{
boardsStore
.
new
({
title
:
'
Test
'
,
list_type
:
'
label
'
,
label
:
{
id
:
1
,
title
:
'
Testing
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
},
});
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
setTimeout
(()
=>
{
const
list
=
boardsStore
.
findList
(
'
id
'
,
listObj
.
id
);
expect
(
list
).
toBeDefined
();
expect
(
list
.
id
).
toBe
(
listObj
.
id
);
expect
(
list
.
position
).
toBe
(
0
);
done
();
},
0
);
});
it
(
'
check for blank state adding
'
,
()
=>
{
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
true
);
});
it
(
'
check for blank state not adding
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
false
);
});
it
(
'
check for blank state adding when closed list exist
'
,
()
=>
{
boardsStore
.
addList
({
list_type
:
'
closed
'
,
});
expect
(
boardsStore
.
shouldAddBlankState
()).
toBe
(
true
);
});
it
(
'
adds the blank state
'
,
()
=>
{
boardsStore
.
addBlankState
();
const
list
=
boardsStore
.
findList
(
'
type
'
,
'
blank
'
,
'
blank
'
);
expect
(
list
).
toBeDefined
();
});
it
(
'
removes list from state
'
,
()
=>
{
boardsStore
.
addList
(
listObj
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
1
);
boardsStore
.
removeList
(
listObj
.
id
,
'
label
'
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
0
);
});
it
(
'
moves the position of lists
'
,
()
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
boardsStore
.
moveList
(
listOne
,
[
listObjDuplicate
.
id
,
listObj
.
id
]);
expect
(
listOne
.
position
).
toBe
(
1
);
});
it
(
'
moves an issue from one list to another
'
,
done
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
setTimeout
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
1
));
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
done
();
},
0
);
});
it
(
'
moves an issue from backlog to a list
'
,
done
=>
{
const
backlog
=
boardsStore
.
addList
({
...
listObj
,
list_type
:
'
backlog
'
,
});
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
setTimeout
(()
=>
{
expect
(
backlog
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
backlog
,
listTwo
,
backlog
.
findIssue
(
1
));
expect
(
backlog
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
done
();
},
0
);
});
it
(
'
moves issue to top of another list
'
,
done
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
setTimeout
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
0
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
null
,
1
);
done
();
},
0
);
});
it
(
'
moves issue to bottom of another list
'
,
done
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
setTimeout
(()
=>
{
listOne
.
issues
[
0
].
id
=
2
;
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveIssueToList
(
listOne
,
listTwo
,
listOne
.
findIssue
(
2
),
1
);
expect
(
listOne
.
issues
.
length
).
toBe
(
0
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
2
);
expect
(
listTwo
.
issues
[
1
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
listOne
.
id
,
listTwo
.
id
,
1
,
null
);
done
();
},
0
);
});
it
(
'
moves issue in list
'
,
done
=>
{
const
issue
=
new
ListIssue
({
title
:
'
Testing
'
,
id
:
2
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
});
const
list
=
boardsStore
.
addList
(
listObj
);
setTimeout
(()
=>
{
list
.
addIssue
(
issue
);
expect
(
list
.
issues
.
length
).
toBe
(
2
);
boardsStore
.
moveIssueInList
(
list
,
issue
,
0
,
1
,
[
1
,
2
]);
expect
(
list
.
issues
[
0
].
id
).
toBe
(
2
);
expect
(
boardsStore
.
moveIssue
).
toHaveBeenCalledWith
(
2
,
null
,
null
,
1
,
null
);
done
();
});
});
});
describe
(
'
setListDetail
'
,
()
=>
{
it
(
'
sets the list detail
'
,
()
=>
{
boardsStore
.
detail
.
list
=
'
not a list
'
;
const
dummyValue
=
'
new list
'
;
boardsStore
.
setListDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
list
).
toEqual
(
dummyValue
);
});
});
describe
(
'
clearDetailIssue
'
,
()
=>
{
it
(
'
resets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
something
'
;
boardsStore
.
clearDetailIssue
();
expect
(
boardsStore
.
detail
.
issue
).
toEqual
({});
});
});
describe
(
'
setIssueDetail
'
,
()
=>
{
it
(
'
sets issue details
'
,
()
=>
{
boardsStore
.
detail
.
issue
=
'
some details
'
;
const
dummyValue
=
'
new details
'
;
boardsStore
.
setIssueDetail
(
dummyValue
);
expect
(
boardsStore
.
detail
.
issue
).
toEqual
(
dummyValue
);
});
});
describe
(
'
startMoving
'
,
()
=>
{
it
(
'
stores list and issue
'
,
()
=>
{
const
dummyIssue
=
'
some issue
'
;
const
dummyList
=
'
some list
'
;
boardsStore
.
startMoving
(
dummyList
,
dummyIssue
);
expect
(
boardsStore
.
moving
.
issue
).
toEqual
(
dummyIssue
);
expect
(
boardsStore
.
moving
.
list
).
toEqual
(
dummyList
);
});
});
describe
(
'
setTimeTrackingLimitToHours
'
,
()
=>
{
it
(
'
sets the timeTracking.LimitToHours option
'
,
()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
false
;
boardsStore
.
setTimeTrackingLimitToHours
(
'
true
'
);
expect
(
boardsStore
.
timeTracking
.
limitToHours
).
toEqual
(
true
);
});
});
describe
(
'
setCurrentBoard
'
,
()
=>
{
const
dummyBoard
=
'
hoverboard
'
;
it
(
'
sets the current board
'
,
()
=>
{
const
{
state
}
=
boardsStore
;
state
.
currentBoard
=
null
;
boardsStore
.
setCurrentBoard
(
dummyBoard
);
expect
(
state
.
currentBoard
).
toEqual
(
dummyBoard
);
});
});
describe
(
'
toggleMultiSelect
'
,
()
=>
{
let
basicIssueObj
;
beforeAll
(()
=>
{
basicIssueObj
=
{
id
:
987654
};
});
afterEach
(()
=>
{
boardsStore
.
clearMultiSelect
();
});
it
(
'
adds issue when not present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
const
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(
x
=>
x
.
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
});
it
(
'
removes issue when issue is present
'
,
()
=>
{
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
let
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(
x
=>
x
.
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
true
);
boardsStore
.
toggleMultiSelect
(
basicIssueObj
);
selectedIds
=
boardsStore
.
multiSelect
.
list
.
map
(
x
=>
x
.
id
);
expect
(
selectedIds
.
includes
(
basicIssueObj
.
id
)).
toEqual
(
false
);
});
});
describe
(
'
clearMultiSelect
'
,
()
=>
{
it
(
'
clears all the multi selected issues
'
,
()
=>
{
const
issue1
=
{
id
:
12345
};
const
issue2
=
{
id
:
12346
};
boardsStore
.
toggleMultiSelect
(
issue1
);
boardsStore
.
toggleMultiSelect
(
issue2
);
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
2
);
boardsStore
.
clearMultiSelect
();
expect
(
boardsStore
.
multiSelect
.
list
.
length
).
toEqual
(
0
);
});
});
describe
(
'
moveMultipleIssuesToList
'
,
()
=>
{
it
(
'
move issues on the new index
'
,
done
=>
{
const
listOne
=
boardsStore
.
addList
(
listObj
);
const
listTwo
=
boardsStore
.
addList
(
listObjDuplicate
);
expect
(
boardsStore
.
state
.
lists
.
length
).
toBe
(
2
);
setTimeout
(()
=>
{
expect
(
listOne
.
issues
.
length
).
toBe
(
1
);
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
boardsStore
.
moveMultipleIssuesToList
({
listFrom
:
listOne
,
listTo
:
listTwo
,
issues
:
listOne
.
issues
,
newIndex
:
0
,
});
expect
(
listTwo
.
issues
.
length
).
toBe
(
1
);
done
();
},
0
);
});
});
describe
(
'
moveMultipleIssuesInList
'
,
()
=>
{
it
(
'
moves multiple issues in list
'
,
done
=>
{
const
issueObj
=
{
title
:
'
Issue #1
'
,
id
:
12345
,
iid
:
2
,
confidential
:
false
,
labels
:
[],
assignees
:
[],
};
const
issue1
=
new
ListIssue
(
issueObj
);
const
issue2
=
new
ListIssue
({
...
issueObj
,
title
:
'
Issue #2
'
,
id
:
12346
,
});
const
list
=
boardsStore
.
addList
(
listObj
);
waitForPromises
()
.
then
(()
=>
{
list
.
addIssue
(
issue1
);
list
.
addIssue
(
issue2
);
expect
(
list
.
issues
.
length
).
toBe
(
3
);
expect
(
list
.
issues
[
0
].
id
).
not
.
toBe
(
issue2
.
id
);
boardsStore
.
moveMultipleIssuesInList
({
list
,
issues
:
[
issue1
,
issue2
],
oldIndicies
:
[
0
],
newIndex
:
1
,
idArray
:
[
1
,
12345
,
12346
],
});
expect
(
list
.
issues
[
0
].
id
).
toBe
(
issue1
.
id
);
expect
(
boardsStore
.
moveMultipleIssues
).
toHaveBeenCalledWith
({
ids
:
[
issue1
.
id
,
issue2
.
id
],
fromListId
:
null
,
toListId
:
null
,
moveBeforeId
:
1
,
moveAfterId
:
null
,
});
done
();
})
.
catch
(
done
.
fail
);
});
});
});
spec/javascripts/boards/mock_data.js
View file @
4eeb6b0d
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
import
{
listObj
}
from
'
../../frontend/boards/mock_data
'
;
export
const
setMockEndpoints
=
(
opts
=
{})
=>
{
const
boardsEndpoint
=
opts
.
boardsEndpoint
||
'
/test/issue-boards/-/boards.json
'
;
const
listsEndpoint
=
opts
.
listsEndpoint
||
'
/test/-/boards/1/lists
'
;
const
bulkUpdatePath
=
opts
.
bulkUpdatePath
||
''
;
const
boardId
=
opts
.
boardId
||
'
1
'
;
boardsStore
.
setEndpoints
({
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
,
});
};
export
const
boardObj
=
{
id
:
1
,
name
:
'
test
'
,
milestone_id
:
null
,
};
export
const
listObj
=
{
id
:
300
,
position
:
0
,
title
:
'
Test
'
,
list_type
:
'
label
'
,
weight
:
3
,
label
:
{
id
:
5000
,
title
:
'
Test
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
textColor
:
'
white
'
,
},
};
export
const
listObjDuplicate
=
{
id
:
listObj
.
id
,
position
:
1
,
title
:
'
Test
'
,
list_type
:
'
label
'
,
weight
:
3
,
label
:
{
id
:
listObj
.
label
.
id
,
title
:
'
Test
'
,
color
:
'
red
'
,
description
:
'
testing;
'
,
},
};
export
*
from
'
../../frontend/boards/mock_data
'
;
export
const
BoardsMockData
=
{
GET
:
{
...
...
@@ -86,59 +40,16 @@ export const boardsMockInterceptor = config => {
return
[
200
,
body
];
};
export
const
mockAssigneesList
=
[
{
id
:
2
,
name
:
'
Terrell Graham
'
,
username
:
'
monserrate.gleichner
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/598fd02741ac58b88854a99d16704309?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/monserrate.gleichner
'
,
path
:
'
/monserrate.gleichner
'
,
},
{
id
:
12
,
name
:
'
Susy Johnson
'
,
username
:
'
tana_harvey
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e021a7b0f3e4ae53b5068d487e68c031?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/tana_harvey
'
,
path
:
'
/tana_harvey
'
,
},
{
id
:
20
,
name
:
'
Conchita Eichmann
'
,
username
:
'
juliana_gulgowski
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/c43c506cb6fd7b37017d3b54b94aa937?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/juliana_gulgowski
'
,
path
:
'
/juliana_gulgowski
'
,
},
{
id
:
6
,
name
:
'
Bryce Turcotte
'
,
username
:
'
melynda
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/cc2518f2c6f19f8fac49e1a5ee092a9b?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/melynda
'
,
path
:
'
/melynda
'
,
},
{
id
:
1
,
name
:
'
Administrator
'
,
username
:
'
root
'
,
state
:
'
active
'
,
avatar_url
:
'
https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon
'
,
web_url
:
'
http://127.0.0.1:3001/root
'
,
path
:
'
/root
'
,
},
];
export
const
setMockEndpoints
=
(
opts
=
{})
=>
{
const
boardsEndpoint
=
opts
.
boardsEndpoint
||
'
/test/issue-boards/-/boards.json
'
;
const
listsEndpoint
=
opts
.
listsEndpoint
||
'
/test/-/boards/1/lists
'
;
const
bulkUpdatePath
=
opts
.
bulkUpdatePath
||
''
;
const
boardId
=
opts
.
boardId
||
'
1
'
;
export
const
mockMilestone
=
{
id
:
1
,
state
:
'
active
'
,
title
:
'
Milestone title
'
,
description
:
'
Harum corporis aut consequatur quae dolorem error sequi quia.
'
,
start_date
:
'
2018-01-01
'
,
due_date
:
'
2019-12-31
'
,
boardsStore
.
setEndpoints
({
boardsEndpoint
,
listsEndpoint
,
bulkUpdatePath
,
boardId
,
});
};
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