diff options
Diffstat (limited to 'spec/frontend/runner')
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); + }); + }); }); |