diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-01 15:10:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-01 15:10:15 +0300 |
commit | cf19a51fc5711144b26f7123c14f9b64a7597195 (patch) | |
tree | 09c151fd3655213e87b1c25beb842a99510122cb /spec/frontend/ci/runner/components | |
parent | 3b1df712c7a15c9b6abadd61e9c8894fdeb0442a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/ci/runner/components')
3 files changed, 139 insertions, 162 deletions
diff --git a/spec/frontend/ci/runner/components/runner_create_form_spec.js b/spec/frontend/ci/runner/components/runner_create_form_spec.js index 243d23aeb38..f11667ee415 100644 --- a/spec/frontend/ci/runner/components/runner_create_form_spec.js +++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js @@ -126,8 +126,8 @@ describe('RunnerCreateForm', () => { expect(wrapper.emitted('saved')[0]).toEqual([mockCreatedRunner]); }); - it('does not show a saving state', () => { - expect(findSubmitBtn().props('loading')).toBe(false); + it('maintains a saving state before navigating away', () => { + expect(findSubmitBtn().props('loading')).toBe(true); }); }); diff --git a/spec/frontend/ci/runner/components/runner_form_fields_spec.js b/spec/frontend/ci/runner/components/runner_form_fields_spec.js index 0e2f2aa2e91..98f170d8f18 100644 --- a/spec/frontend/ci/runner/components/runner_form_fields_spec.js +++ b/spec/frontend/ci/runner/components/runner_form_fields_spec.js @@ -1,4 +1,6 @@ import { nextTick } from 'vue'; +import { GlSkeletonLoader } from '@gitlab/ui'; +import { s__ } from '~/locale'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue'; import { @@ -8,46 +10,97 @@ import { } from '~/ci/runner/constants'; const mockDescription = 'My description'; +const mockNewDescription = 'My new description'; const mockMaxTimeout = 60; const mockTags = 'tag, tag2'; describe('RunnerFormFields', () => { let wrapper; + const findInputByLabel = (label) => wrapper.findByLabelText(label); const findInput = (name) => wrapper.find(`input[name="${name}"]`); - const createComponent = ({ runner } = {}) => { + const expectRendersFields = () => { + expect(wrapper.text()).toContain(s__('Runners|Details')); + expect(wrapper.text()).toContain(s__('Runners|Configuration')); + + expect(wrapper.findAllComponents(GlSkeletonLoader)).toHaveLength(0); + expect(wrapper.findAll('input')).toHaveLength(6); + }; + + const createComponent = ({ ...props } = {}) => { wrapper = mountExtended(RunnerFormFields, { propsData: { - value: runner, + ...props, }, }); }; + describe('when runner is loading', () => { + beforeEach(() => { + createComponent({ loading: true }); + }); + + it('renders a loading frame', () => { + expect(wrapper.text()).toContain(s__('Runners|Details')); + expect(wrapper.text()).toContain(s__('Runners|Configuration')); + + expect(wrapper.findAllComponents(GlSkeletonLoader)).toHaveLength(2); + expect(wrapper.findAll('input')).toHaveLength(0); + }); + + describe('and then is loaded', () => { + beforeEach(() => { + wrapper.setProps({ loading: false, value: { description: mockDescription } }); + }); + + it('renders fields', () => { + expectRendersFields(); + }); + }); + }); + + it('when runner is loaded, renders fields', () => { + createComponent({ + value: { description: mockDescription }, + }); + + expectRendersFields(); + }); + + it('when runner is updated with the same value, only emits when changed (avoids infinite loop)', async () => { + createComponent({ value: null, loading: true }); + await wrapper.setProps({ value: { description: mockDescription }, loading: false }); + await wrapper.setProps({ value: { description: mockDescription }, loading: false }); + + expect(wrapper.emitted('input')).toHaveLength(1); + }); + it('updates runner fields', async () => { - createComponent(); + createComponent({ + value: { description: mockDescription }, + }); expect(wrapper.emitted('input')).toBe(undefined); - findInput('description').setValue(mockDescription); + findInputByLabel(s__('Runners|Runner description')).setValue(mockNewDescription); findInput('max-timeout').setValue(mockMaxTimeout); - findInput('paused').setChecked(true); - findInput('protected').setChecked(true); - findInput('run-untagged').setChecked(true); findInput('tags').setValue(mockTags); await nextTick(); - expect(wrapper.emitted('input')[0][0]).toMatchObject({ - description: mockDescription, - maximumTimeout: mockMaxTimeout, - tagList: mockTags, - }); + expect(wrapper.emitted('input').at(-1)).toEqual([ + { + description: mockNewDescription, + maximumTimeout: mockMaxTimeout, + tagList: mockTags, + }, + ]); }); it('checks checkbox fields', async () => { createComponent({ - runner: { + value: { paused: false, accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: false, @@ -60,11 +113,13 @@ describe('RunnerFormFields', () => { await nextTick(); - expect(wrapper.emitted('input')[0][0]).toEqual({ - paused: true, - accessLevel: ACCESS_LEVEL_REF_PROTECTED, - runUntagged: true, - }); + expect(wrapper.emitted('input').at(-1)).toEqual([ + { + paused: true, + accessLevel: ACCESS_LEVEL_REF_PROTECTED, + runUntagged: true, + }, + ]); }); it('locked checkbox is not shown', () => { @@ -75,7 +130,7 @@ describe('RunnerFormFields', () => { it('when runner is of project type, locked checkbox can be checked', async () => { createComponent({ - runner: { + value: { runnerType: PROJECT_TYPE, locked: false, }, @@ -85,15 +140,17 @@ describe('RunnerFormFields', () => { await nextTick(); - expect(wrapper.emitted('input')[0][0]).toEqual({ - runnerType: PROJECT_TYPE, - locked: true, - }); + expect(wrapper.emitted('input').at(-1)).toEqual([ + { + runnerType: PROJECT_TYPE, + locked: true, + }, + ]); }); it('unchecks checkbox fields', async () => { createComponent({ - runner: { + value: { paused: true, accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true, @@ -106,10 +163,12 @@ describe('RunnerFormFields', () => { await nextTick(); - expect(wrapper.emitted('input')[0][0]).toEqual({ - paused: false, - accessLevel: ACCESS_LEVEL_NOT_PROTECTED, - runUntagged: false, - }); + expect(wrapper.emitted('input').at(-1)).toEqual([ + { + paused: false, + accessLevel: ACCESS_LEVEL_NOT_PROTECTED, + runUntagged: false, + }, + ]); }); }); diff --git a/spec/frontend/ci/runner/components/runner_update_form_spec.js b/spec/frontend/ci/runner/components/runner_update_form_spec.js index d1d4e38f47c..5851078a8d3 100644 --- a/spec/frontend/ci/runner/components/runner_update_form_spec.js +++ b/spec/frontend/ci/runner/components/runner_update_form_spec.js @@ -1,20 +1,17 @@ -import Vue, { nextTick } from 'vue'; -import { GlForm, GlSkeletonLoader } from '@gitlab/ui'; +import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import { GlForm } from '@gitlab/ui'; import { __ } from '~/locale'; +import { createAlert, VARIANT_SUCCESS } from '~/alert'; +import { visitUrl } from '~/lib/utils/url_utility'; + import createMockApollo from 'helpers/mock_apollo_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createAlert, VARIANT_SUCCESS } from '~/alert'; -import { visitUrl } from '~/lib/utils/url_utility'; + +import { runnerToModel } from 'ee_else_ce/ci/runner/runner_update_form_utils'; +import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue'; import RunnerUpdateForm from '~/ci/runner/components/runner_update_form.vue'; -import { - INSTANCE_TYPE, - GROUP_TYPE, - PROJECT_TYPE, - ACCESS_LEVEL_REF_PROTECTED, - ACCESS_LEVEL_NOT_PROTECTED, -} from '~/ci/runner/constants'; import runnerUpdateMutation from '~/ci/runner/graphql/edit/runner_update.mutation.graphql'; import { captureException } from '~/ci/runner/sentry_utils'; import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage'; @@ -38,16 +35,7 @@ describe('RunnerUpdateForm', () => { let runnerUpdateHandler; const findForm = () => wrapper.findComponent(GlForm); - const findPausedCheckbox = () => wrapper.findByTestId('runner-field-paused'); - const findProtectedCheckbox = () => wrapper.findByTestId('runner-field-protected'); - const findRunUntaggedCheckbox = () => wrapper.findByTestId('runner-field-run-untagged'); - const findLockedCheckbox = () => wrapper.findByTestId('runner-field-locked'); - const findFields = () => wrapper.findAll('[data-testid^="runner-field"'); - - const findDescriptionInput = () => wrapper.findByTestId('runner-field-description').find('input'); - const findMaxJobTimeoutInput = () => - wrapper.findByTestId('runner-field-max-timeout').find('input'); - const findTagsInput = () => wrapper.findByTestId('runner-field-tags').find('input'); + const findRunnerFormFields = () => wrapper.findComponent(RunnerFormFields); const findSubmit = () => wrapper.find('[type="submit"]'); const findSubmitDisabledAttr = () => findSubmit().attributes('disabled'); @@ -55,21 +43,10 @@ describe('RunnerUpdateForm', () => { const submitForm = () => findForm().trigger('submit'); const submitFormAndWait = () => submitForm().then(waitForPromises); - const getFieldsModel = () => ({ - paused: findPausedCheckbox().element.checked, - accessLevel: findProtectedCheckbox().element.checked - ? ACCESS_LEVEL_REF_PROTECTED - : ACCESS_LEVEL_NOT_PROTECTED, - runUntagged: findRunUntaggedCheckbox().element.checked, - locked: findLockedCheckbox().element?.checked || false, - maximumTimeout: findMaxJobTimeoutInput().element.value || null, - tagList: findTagsInput().element.value.split(',').filter(Boolean), - }); - const createComponent = ({ props } = {}) => { wrapper = mountExtended(RunnerUpdateForm, { propsData: { - runner: mockRunner, + runner: null, runnerPath: mockRunnerPath, ...props, }, @@ -106,141 +83,82 @@ describe('RunnerUpdateForm', () => { }, }); }); + }); + it('form has fields, submit and cancel buttons', () => { createComponent(); - }); - it('Form has a submit button', () => { + expect(findRunnerFormFields().exists()).toBe(true); expect(findSubmit().exists()).toBe(true); - }); - - it('Form fields match data', () => { - 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(); - - expect(findSubmitDisabledAttr()).toBe('disabled'); - }); - - it('Updates runner with no changes', async () => { - await submitFormAndWait(); - - // Some read-only fields are not submitted - const { __typename, shortSha, runnerType, createdAt, status, ...submitted } = mockRunner; - - expectToHaveSubmittedRunnerContaining(submitted); - }); - describe('When data is being loaded', () => { beforeEach(() => { createComponent({ props: { loading: true } }); }); - it('Form skeleton is shown', () => { - expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true); - expect(findFields()).toHaveLength(0); + it('form has no runner', () => { + expect(findRunnerFormFields().props('value')).toBe(null); }); - it('Form cannot be submitted', () => { + it('form cannot be submitted', () => { expect(findSubmit().props('loading')).toBe(true); }); + }); + + describe('When runner has loaded', () => { + beforeEach(async () => { + createComponent({ props: { loading: true } }); - it('Form is updated when data loads', async () => { - wrapper.setProps({ + await wrapper.setProps({ loading: false, + runner: mockRunner, }); - - await nextTick(); - - expect(findFields()).not.toHaveLength(0); - expect(mockRunner).toMatchObject(getFieldsModel()); }); - }); - it.each` - runnerType | exists | outcome - ${INSTANCE_TYPE} | ${false} | ${'hidden'} - ${GROUP_TYPE} | ${false} | ${'hidden'} - ${PROJECT_TYPE} | ${true} | ${'shown'} - `(`When runner is $runnerType, locked field is $outcome`, ({ runnerType, exists }) => { - const runner = { ...mockRunner, runnerType }; - createComponent({ props: { runner } }); + it('shows runner fields', () => { + expect(findRunnerFormFields().props('value')).toEqual(runnerToModel(mockRunner)); + }); - expect(findLockedCheckbox().exists()).toBe(exists); - }); + it('form has not been submitted', () => { + expect(runnerUpdateHandler).not.toHaveBeenCalled(); + }); - describe('On submit, runner gets updated', () => { - it.each` - test | initialValue | findCheckbox | checked | submitted - ${'pauses'} | ${{ paused: false }} | ${findPausedCheckbox} | ${true} | ${{ paused: true }} - ${'activates'} | ${{ paused: true }} | ${findPausedCheckbox} | ${false} | ${{ paused: false }} - ${'unprotects'} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }} | ${findProtectedCheckbox} | ${true} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }} - ${'protects'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }} | ${findProtectedCheckbox} | ${false} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }} - ${'"runs untagged jobs"'} | ${{ runUntagged: true }} | ${findRunUntaggedCheckbox} | ${false} | ${{ runUntagged: false }} - ${'"runs tagged jobs"'} | ${{ runUntagged: false }} | ${findRunUntaggedCheckbox} | ${true} | ${{ runUntagged: true }} - ${'locks'} | ${{ runnerType: PROJECT_TYPE, locked: true }} | ${findLockedCheckbox} | ${false} | ${{ locked: false }} - ${'unlocks'} | ${{ runnerType: PROJECT_TYPE, locked: false }} | ${findLockedCheckbox} | ${true} | ${{ locked: true }} - `('Checkbox $test runner', async ({ initialValue, findCheckbox, checked, submitted }) => { - const runner = { ...mockRunner, ...initialValue }; - createComponent({ props: { runner } }); - - await findCheckbox().setChecked(checked); - await submitFormAndWait(); + it('Form prevents multiple submissions', async () => { + await submitForm(); - expectToHaveSubmittedRunnerContaining({ - id: runner.id, - ...submitted, - }); + expect(findSubmitDisabledAttr()).toBe('disabled'); }); - it.each` - test | initialValue | findInput | value | submitted - ${'description'} | ${{ description: 'Desc. 1' }} | ${findDescriptionInput} | ${'Desc. 2'} | ${{ description: 'Desc. 2' }} - ${'max timeout'} | ${{ maximumTimeout: 36000 }} | ${findMaxJobTimeoutInput} | ${'40000'} | ${{ maximumTimeout: 40000 }} - ${'tags'} | ${{ tagList: ['tag1'] }} | ${findTagsInput} | ${'tag2, tag3'} | ${{ tagList: ['tag2', 'tag3'] }} - `("Field updates runner's $test", async ({ initialValue, findInput, value, submitted }) => { - const runner = { ...mockRunner, ...initialValue }; - createComponent({ props: { runner } }); - - await findInput().setValue(value); + it('Updates runner with no changes', async () => { await submitFormAndWait(); - expectToHaveSubmittedRunnerContaining({ - id: runner.id, - ...submitted, - }); + // Some read-only fields are not submitted + const { __typename, shortSha, runnerType, createdAt, status, ...submitted } = mockRunner; + + expectToHaveSubmittedRunnerContaining(submitted); }); - it.each` - value | submitted - ${''} | ${{ tagList: [] }} - ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }} - ${'with spaces'} | ${{ tagList: ['with spaces'] }} - ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }} - `('Field updates runner\'s tags for "$value"', async ({ value, submitted }) => { - const runner = { ...mockRunner, tagList: ['tag1'] }; - createComponent({ props: { runner } }); - - await findTagsInput().setValue(value); + it('Updates runner with changes', async () => { + findRunnerFormFields().vm.$emit( + 'input', + runnerToModel({ ...mockRunner, description: 'A new description' }), + ); await submitFormAndWait(); - expectToHaveSubmittedRunnerContaining({ - id: runner.id, - ...submitted, - }); + expectToHaveSubmittedRunnerContaining({ description: 'A new description' }); }); }); describe('On error', () => { - beforeEach(() => { + beforeEach(async () => { createComponent(); + + await wrapper.setProps({ + loading: false, + runner: mockRunner, + }); }); it('On network error, error message is shown', async () => { |