diff options
author | Douwe Maan <douwe@gitlab.com> | 2017-06-08 18:12:04 +0000 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2017-06-08 18:12:04 +0000 |
commit | 4b1c49171dcdac5ba78a81bad94776644d9dbed5 (patch) | |
tree | b355dc30190a3c0bdfccd6746e8e56c23417eb99 /spec | |
parent | 7b6df851209f5845be6a2b2d3a3865510ca540aa (diff) | |
parent | b55bd9ef95734a6805b11a8a8322149e885425a6 (diff) | |
download | gitlab-ce-4b1c49171dcdac5ba78a81bad94776644d9dbed5.tar.gz |
Merge branch '25426-group-dashboard-ui' into 'master'
Resolve "Group dashboard UI"
Closes #25426
See merge request !11098
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/groups/group_members_controller_spec.rb | 7 | ||||
-rw-r--r-- | spec/features/dashboard/groups_list_spec.rb | 122 | ||||
-rw-r--r-- | spec/javascripts/groups/group_item_spec.js | 102 | ||||
-rw-r--r-- | spec/javascripts/groups/groups_spec.js | 64 | ||||
-rw-r--r-- | spec/javascripts/groups/mock_data.js | 110 | ||||
-rw-r--r-- | spec/javascripts/lib/utils/common_utils_spec.js | 8 | ||||
-rw-r--r-- | spec/javascripts/test_bundle.js | 1 |
7 files changed, 394 insertions, 20 deletions
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 60db0192dfd..ed4ad7b600e 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -124,6 +124,13 @@ describe Groups::GroupMembersController do expect(response).to redirect_to(dashboard_groups_path) expect(group.users).not_to include user end + + it 'supports json request' do + delete :leave, group_id: group, format: :json + + expect(response).to have_http_status(200) + expect(json_response['notice']).to eq "You left the \"#{group.name}\" group." + end end context 'and is an owner' do diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index b0e2953dda2..7eb254f8451 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -6,40 +6,124 @@ describe 'Dashboard Groups page', js: true, feature: true do let!(:nested_group) { create(:group, :nested) } let!(:another_group) { create(:group) } - before do + it 'shows groups user is member of' do group.add_owner(user) nested_group.add_owner(user) login_as(user) - visit dashboard_groups_path - end - it 'shows groups user is member of' do expect(page).to have_content(group.full_name) expect(page).to have_content(nested_group.full_name) expect(page).not_to have_content(another_group.full_name) end - it 'filters groups' do - fill_in 'filter_groups', with: group.name - wait_for_requests + describe 'when filtering groups' do + before do + group.add_owner(user) + nested_group.add_owner(user) - expect(page).to have_content(group.full_name) - expect(page).not_to have_content(nested_group.full_name) - expect(page).not_to have_content(another_group.full_name) + login_as(user) + + visit dashboard_groups_path + end + + it 'filters groups' do + fill_in 'filter_groups', with: group.name + wait_for_requests + + expect(page).to have_content(group.full_name) + expect(page).not_to have_content(nested_group.full_name) + expect(page).not_to have_content(another_group.full_name) + end + + it 'resets search when user cleans the input' do + fill_in 'filter_groups', with: group.name + wait_for_requests + + fill_in 'filter_groups', with: "" + wait_for_requests + + expect(page).to have_content(group.full_name) + expect(page).to have_content(nested_group.full_name) + expect(page).not_to have_content(another_group.full_name) + expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 + end end - it 'resets search when user cleans the input' do - fill_in 'filter_groups', with: group.name - wait_for_requests + describe 'group with subgroups' do + let!(:subgroup) { create(:group, :public, parent: group) } - fill_in 'filter_groups', with: "" - wait_for_requests + before do + group.add_owner(user) + subgroup.add_owner(user) - expect(page).to have_content(group.full_name) - expect(page).to have_content(nested_group.full_name) - expect(page).not_to have_content(another_group.full_name) - expect(page.all('.js-groups-list-holder .content-list li').length).to eq 2 + login_as(user) + + visit dashboard_groups_path + end + + it 'shows subgroups inside of its parent group' do + expect(page).to have_selector('.groups-list-tree-container .group-list-tree', count: 2) + expect(page).to have_selector(".groups-list-tree-container #group-#{group.id} #group-#{subgroup.id}", count: 1) + end + + it 'can toggle parent group' do + # Expanded by default + expect(page).to have_selector("#group-#{group.id} .fa-caret-down", count: 1) + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right") + + # Collapse + find("#group-#{group.id}").trigger('click') + + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-down") + expect(page).to have_selector("#group-#{group.id} .fa-caret-right", count: 1) + expect(page).not_to have_selector("#group-#{group.id} #group-#{subgroup.id}") + + # Expand + find("#group-#{group.id}").trigger('click') + + expect(page).to have_selector("#group-#{group.id} .fa-caret-down", count: 1) + expect(page).not_to have_selector("#group-#{group.id} .fa-caret-right") + expect(page).to have_selector("#group-#{group.id} #group-#{subgroup.id}") + end + end + + describe 'when using pagination' do + let(:group2) { create(:group) } + + before do + group.add_owner(user) + group2.add_owner(user) + + allow(Kaminari.config).to receive(:default_per_page).and_return(1) + + login_as(user) + visit dashboard_groups_path + end + + it 'shows pagination' do + expect(page).to have_selector('.gl-pagination') + expect(page).to have_selector('.gl-pagination .page', count: 2) + end + + it 'loads results for next page' do + # Check first page + expect(page).to have_content(group2.full_name) + expect(page).to have_selector("#group-#{group2.id}") + expect(page).not_to have_content(group.full_name) + expect(page).not_to have_selector("#group-#{group.id}") + + # Go to next page + find(".gl-pagination .page:not(.active) a").trigger('click') + + wait_for_requests + + # Check second page + expect(page).to have_content(group.full_name) + expect(page).to have_selector("#group-#{group.id}") + expect(page).not_to have_content(group2.full_name) + expect(page).not_to have_selector("#group-#{group2.id}") + end end end diff --git a/spec/javascripts/groups/group_item_spec.js b/spec/javascripts/groups/group_item_spec.js new file mode 100644 index 00000000000..25e10552d95 --- /dev/null +++ b/spec/javascripts/groups/group_item_spec.js @@ -0,0 +1,102 @@ +import Vue from 'vue'; +import groupItemComponent from '~/groups/components/group_item.vue'; +import GroupsStore from '~/groups/stores/groups_store'; +import { group1 } from './mock_data'; + +describe('Groups Component', () => { + let GroupItemComponent; + let component; + let store; + let group; + + describe('group with default data', () => { + beforeEach((done) => { + GroupItemComponent = Vue.extend(groupItemComponent); + store = new GroupsStore(); + group = store.decorateGroup(group1); + + component = new GroupItemComponent({ + propsData: { + group, + }, + }).$mount(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + component.$destroy(); + }); + + it('should render the group item correctly', () => { + expect(component.$el.classList.contains('group-row')).toBe(true); + expect(component.$el.classList.contains('.no-description')).toBe(false); + expect(component.$el.querySelector('.number-projects').textContent).toContain(group.numberProjects); + expect(component.$el.querySelector('.number-users').textContent).toContain(group.numberUsers); + expect(component.$el.querySelector('.group-visibility')).toBeDefined(); + expect(component.$el.querySelector('.avatar-container')).toBeDefined(); + expect(component.$el.querySelector('.title').textContent).toContain(group.name); + expect(component.$el.querySelector('.access-type').textContent).toContain(group.permissions.humanGroupAccess); + expect(component.$el.querySelector('.description').textContent).toContain(group.description); + expect(component.$el.querySelector('.edit-group')).toBeDefined(); + expect(component.$el.querySelector('.leave-group')).toBeDefined(); + }); + }); + + describe('group without description', () => { + beforeEach((done) => { + GroupItemComponent = Vue.extend(groupItemComponent); + store = new GroupsStore(); + group1.description = ''; + group = store.decorateGroup(group1); + + component = new GroupItemComponent({ + propsData: { + group, + }, + }).$mount(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + component.$destroy(); + }); + + it('should render group item correctly', () => { + expect(component.$el.querySelector('.description').textContent).toBe(''); + expect(component.$el.classList.contains('.no-description')).toBe(false); + }); + }); + + describe('user has not access to group', () => { + beforeEach((done) => { + GroupItemComponent = Vue.extend(groupItemComponent); + store = new GroupsStore(); + group1.permissions.human_group_access = null; + group = store.decorateGroup(group1); + + component = new GroupItemComponent({ + propsData: { + group, + }, + }).$mount(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + component.$destroy(); + }); + + it('should not display access type', () => { + expect(component.$el.querySelector('.access-type')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/groups/groups_spec.js b/spec/javascripts/groups/groups_spec.js new file mode 100644 index 00000000000..2a77f7259da --- /dev/null +++ b/spec/javascripts/groups/groups_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import groupFolderComponent from '~/groups/components/group_folder.vue'; +import groupItemComponent from '~/groups/components/group_item.vue'; +import groupsComponent from '~/groups/components/groups.vue'; +import GroupsStore from '~/groups/stores/groups_store'; +import { groupsData } from './mock_data'; + +describe('Groups Component', () => { + let GroupsComponent; + let store; + let component; + let groups; + + beforeEach((done) => { + Vue.component('group-folder', groupFolderComponent); + Vue.component('group-item', groupItemComponent); + + store = new GroupsStore(); + groups = store.setGroups(groupsData.groups); + + store.storePagination(groupsData.pagination); + + GroupsComponent = Vue.extend(groupsComponent); + + component = new GroupsComponent({ + propsData: { + groups: store.state.groups, + pageInfo: store.state.pageInfo, + }, + }).$mount(); + + Vue.nextTick(() => { + done(); + }); + }); + + afterEach(() => { + component.$destroy(); + }); + + describe('with data', () => { + it('should render a list of groups', () => { + expect(component.$el.classList.contains('groups-list-tree-container')).toBe(true); + expect(component.$el.querySelector('#group-12')).toBeDefined(); + expect(component.$el.querySelector('#group-1119')).toBeDefined(); + expect(component.$el.querySelector('#group-1120')).toBeDefined(); + }); + + it('should render group and its subgroup', () => { + const lists = component.$el.querySelectorAll('.group-list-tree'); + + expect(lists.length).toBe(3); // one parent and two subgroups + + expect(lists[0].querySelector('#group-1119').classList.contains('is-open')).toBe(true); + expect(lists[0].querySelector('#group-1119').classList.contains('has-subgroups')).toBe(true); + + expect(lists[2].querySelector('#group-1120').textContent).toContain(groups[1119].subGroups[1120].name); + }); + + it('should remove prefix of parent group', () => { + expect(component.$el.querySelector('#group-12 #group-1128 .title').textContent).toContain('level2 / level3 / level4'); + }); + }); +}); diff --git a/spec/javascripts/groups/mock_data.js b/spec/javascripts/groups/mock_data.js new file mode 100644 index 00000000000..1c0ec7a97d0 --- /dev/null +++ b/spec/javascripts/groups/mock_data.js @@ -0,0 +1,110 @@ +const group1 = { + id: '12', + name: 'level1', + path: 'level1', + description: 'foo', + visibility: 'public', + avatar_url: null, + web_url: 'http://localhost:3000/groups/level1', + full_name: 'level1', + full_path: 'level1', + parent_id: null, + created_at: '2017-05-15T19:01:23.670Z', + updated_at: '2017-05-15T19:01:23.670Z', + number_projects_with_delimiter: '1', + number_users_with_delimiter: '1', + has_subgroups: true, + permissions: { + human_group_access: 'Master', + }, +}; + +// This group has no direct parent, should be placed as subgroup of group1 +const group14 = { + id: 1128, + name: 'level4', + path: 'level4', + description: 'foo', + visibility: 'public', + avatar_url: null, + web_url: 'http://localhost:3000/groups/level1/level2/level3/level4', + full_name: 'level1 / level2 / level3 / level4', + full_path: 'level1/level2/level3/level4', + parent_id: 1127, + created_at: '2017-05-15T19:02:01.645Z', + updated_at: '2017-05-15T19:02:01.645Z', + number_projects_with_delimiter: '1', + number_users_with_delimiter: '1', + has_subgroups: true, + permissions: { + human_group_access: 'Master', + }, +}; + +const group2 = { + id: 1119, + name: 'devops', + path: 'devops', + description: 'foo', + visibility: 'public', + avatar_url: null, + web_url: 'http://localhost:3000/groups/devops', + full_name: 'devops', + full_path: 'devops', + parent_id: null, + created_at: '2017-05-11T19:35:09.635Z', + updated_at: '2017-05-11T19:35:09.635Z', + number_projects_with_delimiter: '1', + number_users_with_delimiter: '1', + has_subgroups: true, + permissions: { + human_group_access: 'Master', + }, +}; + +const group21 = { + id: 1120, + name: 'chef', + path: 'chef', + description: 'foo', + visibility: 'public', + avatar_url: null, + web_url: 'http://localhost:3000/groups/devops/chef', + full_name: 'devops / chef', + full_path: 'devops/chef', + parent_id: 1119, + created_at: '2017-05-11T19:51:04.060Z', + updated_at: '2017-05-11T19:51:04.060Z', + number_projects_with_delimiter: '1', + number_users_with_delimiter: '1', + has_subgroups: true, + permissions: { + human_group_access: 'Master', + }, +}; + +const groupsData = { + groups: [group1, group14, group2, group21], + pagination: { + Date: 'Mon, 22 May 2017 22:31:52 GMT', + 'X-Prev-Page': '1', + 'X-Content-Type-Options': 'nosniff', + 'X-Total': '31', + 'Transfer-Encoding': 'chunked', + 'X-Runtime': '0.611144', + 'X-Xss-Protection': '1; mode=block', + 'X-Request-Id': 'f5db8368-3ce5-4aa4-89d2-a125d9dead09', + 'X-Ua-Compatible': 'IE=edge', + 'X-Per-Page': '20', + Link: '<http://localhost:3000/dashboard/groups.json?page=1&per_page=20>; rel="prev", <http://localhost:3000/dashboard/groups.json?page=1&per_page=20>; rel="first", <http://localhost:3000/dashboard/groups.json?page=2&per_page=20>; rel="last"', + 'X-Next-Page': '', + Etag: 'W/"a82f846947136271cdb7d55d19ef33d2"', + 'X-Frame-Options': 'DENY', + 'Content-Type': 'application/json; charset=utf-8', + 'Cache-Control': 'max-age=0, private, must-revalidate', + 'X-Total-Pages': '2', + 'X-Page': '2', + }, +}; + +export { groupsData, group1 }; diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index e3938a77680..52cf217c25f 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -150,6 +150,14 @@ import '~/lib/utils/common_utils'; const value = gl.utils.getParameterByName('fakeParameter'); expect(value).toBe(null); }); + + it('should return valid paramentes if URL is provided', () => { + let value = gl.utils.getParameterByName('foo', 'http://cocteau.twins/?foo=bar'); + expect(value).toBe('bar'); + + value = gl.utils.getParameterByName('manan', 'http://cocteau.twins/?foo=bar&manan=canchu'); + expect(value).toBe('canchu'); + }); }); describe('gl.utils.normalizedHeaders', () => { diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 13827a26571..2c34402576b 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -51,7 +51,6 @@ if (process.env.BABEL_ENV === 'coverage') { './environments/environments_bundle.js', './filtered_search/filtered_search_bundle.js', './graphs/graphs_bundle.js', - './issuable/issuable_bundle.js', './issuable/time_tracking/time_tracking_bundle.js', './main.js', './merge_conflicts/merge_conflicts_bundle.js', |