Commit 02ab624c authored by Miguel Rincon's avatar Miguel Rincon

Update runner status badges and locked icon

This change updates runner list with a status badge for each runner that
indicates if a runner is offline, online and/or paused.

It adds a lock icon for runners that are locked to their projects.

Changelog: changed
parent 2b5e3a6a
......@@ -3,7 +3,7 @@ import { GlButton, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import createFlash from '~/flash';
import { __, s__ } from '~/locale';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql';
import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
import { captureException } from '~/runner/sentry_utils';
const i18n = {
......@@ -71,7 +71,7 @@ export default {
runnerUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: runnerUpdateMutation,
mutation: runnerActionsUpdateMutation,
variables: {
input: {
id: this.runner.id,
......
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import RunnerTypeBadge from '../runner_type_badge.vue';
import RunnerStateLockedBadge from '../runner_state_locked_badge.vue';
import RunnerStatePausedBadge from '../runner_state_paused_badge.vue';
import RunnerContactedStateBadge from '../runner_contacted_state_badge.vue';
import RunnerPausedBadge from '../runner_paused_badge.vue';
import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants';
export default {
components: {
RunnerTypeBadge,
RunnerStateLockedBadge,
RunnerStatePausedBadge,
RunnerContactedStateBadge,
RunnerPausedBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -21,12 +21,6 @@ export default {
},
},
computed: {
runnerType() {
return this.runner.runnerType;
},
locked() {
return this.runner.locked;
},
paused() {
return !this.runner.active;
},
......@@ -40,8 +34,7 @@ export default {
<template>
<div>
<runner-type-badge :type="runnerType" size="sm" />
<runner-state-locked-badge v-if="locked" size="sm" />
<runner-state-paused-badge v-if="paused" size="sm" />
<runner-contacted-state-badge :runner="runner" size="sm" />
<runner-paused-badge v-if="paused" size="sm" />
</div>
</template>
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import RunnerName from '../runner_name.vue';
import RunnerTypeBadge from '../runner_type_badge.vue';
import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../../constants';
export default {
components: {
GlIcon,
TooltipOnTruncate,
RunnerName,
RunnerTypeBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
runner: {
......@@ -14,10 +24,19 @@ export default {
},
},
computed: {
runnerType() {
return this.runner.runnerType;
},
locked() {
return this.runner.locked;
},
description() {
return this.runner.description;
},
},
i18n: {
I18N_LOCKED_RUNNER_DESCRIPTION,
},
};
</script>
......@@ -26,6 +45,14 @@ export default {
<slot :runner="runner" name="runner-name">
<runner-name :runner="runner" />
</slot>
<runner-type-badge :type="runnerType" size="sm" />
<gl-icon
v-if="locked"
v-gl-tooltip
:title="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION"
name="lock"
/>
<tooltip-on-truncate class="gl-display-block" :title="description" truncate-target="child">
<div class="gl-text-truncate">
{{ description }}
......
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import {
I18N_ONLINE_RUNNER_DESCRIPTION,
I18N_OFFLINE_RUNNER_DESCRIPTION,
I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_NOT_CONNECTED,
} from '../constants';
export default {
components: {
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
runner: {
required: true,
type: Object,
},
},
computed: {
contactedAtTimeAgo() {
if (this.runner.contactedAt) {
return getTimeago().format(this.runner.contactedAt);
}
return null;
},
badge() {
switch (this.runner.status) {
case STATUS_ONLINE:
return {
variant: 'success',
label: s__('Runners|online'),
tooltip: sprintf(I18N_ONLINE_RUNNER_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo,
}),
};
case STATUS_OFFLINE:
return {
variant: 'muted',
label: s__('Runners|offline'),
tooltip: sprintf(I18N_OFFLINE_RUNNER_DESCRIPTION, {
timeAgo: this.contactedAtTimeAgo,
}),
};
case STATUS_NOT_CONNECTED:
return {
variant: 'muted',
label: s__('Runners|not connected'),
tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
};
default:
return null;
}
},
},
};
</script>
<template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs">
{{ badge.label }}
</gl-badge>
</template>
......@@ -5,7 +5,7 @@ import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import RunnerActionsCell from './cells/runner_actions_cell.vue';
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
import RunnerTypeCell from './cells/runner_type_cell.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
import RunnerTags from './runner_tags.vue';
const tableField = ({ key, label = '', width = 10 }) => {
......@@ -36,7 +36,7 @@ export default {
RunnerActionsCell,
RunnerSummaryCell,
RunnerTags,
RunnerTypeCell,
RunnerStatusCell,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -63,8 +63,8 @@ export default {
},
},
fields: [
tableField({ key: 'type', label: __('Type/State') }),
tableField({ key: 'summary', label: s__('Runners|Runner'), width: 30 }),
tableField({ key: 'status', label: s__('Runners|Status') }),
tableField({ key: 'summary', label: s__('Runners|Runner ID'), width: 30 }),
tableField({ key: 'version', label: __('Version') }),
tableField({ key: 'ipAddress', label: __('IP Address') }),
tableField({ key: 'tagList', label: __('Tags'), width: 20 }),
......@@ -88,8 +88,8 @@ export default {
<gl-skeleton-loader v-for="i in 4" :key="i" />
</template>
<template #cell(type)="{ item }">
<runner-type-cell :runner="item" />
<template #cell(status)="{ item }">
<runner-status-cell :runner="item" />
</template>
<template #cell(summary)="{ item, index }">
......
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants';
export default {
components: {
GlBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
},
i18n: {
I18N_LOCKED_RUNNER_DESCRIPTION,
},
};
</script>
<template>
<gl-badge
v-gl-tooltip="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION"
variant="warning"
v-bind="$attrs"
>
{{ s__('Runners|locked') }}
</gl-badge>
</template>
......@@ -9,17 +9,14 @@ const ALERT_DATA = {
message: s__(
'Runners|This runner is available to all groups and projects in your GitLab instance.',
),
variant: 'success',
anchor: 'shared-runners',
},
[GROUP_TYPE]: {
message: s__('Runners|This runner is available to all projects and subgroups in a group.'),
variant: 'success',
anchor: 'group-runners',
},
[PROJECT_TYPE]: {
message: s__('Runners|This runner is associated with one or more projects.'),
variant: 'info',
anchor: 'specific-runners',
},
};
......@@ -50,7 +47,7 @@ export default {
};
</script>
<template>
<gl-alert v-if="alert" :variant="alert.variant" :dismissible="false">
<gl-alert v-if="alert" variant="info" :dismissible="false">
{{ alert.message }}
<gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link>
</gl-alert>
......
......@@ -12,17 +12,14 @@ import {
const BADGE_DATA = {
[INSTANCE_TYPE]: {
variant: 'success',
text: s__('Runners|shared'),
tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION,
},
[GROUP_TYPE]: {
variant: 'success',
text: s__('Runners|group'),
tooltip: I18N_GROUP_RUNNER_DESCRIPTION,
},
[PROJECT_TYPE]: {
variant: 'info',
text: s__('Runners|specific'),
tooltip: I18N_PROJECT_RUNNER_DESCRIPTION,
},
......@@ -53,7 +50,7 @@ export default {
};
</script>
<template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs">
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" variant="info" v-bind="$attrs">
{{ badge.text }}
</gl-badge>
</template>
......@@ -6,11 +6,24 @@ export const GROUP_RUNNER_COUNT_LIMIT = 1000;
export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.');
export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}');
// Type
export const I18N_INSTANCE_RUNNER_DESCRIPTION = s__('Runners|Available to all projects');
export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
'Runners|Available to all projects and subgroups in the group',
);
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
// Status
export const I18N_ONLINE_RUNNER_DESCRIPTION = s__(
'Runners|Runner is online; last contact was %{timeAgo}',
);
export const I18N_OFFLINE_RUNNER_DESCRIPTION = s__(
'Runners|No recent contact from this runner; last contact was %{timeAgo}',
);
export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__(
'Runners|This runner has never connected to this instance',
);
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects');
export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs');
......
#import "~/runner/graphql/runner_node.fragment.graphql"
# Mutation for updates within the runners list via action
# buttons (play, pause, ...), loads attributes shown in the
# runner list.
mutation runnerActionsUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
...RunnerNode
}
errors
}
}
......@@ -10,4 +10,5 @@ fragment RunnerNode on CiRunner {
locked
tagList
contactedAt
status
}
#import "ee_else_ce/runner/graphql/runner_details.fragment.graphql"
# Mutation for updates from the runner form, loads
# attributes shown in the runner details.
mutation runnerUpdate($input: RunnerUpdateInput!) {
runnerUpdate(input: $input) {
runner {
......
......@@ -29691,6 +29691,9 @@ msgstr ""
msgid "Runners|New runner, has not connected yet"
msgstr ""
msgid "Runners|No recent contact from this runner; last contact was %{timeAgo}"
msgstr ""
msgid "Runners|Not available to run jobs"
msgstr ""
......@@ -29742,6 +29745,9 @@ msgstr ""
msgid "Runners|Runner #%{runner_id}"
msgstr ""
msgid "Runners|Runner ID"
msgstr ""
msgid "Runners|Runner assigned to project."
msgstr ""
......@@ -29751,6 +29757,9 @@ msgstr ""
msgid "Runners|Runner is online, last contact was %{runner_contact} ago"
msgstr ""
msgid "Runners|Runner is online; last contact was %{timeAgo}"
msgstr ""
msgid "Runners|Runner is paused, last contact was %{runner_contact} ago"
msgstr ""
......@@ -29781,12 +29790,18 @@ msgstr ""
msgid "Runners|Something went wrong while fetching the tags suggestions"
msgstr ""
msgid "Runners|Status"
msgstr ""
msgid "Runners|Stop the runner from accepting new jobs."
msgstr ""
msgid "Runners|Tags"
msgstr ""
msgid "Runners|This runner has never connected to this instance"
msgstr ""
msgid "Runners|This runner is associated with one or more projects."
msgstr ""
......@@ -29853,6 +29868,15 @@ msgstr ""
msgid "Runners|locked"
msgstr ""
msgid "Runners|not connected"
msgstr ""
msgid "Runners|offline"
msgstr ""
msgid "Runners|online"
msgstr ""
msgid "Runners|paused"
msgstr ""
......
......@@ -8,12 +8,11 @@ import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue'
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql';
import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
import { captureException } from '~/runner/sentry_utils';
import { runnersData, runnerData } from '../../mock_data';
import { runnersData } from '../../mock_data';
const mockRunner = runnersData.data.runners.nodes[0];
const mockRunnerDetails = runnerData.data.runner;
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value;
......@@ -27,7 +26,7 @@ jest.mock('~/runner/sentry_utils');
describe('RunnerTypeCell', () => {
let wrapper;
const runnerDeleteMutationHandler = jest.fn();
const runnerUpdateMutationHandler = jest.fn();
const runnerActionsUpdateMutationHandler = jest.fn();
const findEditBtn = () => wrapper.findByTestId('edit-runner');
const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
......@@ -46,7 +45,7 @@ describe('RunnerTypeCell', () => {
localVue,
apolloProvider: createMockApollo([
[runnerDeleteMutation, runnerDeleteMutationHandler],
[runnerUpdateMutation, runnerUpdateMutationHandler],
[runnerActionsUpdateMutation, runnerActionsUpdateMutationHandler],
]),
...options,
}),
......@@ -62,10 +61,10 @@ describe('RunnerTypeCell', () => {
},
});
runnerUpdateMutationHandler.mockResolvedValue({
runnerActionsUpdateMutationHandler.mockResolvedValue({
data: {
runnerUpdate: {
runner: mockRunnerDetails,
runner: mockRunner,
errors: [],
},
},
......@@ -74,7 +73,7 @@ describe('RunnerTypeCell', () => {
afterEach(() => {
runnerDeleteMutationHandler.mockReset();
runnerUpdateMutationHandler.mockReset();
runnerActionsUpdateMutationHandler.mockReset();
wrapper.destroy();
});
......@@ -116,12 +115,12 @@ describe('RunnerTypeCell', () => {
describe(`When clicking on the ${icon} button`, () => {
it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => {
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(0);
expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(0);
await findToggleActiveBtn().vm.$emit('click');
expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(1);
expect(runnerUpdateMutationHandler).toHaveBeenCalledWith({
expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(1);
expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledWith({
input: {
id: mockRunner.id,
active: newActiveValue,
......@@ -145,7 +144,7 @@ describe('RunnerTypeCell', () => {
const mockErrorMsg = 'Update error!';
beforeEach(async () => {
runnerUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
runnerActionsUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
await findToggleActiveBtn().vm.$emit('click');
});
......@@ -167,10 +166,10 @@ describe('RunnerTypeCell', () => {
const mockErrorMsg2 = 'User not allowed!';
beforeEach(async () => {
runnerUpdateMutationHandler.mockResolvedValue({
runnerActionsUpdateMutationHandler.mockResolvedValue({
data: {
runnerUpdate: {
runner: runnerData.data.runner,
runner: mockRunner,
errors: [mockErrorMsg, mockErrorMsg2],
},
},
......
import { GlBadge } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import RunnerTypeCell from '~/runner/components/cells/runner_type_cell.vue';
import { INSTANCE_TYPE } from '~/runner/constants';
import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue';
import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants';
describe('RunnerTypeCell', () => {
let wrapper;
const findBadges = () => wrapper.findAllComponents(GlBadge);
const findBadgeAt = (i) => wrapper.findAllComponents(GlBadge).at(i);
const createComponent = ({ runner = {} } = {}) => {
wrapper = mount(RunnerTypeCell, {
wrapper = mount(RunnerStatusCell, {
propsData: {
runner: {
runnerType: INSTANCE_TYPE,
active: true,
locked: false,
status: STATUS_ONLINE,
...runner,
},
},
......@@ -25,24 +25,45 @@ describe('RunnerTypeCell', () => {
wrapper.destroy();
});
it('Displays the runner type', () => {
it('Displays online status', () => {
createComponent();
expect(findBadges()).toHaveLength(1);
expect(findBadges().at(0).text()).toBe('shared');
expect(wrapper.text()).toMatchInterpolatedText('online');
expect(findBadgeAt(0).text()).toBe('online');
});
it('Displays locked and paused states', () => {
it('Displays offline status', () => {
createComponent({
runner: {
status: STATUS_OFFLINE,
},
});
expect(wrapper.text()).toMatchInterpolatedText('offline');
expect(findBadgeAt(0).text()).toBe('offline');
});
it('Displays paused status', () => {
createComponent({
runner: {
active: false,
locked: true,
status: STATUS_ONLINE,
},
});
expect(wrapper.text()).toMatchInterpolatedText('online paused');
expect(findBadgeAt(0).text()).toBe('online');
expect(findBadgeAt(1).text()).toBe('paused');
});
it('Is empty when data is missing', () => {
createComponent({
runner: {
status: null,
},
});
expect(findBadges()).toHaveLength(3);
expect(findBadges().at(0).text()).toBe('shared');
expect(findBadges().at(1).text()).toBe('locked');
expect(findBadges().at(2).text()).toBe('paused');
expect(wrapper.text()).toBe('');
});
});
import { mount } from '@vue/test-utils';
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';
......@@ -8,13 +9,17 @@ const mockDescription = 'runner-1';
describe('RunnerTypeCell', () => {
let wrapper;
const createComponent = (options) => {
wrapper = mount(RunnerSummaryCell, {
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,
runnerType: INSTANCE_TYPE,
...runner,
},
},
...options,
......@@ -33,6 +38,23 @@ describe('RunnerTypeCell', () => {
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);
});
......@@ -40,11 +62,14 @@ describe('RunnerTypeCell', () => {
it('Displays a custom slot', () => {
const slotContent = 'My custom runner summary';
createComponent({
slots: {
'runner-name': slotContent,
createComponent(
{},
{
slots: {
'runner-name': slotContent,
},
},
});
);
expect(wrapper.text()).toContain(slotContent);
});
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerContactedStateBadge from '~/runner/components/runner_contacted_state_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_NOT_CONNECTED } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip');
const createComponent = ({ runner = {} } = {}) => {
wrapper = shallowMount(RunnerContactedStateBadge, {
propsData: {
runner: {
contactedAt: '2021-01-01T00:00:00Z',
status: STATUS_ONLINE,
...runner,
},
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
jest.useFakeTimers('modern');
});
afterEach(() => {
jest.useFakeTimers('legacy');
wrapper.destroy();
});
it('renders online state', () => {
jest.setSystemTime(new Date('2021-01-01T00:01:00Z'));
createComponent();
expect(wrapper.text()).toBe('online');
expect(findBadge().props('variant')).toBe('success');
expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago');
});
it('renders offline state', () => {
jest.setSystemTime(new Date('2021-01-02T00:00:00Z'));
createComponent({
runner: {
status: STATUS_OFFLINE,
},
});
expect(wrapper.text()).toBe('offline');
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe(
'No recent contact from this runner; last contact was 1 day ago',
);
});
it('renders not connected state', () => {
createComponent({
runner: {
contactedAt: null,
status: STATUS_NOT_CONNECTED,
},
});
expect(wrapper.text()).toBe('not connected');
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toMatch('This runner has never connected');
});
it('does not fail when data is missing', () => {
createComponent({
runner: {
status: null,
},
});
expect(wrapper.text()).toBe('');
});
});
......@@ -42,8 +42,8 @@ describe('RunnerList', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text());
expect(headerLabels).toEqual([
'Type/State',
'Runner',
'Status',
'Runner ID',
'Version',
'IP Address',
'Tags',
......@@ -62,7 +62,7 @@ describe('RunnerList', () => {
const { id, description, version, ipAddress, shortSha } = mockRunners[0];
// Badges
expect(findCell({ fieldKey: 'type' }).text()).toMatchInterpolatedText('specific paused');
expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('not connected paused');
// Runner summary
expect(findCell({ fieldKey: 'summary' }).text()).toContain(
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStatePausedBadge from '~/runner/components/runner_state_paused_badge.vue';
import RunnerStatePausedBadge from '~/runner/components/runner_paused_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
describe('RunnerTypeBadge', () => {
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStateLockedBadge from '~/runner/components/runner_state_locked_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
describe('RunnerTypeBadge', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip');
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(RunnerStateLockedBadge, {
propsData: {
...props,
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders locked state', () => {
expect(wrapper.text()).toBe('locked');
expect(findBadge().props('variant')).toBe('warning');
});
it('renders tooltip', () => {
expect(getTooltip().value).toBeDefined();
});
it('passes arbitrary attributes to the badge', () => {
createComponent({ props: { size: 'sm' } });
expect(findBadge().props('size')).toBe('sm');
});
});
......@@ -23,11 +23,11 @@ describe('RunnerTypeAlert', () => {
});
describe.each`
type | exampleText | anchor | variant
${INSTANCE_TYPE} | ${'This runner is available to all groups and projects'} | ${'#shared-runners'} | ${'success'}
${GROUP_TYPE} | ${'This runner is available to all projects and subgroups in a group'} | ${'#group-runners'} | ${'success'}
${PROJECT_TYPE} | ${'This runner is associated with one or more projects'} | ${'#specific-runners'} | ${'info'}
`('When it is an $type level runner', ({ type, exampleText, anchor, variant }) => {
type | exampleText | anchor
${INSTANCE_TYPE} | ${'This runner is available to all groups and projects'} | ${'#shared-runners'}
${GROUP_TYPE} | ${'This runner is available to all projects and subgroups in a group'} | ${'#group-runners'}
${PROJECT_TYPE} | ${'This runner is associated with one or more projects'} | ${'#specific-runners'}
`('When it is an $type level runner', ({ type, exampleText, anchor }) => {
beforeEach(() => {
createComponent({ props: { type } });
});
......@@ -36,8 +36,8 @@ describe('RunnerTypeAlert', () => {
expect(wrapper.text()).toMatch(exampleText);
});
it(`Shows a ${variant} variant`, () => {
expect(findAlert().props('variant')).toBe(variant);
it(`Shows an "info" variant`, () => {
expect(findAlert().props('variant')).toBe('info');
});
it(`Links to anchor "${anchor}"`, () => {
......
......@@ -26,18 +26,18 @@ describe('RunnerTypeBadge', () => {
});
describe.each`
type | text | variant
${INSTANCE_TYPE} | ${'shared'} | ${'success'}
${GROUP_TYPE} | ${'group'} | ${'success'}
${PROJECT_TYPE} | ${'specific'} | ${'info'}
`('displays $type runner', ({ type, text, variant }) => {
type | text
${INSTANCE_TYPE} | ${'shared'}
${GROUP_TYPE} | ${'group'}
${PROJECT_TYPE} | ${'specific'}
`('displays $type runner', ({ type, text }) => {
beforeEach(() => {
createComponent({ props: { type } });
});
it(`as "${text}" with a ${variant} variant`, () => {
it(`as "${text}" with an "info" variant`, () => {
expect(findBadge().text()).toBe(text);
expect(findBadge().props('variant')).toBe(variant);
expect(findBadge().props('variant')).toBe('info');
});
it('with a tooltip', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment