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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/runner')
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js2
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js30
-rw-r--r--spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js164
-rw-r--r--spec/frontend/runner/components/cells/runner_status_cell_spec.js21
-rw-r--r--spec/frontend/runner/components/cells/runner_summary_cell_spec.js91
-rw-r--r--spec/frontend/runner/components/cells/runner_summary_field_spec.js49
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js30
-rw-r--r--spec/frontend/runner/components/runner_header_spec.js6
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js75
-rw-r--r--spec/frontend/runner/components/runner_paused_badge_spec.js5
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js65
-rw-r--r--spec/frontend/runner/components/runner_stacked_layout_banner_spec.js39
-rw-r--r--spec/frontend/runner/components/runner_status_badge_spec.js20
-rw-r--r--spec/frontend/runner/components/runner_tag_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_tags_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_type_badge_spec.js17
-rw-r--r--spec/frontend/runner/components/runner_type_tabs_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js7
-rw-r--r--spec/frontend/runner/components/stat/runner_stats_spec.js33
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js55
-rw-r--r--spec/frontend/runner/runner_edit/runner_edit_app_spec.js (renamed from spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js)13
-rw-r--r--spec/frontend/runner/utils_spec.js13
22 files changed, 531 insertions, 214 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 509681c5a77..7ab4aeee9bc 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
@@ -164,7 +164,7 @@ describe('AdminRunnerShowApp', () => {
});
});
- describe('when runner does not have an edit url ', () => {
+ describe('when runner does not have an edit url', () => {
beforeEach(async () => {
mockRunnerQueryResult({
editAdminUrl: null,
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 97341be7d5d..55a298e1695 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -17,6 +17,7 @@ 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 RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
@@ -33,6 +34,12 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ I18N_INSTANCE_TYPE,
+ I18N_GROUP_TYPE,
+ I18N_PROJECT_TYPE,
INSTANCE_TYPE,
PARAM_KEY_PAUSED,
PARAM_KEY_STATUS,
@@ -80,6 +87,7 @@ describe('AdminRunnersApp', () => {
let localMutations;
let showToast;
+ const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
@@ -139,6 +147,11 @@ describe('AdminRunnersApp', () => {
wrapper.destroy();
});
+ it('shows the feedback banner', () => {
+ createComponent();
+ expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
+ });
+
it('shows the runner setup instructions', () => {
createComponent();
@@ -156,21 +169,16 @@ describe('AdminRunnersApp', () => {
});
it('shows the runner tabs', () => {
- expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
- `All ${mockRunnersCount} Instance ${mockRunnersCount} Group ${mockRunnersCount} Project ${mockRunnersCount}`,
+ const tabs = findRunnerTypeTabs().text();
+ expect(tabs).toMatchInterpolatedText(
+ `All ${mockRunnersCount} ${I18N_INSTANCE_TYPE} ${mockRunnersCount} ${I18N_GROUP_TYPE} ${mockRunnersCount} ${I18N_PROJECT_TYPE} ${mockRunnersCount}`,
);
});
it('shows the total', () => {
- 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}`,
- );
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_ONLINE} ${mockRunnersCount}`);
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_OFFLINE} ${mockRunnersCount}`);
+ expect(findRunnerStats().text()).toContain(`${I18N_STATUS_STALE} ${mockRunnersCount}`);
});
});
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
new file mode 100644
index 00000000000..21ec9f61f37
--- /dev/null
+++ b/spec/frontend/runner/components/cells/runner_stacked_summary_cell_spec.js
@@ -0,0 +1,164 @@
+import { __ } from '~/locale';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import RunnerStackedSummaryCell from '~/runner/components/cells/runner_stacked_summary_cell.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import RunnerTags from '~/runner/components/runner_tags.vue';
+import RunnerSummaryField from '~/runner/components/cells/runner_summary_field.vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+
+import { INSTANCE_TYPE, I18N_INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
+
+import { allRunnersData } from '../../mock_data';
+
+const mockRunner = allRunnersData.data.runners.nodes[0];
+
+describe('RunnerTypeCell', () => {
+ let wrapper;
+
+ const findLockIcon = () => wrapper.findByTestId('lock-icon');
+ const findRunnerTags = () => wrapper.findComponent(RunnerTags);
+ const findRunnerSummaryField = (icon) =>
+ wrapper.findAllComponents(RunnerSummaryField).filter((w) => w.props('icon') === icon)
+ .wrappers[0];
+
+ const createComponent = (runner, options) => {
+ wrapper = mountExtended(RunnerStackedSummaryCell, {
+ propsData: {
+ runner: {
+ ...mockRunner,
+ ...runner,
+ },
+ },
+ stubs: {
+ RunnerSummaryField,
+ },
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Displays the runner name as id and short token', () => {
+ expect(wrapper.text()).toContain(
+ `#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`,
+ );
+ });
+
+ it('Does not display the locked icon', () => {
+ expect(findLockIcon().exists()).toBe(false);
+ });
+
+ it('Displays the locked icon for locked runners', () => {
+ createComponent({
+ runnerType: PROJECT_TYPE,
+ locked: true,
+ });
+
+ expect(findLockIcon().exists()).toBe(true);
+ });
+
+ it('Displays the runner type', () => {
+ createComponent({
+ runnerType: INSTANCE_TYPE,
+ locked: true,
+ });
+
+ expect(wrapper.text()).toContain(I18N_INSTANCE_TYPE);
+ });
+
+ it('Displays the runner version', () => {
+ expect(wrapper.text()).toContain(mockRunner.version);
+ });
+
+ it('Displays the runner description', () => {
+ expect(wrapper.text()).toContain(mockRunner.description);
+ });
+
+ it('Displays last contact', () => {
+ createComponent({
+ contactedAt: '2022-01-02',
+ });
+
+ expect(findRunnerSummaryField('clock').find(TimeAgo).props('time')).toBe('2022-01-02');
+ });
+
+ it('Displays empty last contact', () => {
+ createComponent({
+ contactedAt: null,
+ });
+
+ expect(findRunnerSummaryField('clock').find(TimeAgo).exists()).toBe(false);
+ expect(findRunnerSummaryField('clock').text()).toContain(__('Never'));
+ });
+
+ it('Displays ip address', () => {
+ createComponent({
+ ipAddress: '127.0.0.1',
+ });
+
+ expect(findRunnerSummaryField('disk').text()).toContain('127.0.0.1');
+ });
+
+ it('Displays no ip address', () => {
+ createComponent({
+ ipAddress: null,
+ });
+
+ expect(findRunnerSummaryField('disk')).toBeUndefined();
+ });
+
+ it('Displays job count', () => {
+ expect(findRunnerSummaryField('pipeline').text()).toContain(`${mockRunner.jobCount}`);
+ });
+
+ it('Formats large job counts', () => {
+ createComponent({
+ jobCount: 1000,
+ });
+
+ expect(findRunnerSummaryField('pipeline').text()).toContain('1,000');
+ });
+
+ it('Formats large job counts with a plus symbol', () => {
+ createComponent({
+ jobCount: 1001,
+ });
+
+ expect(findRunnerSummaryField('pipeline').text()).toContain('1,000+');
+ });
+
+ it('Displays created at', () => {
+ expect(findRunnerSummaryField('calendar').find(TimeAgo).props('time')).toBe(
+ mockRunner.createdAt,
+ );
+ });
+
+ it('Displays tag list', () => {
+ createComponent({
+ tagList: ['shell', 'linux'],
+ });
+
+ expect(findRunnerTags().props('tagList')).toEqual(['shell', 'linux']);
+ });
+
+ it('Displays a custom slot', () => {
+ const slotContent = 'My custom runner name';
+
+ createComponent(
+ {},
+ {
+ slots: {
+ 'runner-name': slotContent,
+ },
+ },
+ );
+
+ expect(wrapper.text()).toContain(slotContent);
+ });
+});
diff --git a/spec/frontend/runner/components/cells/runner_status_cell_spec.js b/spec/frontend/runner/components/cells/runner_status_cell_spec.js
index 0f5133d0ae2..1d4e3762c91 100644
--- a/spec/frontend/runner/components/cells/runner_status_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_status_cell_spec.js
@@ -3,7 +3,14 @@ import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import RunnerPausedBadge from '~/runner/components/runner_paused_badge.vue';
-import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants';
+import {
+ I18N_PAUSED,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ INSTANCE_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+} from '~/runner/constants';
describe('RunnerStatusCell', () => {
let wrapper;
@@ -31,8 +38,8 @@ describe('RunnerStatusCell', () => {
it('Displays online status', () => {
createComponent();
- expect(wrapper.text()).toMatchInterpolatedText('online');
- expect(findStatusBadge().text()).toBe('online');
+ expect(wrapper.text()).toContain(I18N_STATUS_ONLINE);
+ expect(findStatusBadge().text()).toBe(I18N_STATUS_ONLINE);
});
it('Displays offline status', () => {
@@ -42,8 +49,8 @@ describe('RunnerStatusCell', () => {
},
});
- expect(wrapper.text()).toMatchInterpolatedText('offline');
- expect(findStatusBadge().text()).toBe('offline');
+ expect(wrapper.text()).toMatchInterpolatedText(I18N_STATUS_OFFLINE);
+ expect(findStatusBadge().text()).toBe(I18N_STATUS_OFFLINE);
});
it('Displays paused status', () => {
@@ -54,8 +61,8 @@ describe('RunnerStatusCell', () => {
},
});
- expect(wrapper.text()).toMatchInterpolatedText('online paused');
- expect(findPausedBadge().text()).toBe('paused');
+ expect(wrapper.text()).toMatchInterpolatedText(`${I18N_STATUS_ONLINE} ${I18N_PAUSED}`);
+ expect(findPausedBadge().text()).toBe(I18N_PAUSED);
});
it('Is empty when data is missing', () => {
diff --git a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
deleted file mode 100644
index b06ab652212..00000000000
--- a/spec/frontend/runner/components/cells/runner_summary_cell_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { __ } from '~/locale';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue';
-import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
-
-const mockId = '1';
-const mockShortSha = '2P6oDVDm';
-const mockDescription = 'runner-1';
-const mockIpAddress = '0.0.0.0';
-
-describe('RunnerTypeCell', () => {
- let wrapper;
-
- const findLockIcon = () => wrapper.findByTestId('lock-icon');
-
- const createComponent = (runner, options) => {
- wrapper = mountExtended(RunnerSummaryCell, {
- propsData: {
- runner: {
- id: `gid://gitlab/Ci::Runner/${mockId}`,
- shortSha: mockShortSha,
- description: mockDescription,
- ipAddress: mockIpAddress,
- runnerType: INSTANCE_TYPE,
- ...runner,
- },
- },
- ...options,
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('Displays the runner name as id and short token', () => {
- expect(wrapper.text()).toContain(`#${mockId} (${mockShortSha})`);
- });
-
- it('Displays the runner type', () => {
- expect(wrapper.text()).toContain('shared');
- });
-
- it('Does not display the locked icon', () => {
- expect(findLockIcon().exists()).toBe(false);
- });
-
- it('Displays the locked icon for locked runners', () => {
- createComponent({
- runnerType: PROJECT_TYPE,
- locked: true,
- });
-
- expect(findLockIcon().exists()).toBe(true);
- });
-
- it('Displays the runner description', () => {
- expect(wrapper.text()).toContain(mockDescription);
- });
-
- it('Displays ip address', () => {
- expect(wrapper.text()).toContain(`${__('IP Address')} ${mockIpAddress}`);
- });
-
- it('Displays no ip address', () => {
- createComponent({
- ipAddress: null,
- });
-
- expect(wrapper.text()).not.toContain(__('IP Address'));
- });
-
- it('Displays a custom slot', () => {
- const slotContent = 'My custom runner summary';
-
- createComponent(
- {},
- {
- slots: {
- 'runner-name': slotContent,
- },
- },
- );
-
- expect(wrapper.text()).toContain(slotContent);
- });
-});
diff --git a/spec/frontend/runner/components/cells/runner_summary_field_spec.js b/spec/frontend/runner/components/cells/runner_summary_field_spec.js
new file mode 100644
index 00000000000..b49addf112f
--- /dev/null
+++ b/spec/frontend/runner/components/cells/runner_summary_field_spec.js
@@ -0,0 +1,49 @@
+import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerSummaryField from '~/runner/components/cells/runner_summary_field.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+describe('RunnerSummaryField', () => {
+ let wrapper;
+
+ const findIcon = () => wrapper.findComponent(GlIcon);
+ const getTooltipValue = () => getBinding(wrapper.element, 'gl-tooltip').value;
+
+ const createComponent = ({ props, ...options } = {}) => {
+ wrapper = shallowMount(RunnerSummaryField, {
+ propsData: {
+ icon: '',
+ tooltip: '',
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ ...options,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows content in slot', () => {
+ createComponent({
+ slots: { default: 'content' },
+ });
+
+ expect(wrapper.text()).toBe('content');
+ });
+
+ it('shows icon', () => {
+ createComponent({ props: { icon: 'git' } });
+
+ expect(findIcon().props('name')).toBe('git');
+ });
+
+ it('shows tooltip', () => {
+ createComponent({ props: { tooltip: 'tooltip' } });
+
+ expect(getTooltipValue()).toBe('tooltip');
+ });
+});
diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js
index 552ee29b6f9..f2281223a25 100644
--- a/spec/frontend/runner/components/runner_details_spec.js
+++ b/spec/frontend/runner/components/runner_details_spec.js
@@ -25,7 +25,12 @@ describe('RunnerDetails', () => {
const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
- const createComponent = ({ props = {}, stubs, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = ({
+ props = {},
+ stubs,
+ mountFn = shallowMountExtended,
+ enforceRunnerTokenExpiresAt = false,
+ } = {}) => {
wrapper = mountFn(RunnerDetails, {
propsData: {
...props,
@@ -34,6 +39,9 @@ describe('RunnerDetails', () => {
RunnerDetail,
...stubs,
},
+ provide: {
+ glFeatures: { enforceRunnerTokenExpiresAt },
+ },
});
};
@@ -63,6 +71,8 @@ describe('RunnerDetails', () => {
${'Maximum job timeout'} | ${{ maximumTimeout: 0 }} | ${'0 seconds'}
${'Maximum job timeout'} | ${{ maximumTimeout: 59 }} | ${'59 seconds'}
${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }} | ${'10 minutes 5 seconds'}
+ ${'Token expiry'} | ${{ tokenExpiresAt: mockOneHourAgo }} | ${'1 hour ago'}
+ ${'Token expiry'} | ${{ tokenExpiresAt: null }} | ${'Never expires'}
`('"$field" field', ({ field, runner, expectedValue }) => {
beforeEach(() => {
createComponent({
@@ -72,6 +82,7 @@ describe('RunnerDetails', () => {
...runner,
},
},
+ enforceRunnerTokenExpiresAt: true,
stubs: {
GlIntersperse,
GlSprintf,
@@ -124,5 +135,22 @@ 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_header_spec.js b/spec/frontend/runner/components/runner_header_spec.js
index 8799c218b06..701d39108cb 100644
--- a/spec/frontend/runner/components/runner_header_spec.js
+++ b/spec/frontend/runner/components/runner_header_spec.js
@@ -1,6 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
+import { I18N_STATUS_ONLINE, I18N_GROUP_TYPE, GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -49,7 +49,7 @@ describe('RunnerHeader', () => {
},
});
- expect(findRunnerStatusBadge().text()).toContain('online');
+ expect(findRunnerStatusBadge().text()).toContain(I18N_STATUS_ONLINE);
});
it('displays the runner type', () => {
@@ -60,7 +60,7 @@ describe('RunnerHeader', () => {
},
});
- expect(findRunnerTypeBadge().text()).toContain('group');
+ expect(findRunnerTypeBadge().text()).toContain(I18N_GROUP_TYPE);
});
it('displays the runner id', () => {
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 7b58a81bb0d..54a9e713721 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -7,6 +7,7 @@ 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 { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/runner/constants';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = allRunnersData.data.runners.nodes;
@@ -22,7 +23,10 @@ describe('RunnerList', () => {
const findCell = ({ row = 0, fieldKey }) =>
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
- const createComponent = ({ props = {}, ...options } = {}, mountFn = shallowMountExtended) => {
+ const createComponent = (
+ { props = {}, provide = {}, ...options } = {},
+ mountFn = shallowMountExtended,
+ ) => {
wrapper = mountFn(RunnerList, {
propsData: {
runners: mockRunners,
@@ -32,6 +36,7 @@ describe('RunnerList', () => {
provide: {
onlineContactTimeoutSecs,
staleTimeoutSecs,
+ ...provide,
},
...options,
});
@@ -60,10 +65,6 @@ describe('RunnerList', () => {
expect(headerLabels).toEqual([
'Status',
'Runner',
- 'Version',
- 'Jobs',
- 'Tags',
- 'Last contact',
'', // actions has no label
]);
});
@@ -83,24 +84,28 @@ describe('RunnerList', () => {
});
it('Displays details of a runner', () => {
- const { id, description, version, shortSha } = mockRunners[0];
-
createComponent({}, mountExtended);
+ const { id, description, version, shortSha } = mockRunners[0];
+ const numericId = getIdFromGraphQLId(id);
+
// Badges
- expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted');
+ expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText(
+ I18N_STATUS_NEVER_CONTACTED,
+ );
// Runner summary
- expect(findCell({ fieldKey: 'summary' }).text()).toContain(
- `#${getIdFromGraphQLId(id)} (${shortSha})`,
- );
- expect(findCell({ fieldKey: 'summary' }).text()).toContain(description);
+ const summary = findCell({ fieldKey: 'summary' }).text();
- // Other fields
- expect(findCell({ fieldKey: 'version' }).text()).toBe(version);
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('0');
- expect(findCell({ fieldKey: 'tagList' }).text()).toBe('');
- expect(findCell({ fieldKey: 'contactedAt' }).text()).toEqual(expect.any(String));
+ expect(summary).toContain(`#${numericId} (${shortSha})`);
+ expect(summary).toContain(I18N_PROJECT_TYPE);
+
+ expect(summary).toContain(version);
+ expect(summary).toContain(description);
+
+ expect(summary).toContain('Last contact');
+ expect(summary).toContain('0'); // job count
+ expect(summary).toContain('Created');
// Actions
expect(findCell({ fieldKey: 'actions' }).exists()).toBe(true);
@@ -159,42 +164,6 @@ describe('RunnerList', () => {
});
});
- describe('Table data formatting', () => {
- let mockRunnersCopy;
-
- beforeEach(() => {
- mockRunnersCopy = [
- {
- ...mockRunners[0],
- },
- ];
- });
-
- it('Formats job counts', () => {
- mockRunnersCopy[0].jobCount = 1;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1');
- });
-
- it('Formats large job counts', () => {
- mockRunnersCopy[0].jobCount = 1000;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000');
- });
-
- it('Formats large job counts with a plus symbol', () => {
- mockRunnersCopy[0].jobCount = 1001;
-
- createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
-
- expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000+');
- });
- });
-
it('Shows runner identifier', () => {
const { id, shortSha } = mockRunners[0];
const numericId = getIdFromGraphQLId(id);
diff --git a/spec/frontend/runner/components/runner_paused_badge_spec.js b/spec/frontend/runner/components/runner_paused_badge_spec.js
index 18cfcfae864..c1c7351aab2 100644
--- a/spec/frontend/runner/components/runner_paused_badge_spec.js
+++ b/spec/frontend/runner/components/runner_paused_badge_spec.js
@@ -2,6 +2,7 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStatePausedBadge from '~/runner/components/runner_paused_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { I18N_PAUSED } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@@ -29,8 +30,8 @@ describe('RunnerTypeBadge', () => {
});
it('renders paused state', () => {
- expect(wrapper.text()).toBe('paused');
- expect(findBadge().props('variant')).toBe('danger');
+ expect(wrapper.text()).toBe(I18N_PAUSED);
+ expect(findBadge().props('variant')).toBe('warning');
});
it('renders tooltip', () => {
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
index c988fb8477d..eca042cae86 100644
--- a/spec/frontend/runner/components/runner_projects_spec.js
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -1,4 +1,4 @@
-import { GlSkeletonLoader } from '@gitlab/ui';
+import { GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -8,7 +8,9 @@ import { createAlert } from '~/flash';
import { sprintf } from '~/locale';
import {
I18N_ASSIGNED_PROJECTS,
- I18N_NONE,
+ I18N_CLEAR_FILTER_PROJECTS,
+ I18N_FILTER_PROJECTS,
+ I18N_NO_PROJECTS_FOUND,
RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
} from '~/runner/constants';
import RunnerProjects from '~/runner/components/runner_projects.vue';
@@ -35,6 +37,7 @@ describe('RunnerProjects', () => {
const findHeading = () => wrapper.find('h3');
const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader);
+ const findGlSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
@@ -64,10 +67,21 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
expect(mockRunnerProjectsQuery).toHaveBeenCalledWith({
id: mockRunner.id,
+ search: '',
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
});
});
+ it('Shows a filter box', () => {
+ createComponent();
+
+ expect(findGlSearchBoxByType().attributes()).toMatchObject({
+ clearbuttontitle: I18N_CLEAR_FILTER_PROJECTS,
+ debounce: '500',
+ placeholder: I18N_FILTER_PROJECTS,
+ });
+ });
+
describe('When there are projects assigned', () => {
beforeEach(async () => {
mockRunnerProjectsQuery.mockResolvedValueOnce(runnerProjectsData);
@@ -110,6 +124,7 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2);
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
id: mockRunner.id,
+ search: '',
first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
after: 'AFTER_CURSOR',
});
@@ -123,10 +138,51 @@ describe('RunnerProjects', () => {
expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3);
expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
id: mockRunner.id,
+ search: '',
last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
before: 'BEFORE_CURSOR',
});
});
+
+ it('When user filters after paginating, the first page is requested', async () => {
+ findGlSearchBoxByType().vm.$emit('input', 'my search');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ search: 'my search',
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ });
+ });
+ });
+
+ describe('When user filters', () => {
+ it('Filtered results are requested', async () => {
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+
+ findGlSearchBoxByType().vm.$emit('input', 'my search');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ search: 'my search',
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ });
+ });
+
+ it('Filtered results are not requested for short searches', async () => {
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+
+ findGlSearchBoxByType().vm.$emit('input', 'm');
+ await waitForPromises();
+
+ findGlSearchBoxByType().vm.$emit('input', 'my');
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+ });
});
});
@@ -136,10 +192,11 @@ describe('RunnerProjects', () => {
expect(findGlSkeletonLoading().exists()).toBe(true);
- expect(wrapper.findByText(I18N_NONE).exists()).toBe(false);
+ expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(false);
expect(findRunnerAssignedItems().length).toBe(0);
expect(findRunnerPagination().attributes('disabled')).toBe('true');
+ expect(findGlSearchBoxByType().props('isLoading')).toBe(true);
});
});
@@ -168,7 +225,7 @@ describe('RunnerProjects', () => {
});
it('Shows a "None" label', () => {
- expect(wrapper.findByText(I18N_NONE).exists()).toBe(true);
+ expect(wrapper.findByText(I18N_NO_PROJECTS_FOUND).exists()).toBe(true);
});
});
diff --git a/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
new file mode 100644
index 00000000000..1a8aced9292
--- /dev/null
+++ b/spec/frontend/runner/components/runner_stacked_layout_banner_spec.js
@@ -0,0 +1,39 @@
+import { nextTick } from 'vue';
+import { GlBanner } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+
+describe('RunnerStackedLayoutBanner', () => {
+ let wrapper;
+
+ const findBanner = () => wrapper.findComponent(GlBanner);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+
+ const createComponent = ({ ...options } = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(RunnerStackedLayoutBanner, {
+ ...options,
+ });
+ };
+
+ it('Displays a banner', () => {
+ createComponent();
+
+ expect(findBanner().props()).toMatchObject({
+ svgPath: expect.stringContaining('data:image/svg+xml;utf8,'),
+ title: expect.any(String),
+ buttonText: expect.any(String),
+ buttonLink: expect.stringContaining('https://gitlab.com/gitlab-org/gitlab/-/issues/'),
+ });
+ expect(findLocalStorageSync().exists()).toBe(true);
+ });
+
+ it('Does not display a banner when dismissed', async () => {
+ findLocalStorageSync().vm.$emit('input', true);
+
+ await nextTick();
+
+ expect(findBanner().exists()).toBe(false);
+ expect(findLocalStorageSync().exists()).toBe(true); // continues syncing after removal
+ });
+});
diff --git a/spec/frontend/runner/components/runner_status_badge_spec.js b/spec/frontend/runner/components/runner_status_badge_spec.js
index bb833bd7d5a..9ab6378304f 100644
--- a/spec/frontend/runner/components/runner_status_badge_spec.js
+++ b/spec/frontend/runner/components/runner_status_badge_spec.js
@@ -3,12 +3,16 @@ import { shallowMount } from '@vue/test-utils';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_NEVER_CONTACTED,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ I18N_NEVER_CONTACTED_TOOLTIP,
+ I18N_STALE_NEVER_CONTACTED_TOOLTIP,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
STATUS_NEVER_CONTACTED,
- I18N_NEVER_CONTACTED_TOOLTIP,
- I18N_STALE_NEVER_CONTACTED_TOOLTIP,
} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
@@ -46,7 +50,7 @@ describe('RunnerTypeBadge', () => {
it('renders online state', () => {
createComponent();
- expect(wrapper.text()).toBe('online');
+ expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(findBadge().props('variant')).toBe('success');
expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago');
});
@@ -59,7 +63,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('never contacted');
+ expect(wrapper.text()).toBe(I18N_STATUS_NEVER_CONTACTED);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe(I18N_NEVER_CONTACTED_TOOLTIP);
});
@@ -72,7 +76,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('offline');
+ expect(wrapper.text()).toBe(I18N_STATUS_OFFLINE);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe('Runner is offline; last contact was 1 day ago');
});
@@ -85,7 +89,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('stale');
+ expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe('Runner is stale; last contact was 1 year ago');
});
@@ -98,7 +102,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('stale');
+ expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe(I18N_STALE_NEVER_CONTACTED_TOOLTIP);
});
@@ -112,7 +116,7 @@ describe('RunnerTypeBadge', () => {
},
});
- expect(wrapper.text()).toBe('online');
+ expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(getTooltip().value).toBe('Runner is online; last contact was never');
});
diff --git a/spec/frontend/runner/components/runner_tag_spec.js b/spec/frontend/runner/components/runner_tag_spec.js
index bd05d4b2cfe..391c17f81cb 100644
--- a/spec/frontend/runner/components/runner_tag_spec.js
+++ b/spec/frontend/runner/components/runner_tag_spec.js
@@ -1,6 +1,8 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+
+import { RUNNER_TAG_BADGE_VARIANT } from '~/runner/constants';
import RunnerTag from '~/runner/components/runner_tag.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -48,7 +50,7 @@ describe('RunnerTag', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props()).toMatchObject({
size: 'sm',
- variant: 'neutral',
+ variant: RUNNER_TAG_BADGE_VARIANT,
});
});
diff --git a/spec/frontend/runner/components/runner_tags_spec.js b/spec/frontend/runner/components/runner_tags_spec.js
index da89a659432..c6bfabdb18a 100644
--- a/spec/frontend/runner/components/runner_tags_spec.js
+++ b/spec/frontend/runner/components/runner_tags_spec.js
@@ -34,7 +34,6 @@ describe('RunnerTags', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props('size')).toBe('sm');
- expect(findBadge().props('variant')).toBe('neutral');
});
it('Displays tags with md size', () => {
@@ -50,7 +49,6 @@ describe('RunnerTags', () => {
props: { tagList: null },
});
- expect(wrapper.text()).toBe('');
- expect(findBadge().exists()).toBe(false);
+ expect(wrapper.html()).toEqual('');
});
});
diff --git a/spec/frontend/runner/components/runner_type_badge_spec.js b/spec/frontend/runner/components/runner_type_badge_spec.js
index 7bb0a2e6e2f..fe922fb9d18 100644
--- a/spec/frontend/runner/components/runner_type_badge_spec.js
+++ b/spec/frontend/runner/components/runner_type_badge_spec.js
@@ -2,7 +2,14 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
+import {
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ I18N_INSTANCE_TYPE,
+ I18N_GROUP_TYPE,
+ I18N_PROJECT_TYPE,
+} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@@ -27,9 +34,9 @@ describe('RunnerTypeBadge', () => {
describe.each`
type | text
- ${INSTANCE_TYPE} | ${'shared'}
- ${GROUP_TYPE} | ${'group'}
- ${PROJECT_TYPE} | ${'specific'}
+ ${INSTANCE_TYPE} | ${I18N_INSTANCE_TYPE}
+ ${GROUP_TYPE} | ${I18N_GROUP_TYPE}
+ ${PROJECT_TYPE} | ${I18N_PROJECT_TYPE}
`('displays $type runner', ({ type, text }) => {
beforeEach(() => {
createComponent({ props: { type } });
@@ -37,7 +44,7 @@ describe('RunnerTypeBadge', () => {
it(`as "${text}" with an "info" variant`, () => {
expect(findBadge().text()).toBe(text);
- expect(findBadge().props('variant')).toBe('info');
+ expect(findBadge().props('variant')).toBe('muted');
});
it('with a tooltip', () => {
diff --git a/spec/frontend/runner/components/runner_type_tabs_spec.js b/spec/frontend/runner/components/runner_type_tabs_spec.js
index 22d2a9e60f7..45ab8684332 100644
--- a/spec/frontend/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/runner/components/runner_type_tabs_spec.js
@@ -28,7 +28,7 @@ const mockCount = (type, multiplier = 1) => {
describe('RunnerTypeTabs', () => {
let wrapper;
- const findTabs = () => wrapper.findAll(GlTab);
+ const findTabs = () => wrapper.findAllComponents(GlTab);
const findActiveTab = () =>
findTabs()
.filter((tab) => tab.attributes('active') === 'true')
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index 3037364d941..7b67a89f989 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -1,6 +1,7 @@
import Vue, { nextTick } from 'vue';
import { GlForm, GlSkeletonLoader } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
+import { __ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -47,6 +48,7 @@ describe('RunnerUpdateForm', () => {
const findSubmit = () => wrapper.find('[type="submit"]');
const findSubmitDisabledAttr = () => findSubmit().attributes('disabled');
+ const findCancelBtn = () => wrapper.findByRole('link', { name: __('Cancel') });
const submitForm = () => findForm().trigger('submit');
const submitFormAndWait = () => submitForm().then(waitForPromises);
@@ -117,6 +119,11 @@ describe('RunnerUpdateForm', () => {
expect(mockRunner).toMatchObject(getFieldsModel());
});
+ it('Form shows a cancel button', () => {
+ expect(runnerUpdateHandler).not.toHaveBeenCalled();
+ expect(findCancelBtn().attributes('href')).toBe(mockRunnerPath);
+ });
+
it('Form prevent multiple submissions', async () => {
await submitForm();
diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js
index 7f1f22be94f..4afbe453903 100644
--- a/spec/frontend/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/runner/components/stat/runner_stats_spec.js
@@ -1,13 +1,20 @@
import { shallowMount, mount } from '@vue/test-utils';
-import { s__ } from '~/locale';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
-import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
+import {
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
+ INSTANCE_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
+} from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
- const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat).wrappers;
+ const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat);
const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(RunnerStats, {
@@ -46,16 +53,28 @@ describe('RunnerStats', () => {
});
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`);
+ expect(text).toContain(`${I18N_STATUS_ONLINE} 3`);
+ expect(text).toContain(`${I18N_STATUS_OFFLINE} 2`);
+ expect(text).toContain(`${I18N_STATUS_STALE} 1`);
+ });
+
+ it('Skips query for other stats', () => {
+ createComponent({
+ props: {
+ variables: { status: STATUS_ONLINE },
+ },
+ });
+
+ expect(findSingleStats().at(0).props('skip')).toBe(false);
+ expect(findSingleStats().at(1).props('skip')).toBe(true);
+ expect(findSingleStats().at(2).props('skip')).toBe(true);
});
it('Displays all counts for filtered searches', () => {
const mockVariables = { paused: true };
createComponent({ props: { variables: mockVariables } });
- findSingleStats().forEach((stat) => {
+ findSingleStats().wrappers.forEach((stat) => {
expect(stat.props('variables')).toMatchObject(mockVariables);
});
});
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 57d64202219..a17502c7eec 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -15,6 +15,7 @@ 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 RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
@@ -28,6 +29,9 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
+ I18N_STATUS_ONLINE,
+ I18N_STATUS_OFFLINE,
+ I18N_STATUS_STALE,
INSTANCE_TYPE,
GROUP_TYPE,
PARAM_KEY_PAUSED,
@@ -74,6 +78,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
+ const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
@@ -122,6 +127,11 @@ describe('GroupRunnersApp', () => {
wrapper.destroy();
});
+ it('shows the feedback banner', () => {
+ createComponent();
+ expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
+ });
+
it('shows the runner tabs with a runner count for each type', async () => {
await createComponent({ mountFn: mountExtended });
@@ -153,15 +163,10 @@ describe('GroupRunnersApp', () => {
groupFullPath: mockGroupFullPath,
});
- 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}`,
- );
+ const text = findRunnerStats().text();
+ expect(text).toContain(`${I18N_STATUS_ONLINE} ${mockGroupRunnersCount}`);
+ expect(text).toContain(`${I18N_STATUS_OFFLINE} ${mockGroupRunnersCount}`);
+ expect(text).toContain(`${I18N_STATUS_STALE} ${mockGroupRunnersCount}`);
});
it('shows the runners list', async () => {
@@ -396,4 +401,36 @@ describe('GroupRunnersApp', () => {
});
});
});
+
+ describe('when user has permission to register group runner', () => {
+ beforeEach(() => {
+ createComponent({
+ propsData: {
+ registrationToken: mockRegistrationToken,
+ groupFullPath: mockGroupFullPath,
+ groupRunnersLimitedCount: mockGroupRunnersCount,
+ },
+ });
+ });
+
+ it('shows the register group runner button', () => {
+ expect(findRegistrationDropdown().exists()).toBe(true);
+ });
+ });
+
+ describe('when user has no permission to register group runner', () => {
+ beforeEach(() => {
+ createComponent({
+ propsData: {
+ registrationToken: null,
+ groupFullPath: mockGroupFullPath,
+ groupRunnersLimitedCount: mockGroupRunnersCount,
+ },
+ });
+ });
+
+ it('does not show the register group runner button', () => {
+ expect(findRegistrationDropdown().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js b/spec/frontend/runner/runner_edit/runner_edit_app_spec.js
index ffe3599ac64..fb118817d51 100644
--- a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
+++ b/spec/frontend/runner/runner_edit/runner_edit_app_spec.js
@@ -9,8 +9,9 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
import runnerFormQuery from '~/runner/graphql/edit/runner_form.query.graphql';
-import AdminRunnerEditApp from '~//runner/admin_runner_edit/admin_runner_edit_app.vue';
+import RunnerEditApp from '~//runner/runner_edit/runner_edit_app.vue';
import { captureException } from '~/runner/sentry_utils';
+import { I18N_STATUS_NEVER_CONTACTED, I18N_INSTANCE_TYPE } from '~/runner/constants';
import { runnerFormData } from '../mock_data';
@@ -24,7 +25,7 @@ const mockRunnerPath = `/admin/runners/${mockRunnerId}`;
Vue.use(VueApollo);
-describe('AdminRunnerEditApp', () => {
+describe('RunnerEditApp', () => {
let wrapper;
let mockRunnerQuery;
@@ -32,7 +33,7 @@ describe('AdminRunnerEditApp', () => {
const findRunnerUpdateForm = () => wrapper.findComponent(RunnerUpdateForm);
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
- wrapper = mountFn(AdminRunnerEditApp, {
+ wrapper = mountFn(RunnerEditApp, {
apolloProvider: createMockApollo([[runnerFormQuery, mockRunnerQuery]]),
propsData: {
runnerId: mockRunnerId,
@@ -69,8 +70,8 @@ describe('AdminRunnerEditApp', () => {
it('displays the runner type and status', async () => {
await createComponentWithApollo({ mountFn: mount });
- expect(findRunnerHeader().text()).toContain(`never contacted`);
- expect(findRunnerHeader().text()).toContain(`shared`);
+ expect(findRunnerHeader().text()).toContain(I18N_STATUS_NEVER_CONTACTED);
+ expect(findRunnerHeader().text()).toContain(I18N_INSTANCE_TYPE);
});
it('displays a loading runner form', () => {
@@ -102,7 +103,7 @@ describe('AdminRunnerEditApp', () => {
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
error: new Error('Error!'),
- component: 'AdminRunnerEditApp',
+ component: 'RunnerEditApp',
});
});
diff --git a/spec/frontend/runner/utils_spec.js b/spec/frontend/runner/utils_spec.js
index 1db9815dfd8..33de1345f85 100644
--- a/spec/frontend/runner/utils_spec.js
+++ b/spec/frontend/runner/utils_spec.js
@@ -1,4 +1,4 @@
-import { formatJobCount, tableField, getPaginationVariables } from '~/runner/utils';
+import { formatJobCount, tableField, getPaginationVariables, parseInterval } from '~/runner/utils';
describe('~/runner/utils', () => {
describe('formatJobCount', () => {
@@ -66,4 +66,15 @@ describe('~/runner/utils', () => {
expect(getPaginationVariables(pagination, pageSize)).toEqual(variables);
});
});
+
+ describe('parseInterval', () => {
+ it.each`
+ case | argument | returnValue
+ ${'parses integer'} | ${'86400'} | ${86400}
+ ${'returns null for undefined'} | ${undefined} | ${null}
+ ${'returns null for null'} | ${null} | ${null}
+ `('$case', ({ argument, returnValue }) => {
+ expect(parseInterval(argument)).toStrictEqual(returnValue);
+ });
+ });
});