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:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 18:40:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-20 18:40:28 +0300
commitb595cb0c1dec83de5bdee18284abe86614bed33b (patch)
tree8c3d4540f193c5ff98019352f554e921b3a41a72 /spec/frontend/runner
parent2f9104a328fc8a4bddeaa4627b595166d24671d0 (diff)
Add latest changes from gitlab-org/gitlab@15-2-stable-eev15.2.0-rc42
Diffstat (limited to 'spec/frontend/runner')
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js29
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js227
-rw-r--r--spec/frontend/runner/components/cells/runner_actions_cell_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_delete_button_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js32
-rw-r--r--spec/frontend/runner/components/runner_filtered_search_bar_spec.js10
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_pause_button_spec.js24
-rw-r--r--spec/frontend/runner/components/runner_type_tabs_spec.js137
-rw-r--r--spec/frontend/runner/components/search_tokens/tag_token_spec.js3
-rw-r--r--spec/frontend/runner/components/stat/runner_count_spec.js148
-rw-r--r--spec/frontend/runner/components/stat/runner_stats_spec.js61
-rw-r--r--spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js213
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js211
-rw-r--r--spec/frontend/runner/mock_data.js212
-rw-r--r--spec/frontend/runner/runner_search_utils_spec.js212
16 files changed, 957 insertions, 574 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 28e7d192938..433be5d5027 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
@@ -9,6 +9,7 @@ import { redirectTo } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue';
+import RunnerDetails from '~/runner/components/runner_details.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';
@@ -37,6 +38,7 @@ describe('AdminRunnerShowApp', () => {
let mockRunnerQuery;
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
+ const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton);
const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton);
const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton);
@@ -179,12 +181,32 @@ describe('AdminRunnerShowApp', () => {
});
});
+ describe('When loading', () => {
+ beforeEach(() => {
+ mockRunnerQueryResult();
+
+ createComponent();
+ });
+
+ it('does not show runner details', () => {
+ expect(findRunnerDetails().exists()).toBe(false);
+ });
+
+ it('does not show runner jobs', () => {
+ expect(findRunnersJobs().exists()).toBe(false);
+ });
+ });
+
describe('When there is an error', () => {
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
await createComponent();
});
+ it('does not show runner details', () => {
+ expect(findRunnerDetails().exists()).toBe(false);
+ });
+
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
@@ -201,13 +223,6 @@ describe('AdminRunnerShowApp', () => {
const stubs = {
GlTab,
GlTabs,
- RunnerDetails: {
- template: `
- <div>
- <slot name="jobs-tab"></slot>
- </div>
- `,
- },
};
it('without a runner, shows no jobs', () => {
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 3d25ad075de..aa1aa723491 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -10,9 +10,11 @@ import {
} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
+import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
+import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
import { createLocalState } from '~/runner/graphql/list/local_state';
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
@@ -20,6 +22,7 @@ import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_
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 RunnerCount from '~/runner/components/stat/runner_count.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
@@ -30,8 +33,6 @@ import {
CREATED_DESC,
DEFAULT_SORT,
INSTANCE_TYPE,
- GROUP_TYPE,
- PROJECT_TYPE,
PARAM_KEY_PAUSED,
PARAM_KEY_STATUS,
PARAM_KEY_TAG,
@@ -40,15 +41,14 @@ import {
STATUS_STALE,
RUNNER_PAGE_SIZE,
} from '~/runner/constants';
-import adminRunnersQuery from '~/runner/graphql/list/admin_runners.query.graphql';
-import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.query.graphql';
+import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.graphql';
+import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql';
import { captureException } from '~/runner/sentry_utils';
-import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import {
- runnersData,
+ allRunnersData,
runnersCountData,
- runnersDataPaginated,
+ allRunnersDataPaginated,
onlineContactTimeoutSecs,
staleTimeoutSecs,
emptyStateSvgPath,
@@ -56,9 +56,12 @@ import {
} from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
-const mockRunners = runnersData.data.runners.nodes;
+const mockRunners = allRunnersData.data.runners.nodes;
const mockRunnersCount = runnersCountData.data.runners.count;
+const mockRunnersHandler = jest.fn();
+const mockRunnersCountHandler = jest.fn();
+
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
@@ -71,8 +74,6 @@ Vue.use(GlToast);
describe('AdminRunnersApp', () => {
let wrapper;
- let mockRunnersQuery;
- let mockRunnersCountQuery;
let cacheConfig;
let localMutations;
@@ -85,7 +86,6 @@ describe('AdminRunnersApp', () => {
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page');
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
- const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const createComponent = ({
props = {},
@@ -96,8 +96,8 @@ describe('AdminRunnersApp', () => {
({ cacheConfig, localMutations } = createLocalState());
const handlers = [
- [adminRunnersQuery, mockRunnersQuery],
- [adminRunnersCountQuery, mockRunnersCountQuery],
+ [allRunnersQuery, mockRunnersHandler],
+ [allRunnersCountQuery, mockRunnersCountHandler],
];
wrapper = mountFn(AdminRunnersApp, {
@@ -116,110 +116,62 @@ describe('AdminRunnersApp', () => {
},
...options,
});
- };
- beforeEach(async () => {
- setWindowLocation('/admin/runners');
+ return waitForPromises();
+ };
- mockRunnersQuery = jest.fn().mockResolvedValue(runnersData);
- mockRunnersCountQuery = jest.fn().mockResolvedValue(runnersCountData);
- createComponent();
- await waitForPromises();
+ beforeEach(() => {
+ mockRunnersHandler.mockResolvedValue(allRunnersData);
+ mockRunnersCountHandler.mockResolvedValue(runnersCountData);
});
afterEach(() => {
- mockRunnersQuery.mockReset();
- mockRunnersCountQuery.mockReset();
+ mockRunnersHandler.mockReset();
+ mockRunnersCountHandler.mockReset();
wrapper.destroy();
});
it('shows the runner tabs with a runner count for each type', async () => {
- mockRunnersCountQuery.mockImplementation(({ type }) => {
- let count;
- switch (type) {
- case INSTANCE_TYPE:
- count = 3;
- break;
- case GROUP_TYPE:
- count = 2;
- break;
- case PROJECT_TYPE:
- count = 1;
- break;
- default:
- count = 6;
- break;
- }
- return Promise.resolve({ data: { runners: { count } } });
- });
-
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
-
- expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
- `All 6 Instance 3 Group 2 Project 1`,
- );
- });
-
- it('shows the runner tabs with a formatted runner count', async () => {
- mockRunnersCountQuery.mockImplementation(({ type }) => {
- let count;
- switch (type) {
- case INSTANCE_TYPE:
- count = 3000;
- break;
- case GROUP_TYPE:
- count = 2000;
- break;
- case PROJECT_TYPE:
- count = 1000;
- break;
- default:
- count = 6000;
- break;
- }
- return Promise.resolve({ data: { runners: { count } } });
- });
-
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
+ await createComponent({ mountFn: mountExtended });
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
- `All 6,000 Instance 3,000 Group 2,000 Project 1,000`,
+ `All ${mockRunnersCount} Instance ${mockRunnersCount} Group ${mockRunnersCount} Project ${mockRunnersCount}`,
);
});
it('shows the runner setup instructions', () => {
+ createComponent();
+
expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
expect(findRegistrationDropdown().props('type')).toBe(INSTANCE_TYPE);
});
it('shows total runner counts', async () => {
- expect(mockRunnersCountQuery).toHaveBeenCalledWith({
- status: STATUS_ONLINE,
- });
- expect(mockRunnersCountQuery).toHaveBeenCalledWith({
- status: STATUS_OFFLINE,
- });
- expect(mockRunnersCountQuery).toHaveBeenCalledWith({
- status: STATUS_STALE,
- });
+ await createComponent({ mountFn: mountExtended });
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockRunnersCount,
- offlineRunnersCount: mockRunnersCount,
- staleRunnersCount: mockRunnersCount,
- });
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_ONLINE });
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_OFFLINE });
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({ status: STATUS_STALE });
+
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Online runners')} ${mockRunnersCount}`,
+ );
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Offline runners')} ${mockRunnersCount}`,
+ );
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Stale runners')} ${mockRunnersCount}`,
+ );
});
- it('shows the runners list', () => {
+ it('shows the runners list', async () => {
+ await createComponent();
+
expect(findRunnerList().props('runners')).toEqual(mockRunners);
});
it('runner item links to the runner admin page', async () => {
- createComponent({ mountFn: mountExtended });
-
- await waitForPromises();
+ await createComponent({ mountFn: mountExtended });
const { id, shortSha } = mockRunners[0];
const numericId = getIdFromGraphQLId(id);
@@ -231,12 +183,9 @@ describe('AdminRunnersApp', () => {
});
it('renders runner actions for each runner', async () => {
- createComponent({ mountFn: mountExtended });
-
- await waitForPromises();
+ await createComponent({ mountFn: mountExtended });
const runnerActions = wrapper.find('tr [data-testid="td-actions"]').find(RunnerActionsCell);
-
const runner = mockRunners[0];
expect(runnerActions.props()).toEqual({
@@ -245,8 +194,10 @@ describe('AdminRunnersApp', () => {
});
});
- it('requests the runners with no filters', () => {
- expect(mockRunnersQuery).toHaveBeenLastCalledWith({
+ it('requests the runners with no filters', async () => {
+ await createComponent();
+
+ expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: undefined,
type: undefined,
sort: DEFAULT_SORT,
@@ -255,9 +206,9 @@ describe('AdminRunnersApp', () => {
});
it('sets tokens in the filtered search', () => {
- createComponent({ mountFn: mountExtended });
+ createComponent();
- expect(findFilteredSearch().props('tokens')).toEqual([
+ expect(findRunnerFilteredSearchBar().props('tokens')).toEqual([
expect.objectContaining({
type: PARAM_KEY_PAUSED,
options: expect.any(Array),
@@ -270,6 +221,7 @@ describe('AdminRunnersApp', () => {
type: PARAM_KEY_TAG,
recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
}),
+ upgradeStatusTokenConfig,
]);
});
@@ -282,12 +234,10 @@ describe('AdminRunnersApp', () => {
const FILTERED_COUNT_QUERIES = 4; // Smart queries that display a count of runners in tabs
beforeEach(async () => {
- mockRunnersCountQuery.mockClear();
+ mockRunnersCountHandler.mockClear();
- createComponent({ mountFn: mountExtended });
+ await createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
-
- await waitForPromises();
});
it('Links to the runner page', async () => {
@@ -298,12 +248,11 @@ describe('AdminRunnersApp', () => {
});
it('When runner is paused or unpaused, some data is refetched', async () => {
- expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
+ expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
- expect(mockRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES + FILTERED_COUNT_QUERIES);
-
+ expect(mockRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES + FILTERED_COUNT_QUERIES);
expect(showToast).toHaveBeenCalledTimes(0);
});
@@ -319,8 +268,12 @@ describe('AdminRunnersApp', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ONLINE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);
- createComponent();
- await waitForPromises();
+ await createComponent({
+ stubs: {
+ RunnerStats,
+ RunnerCount,
+ },
+ });
});
it('sets the filters in the search bar', () => {
@@ -336,7 +289,7 @@ describe('AdminRunnersApp', () => {
});
it('requests the runners with filter parameters', () => {
- expect(mockRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: STATUS_ONLINE,
type: INSTANCE_TYPE,
tagList: ['tag1'],
@@ -346,21 +299,22 @@ describe('AdminRunnersApp', () => {
});
it('fetches count results for requested status', () => {
- expect(mockRunnersCountQuery).toHaveBeenCalledWith({
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({
type: INSTANCE_TYPE,
status: STATUS_ONLINE,
tagList: ['tag1'],
});
-
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockRunnersCount,
- });
});
});
describe('when a filter is selected by the user', () => {
beforeEach(() => {
- mockRunnersCountQuery.mockClear();
+ createComponent({
+ stubs: {
+ RunnerStats,
+ RunnerCount,
+ },
+ });
findRunnerFilteredSearchBar().vm.$emit('input', {
runnerType: null,
@@ -375,12 +329,12 @@ describe('AdminRunnersApp', () => {
it('updates the browser url', () => {
expect(updateHistory).toHaveBeenLastCalledWith({
title: expect.any(String),
- url: 'http://test.host/admin/runners?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC',
+ url: expect.stringContaining('?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC'),
});
});
it('requests the runners with filters', () => {
- expect(mockRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockRunnersHandler).toHaveBeenLastCalledWith({
status: STATUS_ONLINE,
tagList: ['tag1'],
sort: CREATED_ASC,
@@ -389,30 +343,10 @@ describe('AdminRunnersApp', () => {
});
it('fetches count results for requested status', () => {
- expect(mockRunnersCountQuery).toHaveBeenCalledWith({
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({
tagList: ['tag1'],
status: STATUS_ONLINE,
});
-
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockRunnersCount,
- });
- });
-
- it('skips fetching count results for status that were not in filter', () => {
- expect(mockRunnersCountQuery).not.toHaveBeenCalledWith({
- tagList: ['tag1'],
- status: STATUS_OFFLINE,
- });
- expect(mockRunnersCountQuery).not.toHaveBeenCalledWith({
- tagList: ['tag1'],
- status: STATUS_STALE,
- });
-
- expect(findRunnerStats().props()).toMatchObject({
- offlineRunnersCount: null,
- staleRunnersCount: null,
- });
});
});
@@ -458,14 +392,13 @@ describe('AdminRunnersApp', () => {
describe('when no runners are found', () => {
beforeEach(async () => {
- mockRunnersQuery = jest.fn().mockResolvedValue({
+ mockRunnersHandler.mockResolvedValue({
data: {
runners: { nodes: [] },
},
});
- createComponent();
- await waitForPromises();
+ await createComponent();
});
it('shows an empty state', () => {
@@ -490,9 +423,8 @@ describe('AdminRunnersApp', () => {
describe('when runners query fails', () => {
beforeEach(async () => {
- mockRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!'));
- createComponent();
- await waitForPromises();
+ mockRunnersHandler.mockRejectedValue(new Error('Error!'));
+ await createComponent();
});
it('error is shown to the user', async () => {
@@ -509,19 +441,18 @@ describe('AdminRunnersApp', () => {
describe('Pagination', () => {
beforeEach(async () => {
- mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated);
+ mockRunnersHandler.mockResolvedValue(allRunnersDataPaginated);
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
+ await createComponent({ mountFn: mountExtended });
});
it('navigates to the next page', async () => {
await findRunnerPaginationNext().trigger('click');
- expect(mockRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockRunnersHandler).toHaveBeenLastCalledWith({
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
- after: runnersDataPaginated.data.runners.pageInfo.endCursor,
+ after: allRunnersDataPaginated.data.runners.pageInfo.endCursor,
});
});
});
diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
index 7a949cb6505..ffd6f126627 100644
--- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
@@ -4,9 +4,9 @@ import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
-import { runnersData } from '../../mock_data';
+import { allRunnersData } from '../../mock_data';
-const mockRunner = runnersData.data.runners.nodes[0];
+const mockRunner = allRunnersData.data.runners.nodes[0];
describe('RunnerActionsCell', () => {
let wrapper;
diff --git a/spec/frontend/runner/components/runner_delete_button_spec.js b/spec/frontend/runner/components/runner_delete_button_spec.js
index b11c749d0a7..52fe803c536 100644
--- a/spec/frontend/runner/components/runner_delete_button_spec.js
+++ b/spec/frontend/runner/components/runner_delete_button_spec.js
@@ -17,9 +17,9 @@ import {
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
-import { runnersData } from '../mock_data';
+import { allRunnersData } from '../mock_data';
-const mockRunner = runnersData.data.runners.nodes[0];
+const mockRunner = allRunnersData.data.runners.nodes[0];
const mockRunnerId = getIdFromGraphQLId(mockRunner.id);
Vue.use(VueApollo);
diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js
index 9e0f7014750..552ee29b6f9 100644
--- a/spec/frontend/runner/components/runner_details_spec.js
+++ b/spec/frontend/runner/components/runner_details_spec.js
@@ -25,12 +25,7 @@ describe('RunnerDetails', () => {
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
- const createComponent = ({
- props = {},
- stubs,
- mountFn = shallowMountExtended,
- ...options
- } = {}) => {
+ const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerDetails, {
propsData: {
...props,
@@ -39,7 +34,6 @@ describe('RunnerDetails', () => {
RunnerDetail,
...stubs,
},
- ...options,
});
};
@@ -47,16 +41,6 @@ describe('RunnerDetails', () => {
wrapper.destroy();
});
- it('when no runner is present, no contents are shown', () => {
- createComponent({
- props: {
- runner: null,
- },
- });
-
- expect(wrapper.text()).toBe('');
- });
-
describe('Details tab', () => {
describe.each`
field | runner | expectedValue
@@ -141,18 +125,4 @@ describe('RunnerDetails', () => {
});
});
});
-
- describe('Jobs tab slot', () => {
- it('shows job tab slot', () => {
- const JOBS_TAB = '<div>Jobs Tab</div>';
-
- createComponent({
- slots: {
- 'jobs-tab': JOBS_TAB,
- },
- });
-
- expect(wrapper.html()).toContain(JOBS_TAB);
- });
- });
});
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 b1b436e5443..83fb1764c6d 100644
--- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
@@ -89,6 +89,16 @@ describe('RunnerList', () => {
]);
});
+ it('can be configured with null or undefined tokens, which are ignored', () => {
+ createComponent({
+ props: {
+ tokens: [statusTokenConfig, null, undefined],
+ },
+ });
+
+ expect(findFilteredSearch().props('tokens')).toEqual([statusTokenConfig]);
+ });
+
it('fails validation for v-model with the wrong shape', () => {
expect(() => {
createComponent({ props: { value: { filters: 'wrong_filters', sort: 'sort' } } });
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 872394430ae..eca4bbc3490 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -7,9 +7,9 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
-import { runnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
+import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
-const mockRunners = runnersData.data.runners.nodes;
+const mockRunners = allRunnersData.data.runners.nodes;
const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {
diff --git a/spec/frontend/runner/components/runner_pause_button_spec.js b/spec/frontend/runner/components/runner_pause_button_spec.js
index 9ebb30b6ed7..61476007571 100644
--- a/spec/frontend/runner/components/runner_pause_button_spec.js
+++ b/spec/frontend/runner/components/runner_pause_button_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { GlButton } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -16,9 +16,9 @@ import {
} from '~/runner/constants';
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
-import { runnersData } from '../mock_data';
+import { allRunnersData } from '../mock_data';
-const mockRunner = runnersData.data.runners.nodes[0];
+const mockRunner = allRunnersData.data.runners.nodes[0];
Vue.use(VueApollo);
@@ -115,15 +115,20 @@ describe('RunnerPauseButton', () => {
});
describe(`Immediately after the ${icon} button is clicked`, () => {
- beforeEach(async () => {
+ const setup = async () => {
findBtn().vm.$emit('click');
- });
+ await nextTick();
+ };
it('The button has a loading state', async () => {
+ await setup();
+
expect(findBtn().props('loading')).toBe(true);
});
it('The stale tooltip is removed', async () => {
+ await setup();
+
expect(getTooltip()).toBe('');
});
});
@@ -237,15 +242,20 @@ describe('RunnerPauseButton', () => {
});
describe('Immediately after the button is clicked', () => {
- beforeEach(async () => {
+ const setup = async () => {
findBtn().vm.$emit('click');
- });
+ await nextTick();
+ };
it('The button has a loading state', async () => {
+ await setup();
+
expect(findBtn().props('loading')).toBe(true);
});
it('The stale tooltip is removed', async () => {
+ await setup();
+
expect(getTooltip()).toBe('');
});
});
diff --git a/spec/frontend/runner/components/runner_type_tabs_spec.js b/spec/frontend/runner/components/runner_type_tabs_spec.js
index 9da5d842d8f..22d2a9e60f7 100644
--- a/spec/frontend/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/runner/components/runner_type_tabs_spec.js
@@ -1,10 +1,30 @@
import { GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
+import RunnerCount from '~/runner/components/stat/runner_count.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
const mockSearch = { runnerType: null, filters: [], pagination: { page: 1 }, sort: 'CREATED_DESC' };
+const mockCount = (type, multiplier = 1) => {
+ let count;
+ switch (type) {
+ case INSTANCE_TYPE:
+ count = 3;
+ break;
+ case GROUP_TYPE:
+ count = 2;
+ break;
+ case PROJECT_TYPE:
+ count = 1;
+ break;
+ default:
+ count = 6;
+ break;
+ }
+ return count * multiplier;
+};
+
describe('RunnerTypeTabs', () => {
let wrapper;
@@ -13,33 +33,94 @@ describe('RunnerTypeTabs', () => {
findTabs()
.filter((tab) => tab.attributes('active') === 'true')
.at(0);
- const getTabsTitles = () => findTabs().wrappers.map((tab) => tab.text());
+ const getTabsTitles = () => findTabs().wrappers.map((tab) => tab.text().replace(/\s+/g, ' '));
- const createComponent = ({ props, ...options } = {}) => {
+ const createComponent = ({ props, stubs, ...options } = {}) => {
wrapper = shallowMount(RunnerTypeTabs, {
propsData: {
value: mockSearch,
+ countScope: INSTANCE_TYPE,
+ countVariables: {},
...props,
},
stubs: {
GlTab,
+ ...stubs,
},
...options,
});
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
it('Renders all options to filter runners by default', () => {
+ createComponent();
+
expect(getTabsTitles()).toEqual(['All', 'Instance', 'Group', 'Project']);
});
+ it('Shows count when receiving a number', () => {
+ createComponent({
+ stubs: {
+ RunnerCount: {
+ props: ['variables'],
+ render() {
+ return this.$scopedSlots.default({
+ count: mockCount(this.variables.type),
+ });
+ },
+ },
+ },
+ });
+
+ expect(getTabsTitles()).toEqual([`All 6`, `Instance 3`, `Group 2`, `Project 1`]);
+ });
+
+ it('Shows formatted count when receiving a large number', () => {
+ createComponent({
+ stubs: {
+ RunnerCount: {
+ props: ['variables'],
+ render() {
+ return this.$scopedSlots.default({
+ count: mockCount(this.variables.type, 1000),
+ });
+ },
+ },
+ },
+ });
+
+ expect(getTabsTitles()).toEqual([
+ `All 6,000`,
+ `Instance 3,000`,
+ `Group 2,000`,
+ `Project 1,000`,
+ ]);
+ });
+
+ it('Renders a count next to each tab', () => {
+ const mockVariables = {
+ paused: true,
+ status: 'ONLINE',
+ };
+
+ createComponent({
+ props: {
+ countVariables: mockVariables,
+ },
+ });
+
+ findTabs().wrappers.forEach((tab) => {
+ expect(tab.find(RunnerCount).props()).toEqual({
+ scope: INSTANCE_TYPE,
+ skip: false,
+ variables: expect.objectContaining(mockVariables),
+ });
+ });
+ });
+
it('Renders fewer options to filter runners', () => {
createComponent({
props: {
@@ -51,6 +132,8 @@ describe('RunnerTypeTabs', () => {
});
it('"All" is selected by default', () => {
+ createComponent();
+
expect(findActiveTab().text()).toBe('All');
});
@@ -71,6 +154,7 @@ describe('RunnerTypeTabs', () => {
const emittedValue = () => wrapper.emitted('input')[0][0];
beforeEach(() => {
+ createComponent();
findTabs().at(2).vm.$emit('click');
});
@@ -89,27 +173,30 @@ describe('RunnerTypeTabs', () => {
});
});
- describe('When using a custom slot', () => {
- const mockContent = 'content';
-
- beforeEach(() => {
- createComponent({
- scopedSlots: {
- title: `
- <span>
- {{props.tab.title}} ${mockContent}
- </span>`,
- },
+ describe('Component API', () => {
+ describe('When .refetch() is called', () => {
+ let mockRefetch;
+
+ beforeEach(() => {
+ mockRefetch = jest.fn();
+
+ createComponent({
+ stubs: {
+ RunnerCount: {
+ methods: {
+ refetch: mockRefetch,
+ },
+ render() {},
+ },
+ },
+ });
+
+ wrapper.vm.refetch();
});
- });
- it('Renders tabs with additional information', () => {
- expect(findTabs().wrappers.map((tab) => tab.text())).toEqual([
- `All ${mockContent}`,
- `Instance ${mockContent}`,
- `Group ${mockContent}`,
- `Project ${mockContent}`,
- ]);
+ it('refetch is called for each count', () => {
+ expect(mockRefetch).toHaveBeenCalledTimes(4);
+ });
});
});
});
diff --git a/spec/frontend/runner/components/search_tokens/tag_token_spec.js b/spec/frontend/runner/components/search_tokens/tag_token_spec.js
index 52557ff716d..22f0561ca5f 100644
--- a/spec/frontend/runner/components/search_tokens/tag_token_spec.js
+++ b/spec/frontend/runner/components/search_tokens/tag_token_spec.js
@@ -134,8 +134,6 @@ describe('TagToken', () => {
describe('when the users filters suggestions', () => {
beforeEach(async () => {
findGlFilteredSearchToken().vm.$emit('input', { data: mockSearchTerm });
-
- jest.runAllTimers();
});
it('requests filtered tags suggestions', async () => {
@@ -145,6 +143,7 @@ describe('TagToken', () => {
});
it('shows the loading icon', async () => {
+ findGlFilteredSearchToken().vm.$emit('input', { data: mockSearchTerm });
await nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
diff --git a/spec/frontend/runner/components/stat/runner_count_spec.js b/spec/frontend/runner/components/stat/runner_count_spec.js
new file mode 100644
index 00000000000..89b51b1b4a7
--- /dev/null
+++ b/spec/frontend/runner/components/stat/runner_count_spec.js
@@ -0,0 +1,148 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMount } from '@vue/test-utils';
+import RunnerCount from '~/runner/components/stat/runner_count.vue';
+import { INSTANCE_TYPE, GROUP_TYPE } from '~/runner/constants';
+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 { runnersCountData, groupRunnersCountData } from '../../mock_data';
+
+jest.mock('~/runner/sentry_utils');
+
+Vue.use(VueApollo);
+
+describe('RunnerCount', () => {
+ let wrapper;
+ let mockRunnersCountHandler;
+ let mockGroupRunnersCountHandler;
+
+ const createComponent = ({ props = {}, ...options } = {}) => {
+ const handlers = [
+ [allRunnersCountQuery, mockRunnersCountHandler],
+ [groupRunnersCountQuery, mockGroupRunnersCountHandler],
+ ];
+
+ wrapper = shallowMount(RunnerCount, {
+ apolloProvider: createMockApollo(handlers),
+ propsData: {
+ ...props,
+ },
+ scopedSlots: {
+ default: '<strong>{{props.count}}</strong>',
+ },
+ ...options,
+ });
+
+ return waitForPromises();
+ };
+
+ beforeEach(() => {
+ mockRunnersCountHandler = jest.fn().mockResolvedValue(runnersCountData);
+ mockGroupRunnersCountHandler = jest.fn().mockResolvedValue(groupRunnersCountData);
+ });
+
+ describe('in admin scope', () => {
+ const mockVariables = { status: 'ONLINE' };
+
+ beforeEach(async () => {
+ await createComponent({ props: { scope: INSTANCE_TYPE } });
+ });
+
+ it('fetches data from admin query', () => {
+ expect(mockRunnersCountHandler).toHaveBeenCalledTimes(1);
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith({});
+ });
+
+ it('fetches data with filters', async () => {
+ await createComponent({ props: { scope: INSTANCE_TYPE, variables: mockVariables } });
+
+ expect(mockRunnersCountHandler).toHaveBeenCalledTimes(2);
+ expect(mockRunnersCountHandler).toHaveBeenCalledWith(mockVariables);
+
+ expect(wrapper.html()).toBe(`<strong>${runnersCountData.data.runners.count}</strong>`);
+ });
+
+ it('does not fetch from the group query', async () => {
+ expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
+ });
+
+ describe('when this query is skipped after data was loaded', () => {
+ beforeEach(async () => {
+ wrapper.setProps({ skip: true });
+
+ await nextTick();
+ });
+
+ it('clears current data', () => {
+ expect(wrapper.html()).toBe('<strong></strong>');
+ });
+ });
+ });
+
+ describe('when skipping query', () => {
+ beforeEach(async () => {
+ await createComponent({ props: { scope: INSTANCE_TYPE, skip: true } });
+ });
+
+ it('does not fetch data', async () => {
+ expect(mockRunnersCountHandler).not.toHaveBeenCalled();
+ expect(mockGroupRunnersCountHandler).not.toHaveBeenCalled();
+
+ expect(wrapper.html()).toBe('<strong></strong>');
+ });
+ });
+
+ describe('when runners query fails', () => {
+ const mockError = new Error('error!');
+
+ beforeEach(async () => {
+ mockRunnersCountHandler.mockRejectedValue(mockError);
+
+ await createComponent({ props: { scope: INSTANCE_TYPE } });
+ });
+
+ it('data is not shown and error is reported', async () => {
+ expect(wrapper.html()).toBe('<strong></strong>');
+
+ expect(captureException).toHaveBeenCalledWith({
+ component: 'RunnerCount',
+ error: mockError,
+ });
+ });
+ });
+
+ describe('in group scope', () => {
+ beforeEach(async () => {
+ await createComponent({ props: { scope: GROUP_TYPE } });
+ });
+
+ it('fetches data from the group query', async () => {
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(1);
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({});
+
+ expect(wrapper.html()).toBe(
+ `<strong>${groupRunnersCountData.data.group.runners.count}</strong>`,
+ );
+ });
+
+ it('does not fetch from the group query', () => {
+ expect(mockRunnersCountHandler).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when .refetch() is called', () => {
+ beforeEach(async () => {
+ await createComponent({ props: { scope: INSTANCE_TYPE } });
+ wrapper.vm.refetch();
+ });
+
+ it('data is not shown and error is reported', async () => {
+ expect(mockRunnersCountHandler).toHaveBeenCalledTimes(2);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js
index 68db8621ef0..f1ba6403dfb 100644
--- a/spec/frontend/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/runner/components/stat/runner_stats_spec.js
@@ -1,21 +1,24 @@
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 { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
+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 createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
+ const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(RunnerStats, {
propsData: {
- onlineRunnersCount: 3,
- offlineRunnersCount: 2,
- staleRunnersCount: 1,
+ scope: INSTANCE_TYPE,
+ variables: {},
...props,
},
+ ...options,
});
};
@@ -24,13 +27,46 @@ describe('RunnerStats', () => {
});
it('Displays all the stats', () => {
- createComponent({ mountFn: mount });
+ const mockCounts = {
+ [STATUS_ONLINE]: 3,
+ [STATUS_OFFLINE]: 2,
+ [STATUS_STALE]: 1,
+ };
+
+ createComponent({
+ mountFn: mount,
+ stubs: {
+ RunnerCount: {
+ props: ['variables'],
+ render() {
+ return this.$scopedSlots.default({
+ count: mockCounts[this.variables.status],
+ });
+ },
+ },
+ },
+ });
+
+ const text = wrapper.text();
+ expect(text).toMatch(`${s__('Runners|Online runners')} 3`);
+ expect(text).toMatch(`${s__('Runners|Offline runners')} 2`);
+ expect(text).toMatch(`${s__('Runners|Stale runners')} 1`);
+ });
- const stats = wrapper.text();
+ it('Displays counts for filtered searches', () => {
+ createComponent({ props: { variables: { paused: true } } });
- expect(stats).toMatch('Online runners 3');
- expect(stats).toMatch('Offline runners 2');
- expect(stats).toMatch('Stale runners 1');
+ 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`
@@ -38,9 +74,10 @@ describe('RunnerStats', () => {
${0} | ${STATUS_ONLINE}
${1} | ${STATUS_OFFLINE}
${2} | ${STATUS_STALE}
- `('Displays status types at index $i', ({ i, status }) => {
- createComponent();
+ `('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);
});
});
diff --git a/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js b/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js
new file mode 100644
index 00000000000..2065874c288
--- /dev/null
+++ b/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js
@@ -0,0 +1,213 @@
+import Vue from 'vue';
+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';
+import { redirectTo } from '~/lib/utils/url_utility';
+
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import RunnerHeader from '~/runner/components/runner_header.vue';
+import RunnerDetails from '~/runner/components/runner_details.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 runnerQuery from '~/runner/graphql/show/runner.query.graphql';
+import GroupRunnerShowApp from '~/runner/group_runner_show/group_runner_show_app.vue';
+import { captureException } from '~/runner/sentry_utils';
+import { saveAlertToLocalStorage } from '~/runner/local_storage_alert/save_alert_to_local_storage';
+
+import { runnerData } from '../mock_data';
+
+jest.mock('~/runner/local_storage_alert/save_alert_to_local_storage');
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+jest.mock('~/lib/utils/url_utility');
+
+const mockRunner = runnerData.data.runner;
+const mockRunnerGraphqlId = mockRunner.id;
+const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
+const mockRunnersPath = '/groups/group1/-/runners';
+const mockEditGroupRunnerPath = `/groups/group1/-/runners/${mockRunnerId}/edit`;
+
+Vue.use(VueApollo);
+
+describe('GroupRunnerShowApp', () => {
+ let wrapper;
+ let mockRunnerQuery;
+
+ const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
+ const findRunnerDetails = () => wrapper.findComponent(RunnerDetails);
+ const findRunnerDeleteButton = () => wrapper.findComponent(RunnerDeleteButton);
+ const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton);
+ const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton);
+
+ const mockRunnerQueryResult = (runner = {}) => {
+ mockRunnerQuery = jest.fn().mockResolvedValue({
+ data: {
+ runner: { ...mockRunner, ...runner },
+ },
+ });
+ };
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
+ wrapper = mountFn(GroupRunnerShowApp, {
+ apolloProvider: createMockApollo([[runnerQuery, mockRunnerQuery]]),
+ propsData: {
+ runnerId: mockRunnerId,
+ runnersPath: mockRunnersPath,
+ editGroupRunnerPath: mockEditGroupRunnerPath,
+ ...props,
+ },
+ ...options,
+ });
+
+ return waitForPromises();
+ };
+
+ afterEach(() => {
+ mockRunnerQuery.mockReset();
+ wrapper.destroy();
+ });
+
+ describe('When showing runner details', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult();
+
+ await createComponent({ mountFn: mountExtended });
+ });
+
+ it('expect GraphQL ID to be requested', async () => {
+ expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
+ });
+
+ it('displays the header', async () => {
+ expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId}`);
+ });
+
+ it('displays edit, pause, delete buttons', async () => {
+ expect(findRunnerEditButton().exists()).toBe(true);
+ expect(findRunnerPauseButton().exists()).toBe(true);
+ expect(findRunnerDeleteButton().exists()).toBe(true);
+ });
+
+ it('shows basic runner details', () => {
+ const expected = `Description Instance runner
+ Last contact Never contacted
+ Version 1.0.0
+ IP Address 127.0.0.1
+ Executor None
+ Architecture None
+ Platform darwin
+ Configuration Runs untagged jobs
+ Maximum job timeout None
+ Tags None`.replace(/\s+/g, ' ');
+
+ expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected);
+ });
+
+ it('renders runner details component', () => {
+ expect(findRunnerDetails().props('runner')).toEqual(mockRunner);
+ });
+
+ describe('when runner cannot be updated', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult({
+ userPermissions: {
+ ...mockRunner.userPermissions,
+ updateRunner: false,
+ },
+ });
+
+ await createComponent({
+ mountFn: mountExtended,
+ });
+ });
+
+ it('does not display edit and pause buttons', () => {
+ expect(findRunnerEditButton().exists()).toBe(false);
+ expect(findRunnerPauseButton().exists()).toBe(false);
+ });
+
+ it('displays delete button', () => {
+ expect(findRunnerDeleteButton().exists()).toBe(true);
+ });
+ });
+
+ describe('when runner cannot be deleted', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult({
+ userPermissions: {
+ ...mockRunner.userPermissions,
+ deleteRunner: false,
+ },
+ });
+
+ await createComponent({
+ mountFn: mountExtended,
+ });
+ });
+
+ it('does not display delete button', () => {
+ expect(findRunnerDeleteButton().exists()).toBe(false);
+ });
+
+ it('displays edit and pause buttons', () => {
+ expect(findRunnerEditButton().exists()).toBe(true);
+ expect(findRunnerPauseButton().exists()).toBe(true);
+ });
+ });
+
+ describe('when runner is deleted', () => {
+ beforeEach(async () => {
+ await createComponent({
+ mountFn: mountExtended,
+ });
+ });
+
+ it('redirects to the runner list page', () => {
+ findRunnerDeleteButton().vm.$emit('deleted', { message: 'Runner deleted' });
+
+ expect(saveAlertToLocalStorage).toHaveBeenCalledWith({
+ message: 'Runner deleted',
+ variant: VARIANT_SUCCESS,
+ });
+ expect(redirectTo).toHaveBeenCalledWith(mockRunnersPath);
+ });
+ });
+ });
+
+ describe('When loading', () => {
+ beforeEach(() => {
+ mockRunnerQueryResult();
+
+ createComponent();
+ });
+
+ it('does not show runner details', () => {
+ expect(findRunnerDetails().exists()).toBe(false);
+ });
+ });
+
+ describe('When there is an error', () => {
+ beforeEach(async () => {
+ mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
+ await createComponent();
+ });
+
+ it('does not show runner details', () => {
+ expect(findRunnerDetails().exists()).toBe(false);
+ });
+
+ it('error is reported to sentry', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ error: new Error('Error!'),
+ component: 'GroupRunnerShowApp',
+ });
+ });
+
+ it('error is shown to the user', () => {
+ expect(createAlert).toHaveBeenCalled();
+ });
+ });
+});
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 eb9f85a7d0f..9c42b0d6865 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -10,6 +10,7 @@ import {
} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
+import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
@@ -18,6 +19,7 @@ import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_
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 RunnerCount from '~/runner/components/stat/runner_count.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
@@ -28,7 +30,6 @@ import {
DEFAULT_SORT,
INSTANCE_TYPE,
GROUP_TYPE,
- PROJECT_TYPE,
PARAM_KEY_PAUSED,
PARAM_KEY_STATUS,
PARAM_KEY_TAG,
@@ -38,11 +39,10 @@ import {
RUNNER_PAGE_SIZE,
I18N_EDIT,
} from '~/runner/constants';
-import getGroupRunnersQuery from '~/runner/graphql/list/group_runners.query.graphql';
-import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
+import groupRunnersQuery from '~/runner/graphql/list/group_runners.query.graphql';
+import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql';
import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue';
import { captureException } from '~/runner/sentry_utils';
-import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import {
groupRunnersData,
groupRunnersDataPaginated,
@@ -61,6 +61,9 @@ const mockRegistrationToken = 'AABBCC';
const mockGroupRunnersEdges = groupRunnersData.data.group.runners.edges;
const mockGroupRunnersCount = mockGroupRunnersEdges.length;
+const mockGroupRunnersHandler = jest.fn();
+const mockGroupRunnersCountHandler = jest.fn();
+
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
@@ -70,8 +73,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
- let mockGroupRunnersQuery;
- let mockGroupRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
@@ -83,17 +84,11 @@ describe('GroupRunnersApp', () => {
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page');
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
- const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
-
- const mockCountQueryResult = (count) =>
- Promise.resolve({
- data: { group: { id: groupRunnersCountData.data.group.id, runners: { count } } },
- });
- const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
const handlers = [
- [getGroupRunnersQuery, mockGroupRunnersQuery],
- [getGroupRunnersCountQuery, mockGroupRunnersCountQuery],
+ [groupRunnersQuery, mockGroupRunnersHandler],
+ [groupRunnersCountQuery, mockGroupRunnersCountHandler],
];
wrapper = mountFn(GroupRunnersApp, {
@@ -110,90 +105,76 @@ describe('GroupRunnersApp', () => {
emptyStateSvgPath,
emptyStateFilteredSvgPath,
},
+ ...options,
});
+
+ return waitForPromises();
};
beforeEach(async () => {
- setWindowLocation(`/groups/${mockGroupFullPath}/-/runners`);
+ mockGroupRunnersHandler.mockResolvedValue(groupRunnersData);
+ mockGroupRunnersCountHandler.mockResolvedValue(groupRunnersCountData);
+ });
- mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersData);
- mockGroupRunnersCountQuery = jest.fn().mockResolvedValue(groupRunnersCountData);
+ afterEach(() => {
+ mockGroupRunnersHandler.mockReset();
+ mockGroupRunnersCountHandler.mockReset();
+ wrapper.destroy();
+ });
+
+ it('shows the runner tabs with a runner count for each type', async () => {
+ await createComponent({ mountFn: mountExtended });
+ expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
+ `All ${mockGroupRunnersCount} Group ${mockGroupRunnersCount} Project ${mockGroupRunnersCount}`,
+ );
+ });
+
+ it('shows the runner setup instructions', () => {
createComponent();
- await waitForPromises();
+
+ expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
+ expect(findRegistrationDropdown().props('type')).toBe(GROUP_TYPE);
});
it('shows total runner counts', async () => {
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
- groupFullPath: mockGroupFullPath,
+ await createComponent({ mountFn: mountExtended });
+
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
status: STATUS_ONLINE,
- });
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
groupFullPath: mockGroupFullPath,
- status: STATUS_OFFLINE,
});
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
+ status: STATUS_OFFLINE,
groupFullPath: mockGroupFullPath,
- status: STATUS_STALE,
- });
-
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockGroupRunnersCount,
- offlineRunnersCount: mockGroupRunnersCount,
- staleRunnersCount: mockGroupRunnersCount,
- });
- });
-
- it('shows the runner tabs with a runner count for each type', async () => {
- mockGroupRunnersCountQuery.mockImplementation(({ type }) => {
- switch (type) {
- case GROUP_TYPE:
- return mockCountQueryResult(2);
- case PROJECT_TYPE:
- return mockCountQueryResult(1);
- default:
- return mockCountQueryResult(4);
- }
});
-
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
-
- expect(findRunnerTypeTabs().text()).toMatchInterpolatedText('All 4 Group 2 Project 1');
- });
-
- it('shows the runner tabs with a formatted runner count', async () => {
- mockGroupRunnersCountQuery.mockImplementation(({ type }) => {
- switch (type) {
- case GROUP_TYPE:
- return mockCountQueryResult(2000);
- case PROJECT_TYPE:
- return mockCountQueryResult(1000);
- default:
- return mockCountQueryResult(3000);
- }
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
+ status: STATUS_STALE,
+ groupFullPath: mockGroupFullPath,
});
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
-
- expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
- 'All 3,000 Group 2,000 Project 1,000',
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Online runners')} ${mockGroupRunnersCount}`,
+ );
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Offline runners')} ${mockGroupRunnersCount}`,
+ );
+ expect(findRunnerStats().text()).toContain(
+ `${s__('Runners|Stale runners')} ${mockGroupRunnersCount}`,
);
});
- it('shows the runner setup instructions', () => {
- expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
- expect(findRegistrationDropdown().props('type')).toBe(GROUP_TYPE);
- });
+ it('shows the runners list', async () => {
+ await createComponent();
- it('shows the runners list', () => {
const runners = findRunnerList().props('runners');
expect(runners).toEqual(mockGroupRunnersEdges.map(({ node }) => node));
});
- it('requests the runners with group path and no other filters', () => {
- expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
+ it('requests the runners with group path and no other filters', async () => {
+ await createComponent();
+
+ expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: undefined,
type: undefined,
@@ -203,9 +184,9 @@ describe('GroupRunnersApp', () => {
});
it('sets tokens in the filtered search', () => {
- createComponent({ mountFn: mountExtended });
+ createComponent();
- const tokens = findFilteredSearch().props('tokens');
+ const tokens = findRunnerFilteredSearchBar().props('tokens');
expect(tokens).toEqual([
expect.objectContaining({
@@ -229,12 +210,8 @@ describe('GroupRunnersApp', () => {
const FILTERED_COUNT_QUERIES = 3; // Smart queries that display a count of runners in tabs
beforeEach(async () => {
- mockGroupRunnersCountQuery.mockClear();
-
- createComponent({ mountFn: mountExtended });
+ await createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
-
- await waitForPromises();
});
it('view link is displayed correctly', () => {
@@ -254,11 +231,11 @@ describe('GroupRunnersApp', () => {
});
it('When runner is paused or unpaused, some data is refetched', async () => {
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(COUNT_QUERIES);
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(COUNT_QUERIES);
findRunnerActionsCell().vm.$emit('toggledPaused');
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledTimes(
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledTimes(
COUNT_QUERIES + FILTERED_COUNT_QUERIES,
);
@@ -277,8 +254,12 @@ describe('GroupRunnersApp', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ONLINE}&runner_type[]=${INSTANCE_TYPE}`);
- createComponent();
- await waitForPromises();
+ await createComponent({
+ stubs: {
+ RunnerStats,
+ RunnerCount,
+ },
+ });
});
it('sets the filters in the search bar', () => {
@@ -291,7 +272,7 @@ describe('GroupRunnersApp', () => {
});
it('requests the runners with filter parameters', () => {
- expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: STATUS_ONLINE,
type: INSTANCE_TYPE,
@@ -301,20 +282,23 @@ describe('GroupRunnersApp', () => {
});
it('fetches count results for requested status', () => {
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
groupFullPath: mockGroupFullPath,
type: INSTANCE_TYPE,
status: STATUS_ONLINE,
});
-
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockGroupRunnersCount,
- });
});
});
describe('when a filter is selected by the user', () => {
beforeEach(async () => {
+ createComponent({
+ stubs: {
+ RunnerStats,
+ RunnerCount,
+ },
+ });
+
findRunnerFilteredSearchBar().vm.$emit('input', {
runnerType: null,
filters: [
@@ -330,12 +314,12 @@ describe('GroupRunnersApp', () => {
it('updates the browser url', () => {
expect(updateHistory).toHaveBeenLastCalledWith({
title: expect.any(String),
- url: 'http://test.host/groups/group1/-/runners?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC',
+ url: expect.stringContaining('?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC'),
});
});
it('requests the runners with filters', () => {
- expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
status: STATUS_ONLINE,
tagList: ['tag1'],
@@ -345,33 +329,11 @@ describe('GroupRunnersApp', () => {
});
it('fetches count results for requested status', () => {
- expect(mockGroupRunnersCountQuery).toHaveBeenCalledWith({
+ expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({
groupFullPath: mockGroupFullPath,
tagList: ['tag1'],
status: STATUS_ONLINE,
});
-
- expect(findRunnerStats().props()).toMatchObject({
- onlineRunnersCount: mockGroupRunnersCount,
- });
- });
-
- it('skips fetching count results for status that were not in filter', () => {
- expect(mockGroupRunnersCountQuery).not.toHaveBeenCalledWith({
- groupFullPath: mockGroupFullPath,
- tagList: ['tag1'],
- status: STATUS_OFFLINE,
- });
- expect(mockGroupRunnersCountQuery).not.toHaveBeenCalledWith({
- groupFullPath: mockGroupFullPath,
- tagList: ['tag1'],
- status: STATUS_STALE,
- });
-
- expect(findRunnerStats().props()).toMatchObject({
- offlineRunnersCount: null,
- staleRunnersCount: null,
- });
});
});
@@ -382,7 +344,7 @@ describe('GroupRunnersApp', () => {
describe('when no runners are found', () => {
beforeEach(async () => {
- mockGroupRunnersQuery = jest.fn().mockResolvedValue({
+ mockGroupRunnersHandler.mockResolvedValue({
data: {
group: {
id: '1',
@@ -390,8 +352,7 @@ describe('GroupRunnersApp', () => {
},
},
});
- createComponent();
- await waitForPromises();
+ await createComponent();
});
it('shows an empty state', async () => {
@@ -401,9 +362,8 @@ describe('GroupRunnersApp', () => {
describe('when runners query fails', () => {
beforeEach(async () => {
- mockGroupRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!'));
- createComponent();
- await waitForPromises();
+ mockGroupRunnersHandler.mockRejectedValue(new Error('Error!'));
+ await createComponent();
});
it('error is shown to the user', async () => {
@@ -420,16 +380,15 @@ describe('GroupRunnersApp', () => {
describe('Pagination', () => {
beforeEach(async () => {
- mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersDataPaginated);
+ mockGroupRunnersHandler.mockResolvedValue(groupRunnersDataPaginated);
- createComponent({ mountFn: mountExtended });
- await waitForPromises();
+ await createComponent({ mountFn: mountExtended });
});
it('navigates to the next page', async () => {
await findRunnerPaginationNext().trigger('click');
- expect(mockGroupRunnersQuery).toHaveBeenLastCalledWith({
+ expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({
groupFullPath: mockGroupFullPath,
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js
index 3368fc21544..e5472ace817 100644
--- a/spec/frontend/runner/mock_data.js
+++ b/spec/frontend/runner/mock_data.js
@@ -10,14 +10,216 @@ import runnerJobsData from 'test_fixtures/graphql/runner/show/runner_jobs.query.
import runnerFormData from 'test_fixtures/graphql/runner/edit/runner_form.query.graphql.json';
// List queries
-import runnersData from 'test_fixtures/graphql/runner/list/admin_runners.query.graphql.json';
-import runnersDataPaginated from 'test_fixtures/graphql/runner/list/admin_runners.query.graphql.paginated.json';
-import runnersCountData from 'test_fixtures/graphql/runner/list/admin_runners_count.query.graphql.json';
+import allRunnersData from 'test_fixtures/graphql/runner/list/all_runners.query.graphql.json';
+import allRunnersDataPaginated from 'test_fixtures/graphql/runner/list/all_runners.query.graphql.paginated.json';
+import runnersCountData from 'test_fixtures/graphql/runner/list/all_runners_count.query.graphql.json';
import groupRunnersData from 'test_fixtures/graphql/runner/list/group_runners.query.graphql.json';
import groupRunnersDataPaginated from 'test_fixtures/graphql/runner/list/group_runners.query.graphql.paginated.json';
import groupRunnersCountData from 'test_fixtures/graphql/runner/list/group_runners_count.query.graphql.json';
+import { RUNNER_PAGE_SIZE } from '~/runner/constants';
+
// Other mock data
+
+// Mock searches and their corresponding urls
+export const mockSearchExamples = [
+ {
+ name: 'a default query',
+ 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',
+ urlQuery: '?status[]=ACTIVE',
+ search: {
+ runnerType: null,
+ filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'a single term text search',
+ urlQuery: '?search=something',
+ search: {
+ runnerType: null,
+ filters: [
+ {
+ type: 'filtered-search-term',
+ value: { data: 'something' },
+ },
+ ],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { search: 'something', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'a two terms text search',
+ urlQuery: '?search=something+else',
+ search: {
+ runnerType: null,
+ filters: [
+ {
+ type: 'filtered-search-term',
+ value: { data: 'something' },
+ },
+ {
+ type: 'filtered-search-term',
+ value: { data: 'else' },
+ },
+ ],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { search: 'something else', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'single instance type',
+ urlQuery: '?runner_type[]=INSTANCE_TYPE',
+ search: {
+ runnerType: 'INSTANCE_TYPE',
+ filters: [],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'multiple runner status',
+ urlQuery: '?status[]=ACTIVE&status[]=PAUSED',
+ search: {
+ runnerType: null,
+ filters: [
+ { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
+ { type: 'status', value: { data: 'PAUSED', operator: '=' } },
+ ],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'multiple status, a single instance type and a non default sort',
+ urlQuery: '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&sort=CREATED_ASC',
+ search: {
+ runnerType: 'INSTANCE_TYPE',
+ filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }],
+ pagination: { page: 1 },
+ sort: 'CREATED_ASC',
+ },
+ graphqlVariables: {
+ status: 'ACTIVE',
+ type: 'INSTANCE_TYPE',
+ sort: 'CREATED_ASC',
+ first: RUNNER_PAGE_SIZE,
+ },
+ },
+ {
+ name: 'a tag',
+ urlQuery: '?tag[]=tag-1',
+ search: {
+ runnerType: null,
+ filters: [{ type: 'tag', value: { data: 'tag-1', operator: '=' } }],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: {
+ tagList: ['tag-1'],
+ first: 20,
+ sort: 'CREATED_DESC',
+ },
+ },
+ {
+ name: 'two tags',
+ urlQuery: '?tag[]=tag-1&tag[]=tag-2',
+ search: {
+ runnerType: null,
+ filters: [
+ { type: 'tag', value: { data: 'tag-1', operator: '=' } },
+ { type: 'tag', value: { data: 'tag-2', operator: '=' } },
+ ],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: {
+ tagList: ['tag-1', 'tag-2'],
+ first: 20,
+ sort: 'CREATED_DESC',
+ },
+ },
+ {
+ name: 'the next page',
+ urlQuery: '?page=2&after=AFTER_CURSOR',
+ search: {
+ runnerType: null,
+ filters: [],
+ pagination: { page: 2, after: 'AFTER_CURSOR' },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { sort: 'CREATED_DESC', after: 'AFTER_CURSOR', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'the previous page',
+ urlQuery: '?page=2&before=BEFORE_CURSOR',
+ search: {
+ runnerType: null,
+ filters: [],
+ pagination: { page: 2, before: 'BEFORE_CURSOR' },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { sort: 'CREATED_DESC', before: 'BEFORE_CURSOR', last: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'the next page filtered by a status, an instance type, tags and a non default sort',
+ urlQuery:
+ '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&tag[]=tag-1&tag[]=tag-2&sort=CREATED_ASC&page=2&after=AFTER_CURSOR',
+ search: {
+ runnerType: 'INSTANCE_TYPE',
+ filters: [
+ { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
+ { type: 'tag', value: { data: 'tag-1', operator: '=' } },
+ { type: 'tag', value: { data: 'tag-2', operator: '=' } },
+ ],
+ pagination: { page: 2, after: 'AFTER_CURSOR' },
+ sort: 'CREATED_ASC',
+ },
+ graphqlVariables: {
+ status: 'ACTIVE',
+ type: 'INSTANCE_TYPE',
+ tagList: ['tag-1', 'tag-2'],
+ sort: 'CREATED_ASC',
+ after: 'AFTER_CURSOR',
+ first: RUNNER_PAGE_SIZE,
+ },
+ },
+ {
+ name: 'paused runners',
+ urlQuery: '?paused[]=true',
+ search: {
+ runnerType: null,
+ filters: [{ type: 'paused', value: { data: 'true', operator: '=' } }],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { paused: true, sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+ {
+ name: 'active runners',
+ urlQuery: '?paused[]=false',
+ search: {
+ runnerType: null,
+ filters: [{ type: 'paused', value: { data: 'false', operator: '=' } }],
+ pagination: { page: 1 },
+ sort: 'CREATED_DESC',
+ },
+ graphqlVariables: { paused: false, sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
+ },
+];
+
export const onlineContactTimeoutSecs = 2 * 60 * 60;
export const staleTimeoutSecs = 7889238; // Ruby's `3.months`
@@ -25,8 +227,8 @@ export const emptyStateSvgPath = 'emptyStateSvgPath.svg';
export const emptyStateFilteredSvgPath = 'emptyStateFilteredSvgPath.svg';
export {
- runnersData,
- runnersDataPaginated,
+ allRunnersData,
+ allRunnersDataPaginated,
runnersCountData,
groupRunnersData,
groupRunnersDataPaginated,
diff --git a/spec/frontend/runner/runner_search_utils_spec.js b/spec/frontend/runner/runner_search_utils_spec.js
index 1f102f86b2a..6f954143ab1 100644
--- a/spec/frontend/runner/runner_search_utils_spec.js
+++ b/spec/frontend/runner/runner_search_utils_spec.js
@@ -1,4 +1,3 @@
-import { RUNNER_PAGE_SIZE } from '~/runner/constants';
import {
searchValidator,
updateOutdatedUrl,
@@ -6,209 +5,12 @@ import {
fromSearchToUrl,
fromSearchToVariables,
isSearchFiltered,
-} from '~/runner/runner_search_utils';
+} from 'ee_else_ce/runner/runner_search_utils';
+import { mockSearchExamples } from './mock_data';
describe('search_params.js', () => {
- const examples = [
- {
- name: 'a default query',
- 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',
- urlQuery: '?status[]=ACTIVE',
- search: {
- runnerType: null,
- filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'a single term text search',
- urlQuery: '?search=something',
- search: {
- runnerType: null,
- filters: [
- {
- type: 'filtered-search-term',
- value: { data: 'something' },
- },
- ],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { search: 'something', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'a two terms text search',
- urlQuery: '?search=something+else',
- search: {
- runnerType: null,
- filters: [
- {
- type: 'filtered-search-term',
- value: { data: 'something' },
- },
- {
- type: 'filtered-search-term',
- value: { data: 'else' },
- },
- ],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { search: 'something else', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'single instance type',
- urlQuery: '?runner_type[]=INSTANCE_TYPE',
- search: {
- runnerType: 'INSTANCE_TYPE',
- filters: [],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'multiple runner status',
- urlQuery: '?status[]=ACTIVE&status[]=PAUSED',
- search: {
- runnerType: null,
- filters: [
- { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
- { type: 'status', value: { data: 'PAUSED', operator: '=' } },
- ],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'multiple status, a single instance type and a non default sort',
- urlQuery: '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&sort=CREATED_ASC',
- search: {
- runnerType: 'INSTANCE_TYPE',
- filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }],
- pagination: { page: 1 },
- sort: 'CREATED_ASC',
- },
- graphqlVariables: {
- status: 'ACTIVE',
- type: 'INSTANCE_TYPE',
- sort: 'CREATED_ASC',
- first: RUNNER_PAGE_SIZE,
- },
- },
- {
- name: 'a tag',
- urlQuery: '?tag[]=tag-1',
- search: {
- runnerType: null,
- filters: [{ type: 'tag', value: { data: 'tag-1', operator: '=' } }],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: {
- tagList: ['tag-1'],
- first: 20,
- sort: 'CREATED_DESC',
- },
- },
- {
- name: 'two tags',
- urlQuery: '?tag[]=tag-1&tag[]=tag-2',
- search: {
- runnerType: null,
- filters: [
- { type: 'tag', value: { data: 'tag-1', operator: '=' } },
- { type: 'tag', value: { data: 'tag-2', operator: '=' } },
- ],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: {
- tagList: ['tag-1', 'tag-2'],
- first: 20,
- sort: 'CREATED_DESC',
- },
- },
- {
- name: 'the next page',
- urlQuery: '?page=2&after=AFTER_CURSOR',
- search: {
- runnerType: null,
- filters: [],
- pagination: { page: 2, after: 'AFTER_CURSOR' },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { sort: 'CREATED_DESC', after: 'AFTER_CURSOR', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'the previous page',
- urlQuery: '?page=2&before=BEFORE_CURSOR',
- search: {
- runnerType: null,
- filters: [],
- pagination: { page: 2, before: 'BEFORE_CURSOR' },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { sort: 'CREATED_DESC', before: 'BEFORE_CURSOR', last: RUNNER_PAGE_SIZE },
- },
- {
- name: 'the next page filtered by a status, an instance type, tags and a non default sort',
- urlQuery:
- '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&tag[]=tag-1&tag[]=tag-2&sort=CREATED_ASC&page=2&after=AFTER_CURSOR',
- search: {
- runnerType: 'INSTANCE_TYPE',
- filters: [
- { type: 'status', value: { data: 'ACTIVE', operator: '=' } },
- { type: 'tag', value: { data: 'tag-1', operator: '=' } },
- { type: 'tag', value: { data: 'tag-2', operator: '=' } },
- ],
- pagination: { page: 2, after: 'AFTER_CURSOR' },
- sort: 'CREATED_ASC',
- },
- graphqlVariables: {
- status: 'ACTIVE',
- type: 'INSTANCE_TYPE',
- tagList: ['tag-1', 'tag-2'],
- sort: 'CREATED_ASC',
- after: 'AFTER_CURSOR',
- first: RUNNER_PAGE_SIZE,
- },
- },
- {
- name: 'paused runners',
- urlQuery: '?paused[]=true',
- search: {
- runnerType: null,
- filters: [{ type: 'paused', value: { data: 'true', operator: '=' } }],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { paused: true, sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- {
- name: 'active runners',
- urlQuery: '?paused[]=false',
- search: {
- runnerType: null,
- filters: [{ type: 'paused', value: { data: 'false', operator: '=' } }],
- pagination: { page: 1 },
- sort: 'CREATED_DESC',
- },
- graphqlVariables: { paused: false, sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE },
- },
- ];
-
describe('searchValidator', () => {
- examples.forEach(({ name, search }) => {
+ mockSearchExamples.forEach(({ name, search }) => {
it(`Validates ${name} as a search object`, () => {
expect(searchValidator(search)).toBe(true);
});
@@ -235,7 +37,7 @@ describe('search_params.js', () => {
});
describe('fromUrlQueryToSearch', () => {
- examples.forEach(({ name, urlQuery, search }) => {
+ mockSearchExamples.forEach(({ name, urlQuery, search }) => {
it(`Converts ${name} to a search object`, () => {
expect(fromUrlQueryToSearch(urlQuery)).toEqual(search);
});
@@ -268,7 +70,7 @@ describe('search_params.js', () => {
});
describe('fromSearchToUrl', () => {
- examples.forEach(({ name, urlQuery, search }) => {
+ mockSearchExamples.forEach(({ name, urlQuery, search }) => {
it(`Converts ${name} to a url`, () => {
expect(fromSearchToUrl(search)).toBe(`http://test.host/${urlQuery}`);
});
@@ -295,7 +97,7 @@ describe('search_params.js', () => {
});
describe('fromSearchToVariables', () => {
- examples.forEach(({ name, graphqlVariables, search }) => {
+ mockSearchExamples.forEach(({ name, graphqlVariables, search }) => {
it(`Converts ${name} to a GraphQL query variables object`, () => {
expect(fromSearchToVariables(search)).toEqual(graphqlVariables);
});
@@ -335,7 +137,7 @@ describe('search_params.js', () => {
});
describe('isSearchFiltered', () => {
- examples.forEach(({ name, search, isDefault }) => {
+ mockSearchExamples.forEach(({ name, search, isDefault }) => {
it(`Given ${name}, evaluates to ${isDefault ? 'not ' : ''}filtered`, () => {
expect(isSearchFiltered(search)).toBe(!isDefault);
});