diff options
Diffstat (limited to 'spec/frontend/runner/components')
14 files changed, 423 insertions, 235 deletions
diff --git a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js index b2e8c5a3ad9..b06ab652212 100644 --- a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js +++ b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js @@ -1,3 +1,4 @@ +import { __ } from '~/locale'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue'; import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants'; @@ -61,8 +62,16 @@ describe('RunnerTypeCell', () => { expect(wrapper.text()).toContain(mockDescription); }); - it('Displays the runner ip address', () => { - expect(wrapper.text()).toContain(mockIpAddress); + it('Displays ip address', () => { + expect(wrapper.text()).toContain(`${__('IP Address')} ${mockIpAddress}`); + }); + + it('Displays no ip address', () => { + createComponent({ + ipAddress: null, + }); + + expect(wrapper.text()).not.toContain(__('IP Address')); }); it('Displays a custom slot', () => { diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js index ed1a698d36f..19344a68f79 100644 --- a/spec/frontend/runner/components/registration/registration_token_spec.js +++ b/spec/frontend/runner/components/registration/registration_token_spec.js @@ -1,5 +1,5 @@ import { GlToast } from '@gitlab/ui'; -import { createLocalVue } from '@vue/test-utils'; +import Vue from 'vue'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import RegistrationToken from '~/runner/components/registration/registration_token.vue'; import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue'; @@ -11,28 +11,17 @@ describe('RegistrationToken', () => { let wrapper; let showToast; - const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility); - - const vueWithGlToast = () => { - const localVue = createLocalVue(); - localVue.use(GlToast); - return localVue; - }; + Vue.use(GlToast); - const createComponent = ({ - props = {}, - withGlToast = true, - mountFn = shallowMountExtended, - } = {}) => { - const localVue = withGlToast ? vueWithGlToast() : undefined; + const findInputCopyToggleVisibility = () => wrapper.findComponent(InputCopyToggleVisibility); + const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => { wrapper = mountFn(RegistrationToken, { propsData: { value: mockToken, inputId: 'token-value', ...props, }, - localVue, }); showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null; @@ -69,13 +58,5 @@ describe('RegistrationToken', () => { expect(showToast).toHaveBeenCalledTimes(1); expect(showToast).toHaveBeenCalledWith('Registration token copied!'); }); - - it('does not fail when toast is not defined', () => { - createComponent({ withGlToast: false }); - findInputCopyToggleVisibility().vm.$emit('copy'); - - // This block also tests for unhandled errors - expect(showToast).toBeNull(); - }); }); }); diff --git a/spec/frontend/runner/components/runner_assigned_item_spec.js b/spec/frontend/runner/components/runner_assigned_item_spec.js index 1ff6983fbe7..cc09046c000 100644 --- a/spec/frontend/runner/components/runner_assigned_item_spec.js +++ b/spec/frontend/runner/components/runner_assigned_item_spec.js @@ -1,10 +1,12 @@ -import { GlAvatar } from '@gitlab/ui'; +import { GlAvatar, GlBadge } from '@gitlab/ui'; +import { s__ } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue'; import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; const mockHref = '/group/project'; const mockName = 'Project'; +const mockDescription = 'Project description'; const mockFullName = 'Group / Project'; const mockAvatarUrl = '/avatar.png'; @@ -12,6 +14,7 @@ describe('RunnerAssignedItem', () => { let wrapper; const findAvatar = () => wrapper.findByTestId('item-avatar'); + const findBadge = () => wrapper.findComponent(GlBadge); const createComponent = ({ props = {} } = {}) => { wrapper = shallowMountExtended(RunnerAssignedItem, { @@ -20,6 +23,7 @@ describe('RunnerAssignedItem', () => { name: mockName, fullName: mockFullName, avatarUrl: mockAvatarUrl, + description: mockDescription, ...props, }, }); @@ -51,4 +55,14 @@ describe('RunnerAssignedItem', () => { expect(groupFullName.attributes('href')).toBe(mockHref); }); + + it('Shows description', () => { + expect(wrapper.text()).toContain(mockDescription); + }); + + it('Shows owner badge', () => { + createComponent({ props: { isOwner: true } }); + + expect(findBadge().text()).toBe(s__('Runner|Owner')); + }); }); diff --git a/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js new file mode 100644 index 00000000000..0ac89e82314 --- /dev/null +++ b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js @@ -0,0 +1,101 @@ +import Vue from 'vue'; +import { GlFormCheckbox } from '@gitlab/ui'; +import VueApollo from 'vue-apollo'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import RunnerBulkDeleteCheckbox from '~/runner/components/runner_bulk_delete_checkbox.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { createLocalState } from '~/runner/graphql/list/local_state'; +import { allRunnersData } from '../mock_data'; + +Vue.use(VueApollo); + +jest.mock('~/flash'); + +describe('RunnerBulkDeleteCheckbox', () => { + let wrapper; + let mockState; + let mockCheckedRunnerIds; + + const findCheckbox = () => wrapper.findComponent(GlFormCheckbox); + + const mockRunners = allRunnersData.data.runners.nodes; + const mockIds = allRunnersData.data.runners.nodes.map(({ id }) => id); + const mockId = mockIds[0]; + const mockIdAnotherPage = 'RUNNER_IN_ANOTHER_PAGE_ID'; + + const createComponent = ({ props = {} } = {}) => { + const { cacheConfig, localMutations } = mockState; + const apolloProvider = createMockApollo(undefined, undefined, cacheConfig); + + wrapper = shallowMountExtended(RunnerBulkDeleteCheckbox, { + apolloProvider, + provide: { + localMutations, + }, + propsData: { + runners: mockRunners, + ...props, + }, + }); + }; + + beforeEach(() => { + mockState = createLocalState(); + + jest + .spyOn(mockState.cacheConfig.typePolicies.Query.fields, 'checkedRunnerIds') + .mockImplementation(() => mockCheckedRunnerIds); + + jest.spyOn(mockState.localMutations, 'setRunnersChecked'); + }); + + describe.each` + case | is | checkedRunnerIds | disabled | checked | indeterminate + ${'no runners'} | ${'unchecked'} | ${[]} | ${undefined} | ${undefined} | ${undefined} + ${'no runners in this page'} | ${'unchecked'} | ${[mockIdAnotherPage]} | ${undefined} | ${undefined} | ${undefined} + ${'all runners'} | ${'checked'} | ${mockIds} | ${undefined} | ${'true'} | ${undefined} + ${'some runners'} | ${'indeterminate'} | ${[mockId]} | ${undefined} | ${undefined} | ${'true'} + ${'all plus other runners'} | ${'checked'} | ${[...mockIds, mockIdAnotherPage]} | ${undefined} | ${'true'} | ${undefined} + `('When $case are checked', ({ is, checkedRunnerIds, disabled, checked, indeterminate }) => { + beforeEach(async () => { + mockCheckedRunnerIds = checkedRunnerIds; + + createComponent(); + }); + + it(`is ${is}`, () => { + expect(findCheckbox().attributes('disabled')).toBe(disabled); + expect(findCheckbox().attributes('checked')).toBe(checked); + expect(findCheckbox().attributes('indeterminate')).toBe(indeterminate); + }); + }); + + describe('When user selects', () => { + beforeEach(() => { + mockCheckedRunnerIds = mockIds; + createComponent(); + }); + + it.each([[true], [false]])('sets checked to %s', (checked) => { + findCheckbox().vm.$emit('change', checked); + + expect(mockState.localMutations.setRunnersChecked).toHaveBeenCalledTimes(1); + expect(mockState.localMutations.setRunnersChecked).toHaveBeenCalledWith({ + isChecked: checked, + runners: mockRunners, + }); + }); + }); + + describe('When runners are loading', () => { + beforeEach(() => { + createComponent({ props: { runners: [] } }); + }); + + it(`is disabled`, () => { + expect(findCheckbox().attributes('disabled')).toBe('true'); + expect(findCheckbox().attributes('checked')).toBe(undefined); + expect(findCheckbox().attributes('indeterminate')).toBe(undefined); + }); + }); +}); diff --git a/spec/frontend/runner/components/runner_bulk_delete_spec.js b/spec/frontend/runner/components/runner_bulk_delete_spec.js index f5b56396cf1..6df918c684f 100644 --- a/spec/frontend/runner/components/runner_bulk_delete_spec.js +++ b/spec/frontend/runner/components/runner_bulk_delete_spec.js @@ -1,37 +1,65 @@ import Vue from 'vue'; -import { GlSprintf } from '@gitlab/ui'; +import { GlModal, GlSprintf } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; +import { createAlert } from '~/flash'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import { s__ } from '~/locale'; import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; +import BulkRunnerDeleteMutation from '~/runner/graphql/list/bulk_runner_delete.mutation.graphql'; import { createLocalState } from '~/runner/graphql/list/local_state'; import waitForPromises from 'helpers/wait_for_promises'; +import { allRunnersData } from '../mock_data'; Vue.use(VueApollo); -jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); +jest.mock('~/flash'); describe('RunnerBulkDelete', () => { let wrapper; + let apolloCache; let mockState; let mockCheckedRunnerIds; - const findClearBtn = () => wrapper.findByTestId('clear-btn'); - const findDeleteBtn = () => wrapper.findByTestId('delete-btn'); + const findClearBtn = () => wrapper.findByText(s__('Runners|Clear selection')); + const findDeleteBtn = () => wrapper.findByText(s__('Runners|Delete selected')); + const findModal = () => wrapper.findComponent(GlModal); + + const mockRunners = allRunnersData.data.runners.nodes; + const mockId1 = allRunnersData.data.runners.nodes[0].id; + const mockId2 = allRunnersData.data.runners.nodes[1].id; + + const bulkRunnerDeleteHandler = jest.fn(); const createComponent = () => { const { cacheConfig, localMutations } = mockState; + const apolloProvider = createMockApollo( + [[BulkRunnerDeleteMutation, bulkRunnerDeleteHandler]], + undefined, + cacheConfig, + ); wrapper = shallowMountExtended(RunnerBulkDelete, { - apolloProvider: createMockApollo(undefined, undefined, cacheConfig), + apolloProvider, provide: { localMutations, }, + propsData: { + runners: mockRunners, + }, + directives: { + GlTooltip: createMockDirective(), + }, stubs: { GlSprintf, + GlModal, }, }); + + apolloCache = apolloProvider.defaultClient.cache; + jest.spyOn(apolloCache, 'evict'); + jest.spyOn(apolloCache, 'gc'); }; beforeEach(() => { @@ -43,6 +71,7 @@ describe('RunnerBulkDelete', () => { }); afterEach(() => { + bulkRunnerDeleteHandler.mockReset(); wrapper.destroy(); }); @@ -61,10 +90,10 @@ describe('RunnerBulkDelete', () => { }); describe.each` - count | ids | text - ${1} | ${['gid:Runner/1']} | ${'1 runner'} - ${2} | ${['gid:Runner/1', 'gid:Runner/2']} | ${'2 runners'} - `('When $count runner(s) are checked', ({ count, ids, text }) => { + count | ids | text + ${1} | ${[mockId1]} | ${'1 runner'} + ${2} | ${[mockId1, mockId2]} | ${'2 runners'} + `('When $count runner(s) are checked', ({ ids, text }) => { beforeEach(() => { mockCheckedRunnerIds = ids; @@ -86,18 +115,129 @@ describe('RunnerBulkDelete', () => { }); it('shows confirmation modal', () => { - expect(confirmAction).toHaveBeenCalledTimes(0); + const modalId = getBinding(findDeleteBtn().element, 'gl-modal'); + + expect(findModal().props('modal-id')).toBe(modalId); + expect(findModal().text()).toContain(text); + }); + }); + + describe('when runners are deleted', () => { + let evt; + let mockHideModal; + + beforeEach(() => { + mockCheckedRunnerIds = [mockId1, mockId2]; + + createComponent(); + + jest.spyOn(mockState.localMutations, 'clearChecked').mockImplementation(() => {}); + mockHideModal = jest.spyOn(findModal().vm, 'hide'); + }); + + describe('when deletion is successful', () => { + beforeEach(() => { + bulkRunnerDeleteHandler.mockResolvedValue({ + data: { + bulkRunnerDelete: { deletedIds: mockCheckedRunnerIds, errors: [] }, + }, + }); + + evt = { + preventDefault: jest.fn(), + }; + findModal().vm.$emit('primary', evt); + }); + + it('has loading state', async () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(true); + expect(findModal().props('actionCancel').attributes.loading).toBe(true); + + await waitForPromises(); + + expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findModal().props('actionCancel').attributes.loading).toBe(false); + }); + + it('modal is not prevented from closing', () => { + expect(evt.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('mutation is called', async () => { + expect(bulkRunnerDeleteHandler).toHaveBeenCalledWith({ + input: { ids: mockCheckedRunnerIds }, + }); + }); + + it('user interface is updated', async () => { + const { evict, gc } = apolloCache; + + expect(evict).toHaveBeenCalledTimes(mockCheckedRunnerIds.length); + expect(evict).toHaveBeenCalledWith({ + id: expect.stringContaining(mockCheckedRunnerIds[0]), + }); + expect(evict).toHaveBeenCalledWith({ + id: expect.stringContaining(mockCheckedRunnerIds[1]), + }); + + expect(gc).toHaveBeenCalledTimes(1); + }); + + it('modal is hidden', () => { + expect(mockHideModal).toHaveBeenCalledTimes(1); + }); + }); + + describe('when deletion fails', () => { + beforeEach(() => { + bulkRunnerDeleteHandler.mockRejectedValue(new Error('error!')); + + evt = { + preventDefault: jest.fn(), + }; + findModal().vm.$emit('primary', evt); + }); + + it('has loading state', async () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(true); + expect(findModal().props('actionCancel').attributes.loading).toBe(true); + + await waitForPromises(); + + expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findModal().props('actionCancel').attributes.loading).toBe(false); + }); + + it('modal is not prevented from closing', () => { + expect(evt.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('mutation is called', () => { + expect(bulkRunnerDeleteHandler).toHaveBeenCalledWith({ + input: { ids: mockCheckedRunnerIds }, + }); + }); + + it('user interface is not updated', async () => { + await waitForPromises(); - findDeleteBtn().vm.$emit('click'); + const { evict, gc } = apolloCache; - expect(confirmAction).toHaveBeenCalledTimes(1); + expect(evict).not.toHaveBeenCalled(); + expect(gc).not.toHaveBeenCalled(); + expect(mockState.localMutations.clearChecked).not.toHaveBeenCalled(); + }); - const [, confirmOptions] = confirmAction.mock.calls[0]; - const { title, modalHtmlMessage, primaryBtnText } = confirmOptions; + it('alert is called', async () => { + await waitForPromises(); - expect(title).toMatch(text); - expect(primaryBtnText).toMatch(text); - expect(modalHtmlMessage).toMatch(`<strong>${count}</strong>`); + expect(createAlert).toHaveBeenCalled(); + expect(createAlert).toHaveBeenCalledWith({ + message: expect.any(String), + captureError: true, + error: expect.any(Error), + }); + }); }); }); }); diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js index 83fb1764c6d..e35bec3aa38 100644 --- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js +++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js @@ -143,7 +143,7 @@ describe('RunnerList', () => { runnerType: INSTANCE_TYPE, filters: mockFilters, sort: mockOtherSort, - pagination: { page: 1 }, + pagination: {}, }); }); }); @@ -156,7 +156,7 @@ describe('RunnerList', () => { runnerType: null, filters: mockFilters, sort: mockDefaultSort, - pagination: { page: 1 }, + pagination: {}, }); }); @@ -167,7 +167,7 @@ describe('RunnerList', () => { runnerType: null, filters: [], sort: mockOtherSort, - pagination: { page: 1 }, + pagination: {}, }); }); }); diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js index 20582aaaf40..4d38afb25ee 100644 --- a/spec/frontend/runner/components/runner_jobs_spec.js +++ b/spec/frontend/runner/components/runner_jobs_spec.js @@ -73,8 +73,7 @@ describe('RunnerJobs', () => { it('Shows jobs', () => { const jobs = findRunnerJobsTable().props('jobs'); - expect(jobs).toHaveLength(mockJobs.length); - expect(jobs[0]).toMatchObject(mockJobs[0]); + expect(jobs).toEqual(mockJobs); }); describe('When "Next" page is clicked', () => { diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js index eca4bbc3490..7b58a81bb0d 100644 --- a/spec/frontend/runner/components/runner_list_spec.js +++ b/spec/frontend/runner/components/runner_list_spec.js @@ -88,9 +88,7 @@ describe('RunnerList', () => { createComponent({}, mountExtended); // Badges - expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText( - 'never contacted paused', - ); + expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted'); // Runner summary expect(findCell({ fieldKey: 'summary' }).text()).toContain( @@ -124,10 +122,10 @@ describe('RunnerList', () => { expect(findCell({ fieldKey: 'checkbox' }).find('input').exists()).toBe(true); }); - it('Emits a checked event', () => { + it('Emits a checked event', async () => { const checkbox = findCell({ fieldKey: 'checkbox' }).find('input'); - checkbox.setChecked(); + await checkbox.setChecked(); expect(wrapper.emitted('checked')).toHaveLength(1); expect(wrapper.emitted('checked')[0][0]).toEqual({ diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js index e144b52ceb3..499cc59250d 100644 --- a/spec/frontend/runner/components/runner_pagination_spec.js +++ b/spec/frontend/runner/components/runner_pagination_spec.js @@ -1,5 +1,5 @@ -import { GlPagination } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { GlKeysetPagination } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import RunnerPagination from '~/runner/components/runner_pagination.vue'; const mockStartCursor = 'START_CURSOR'; @@ -8,21 +8,11 @@ const mockEndCursor = 'END_CURSOR'; describe('RunnerPagination', () => { let wrapper; - const findPagination = () => wrapper.findComponent(GlPagination); + const findPagination = () => wrapper.findComponent(GlKeysetPagination); - const createComponent = ({ page = 1, hasPreviousPage = false, hasNextPage = true } = {}) => { - wrapper = mount(RunnerPagination, { - propsData: { - value: { - page, - }, - pageInfo: { - hasPreviousPage, - hasNextPage, - startCursor: mockStartCursor, - endCursor: mockEndCursor, - }, - }, + const createComponent = (propsData = {}) => { + wrapper = shallowMount(RunnerPagination, { + propsData, }); }; @@ -30,114 +20,96 @@ describe('RunnerPagination', () => { wrapper.destroy(); }); - describe('When on the first page', () => { - beforeEach(() => { - createComponent({ - page: 1, - hasPreviousPage: false, - hasNextPage: true, - }); - }); - - it('Contains the current page information', () => { - expect(findPagination().props('value')).toBe(1); - expect(findPagination().props('prevPage')).toBe(null); - expect(findPagination().props('nextPage')).toBe(2); - }); - - it('Goes to the second page', () => { - findPagination().vm.$emit('input', 2); - - expect(wrapper.emitted('input')[0]).toEqual([ - { - after: mockEndCursor, - page: 2, - }, - ]); - }); - }); - describe('When in between pages', () => { + const mockPageInfo = { + startCursor: mockStartCursor, + endCursor: mockEndCursor, + hasPreviousPage: true, + hasNextPage: true, + }; + beforeEach(() => { createComponent({ - page: 2, - hasPreviousPage: true, - hasNextPage: true, + pageInfo: mockPageInfo, }); }); it('Contains the current page information', () => { - expect(findPagination().props('value')).toBe(2); - expect(findPagination().props('prevPage')).toBe(1); - expect(findPagination().props('nextPage')).toBe(3); + expect(findPagination().props()).toMatchObject(mockPageInfo); }); - it('Shows the next and previous pages', () => { - const links = findPagination().findAll('a'); - - expect(links).toHaveLength(2); - expect(links.at(0).text()).toBe('Previous'); - expect(links.at(1).text()).toBe('Next'); - }); - - it('Goes to the last page', () => { - findPagination().vm.$emit('input', 3); + it('Goes to the prev page', () => { + findPagination().vm.$emit('prev'); expect(wrapper.emitted('input')[0]).toEqual([ { - after: mockEndCursor, - page: 3, + before: mockStartCursor, }, ]); }); - it('Goes to the first page', () => { - findPagination().vm.$emit('input', 1); + it('Goes to the next page', () => { + findPagination().vm.$emit('next'); expect(wrapper.emitted('input')[0]).toEqual([ { - page: 1, + after: mockEndCursor, }, ]); }); }); - describe('When in the last page', () => { + describe.each` + page | hasPreviousPage | hasNextPage + ${'first'} | ${false} | ${true} + ${'last'} | ${true} | ${false} + `('When on the $page page', ({ page, hasPreviousPage, hasNextPage }) => { + const mockPageInfo = { + startCursor: mockStartCursor, + endCursor: mockEndCursor, + hasPreviousPage, + hasNextPage, + }; + beforeEach(() => { createComponent({ - page: 3, - hasPreviousPage: true, - hasNextPage: false, + pageInfo: mockPageInfo, }); }); - it('Contains the current page', () => { - expect(findPagination().props('value')).toBe(3); - expect(findPagination().props('prevPage')).toBe(2); - expect(findPagination().props('nextPage')).toBe(null); + it(`Contains the ${page} page information`, () => { + expect(findPagination().props()).toMatchObject(mockPageInfo); }); }); - describe('When only one page', () => { + describe('When no other pages', () => { beforeEach(() => { createComponent({ - page: 1, - hasPreviousPage: false, - hasNextPage: false, + pageInfo: { + hasPreviousPage: false, + hasNextPage: false, + }, }); }); - it('does not display pagination', () => { - expect(wrapper.html()).toBe(''); + it('is not shown', () => { + expect(findPagination().exists()).toBe(false); }); + }); - it('Contains the current page', () => { - expect(findPagination().props('value')).toBe(1); + describe('When adding more attributes', () => { + beforeEach(() => { + createComponent({ + pageInfo: { + hasPreviousPage: true, + hasNextPage: false, + }, + disabled: true, + }); }); - it('Shows no more page buttons', () => { - expect(findPagination().props('prevPage')).toBe(null); - expect(findPagination().props('nextPage')).toBe(null); + it('attributes are passed', () => { + expect(findPagination().props('disabled')).toBe(true); }); }); }); diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js index 6932b3b5197..c988fb8477d 100644 --- a/spec/frontend/runner/components/runner_projects_spec.js +++ b/spec/frontend/runner/components/runner_projects_spec.js @@ -95,6 +95,7 @@ describe('RunnerProjects', () => { name, fullName: nameWithNamespace, avatarUrl, + isOwner: true, // first project is always owner }); }); diff --git a/spec/frontend/runner/components/stat/runner_count_spec.js b/spec/frontend/runner/components/stat/runner_count_spec.js index 89b51b1b4a7..2a6a745099f 100644 --- a/spec/frontend/runner/components/stat/runner_count_spec.js +++ b/spec/frontend/runner/components/stat/runner_count_spec.js @@ -7,8 +7,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { captureException } from '~/runner/sentry_utils'; -import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql'; -import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql'; +import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql'; +import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql'; import { runnersCountData, groupRunnersCountData } from '../../mock_data'; diff --git a/spec/frontend/runner/components/stat/runner_single_stat_spec.js b/spec/frontend/runner/components/stat/runner_single_stat_spec.js new file mode 100644 index 00000000000..964a6a6ff71 --- /dev/null +++ b/spec/frontend/runner/components/stat/runner_single_stat_spec.js @@ -0,0 +1,61 @@ +import { GlSingleStat } from '@gitlab/ui/dist/charts'; +import { shallowMount } from '@vue/test-utils'; +import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue'; +import RunnerCount from '~/runner/components/stat/runner_count.vue'; +import { INSTANCE_TYPE, GROUP_TYPE } from '~/runner/constants'; + +describe('RunnerStats', () => { + let wrapper; + + const findRunnerCount = () => wrapper.findComponent(RunnerCount); + const findGlSingleStat = () => wrapper.findComponent(GlSingleStat); + + const createComponent = ({ props = {}, count, mountFn = shallowMount, ...options } = {}) => { + wrapper = mountFn(RunnerSingleStat, { + propsData: { + scope: INSTANCE_TYPE, + title: 'My title', + variables: {}, + ...props, + }, + stubs: { + RunnerCount: { + props: ['scope', 'variables', 'skip'], + render() { + return this.$scopedSlots.default({ + count, + }); + }, + }, + }, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + case | count | value + ${'number'} | ${99} | ${'99'} + ${'long number'} | ${1000} | ${'1,000'} + ${'empty number'} | ${null} | ${'-'} + `('formats $case', ({ count, value }) => { + createComponent({ count }); + + expect(findGlSingleStat().props('value')).toBe(value); + }); + + it('Passes runner count props', () => { + const props = { + scope: GROUP_TYPE, + variables: { paused: true }, + skip: true, + }; + + createComponent({ props }); + + expect(findRunnerCount().props()).toEqual(props); + }); +}); diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js index f1ba6403dfb..7f1f22be94f 100644 --- a/spec/frontend/runner/components/stat/runner_stats_spec.js +++ b/spec/frontend/runner/components/stat/runner_stats_spec.js @@ -1,15 +1,13 @@ import { shallowMount, mount } from '@vue/test-utils'; import { s__ } from '~/locale'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; -import RunnerCount from '~/runner/components/stat/runner_count.vue'; -import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue'; +import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue'; import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants'; describe('RunnerStats', () => { let wrapper; - const findRunnerCountAt = (i) => wrapper.findAllComponents(RunnerCount).at(i); - const findRunnerStatusStatAt = (i) => wrapper.findAllComponents(RunnerStatusStat).at(i); + const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat).wrappers; const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => { wrapper = mountFn(RunnerStats, { @@ -53,31 +51,12 @@ describe('RunnerStats', () => { expect(text).toMatch(`${s__('Runners|Stale runners')} 1`); }); - it('Displays counts for filtered searches', () => { - createComponent({ props: { variables: { paused: true } } }); + it('Displays all counts for filtered searches', () => { + const mockVariables = { paused: true }; + createComponent({ props: { variables: mockVariables } }); - expect(findRunnerCountAt(0).props('variables').paused).toBe(true); - expect(findRunnerCountAt(1).props('variables').paused).toBe(true); - expect(findRunnerCountAt(2).props('variables').paused).toBe(true); - }); - - it('Skips overlapping statuses', () => { - createComponent({ props: { variables: { status: STATUS_ONLINE } } }); - - expect(findRunnerCountAt(0).props('skip')).toBe(false); - expect(findRunnerCountAt(1).props('skip')).toBe(true); - expect(findRunnerCountAt(2).props('skip')).toBe(true); - }); - - it.each` - i | status - ${0} | ${STATUS_ONLINE} - ${1} | ${STATUS_OFFLINE} - ${2} | ${STATUS_STALE} - `('Displays status $status at index $i', ({ i, status }) => { - createComponent({ mountFn: mount }); - - expect(findRunnerCountAt(i).props('variables').status).toBe(status); - expect(findRunnerStatusStatAt(i).props('status')).toBe(status); + findSingleStats().forEach((stat) => { + expect(stat.props('variables')).toMatchObject(mockVariables); + }); }); }); diff --git a/spec/frontend/runner/components/stat/runner_status_stat_spec.js b/spec/frontend/runner/components/stat/runner_status_stat_spec.js deleted file mode 100644 index 3218272eac7..00000000000 --- a/spec/frontend/runner/components/stat/runner_status_stat_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import { GlSingleStat } from '@gitlab/ui/dist/charts'; -import { shallowMount, mount } from '@vue/test-utils'; -import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue'; -import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants'; - -describe('RunnerStatusStat', () => { - let wrapper; - - const findSingleStat = () => wrapper.findComponent(GlSingleStat); - - const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => { - wrapper = mountFn(RunnerStatusStat, { - propsData: { - status: STATUS_ONLINE, - value: 99, - ...props, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe.each` - status | variant | title | badge - ${STATUS_ONLINE} | ${'success'} | ${'Online runners'} | ${'online'} - ${STATUS_OFFLINE} | ${'muted'} | ${'Offline runners'} | ${'offline'} - ${STATUS_STALE} | ${'warning'} | ${'Stale runners'} | ${'stale'} - `('Renders a stat for status "$status"', ({ status, variant, title, badge }) => { - beforeEach(() => { - createComponent({ props: { status } }, mount); - }); - - it('Renders text', () => { - expect(wrapper.text()).toMatch(new RegExp(`${title} 99\\s+${badge}`)); - }); - - it(`Uses variant ${variant}`, () => { - expect(findSingleStat().props('variant')).toBe(variant); - }); - }); - - it('Formats stat number', () => { - createComponent({ props: { value: 1000 } }, mount); - - expect(wrapper.text()).toMatch('Online runners 1,000'); - }); - - it('Shows a null result', () => { - createComponent({ props: { value: null } }, mount); - - expect(wrapper.text()).toMatch('Online runners -'); - }); - - it('Shows an undefined result', () => { - createComponent({ props: { value: undefined } }, mount); - - expect(wrapper.text()).toMatch('Online runners -'); - }); - - it('Shows result for an unknown status', () => { - createComponent({ props: { status: 'UNKNOWN' } }, mount); - - expect(wrapper.text()).toMatch('Runners 99'); - }); -}); |