Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/runner')
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js64
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js26
-rw-r--r--spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap2
-rw-r--r--spec/frontend/runner/components/cells/runner_status_cell_spec.js17
-rw-r--r--spec/frontend/runner/components/registration/registration_dropdown_spec.js16
-rw-r--r--spec/frontend/runner/components/registration/registration_token_spec.js1
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js70
-rw-r--r--spec/frontend/runner/components/runner_jobs_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_list_empty_state_spec.js76
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js4
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js10
-rw-r--r--spec/frontend/runner/mock_data.js5
-rw-r--r--spec/frontend/runner/runner_search_utils_spec.js20
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);
+ });
+ });
});