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/components')
-rw-r--r--spec/frontend/runner/components/cells/link_cell_spec.js2
-rw-r--r--spec/frontend/runner/components/cells/runner_actions_cell_spec.js4
-rw-r--r--spec/frontend/runner/components/cells/runner_owner_cell_spec.js111
-rw-r--r--spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js6
-rw-r--r--spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js99
-rw-r--r--spec/frontend/runner/components/runner_delete_button_spec.js31
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js28
-rw-r--r--spec/frontend/runner/components/runner_filtered_search_bar_spec.js33
-rw-r--r--spec/frontend/runner/components/runner_list_empty_state_spec.js57
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js56
-rw-r--r--spec/frontend/runner/components/runner_membership_toggle_spec.js57
-rw-r--r--spec/frontend/runner/components/runner_stacked_layout_banner_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_type_tabs_spec.js20
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js2
-rw-r--r--spec/frontend/runner/components/search_tokens/tag_token_spec.js43
15 files changed, 408 insertions, 143 deletions
diff --git a/spec/frontend/runner/components/cells/link_cell_spec.js b/spec/frontend/runner/components/cells/link_cell_spec.js
index a59a0eaa5d8..46ab1adb6b6 100644
--- a/spec/frontend/runner/components/cells/link_cell_spec.js
+++ b/spec/frontend/runner/components/cells/link_cell_spec.js
@@ -5,7 +5,7 @@ import LinkCell from '~/runner/components/cells/link_cell.vue';
describe('LinkCell', () => {
let wrapper;
- const findGlLink = () => wrapper.find(GlLink);
+ const findGlLink = () => wrapper.findComponent(GlLink);
const findSpan = () => wrapper.find('span');
const createComponent = ({ props = {}, ...options } = {}) => {
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 ffd6f126627..58974d4f85f 100644
--- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
@@ -122,7 +122,7 @@ describe('RunnerActionsCell', () => {
expect(wrapper.emitted('deleted')).toEqual([[value]]);
});
- it('Renders the runner delete disabled button when user cannot delete', () => {
+ it('Does not render the runner delete button when user cannot delete', () => {
createComponent({
runner: {
userPermissions: {
@@ -132,7 +132,7 @@ describe('RunnerActionsCell', () => {
},
});
- expect(findDeleteBtn().props('disabled')).toBe(true);
+ expect(findDeleteBtn().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/runner/components/cells/runner_owner_cell_spec.js b/spec/frontend/runner/components/cells/runner_owner_cell_spec.js
new file mode 100644
index 00000000000..e9965d8855d
--- /dev/null
+++ b/spec/frontend/runner/components/cells/runner_owner_cell_spec.js
@@ -0,0 +1,111 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+import RunnerOwnerCell from '~/runner/components/cells/runner_owner_cell.vue';
+
+import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
+
+describe('RunnerOwnerCell', () => {
+ let wrapper;
+
+ const findLink = () => wrapper.findComponent(GlLink);
+ const getLinkTooltip = () => getBinding(findLink().element, 'gl-tooltip').value;
+
+ const createComponent = ({ runner } = {}) => {
+ wrapper = shallowMount(RunnerOwnerCell, {
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ propsData: {
+ runner,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('When its an instance runner', () => {
+ beforeEach(() => {
+ createComponent({
+ runner: {
+ runnerType: INSTANCE_TYPE,
+ },
+ });
+ });
+
+ it('shows an administrator label', () => {
+ expect(findLink().exists()).toBe(false);
+ expect(wrapper.text()).toBe(s__('Runners|Administrator'));
+ });
+ });
+
+ describe('When its a group runner', () => {
+ const mockName = 'Group 2';
+ const mockFullName = 'Group 1 / Group 2';
+ const mockWebUrl = '/group-1/group-2';
+
+ beforeEach(() => {
+ createComponent({
+ runner: {
+ runnerType: GROUP_TYPE,
+ groups: {
+ nodes: [
+ {
+ name: mockName,
+ fullName: mockFullName,
+ webUrl: mockWebUrl,
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ it('Displays a group link', () => {
+ expect(findLink().attributes('href')).toBe(mockWebUrl);
+ expect(wrapper.text()).toBe(mockName);
+ expect(getLinkTooltip()).toBe(mockFullName);
+ });
+ });
+
+ describe('When its a project runner', () => {
+ const mockName = 'Project 1';
+ const mockNameWithNamespace = 'Group 1 / Project 1';
+ const mockWebUrl = '/group-1/project-1';
+
+ beforeEach(() => {
+ createComponent({
+ runner: {
+ runnerType: PROJECT_TYPE,
+ ownerProject: {
+ name: mockName,
+ nameWithNamespace: mockNameWithNamespace,
+ webUrl: mockWebUrl,
+ },
+ },
+ });
+ });
+
+ it('Displays a project link', () => {
+ expect(findLink().attributes('href')).toBe(mockWebUrl);
+ expect(wrapper.text()).toBe(mockName);
+ expect(getLinkTooltip()).toBe(mockNameWithNamespace);
+ });
+ });
+
+ describe('When its an empty runner', () => {
+ beforeEach(() => {
+ createComponent({
+ runner: {},
+ });
+ });
+
+ it('shows no label', () => {
+ expect(wrapper.text()).toBe('');
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
index 21ec9f61f37..e7cadefc140 100644
--- a/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
@@ -85,7 +85,7 @@ describe('RunnerTypeCell', () => {
contactedAt: '2022-01-02',
});
- expect(findRunnerSummaryField('clock').find(TimeAgo).props('time')).toBe('2022-01-02');
+ expect(findRunnerSummaryField('clock').findComponent(TimeAgo).props('time')).toBe('2022-01-02');
});
it('Displays empty last contact', () => {
@@ -93,7 +93,7 @@ describe('RunnerTypeCell', () => {
contactedAt: null,
});
- expect(findRunnerSummaryField('clock').find(TimeAgo).exists()).toBe(false);
+ expect(findRunnerSummaryField('clock').findComponent(TimeAgo).exists()).toBe(false);
expect(findRunnerSummaryField('clock').text()).toContain(__('Never'));
});
@@ -134,7 +134,7 @@ describe('RunnerTypeCell', () => {
});
it('Displays created at', () => {
- expect(findRunnerSummaryField('calendar').find(TimeAgo).props('time')).toBe(
+ expect(findRunnerSummaryField('calendar').findComponent(TimeAgo).props('time')).toBe(
mockRunner.createdAt,
);
});
diff --git a/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js
index 0ac89e82314..424a4e61ccd 100644
--- a/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js
+++ b/spec/frontend/runner/components/runner_bulk_delete_checkbox_spec.js
@@ -5,11 +5,21 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerBulkDeleteCheckbox from '~/runner/components/runner_bulk_delete_checkbox.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createLocalState } from '~/runner/graphql/list/local_state';
-import { allRunnersData } from '../mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+const makeRunner = (id, deleteRunner = true) => ({
+ id,
+ userPermissions: { deleteRunner },
+});
+
+// Multi-select checkbox possible states:
+const stateToAttrs = {
+ unchecked: { disabled: undefined, checked: undefined, indeterminate: undefined },
+ checked: { disabled: undefined, checked: 'true', indeterminate: undefined },
+ indeterminate: { disabled: undefined, checked: undefined, indeterminate: 'true' },
+ disabled: { disabled: 'true', checked: undefined, indeterminate: undefined },
+};
describe('RunnerBulkDeleteCheckbox', () => {
let wrapper;
@@ -18,12 +28,14 @@ describe('RunnerBulkDeleteCheckbox', () => {
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
- const mockRunners = allRunnersData.data.runners.nodes;
- const mockIds = allRunnersData.data.runners.nodes.map(({ id }) => id);
- const mockId = mockIds[0];
- const mockIdAnotherPage = 'RUNNER_IN_ANOTHER_PAGE_ID';
+ const expectCheckboxToBe = (state) => {
+ const expected = stateToAttrs[state];
+ expect(findCheckbox().attributes('disabled')).toBe(expected.disabled);
+ expect(findCheckbox().attributes('checked')).toBe(expected.checked);
+ expect(findCheckbox().attributes('indeterminate')).toBe(expected.indeterminate);
+ };
- const createComponent = ({ props = {} } = {}) => {
+ const createComponent = ({ runners = [] } = {}) => {
const { cacheConfig, localMutations } = mockState;
const apolloProvider = createMockApollo(undefined, undefined, cacheConfig);
@@ -33,8 +45,7 @@ describe('RunnerBulkDeleteCheckbox', () => {
localMutations,
},
propsData: {
- runners: mockRunners,
- ...props,
+ runners,
},
});
};
@@ -49,31 +60,61 @@ describe('RunnerBulkDeleteCheckbox', () => {
jest.spyOn(mockState.localMutations, 'setRunnersChecked');
});
- describe.each`
- case | is | checkedRunnerIds | disabled | checked | indeterminate
- ${'no runners'} | ${'unchecked'} | ${[]} | ${undefined} | ${undefined} | ${undefined}
- ${'no runners in this page'} | ${'unchecked'} | ${[mockIdAnotherPage]} | ${undefined} | ${undefined} | ${undefined}
- ${'all runners'} | ${'checked'} | ${mockIds} | ${undefined} | ${'true'} | ${undefined}
- ${'some runners'} | ${'indeterminate'} | ${[mockId]} | ${undefined} | ${undefined} | ${'true'}
- ${'all plus other runners'} | ${'checked'} | ${[...mockIds, mockIdAnotherPage]} | ${undefined} | ${'true'} | ${undefined}
- `('When $case are checked', ({ is, checkedRunnerIds, disabled, checked, indeterminate }) => {
- beforeEach(async () => {
+ describe('when all runners can be deleted', () => {
+ const mockIds = ['1', '2', '3'];
+ const mockIdAnotherPage = '4';
+ const mockRunners = mockIds.map((id) => makeRunner(id));
+
+ it.each`
+ case | checkedRunnerIds | state
+ ${'no runners'} | ${[]} | ${'unchecked'}
+ ${'no runners in this page'} | ${[mockIdAnotherPage]} | ${'unchecked'}
+ ${'all runners'} | ${mockIds} | ${'checked'}
+ ${'some runners'} | ${[mockIds[0]]} | ${'indeterminate'}
+ ${'all plus other runners'} | ${[...mockIds, mockIdAnotherPage]} | ${'checked'}
+ `('if $case are checked, checkbox is $state', ({ checkedRunnerIds, state }) => {
mockCheckedRunnerIds = checkedRunnerIds;
- createComponent();
+ createComponent({ runners: mockRunners });
+ expectCheckboxToBe(state);
});
+ });
+
+ describe('when some runners cannot be deleted', () => {
+ it('all allowed runners are selected, checkbox is checked', () => {
+ mockCheckedRunnerIds = ['a', 'b', 'c'];
+ createComponent({
+ runners: [makeRunner('a'), makeRunner('b'), makeRunner('c', false)],
+ });
- it(`is ${is}`, () => {
- expect(findCheckbox().attributes('disabled')).toBe(disabled);
- expect(findCheckbox().attributes('checked')).toBe(checked);
- expect(findCheckbox().attributes('indeterminate')).toBe(indeterminate);
+ expectCheckboxToBe('checked');
+ });
+
+ it('some allowed runners are selected, checkbox is indeterminate', () => {
+ mockCheckedRunnerIds = ['a', 'b'];
+ createComponent({
+ runners: [makeRunner('a'), makeRunner('b'), makeRunner('c')],
+ });
+
+ expectCheckboxToBe('indeterminate');
+ });
+
+ it('no allowed runners are selected, checkbox is disabled', () => {
+ mockCheckedRunnerIds = ['a', 'b'];
+ createComponent({
+ runners: [makeRunner('a', false), makeRunner('b', false)],
+ });
+
+ expectCheckboxToBe('disabled');
});
});
describe('When user selects', () => {
+ const mockRunners = [makeRunner('1'), makeRunner('2')];
+
beforeEach(() => {
- mockCheckedRunnerIds = mockIds;
- createComponent();
+ mockCheckedRunnerIds = ['1', '2'];
+ createComponent({ runners: mockRunners });
});
it.each([[true], [false]])('sets checked to %s', (checked) => {
@@ -89,13 +130,11 @@ describe('RunnerBulkDeleteCheckbox', () => {
describe('When runners are loading', () => {
beforeEach(() => {
- createComponent({ props: { runners: [] } });
+ createComponent();
});
- it(`is disabled`, () => {
- expect(findCheckbox().attributes('disabled')).toBe('true');
- expect(findCheckbox().attributes('checked')).toBe(undefined);
- expect(findCheckbox().attributes('indeterminate')).toBe(undefined);
+ it('is disabled', () => {
+ expectCheckboxToBe('disabled');
});
});
});
diff --git a/spec/frontend/runner/components/runner_delete_button_spec.js b/spec/frontend/runner/components/runner_delete_button_spec.js
index 52fe803c536..c8fb7a69379 100644
--- a/spec/frontend/runner/components/runner_delete_button_spec.js
+++ b/spec/frontend/runner/components/runner_delete_button_spec.js
@@ -9,11 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/flash';
-import {
- I18N_DELETE_RUNNER,
- I18N_DELETE_DISABLED_MANY_PROJECTS,
- I18N_DELETE_DISABLED_UNKNOWN_REASON,
-} from '~/runner/constants';
+import { I18N_DELETE_RUNNER } from '~/runner/constants';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
@@ -267,29 +263,4 @@ describe('RunnerDeleteButton', () => {
});
});
});
-
- describe.each`
- reason | runner | tooltip
- ${'runner belongs to more than 1 project'} | ${{ projectCount: 2 }} | ${I18N_DELETE_DISABLED_MANY_PROJECTS}
- ${'unknown reason'} | ${{}} | ${I18N_DELETE_DISABLED_UNKNOWN_REASON}
- `('When button is disabled because $reason', ({ runner, tooltip }) => {
- beforeEach(() => {
- createComponent({
- props: {
- disabled: true,
- runner,
- },
- });
- });
-
- it('Displays a disabled delete button', () => {
- expect(findBtn().props('disabled')).toBe(true);
- });
-
- it(`Tooltip "${tooltip}" is shown`, () => {
- // tabindex is required for a11y
- expect(wrapper.attributes('tabindex')).toBe('0');
- expect(getTooltip()).toBe(tooltip);
- });
- });
});
diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js
index f2281223a25..e6cc936e260 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,
- enforceRunnerTokenExpiresAt = false,
- } = {}) => {
+ const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerDetails, {
propsData: {
...props,
@@ -39,9 +34,6 @@ describe('RunnerDetails', () => {
RunnerDetail,
...stubs,
},
- provide: {
- glFeatures: { enforceRunnerTokenExpiresAt },
- },
});
};
@@ -82,7 +74,6 @@ describe('RunnerDetails', () => {
...runner,
},
},
- enforceRunnerTokenExpiresAt: true,
stubs: {
GlIntersperse,
GlSprintf,
@@ -135,22 +126,5 @@ describe('RunnerDetails', () => {
expect(findDetailGroups().props('runner')).toEqual(mockGroupRunner);
});
});
-
- describe('Token expiration field', () => {
- it.each`
- case | flag | shown
- ${'is shown when feature flag is enabled'} | ${true} | ${true}
- ${'is not shown when feature flag is disabled'} | ${false} | ${false}
- `('$case', ({ flag, shown }) => {
- createComponent({
- props: {
- runner: mockGroupRunner,
- },
- enforceRunnerTokenExpiresAt: flag,
- });
-
- expect(findDd('Token expiry', wrapper).exists()).toBe(shown);
- });
- });
});
});
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 e35bec3aa38..c92e19f9263 100644
--- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
@@ -4,10 +4,26 @@ import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_
import { statusTokenConfig } from '~/runner/components/search_tokens/status_token_config';
import TagToken from '~/runner/components/search_tokens/tag_token.vue';
import { tagTokenConfig } from '~/runner/components/search_tokens/tag_token_config';
-import { PARAM_KEY_STATUS, PARAM_KEY_TAG, STATUS_ONLINE, INSTANCE_TYPE } from '~/runner/constants';
+import {
+ PARAM_KEY_STATUS,
+ PARAM_KEY_TAG,
+ STATUS_ONLINE,
+ INSTANCE_TYPE,
+ DEFAULT_MEMBERSHIP,
+ DEFAULT_SORT,
+ CONTACTED_DESC,
+} from '~/runner/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
+const mockSearch = {
+ runnerType: null,
+ membership: DEFAULT_MEMBERSHIP,
+ filters: [],
+ pagination: { page: 1 },
+ sort: DEFAULT_SORT,
+};
+
describe('RunnerList', () => {
let wrapper;
@@ -15,8 +31,7 @@ describe('RunnerList', () => {
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
const findSortOptions = () => wrapper.findAllComponents(GlDropdownItem);
- const mockDefaultSort = 'CREATED_DESC';
- const mockOtherSort = 'CONTACTED_DESC';
+ const mockOtherSort = CONTACTED_DESC;
const mockFilters = [
{ type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } },
{ type: 'filtered-search-term', value: { data: '' } },
@@ -32,11 +47,7 @@ describe('RunnerList', () => {
propsData: {
namespace: 'runners',
tokens: [],
- value: {
- runnerType: null,
- filters: [],
- sort: mockDefaultSort,
- },
+ value: mockSearch,
...props,
},
stubs: {
@@ -115,6 +126,7 @@ describe('RunnerList', () => {
props: {
value: {
runnerType: INSTANCE_TYPE,
+ membership: DEFAULT_MEMBERSHIP,
sort: mockOtherSort,
filters: mockFilters,
},
@@ -141,6 +153,7 @@ describe('RunnerList', () => {
expectToHaveLastEmittedInput({
runnerType: INSTANCE_TYPE,
+ membership: DEFAULT_MEMBERSHIP,
filters: mockFilters,
sort: mockOtherSort,
pagination: {},
@@ -154,8 +167,9 @@ describe('RunnerList', () => {
expectToHaveLastEmittedInput({
runnerType: null,
+ membership: DEFAULT_MEMBERSHIP,
filters: mockFilters,
- sort: mockDefaultSort,
+ sort: DEFAULT_SORT,
pagination: {},
});
});
@@ -165,6 +179,7 @@ describe('RunnerList', () => {
expectToHaveLastEmittedInput({
runnerType: null,
+ membership: DEFAULT_MEMBERSHIP,
filters: [],
sort: mockOtherSort,
pagination: {},
diff --git a/spec/frontend/runner/components/runner_list_empty_state_spec.js b/spec/frontend/runner/components/runner_list_empty_state_spec.js
index 59cff863106..038162b889e 100644
--- a/spec/frontend/runner/components/runner_list_empty_state_spec.js
+++ b/spec/frontend/runner/components/runner_list_empty_state_spec.js
@@ -8,6 +8,7 @@ import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vu
const mockSvgPath = 'mock-svg-path.svg';
const mockFilteredSvgPath = 'mock-filtered-svg-path.svg';
+const mockRegistrationToken = 'REGISTRATION_TOKEN';
describe('RunnerListEmptyState', () => {
let wrapper;
@@ -21,6 +22,7 @@ describe('RunnerListEmptyState', () => {
propsData: {
svgPath: mockSvgPath,
filteredSvgPath: mockFilteredSvgPath,
+ registrationToken: mockRegistrationToken,
...props,
},
directives: {
@@ -35,27 +37,52 @@ describe('RunnerListEmptyState', () => {
};
describe('when search is not filtered', () => {
- beforeEach(() => {
- createComponent();
- });
+ const title = s__('Runners|Get started with runners');
- it('renders an illustration', () => {
- expect(findEmptyState().props('svgPath')).toBe(mockSvgPath);
- });
+ describe('when there is a registration token', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders an illustration', () => {
+ expect(findEmptyState().props('svgPath')).toBe(mockSvgPath);
+ });
+
+ it('displays "no results" text with instructions', () => {
+ 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.',
+ );
- 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}`);
+ });
- 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);
+ });
});
- it('opens a runner registration instructions modal with a link', () => {
- const { value } = getBinding(findLink().element, 'gl-modal');
+ describe('when there is no registration token', () => {
+ beforeEach(() => {
+ createComponent({ props: { registrationToken: null } });
+ });
+
+ it('renders an illustration', () => {
+ expect(findEmptyState().props('svgPath')).toBe(mockSvgPath);
+ });
+
+ it('displays "no results" text', () => {
+ const desc = s__(
+ 'Runners|Runners are the agents that run your CI/CD jobs. To register new runners, please contact your administrator.',
+ );
+
+ expect(findEmptyState().text()).toMatchInterpolatedText(`${title} ${desc}`);
+ });
- expect(findRunnerInstructionsModal().props('modalId')).toEqual(value);
+ it('has no registration instructions link', () => {
+ expect(findLink().exists()).toBe(false);
+ });
});
});
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 54a9e713721..a31990f8f7e 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -1,12 +1,19 @@
import { GlTableLite, GlSkeletonLoader } from '@gitlab/ui';
+import HelpPopover from '~/vue_shared/components/help_popover.vue';
import {
extendedWrapper,
shallowMountExtended,
mountExtended,
} from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { createLocalState } from '~/runner/graphql/list/local_state';
+
import RunnerList from '~/runner/components/runner_list.vue';
-import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
+import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
+import RunnerBulkDeleteCheckbox from '~/runner/components/runner_bulk_delete_checkbox.vue';
+
import { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/runner/constants';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
@@ -15,6 +22,8 @@ const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {
let wrapper;
+ let cacheConfig;
+ let localMutations;
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findTable = () => wrapper.findComponent(GlTableLite);
@@ -22,18 +31,24 @@ describe('RunnerList', () => {
const findRows = () => wrapper.findAll('[data-testid^="runner-row-"]');
const findCell = ({ row = 0, fieldKey }) =>
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
+ const findRunnerBulkDelete = () => wrapper.findComponent(RunnerBulkDelete);
+ const findRunnerBulkDeleteCheckbox = () => wrapper.findComponent(RunnerBulkDeleteCheckbox);
const createComponent = (
{ props = {}, provide = {}, ...options } = {},
mountFn = shallowMountExtended,
) => {
+ ({ cacheConfig, localMutations } = createLocalState());
+
wrapper = mountFn(RunnerList, {
+ apolloProvider: createMockApollo([], {}, cacheConfig),
propsData: {
runners: mockRunners,
activeRunnersCount: mockActiveRunnersCount,
...props,
},
provide: {
+ localMutations,
onlineContactTimeoutSecs,
staleTimeoutSecs,
...provide,
@@ -50,7 +65,7 @@ describe('RunnerList', () => {
createComponent(
{
stubs: {
- RunnerStatusPopover: {
+ HelpPopover: {
template: '<div/>',
},
},
@@ -60,11 +75,13 @@ describe('RunnerList', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text());
- expect(findHeaders().at(0).findComponent(RunnerStatusPopover).exists()).toBe(true);
+ expect(findHeaders().at(0).findComponent(HelpPopover).exists()).toBe(true);
+ expect(findHeaders().at(2).findComponent(HelpPopover).exists()).toBe(true);
expect(headerLabels).toEqual([
- 'Status',
- 'Runner',
+ s__('Runners|Status'),
+ s__('Runners|Runner'),
+ s__('Runners|Owner'),
'', // actions has no label
]);
});
@@ -123,21 +140,40 @@ describe('RunnerList', () => {
);
});
+ it('runner bulk delete is available', () => {
+ expect(findRunnerBulkDelete().props('runners')).toEqual(mockRunners);
+ });
+
+ it('runner bulk delete checkbox is available', () => {
+ expect(findRunnerBulkDeleteCheckbox().props('runners')).toEqual(mockRunners);
+ });
+
it('Displays a checkbox field', () => {
expect(findCell({ fieldKey: 'checkbox' }).find('input').exists()).toBe(true);
});
- it('Emits a checked event', async () => {
- const checkbox = findCell({ fieldKey: 'checkbox' }).find('input');
+ it('Sets a runner as checked', async () => {
+ const runner = mockRunners[0];
+ const setRunnerCheckedMock = jest
+ .spyOn(localMutations, 'setRunnerChecked')
+ .mockImplementation(() => {});
+ const checkbox = findCell({ fieldKey: 'checkbox' }).find('input');
await checkbox.setChecked();
- expect(wrapper.emitted('checked')).toHaveLength(1);
- expect(wrapper.emitted('checked')[0][0]).toEqual({
+ expect(setRunnerCheckedMock).toHaveBeenCalledTimes(1);
+ expect(setRunnerCheckedMock).toHaveBeenCalledWith({
+ runner,
isChecked: true,
- runner: mockRunners[0],
});
});
+
+ it('Emits a deleted event', async () => {
+ const event = { message: 'Deleted!' };
+ findRunnerBulkDelete().vm.$emit('deleted', event);
+
+ expect(wrapper.emitted('deleted')).toEqual([[event]]);
+ });
});
describe('Scoped cell slots', () => {
diff --git a/spec/frontend/runner/components/runner_membership_toggle_spec.js b/spec/frontend/runner/components/runner_membership_toggle_spec.js
new file mode 100644
index 00000000000..1a7ae22618a
--- /dev/null
+++ b/spec/frontend/runner/components/runner_membership_toggle_spec.js
@@ -0,0 +1,57 @@
+import { GlToggle } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import RunnerMembershipToggle from '~/runner/components/runner_membership_toggle.vue';
+import {
+ I18N_SHOW_ONLY_INHERITED,
+ MEMBERSHIP_DESCENDANTS,
+ MEMBERSHIP_ALL_AVAILABLE,
+} from '~/runner/constants';
+
+describe('RunnerMembershipToggle', () => {
+ let wrapper;
+
+ const findToggle = () => wrapper.findComponent(GlToggle);
+
+ const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
+ wrapper = mountFn(RunnerMembershipToggle, {
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Displays text', () => {
+ createComponent({ mountFn: mount });
+
+ expect(wrapper.text()).toBe(I18N_SHOW_ONLY_INHERITED);
+ });
+
+ it.each`
+ membershipValue | toggleValue
+ ${MEMBERSHIP_DESCENDANTS} | ${true}
+ ${MEMBERSHIP_ALL_AVAILABLE} | ${false}
+ `(
+ 'Displays a membership of $membershipValue as enabled=$toggleValue',
+ ({ membershipValue, toggleValue }) => {
+ createComponent({ props: { value: membershipValue } });
+
+ expect(findToggle().props('value')).toBe(toggleValue);
+ },
+ );
+
+ it.each`
+ changeEvt | membershipValue
+ ${true} | ${MEMBERSHIP_DESCENDANTS}
+ ${false} | ${MEMBERSHIP_ALL_AVAILABLE}
+ `(
+ 'Emits $changeEvt when value is changed to $membershipValue',
+ ({ changeEvt, membershipValue }) => {
+ createComponent();
+ findToggle().vm.$emit('change', changeEvt);
+
+ expect(wrapper.emitted('input')).toStrictEqual([[membershipValue]]);
+ },
+ );
+});
diff --git a/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
index 1a8aced9292..d1f04f0ee37 100644
--- a/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
+++ b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
@@ -29,6 +29,8 @@ describe('RunnerStackedLayoutBanner', () => {
});
it('Does not display a banner when dismissed', async () => {
+ createComponent();
+
findLocalStorageSync().vm.$emit('input', true);
await nextTick();
diff --git a/spec/frontend/runner/components/runner_type_tabs_spec.js b/spec/frontend/runner/components/runner_type_tabs_spec.js
index 45ab8684332..dde35533bc3 100644
--- a/spec/frontend/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/runner/components/runner_type_tabs_spec.js
@@ -2,9 +2,21 @@ 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' };
+import {
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ DEFAULT_MEMBERSHIP,
+ DEFAULT_SORT,
+} from '~/runner/constants';
+
+const mockSearch = {
+ runnerType: null,
+ membership: DEFAULT_MEMBERSHIP,
+ filters: [],
+ pagination: { page: 1 },
+ sort: DEFAULT_SORT,
+};
const mockCount = (type, multiplier = 1) => {
let count;
@@ -113,7 +125,7 @@ describe('RunnerTypeTabs', () => {
});
findTabs().wrappers.forEach((tab) => {
- expect(tab.find(RunnerCount).props()).toEqual({
+ expect(tab.findComponent(RunnerCount).props()).toEqual({
scope: INSTANCE_TYPE,
skip: false,
variables: expect.objectContaining(mockVariables),
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index 7b67a89f989..e12736216a0 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -145,7 +145,7 @@ describe('RunnerUpdateForm', () => {
});
it('Form skeleton is shown', () => {
- expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
+ expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
expect(findFields()).toHaveLength(0);
});
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 22f0561ca5f..a7363eb11cd 100644
--- a/spec/frontend/runner/components/search_tokens/tag_token_spec.js
+++ b/spec/frontend/runner/components/search_tokens/tag_token_spec.js
@@ -77,7 +77,7 @@ describe('TagToken', () => {
const findToken = () => wrapper.findComponent(GlToken);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- beforeEach(async () => {
+ beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(TAG_SUGGESTIONS_PATH, { params: { search: '' } }).reply(200, mockTags);
@@ -86,9 +86,6 @@ describe('TagToken', () => {
.reply(200, mockTagsFiltered);
getRecentlyUsedSuggestions.mockReturnValue([]);
-
- createComponent();
- await waitForPromises();
});
afterEach(() => {
@@ -97,11 +94,17 @@ describe('TagToken', () => {
});
describe('when the tags token is displayed', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
it('requests tags suggestions', () => {
expect(mock.history.get[0].params).toEqual({ search: '' });
});
- it('displays tags suggestions', () => {
+ it('displays tags suggestions', async () => {
+ await waitForPromises();
+
mockTags.forEach(({ name }, i) => {
expect(findGlFilteredSearchSuggestions().at(i).text()).toBe(name);
});
@@ -132,13 +135,13 @@ describe('TagToken', () => {
});
describe('when the users filters suggestions', () => {
- beforeEach(async () => {
+ beforeEach(() => {
+ createComponent();
+
findGlFilteredSearchToken().vm.$emit('input', { data: mockSearchTerm });
});
- it('requests filtered tags suggestions', async () => {
- await waitForPromises();
-
+ it('requests filtered tags suggestions', () => {
expect(mock.history.get[1].params).toEqual({ search: mockSearchTerm });
});
@@ -166,7 +169,7 @@ describe('TagToken', () => {
await waitForPromises();
});
- it('error is shown', async () => {
+ it('error is shown', () => {
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({ message: expect.any(String) });
});
@@ -180,8 +183,26 @@ describe('TagToken', () => {
await waitForPromises();
});
- it('selected tag is displayed', async () => {
+ it('selected tag is displayed', () => {
expect(findToken().exists()).toBe(true);
});
});
+
+ describe('when suggestions are disabled', () => {
+ beforeEach(async () => {
+ createComponent({
+ config: {
+ ...mockTagTokenConfig,
+ suggestionsDisabled: true,
+ },
+ });
+
+ await waitForPromises();
+ });
+
+ it('displays no suggestions', () => {
+ expect(findGlFilteredSearchSuggestions()).toHaveLength(0);
+ expect(mock.history.get).toHaveLength(0);
+ });
+ });
});