diff options
Diffstat (limited to 'spec/frontend/admin')
7 files changed, 286 insertions, 50 deletions
diff --git a/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js new file mode 100644 index 00000000000..7c20bbe21c8 --- /dev/null +++ b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js @@ -0,0 +1,134 @@ +import { GlTable, GlBadge, GlEmptyState, GlLink } from '@gitlab/ui'; +import { GlSingleStat } from '@gitlab/ui/dist/charts'; +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import DevopsScore from '~/analytics/devops_report/components/devops_score.vue'; +import { + devopsScoreMetricsData, + devopsReportDocsPath, + noDataImagePath, + devopsScoreTableHeaders, +} from '../mock_data'; + +describe('DevopsScore', () => { + let wrapper; + + const createComponent = ({ devopsScoreMetrics = devopsScoreMetricsData } = {}) => { + wrapper = extendedWrapper( + mount(DevopsScore, { + provide: { + devopsScoreMetrics, + devopsReportDocsPath, + noDataImagePath, + }, + }), + ); + }; + + const findTable = () => wrapper.findComponent(GlTable); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findCol = (testId) => findTable().find(`[data-testid="${testId}"]`); + const findUsageCol = () => findCol('usageCol'); + const findDevopsScoreApp = () => wrapper.findByTestId('devops-score-app'); + + describe('with no data', () => { + beforeEach(() => { + createComponent({ devopsScoreMetrics: {} }); + }); + + describe('empty state', () => { + it('displays the empty state', () => { + expect(findEmptyState().exists()).toBe(true); + }); + + it('displays the correct message', () => { + expect(findEmptyState().text()).toBe( + 'Data is still calculating... It may be several days before you see feature usage data. See example DevOps Score page in our documentation.', + ); + }); + + it('contains a link to the feature documentation', () => { + expect(wrapper.findComponent(GlLink).exists()).toBe(true); + }); + }); + + it('does not display the devops score app', () => { + expect(findDevopsScoreApp().exists()).toBe(false); + }); + }); + + describe('with data', () => { + beforeEach(() => { + createComponent(); + }); + + it('does not display the empty state', () => { + expect(findEmptyState().exists()).toBe(false); + }); + + it('displays the devops score app', () => { + expect(findDevopsScoreApp().exists()).toBe(true); + }); + + describe('devops score app', () => { + it('displays the title note', () => { + expect(wrapper.findByTestId('devops-score-note-text').text()).toBe( + 'DevOps score metrics are based on usage over the last 30 days. Last updated: 2020-06-29 08:16.', + ); + }); + + it('displays the single stat section', () => { + const component = wrapper.findComponent(GlSingleStat); + + expect(component.exists()).toBe(true); + expect(component.props('value')).toBe(devopsScoreMetricsData.averageScore.value); + }); + + describe('devops score table', () => { + it('displays the table', () => { + expect(findTable().exists()).toBe(true); + }); + + describe('table headings', () => { + let headers; + + beforeEach(() => { + headers = findTable().findAll("[data-testid='header']"); + }); + + it('displays the correct number of headings', () => { + expect(headers).toHaveLength(devopsScoreTableHeaders.length); + }); + + describe.each(devopsScoreTableHeaders)('header fields', ({ label, index }) => { + let headerWrapper; + + beforeEach(() => { + headerWrapper = headers.at(index); + }); + + it(`displays the correct table heading text for "${label}"`, () => { + expect(headerWrapper.text()).toContain(label); + }); + }); + }); + + describe('table columns', () => { + describe('Your usage', () => { + it('displays the corrrect value', () => { + expect(findUsageCol().text()).toContain('3.2'); + }); + + it('displays the corrrect badge', () => { + const badge = findUsageCol().find(GlBadge); + + expect(badge.exists()).toBe(true); + expect(badge.props('variant')).toBe('muted'); + expect(badge.text()).toBe('Low'); + }); + }); + }); + }); + }); + }); +}); diff --git a/spec/frontend/admin/analytics/devops_score/mock_data.js b/spec/frontend/admin/analytics/devops_score/mock_data.js new file mode 100644 index 00000000000..ae0c01a2661 --- /dev/null +++ b/spec/frontend/admin/analytics/devops_score/mock_data.js @@ -0,0 +1,46 @@ +export const devopsScoreTableHeaders = [ + { + index: 0, + label: '', + }, + { + index: 1, + label: 'Your usage', + }, + { + index: 2, + label: 'Leader usage', + }, + { + index: 3, + label: 'Score', + }, +]; + +export const devopsScoreMetricsData = { + createdAt: '2020-06-29 08:16', + cards: [ + { + title: 'Issues created per active user', + usage: '3.2', + leadInstance: '10.2', + score: '0', + scoreLevel: { + label: 'Low', + variant: 'muted', + }, + }, + ], + averageScore: { + value: '10', + scoreLevel: { + label: 'High', + icon: 'check-circle', + variant: 'success', + }, + }, +}; + +export const devopsReportDocsPath = 'docs-path'; + +export const noDataImagePath = 'image-path'; diff --git a/spec/frontend/admin/users/components/actions/actions_spec.js b/spec/frontend/admin/users/components/actions/actions_spec.js index 5e232f34311..5db5b8a90a9 100644 --- a/spec/frontend/admin/users/components/actions/actions_spec.js +++ b/spec/frontend/admin/users/components/actions/actions_spec.js @@ -71,6 +71,7 @@ describe('Action components', () => { }); describe('DELETE_ACTION_COMPONENTS', () => { + const oncallSchedules = [{ name: 'schedule1' }, { name: 'schedule2' }]; it.each(DELETE_ACTIONS)('renders a dropdown item for "%s"', async (action) => { initComponent({ component: Actions[capitalizeFirstCharacter(action)], @@ -80,6 +81,7 @@ describe('Action components', () => { delete: '/delete', block: '/block', }, + oncallSchedules, }, stubs: { SharedDeleteAction }, }); @@ -92,6 +94,9 @@ describe('Action components', () => { expect(sharedAction.attributes('data-delete-user-url')).toBe('/delete'); expect(sharedAction.attributes('data-gl-modal-action')).toBe(kebabCase(action)); expect(sharedAction.attributes('data-username')).toBe('John Doe'); + expect(sharedAction.attributes('data-oncall-schedules')).toBe( + JSON.stringify(oncallSchedules), + ); expect(findDropdownItem().exists()).toBe(true); }); }); diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js index 0745d961f25..debe964e7aa 100644 --- a/spec/frontend/admin/users/components/user_actions_spec.js +++ b/spec/frontend/admin/users/components/user_actions_spec.js @@ -1,5 +1,5 @@ import { GlDropdownDivider } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import Actions from '~/admin/users/components/actions'; import AdminUserActions from '~/admin/users/components/user_actions.vue'; import { I18N_USER_ACTIONS } from '~/admin/users/constants'; @@ -14,12 +14,14 @@ describe('AdminUserActions component', () => { const user = users[0]; const userPaths = generateUserPaths(paths, user.username); - const findEditButton = () => wrapper.find('[data-testid="edit"]'); - const findActionsDropdown = () => wrapper.find('[data-testid="actions"'); - const findDropdownDivider = () => wrapper.find(GlDropdownDivider); + const findUserActions = (id) => wrapper.findByTestId(`user-actions-${id}`); + const findEditButton = (id = user.id) => findUserActions(id).find('[data-testid="edit"]'); + const findActionsDropdown = (id = user.id) => + findUserActions(id).find('[data-testid="dropdown-toggle"]'); + const findDropdownDivider = () => wrapper.findComponent(GlDropdownDivider); const initComponent = ({ actions = [] } = {}) => { - wrapper = shallowMount(AdminUserActions, { + wrapper = shallowMountExtended(AdminUserActions, { propsData: { user: { ...user, diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js index 424b0deebd3..708c9e1979e 100644 --- a/spec/frontend/admin/users/components/users_table_spec.js +++ b/spec/frontend/admin/users/components/users_table_spec.js @@ -1,16 +1,36 @@ -import { GlTable } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { GlTable, GlSkeletonLoader } from '@gitlab/ui'; +import { createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; + +import createMockApollo from 'helpers/mock_apollo_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import AdminUserActions from '~/admin/users/components/user_actions.vue'; import AdminUserAvatar from '~/admin/users/components/user_avatar.vue'; import AdminUsersTable from '~/admin/users/components/users_table.vue'; +import getUsersGroupCountsQuery from '~/admin/users/graphql/queries/get_users_group_counts.query.graphql'; +import createFlash from '~/flash'; import AdminUserDate from '~/vue_shared/components/user_date.vue'; -import { users, paths } from '../mock_data'; +import { users, paths, createGroupCountResponse } from '../mock_data'; + +jest.mock('~/flash'); + +const localVue = createLocalVue(); +localVue.use(VueApollo); describe('AdminUsersTable component', () => { let wrapper; + const user = users[0]; + const createFetchGroupCount = (data) => + jest.fn().mockResolvedValue(createGroupCountResponse(data)); + const fetchGroupCountsLoading = jest.fn().mockResolvedValue(new Promise(() => {})); + const fetchGroupCountsError = jest.fn().mockRejectedValue(new Error('Network error')); + const fetchGroupCountsResponse = createFetchGroupCount([{ id: user.id, groupCount: 5 }]); + + const findUserGroupCount = (id) => wrapper.findByTestId(`user-group-count-${id}`); + const findUserGroupCountLoader = (id) => findUserGroupCount(id).find(GlSkeletonLoader); const getCellByLabel = (trIdx, label) => { return wrapper .find(GlTable) @@ -20,8 +40,16 @@ describe('AdminUsersTable component', () => { .find(`[data-label="${label}"][role="cell"]`); }; - const initComponent = (props = {}) => { - wrapper = mount(AdminUsersTable, { + function createMockApolloProvider(resolverMock) { + const requestHandlers = [[getUsersGroupCountsQuery, resolverMock]]; + + return createMockApollo(requestHandlers); + } + + const initComponent = (props = {}, resolverMock = fetchGroupCountsResponse) => { + wrapper = mountExtended(AdminUsersTable, { + localVue, + apolloProvider: createMockApolloProvider(resolverMock), propsData: { users, paths, @@ -36,8 +64,6 @@ describe('AdminUsersTable component', () => { }); describe('when there are users', () => { - const user = users[0]; - beforeEach(() => { initComponent(); }); @@ -69,4 +95,51 @@ describe('AdminUsersTable component', () => { expect(wrapper.text()).toContain('No users found'); }); }); + + describe('group counts', () => { + describe('when fetching the data', () => { + beforeEach(() => { + initComponent({}, fetchGroupCountsLoading); + }); + + it('renders a loader for each user', () => { + expect(findUserGroupCountLoader(user.id).exists()).toBe(true); + }); + }); + + describe('when the data has been fetched', () => { + beforeEach(() => { + initComponent(); + }); + + it("renders the user's group count", () => { + expect(findUserGroupCount(user.id).text()).toBe('5'); + }); + + describe("and a user's group count is null", () => { + beforeEach(() => { + initComponent({}, createFetchGroupCount([{ id: user.id, groupCount: null }])); + }); + + it("renders the user's group count as 0", () => { + expect(findUserGroupCount(user.id).text()).toBe('0'); + }); + }); + }); + + describe('when there is an error while fetching the data', () => { + beforeEach(() => { + initComponent({}, fetchGroupCountsError); + }); + + it('creates a flash message and captures the error', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: 'Could not load user group counts. Please refresh the page to try again.', + captureError: true, + error: expect.any(Error), + }); + }); + }); + }); }); diff --git a/spec/frontend/admin/users/mock_data.js b/spec/frontend/admin/users/mock_data.js index c3918ef5173..4689ab36773 100644 --- a/spec/frontend/admin/users/mock_data.js +++ b/spec/frontend/admin/users/mock_data.js @@ -10,7 +10,7 @@ export const users = [ 'https://secure.gravatar.com/avatar/054f062d8b1a42b123f17e13a173cda8?s=80\\u0026d=identicon', badges: [ { text: 'Admin', variant: 'success' }, - { text: "It's you!", variant: null }, + { text: "It's you!", variant: 'muted' }, ], projectsCount: 0, actions: [], @@ -31,3 +31,16 @@ export const paths = { deleteWithContributions: '/admin/users/id', adminUser: '/admin/users/id', }; + +export const createGroupCountResponse = (groupCounts) => ({ + data: { + users: { + nodes: groupCounts.map(({ id, groupCount }) => ({ + id: `gid://gitlab/User/${id}`, + groupCount, + __typename: 'UserCore', + })), + __typename: 'UserCoreConnection', + }, + }, +}); diff --git a/spec/frontend/admin/users/tabs_spec.js b/spec/frontend/admin/users/tabs_spec.js deleted file mode 100644 index 39ba8618486..00000000000 --- a/spec/frontend/admin/users/tabs_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import initTabs from '~/admin/users/tabs'; -import Api from '~/api'; - -jest.mock('~/api.js'); -jest.mock('~/lib/utils/common_utils'); - -describe('tabs', () => { - beforeEach(() => { - setFixtures(` - <div> - <div class="js-users-tab-item"> - <a href="#users" data-testid='users-tab'>Users</a> - </div> - <div class="js-users-tab-item"> - <a href="#cohorts" data-testid='cohorts-tab'>Cohorts</a> - </div> - </div`); - - initTabs(); - }); - - afterEach(() => {}); - - describe('tracking', () => { - it('tracks event when cohorts tab is clicked', () => { - document.querySelector('[data-testid="cohorts-tab"]').click(); - - expect(Api.trackRedisHllUserEvent).toHaveBeenCalledWith('i_analytics_cohorts'); - }); - - it('does not track an event when users tab is clicked', () => { - document.querySelector('[data-testid="users-tab"]').click(); - - expect(Api.trackRedisHllUserEvent).not.toHaveBeenCalled(); - }); - }); -}); |