diff options
Diffstat (limited to 'spec/frontend/runner')
13 files changed, 229 insertions, 86 deletions
diff --git a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js index 07259ec3538..28e7d192938 100644 --- a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js +++ b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; -import { mount, shallowMount } from '@vue/test-utils'; +import { GlTab, GlTabs } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; +import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { createAlert, VARIANT_SUCCESS } from '~/flash'; @@ -11,6 +12,7 @@ import RunnerHeader from '~/runner/components/runner_header.vue'; import RunnerPauseButton from '~/runner/components/runner_pause_button.vue'; import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue'; import RunnerEditButton from '~/runner/components/runner_edit_button.vue'; +import RunnersJobs from '~/runner/components/runner_jobs.vue'; import runnerQuery from '~/runner/graphql/show/runner.query.graphql'; import AdminRunnerShowApp from '~/runner/admin_runner_show/admin_runner_show_app.vue'; import { captureException } from '~/runner/sentry_utils'; @@ -38,6 +40,8 @@ describe('AdminRunnerShowApp', () => { const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton); const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton); const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton); + const findRunnersJobs = () => wrapper.findComponent(RunnersJobs); + const findJobCountBadge = () => wrapper.findByTestId('job-count-badge'); const mockRunnerQueryResult = (runner = {}) => { mockRunnerQuery = jest.fn().mockResolvedValue({ @@ -47,7 +51,7 @@ describe('AdminRunnerShowApp', () => { }); }; - const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => { + const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => { wrapper = mountFn(AdminRunnerShowApp, { apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]), propsData: { @@ -55,6 +59,7 @@ describe('AdminRunnerShowApp', () => { runnersPath: mockRunnersPath, ...props, }, + ...options, }); return waitForPromises(); @@ -69,7 +74,7 @@ describe('AdminRunnerShowApp', () => { beforeEach(async () => { mockRunnerQueryResult(); - await createComponent({ mountFn: mount }); + await createComponent({ mountFn: mountExtended }); }); it('expect GraphQL ID to be requested', async () => { @@ -110,7 +115,7 @@ describe('AdminRunnerShowApp', () => { }); await createComponent({ - mountFn: mount, + mountFn: mountExtended, }); }); @@ -129,7 +134,7 @@ describe('AdminRunnerShowApp', () => { }); await createComponent({ - mountFn: mount, + mountFn: mountExtended, }); }); @@ -141,7 +146,7 @@ describe('AdminRunnerShowApp', () => { describe('when runner is deleted', () => { beforeEach(async () => { await createComponent({ - mountFn: mount, + mountFn: mountExtended, }); }); @@ -163,7 +168,7 @@ describe('AdminRunnerShowApp', () => { }); await createComponent({ - mountFn: mount, + mountFn: mountExtended, }); }); @@ -191,4 +196,49 @@ describe('AdminRunnerShowApp', () => { expect(createAlert).toHaveBeenCalled(); }); }); + + describe('Jobs tab', () => { + const stubs = { + GlTab, + GlTabs, + RunnerDetails: { + template: ` + <div> + <slot name="jobs-tab"></slot> + </div> + `, + }, + }; + + it('without a runner, shows no jobs', () => { + mockRunnerQuery = jest.fn().mockResolvedValue({ + data: { + runner: null, + }, + }); + + createComponent({ stubs }); + + expect(findJobCountBadge().exists()).toBe(false); + expect(findRunnersJobs().exists()).toBe(false); + }); + + it('without a job count, shows no jobs count', async () => { + mockRunnerQueryResult({ jobCount: null }); + + await createComponent({ stubs }); + + expect(findJobCountBadge().exists()).toBe(false); + }); + + it('with a job count, shows jobs count', async () => { + const runner = { jobCount: 3 }; + mockRunnerQueryResult(runner); + + await createComponent({ stubs }); + + expect(findJobCountBadge().text()).toBe('3'); + expect(findRunnersJobs().props('runner')).toEqual({ ...mockRunner, ...runner }); + }); + }); }); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index 405813be4e3..3d25ad075de 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -18,6 +18,7 @@ import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue'; import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; import RunnerList from '~/runner/components/runner_list.vue'; +import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vue'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue'; import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue'; @@ -50,6 +51,8 @@ import { runnersDataPaginated, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, } from '../mock_data'; const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; @@ -78,6 +81,7 @@ describe('AdminRunnersApp', () => { const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown); const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs); const findRunnerList = () => wrapper.findComponent(RunnerList); + const findRunnerListEmptyState = () => wrapper.findComponent(RunnerListEmptyState); const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination)); const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page'); const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar); @@ -106,6 +110,8 @@ describe('AdminRunnersApp', () => { localMutations, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, ...provide, }, ...options, @@ -457,12 +463,28 @@ describe('AdminRunnersApp', () => { runners: { nodes: [] }, }, }); + createComponent(); await waitForPromises(); }); - it('shows a message for no results', async () => { - expect(wrapper.text()).toContain('No runners found'); + it('shows an empty state', () => { + expect(findRunnerListEmptyState().props('isSearchFiltered')).toBe(false); + }); + + describe('when a filter is selected by the user', () => { + beforeEach(async () => { + findRunnerFilteredSearchBar().vm.$emit('input', { + runnerType: null, + filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }], + sort: CREATED_ASC, + }); + await waitForPromises(); + }); + + it('shows an empty state for a filtered search', () => { + expect(findRunnerListEmptyState().props('isSearchFiltered')).toBe(true); + }); }); }); diff --git a/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap index 80a04401760..b27a1adf01b 100644 --- a/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap +++ b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RunnerStatusPopover renders complete text 1`] = `"Never contacted: Runner has never contacted GitLab (when you register a runner, use gitlab-runner run to bring it online) Online: Runner has contacted GitLab within the last 2 hours Offline: Runner has not contacted GitLab in more than 2 hours Stale: Runner has not contacted GitLab in more than 2 months"`; +exports[`RunnerStatusPopover renders complete text 1`] = `"Never contacted: Runner has never contacted GitLab (when you register a runner, use gitlab-runner run to bring it online) Online: Runner has contacted GitLab within the last 2 hours Offline: Runner has not contacted GitLab in more than 2 hours Stale: Runner has not contacted GitLab in more than 3 months"`; diff --git a/spec/frontend/runner/components/cells/runner_status_cell_spec.js b/spec/frontend/runner/components/cells/runner_status_cell_spec.js index 20a1cdf7236..0f5133d0ae2 100644 --- a/spec/frontend/runner/components/cells/runner_status_cell_spec.js +++ b/spec/frontend/runner/components/cells/runner_status_cell_spec.js @@ -1,12 +1,15 @@ -import { GlBadge } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue'; + +import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue'; +import RunnerPausedBadge from '~/runner/components/runner_paused_badge.vue'; import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants'; -describe('RunnerTypeCell', () => { +describe('RunnerStatusCell', () => { let wrapper; - const findBadgeAt = (i) => wrapper.findAllComponents(GlBadge).at(i); + const findStatusBadge = () => wrapper.findComponent(RunnerStatusBadge); + const findPausedBadge = () => wrapper.findComponent(RunnerPausedBadge); const createComponent = ({ runner = {} } = {}) => { wrapper = mount(RunnerStatusCell, { @@ -29,7 +32,7 @@ describe('RunnerTypeCell', () => { createComponent(); expect(wrapper.text()).toMatchInterpolatedText('online'); - expect(findBadgeAt(0).text()).toBe('online'); + expect(findStatusBadge().text()).toBe('online'); }); it('Displays offline status', () => { @@ -40,7 +43,7 @@ describe('RunnerTypeCell', () => { }); expect(wrapper.text()).toMatchInterpolatedText('offline'); - expect(findBadgeAt(0).text()).toBe('offline'); + expect(findStatusBadge().text()).toBe('offline'); }); it('Displays paused status', () => { @@ -52,9 +55,7 @@ describe('RunnerTypeCell', () => { }); expect(wrapper.text()).toMatchInterpolatedText('online paused'); - - expect(findBadgeAt(0).text()).toBe('online'); - expect(findBadgeAt(1).text()).toBe('paused'); + expect(findPausedBadge().text()).toBe('paused'); }); it('Is empty when data is missing', () => { diff --git a/spec/frontend/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/runner/components/registration/registration_dropdown_spec.js index 81c2788f084..d3f38bc1d26 100644 --- a/spec/frontend/runner/components/registration/registration_dropdown_spec.js +++ b/spec/frontend/runner/components/registration/registration_dropdown_spec.js @@ -1,4 +1,4 @@ -import { GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui'; +import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui'; import { mount, shallowMount, createWrapper } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; @@ -24,6 +24,8 @@ import { const mockToken = '0123456789'; const maskToken = '**********'; +Vue.use(VueApollo); + describe('RegistrationDropdown', () => { let wrapper; @@ -32,9 +34,11 @@ describe('RegistrationDropdown', () => { const findRegistrationInstructionsDropdownItem = () => wrapper.findComponent(GlDropdownItem); const findTokenDropdownItem = () => wrapper.findComponent(GlDropdownForm); const findRegistrationToken = () => wrapper.findComponent(RegistrationToken); - const findRegistrationTokenInput = () => wrapper.findByTestId('token-value').find('input'); + const findRegistrationTokenInput = () => + wrapper.findByLabelText(RegistrationToken.i18n.registrationToken); const findTokenResetDropdownItem = () => wrapper.findComponent(RegistrationTokenResetDropdownItem); + const findModal = () => wrapper.findComponent(GlModal); const findModalContent = () => createWrapper(document.body) .find('[data-testid="runner-instructions-modal"]') @@ -43,6 +47,8 @@ describe('RegistrationDropdown', () => { const openModal = async () => { await findRegistrationInstructionsDropdownItem().trigger('click'); + findModal().vm.$emit('shown'); + await waitForPromises(); }; @@ -60,8 +66,6 @@ describe('RegistrationDropdown', () => { }; const createComponentWithModal = () => { - Vue.use(VueApollo); - const requestHandlers = [ [getRunnerPlatformsQuery, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)], [getRunnerSetupInstructionsQuery, jest.fn().mockResolvedValue(mockGraphqlInstructions)], @@ -169,10 +173,10 @@ describe('RegistrationDropdown', () => { await nextTick(); }; - it('Updates token in input', async () => { + it('Updates token input', async () => { createComponent({}, mount); - expect(findRegistrationTokenInput().props('value')).not.toBe(newToken); + expect(findRegistrationToken().props('value')).not.toBe(newToken); await resetToken(); diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js index cb42c7c8493..ed1a698d36f 100644 --- a/spec/frontend/runner/components/registration/registration_token_spec.js +++ b/spec/frontend/runner/components/registration/registration_token_spec.js @@ -29,6 +29,7 @@ describe('RegistrationToken', () => { wrapper = mountFn(RegistrationToken, { propsData: { value: mockToken, + inputId: 'token-value', ...props, }, localVue, diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js index 162d21febfd..9e0f7014750 100644 --- a/spec/frontend/runner/components/runner_details_spec.js +++ b/spec/frontend/runner/components/runner_details_spec.js @@ -1,14 +1,13 @@ -import { GlSprintf, GlIntersperse, GlTab } from '@gitlab/ui'; -import { createWrapper, ErrorWrapper } from '@vue/test-utils'; +import { GlSprintf, GlIntersperse } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { useFakeDate } from 'helpers/fake_date'; +import { findDd } from 'helpers/dl_locator_helper'; import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants'; import RunnerDetails from '~/runner/components/runner_details.vue'; import RunnerDetail from '~/runner/components/runner_detail.vue'; import RunnerGroups from '~/runner/components/runner_groups.vue'; -import RunnersJobs from '~/runner/components/runner_jobs.vue'; import RunnerTags from '~/runner/components/runner_tags.vue'; import RunnerTag from '~/runner/components/runner_tag.vue'; @@ -24,25 +23,14 @@ describe('RunnerDetails', () => { useFakeDate(mockNow); - /** - * Find the definition (<dd>) that corresponds to this term (<dt>) - * @param {string} dtLabel - Label for this value - * @returns Wrapper - */ - const findDd = (dtLabel) => { - const dt = wrapper.findByText(dtLabel).element; - const dd = dt.nextElementSibling; - if (dt.tagName === 'DT' && dd.tagName === 'DD') { - return createWrapper(dd, {}); - } - return ErrorWrapper(dtLabel); - }; - const findDetailGroups = () => wrapper.findComponent(RunnerGroups); - const findRunnersJobs = () => wrapper.findComponent(RunnersJobs); - const findJobCountBadge = () => wrapper.findByTestId('job-count-badge'); - const createComponent = ({ props = {}, mountFn = shallowMountExtended, stubs } = {}) => { + const createComponent = ({ + props = {}, + stubs, + mountFn = shallowMountExtended, + ...options + } = {}) => { wrapper = mountFn(RunnerDetails, { propsData: { ...props, @@ -51,6 +39,7 @@ describe('RunnerDetails', () => { RunnerDetail, ...stubs, }, + ...options, }); }; @@ -108,7 +97,7 @@ describe('RunnerDetails', () => { }); it(`displays expected value "${expectedValue}"`, () => { - expect(findDd(field).text()).toBe(expectedValue); + expect(findDd(field, wrapper).text()).toBe(expectedValue); }); }); @@ -123,7 +112,7 @@ describe('RunnerDetails', () => { stubs, }); - expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2'); + expect(findDd('Tags', wrapper).text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2'); }); it('displays "None" when runner has no tags', () => { @@ -134,7 +123,7 @@ describe('RunnerDetails', () => { stubs, }); - expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('None'); + expect(findDd('Tags', wrapper).text().replace(/\s+/g, ' ')).toBe('None'); }); }); @@ -153,40 +142,17 @@ describe('RunnerDetails', () => { }); }); - describe('Jobs tab', () => { - const stubs = { GlTab }; - - it('without a runner, shows no jobs', () => { - createComponent({ - props: { runner: null }, - stubs, - }); - - expect(findJobCountBadge().exists()).toBe(false); - expect(findRunnersJobs().exists()).toBe(false); - }); + describe('Jobs tab slot', () => { + it('shows job tab slot', () => { + const JOBS_TAB = '<div>Jobs Tab</div>'; - it('without a job count, shows no jobs count', () => { createComponent({ - props: { - runner: { ...mockRunner, jobCount: undefined }, + slots: { + 'jobs-tab': JOBS_TAB, }, - stubs, - }); - - expect(findJobCountBadge().exists()).toBe(false); - }); - - it('with a job count, shows jobs count', () => { - const runner = { ...mockRunner, jobCount: 3 }; - - createComponent({ - props: { runner }, - stubs, }); - expect(findJobCountBadge().text()).toBe('3'); - expect(findRunnersJobs().props('runner')).toBe(runner); + expect(wrapper.html()).toContain(JOBS_TAB); }); }); }); diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js index 8ac5685a0dd..20582aaaf40 100644 --- a/spec/frontend/runner/components/runner_jobs_spec.js +++ b/spec/frontend/runner/components/runner_jobs_spec.js @@ -1,4 +1,4 @@ -import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; +import { GlSkeletonLoader } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -28,7 +28,7 @@ describe('RunnerJobs', () => { let wrapper; let mockRunnerJobsQuery; - const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading); + const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader); const findRunnerJobsTable = () => wrapper.findComponent(RunnerJobsTable); const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); diff --git a/spec/frontend/runner/components/runner_list_empty_state_spec.js b/spec/frontend/runner/components/runner_list_empty_state_spec.js new file mode 100644 index 00000000000..59cff863106 --- /dev/null +++ b/spec/frontend/runner/components/runner_list_empty_state_spec.js @@ -0,0 +1,76 @@ +import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; + +import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vue'; + +const mockSvgPath = 'mock-svg-path.svg'; +const mockFilteredSvgPath = 'mock-filtered-svg-path.svg'; + +describe('RunnerListEmptyState', () => { + let wrapper; + + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findLink = () => wrapper.findComponent(GlLink); + const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal); + + const createComponent = ({ props, mountFn = shallowMountExtended } = {}) => { + wrapper = mountFn(RunnerListEmptyState, { + propsData: { + svgPath: mockSvgPath, + filteredSvgPath: mockFilteredSvgPath, + ...props, + }, + directives: { + GlModal: createMockDirective(), + }, + stubs: { + GlEmptyState, + GlSprintf, + GlLink, + }, + }); + }; + + describe('when search is not filtered', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders an illustration', () => { + expect(findEmptyState().props('svgPath')).toBe(mockSvgPath); + }); + + it('displays "no results" text', () => { + const title = s__('Runners|Get started with runners'); + const desc = s__( + 'Runners|Runners are the agents that run your CI/CD jobs. Follow the %{linkStart}installation and registration instructions%{linkEnd} to set up a runner.', + ); + + expect(findEmptyState().text()).toMatchInterpolatedText(`${title} ${desc}`); + }); + + it('opens a runner registration instructions modal with a link', () => { + const { value } = getBinding(findLink().element, 'gl-modal'); + + expect(findRunnerInstructionsModal().props('modalId')).toEqual(value); + }); + }); + + describe('when search is filtered', () => { + beforeEach(() => { + createComponent({ props: { isSearchFiltered: true } }); + }); + + it('renders a "filtered search" illustration', () => { + expect(findEmptyState().props('svgPath')).toBe(mockFilteredSvgPath); + }); + + it('displays "no filtered results" text', () => { + expect(findEmptyState().text()).toContain(s__('Runners|No results found')); + expect(findEmptyState().text()).toContain(s__('Runners|Edit your search and try again')); + }); + }); +}); diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js index 04627e2307b..6932b3b5197 100644 --- a/spec/frontend/runner/components/runner_projects_spec.js +++ b/spec/frontend/runner/components/runner_projects_spec.js @@ -1,4 +1,4 @@ -import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; +import { GlSkeletonLoader } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -34,7 +34,7 @@ describe('RunnerProjects', () => { let mockRunnerProjectsQuery; const findHeading = () => wrapper.find('h3'); - const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading); + const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader); const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem); const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 52bd51a974b..eb9f85a7d0f 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -16,6 +16,7 @@ import { updateHistory } from '~/lib/utils/url_utility'; import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; import RunnerList from '~/runner/components/runner_list.vue'; +import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vue'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue'; import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue'; @@ -48,6 +49,8 @@ import { groupRunnersCountData, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, } from '../mock_data'; Vue.use(VueApollo); @@ -75,6 +78,7 @@ describe('GroupRunnersApp', () => { const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown); const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs); const findRunnerList = () => wrapper.findComponent(RunnerList); + const findRunnerListEmptyState = () => wrapper.findComponent(RunnerListEmptyState); const findRunnerRow = (id) => extendedWrapper(wrapper.findByTestId(`runner-row-${id}`)); const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination)); const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page'); @@ -103,6 +107,8 @@ describe('GroupRunnersApp', () => { provide: { onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, }, }); }; @@ -388,8 +394,8 @@ describe('GroupRunnersApp', () => { await waitForPromises(); }); - it('shows a message for no results', async () => { - expect(wrapper.text()).toContain('No runners found'); + it('shows an empty state', async () => { + expect(findRunnerListEmptyState().exists()).toBe(true); }); }); diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 1c2333b552c..3368fc21544 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -19,7 +19,10 @@ import groupRunnersCountData from 'test_fixtures/graphql/runner/list/group_runne // Other mock data export const onlineContactTimeoutSecs = 2 * 60 * 60; -export const staleTimeoutSecs = 5259492; // Ruby's `2.months` +export const staleTimeoutSecs = 7889238; // Ruby's `3.months` + +export const emptyStateSvgPath = 'emptyStateSvgPath.svg'; +export const emptyStateFilteredSvgPath = 'emptyStateFilteredSvgPath.svg'; export { runnersData, diff --git a/spec/frontend/runner/runner_search_utils_spec.js b/spec/frontend/runner/runner_search_utils_spec.js index a3c1458ed26..1f102f86b2a 100644 --- a/spec/frontend/runner/runner_search_utils_spec.js +++ b/spec/frontend/runner/runner_search_utils_spec.js @@ -5,6 +5,7 @@ import { fromUrlQueryToSearch, fromSearchToUrl, fromSearchToVariables, + isSearchFiltered, } from '~/runner/runner_search_utils'; describe('search_params.js', () => { @@ -14,6 +15,7 @@ describe('search_params.js', () => { urlQuery: '', search: { runnerType: null, filters: [], pagination: { page: 1 }, sort: 'CREATED_DESC' }, graphqlVariables: { sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, + isDefault: true, }, { name: 'a single status', @@ -268,7 +270,7 @@ describe('search_params.js', () => { describe('fromSearchToUrl', () => { examples.forEach(({ name, urlQuery, search }) => { it(`Converts ${name} to a url`, () => { - expect(fromSearchToUrl(search)).toEqual(`http://test.host/${urlQuery}`); + expect(fromSearchToUrl(search)).toBe(`http://test.host/${urlQuery}`); }); }); @@ -280,7 +282,7 @@ describe('search_params.js', () => { const search = { filters: [], sort: 'CREATED_DESC' }; const expectedUrl = `http://test.host/`; - expect(fromSearchToUrl(search, initalUrl)).toEqual(expectedUrl); + expect(fromSearchToUrl(search, initalUrl)).toBe(expectedUrl); }); it('When unrelated search parameter is present, it does not get removed', () => { @@ -288,7 +290,7 @@ describe('search_params.js', () => { const search = { filters: [], sort: 'CREATED_DESC' }; const expectedUrl = `http://test.host/?unrelated=UNRELATED`; - expect(fromSearchToUrl(search, initialUrl)).toEqual(expectedUrl); + expect(fromSearchToUrl(search, initialUrl)).toBe(expectedUrl); }); }); @@ -331,4 +333,16 @@ describe('search_params.js', () => { }); }); }); + + describe('isSearchFiltered', () => { + examples.forEach(({ name, search, isDefault }) => { + it(`Given ${name}, evaluates to ${isDefault ? 'not ' : ''}filtered`, () => { + expect(isSearchFiltered(search)).toBe(!isDefault); + }); + }); + + it('given a missing pagination, evaluates as not filtered', () => { + expect(isSearchFiltered({ pagination: null })).toBe(false); + }); + }); }); |