summaryrefslogtreecommitdiff
path: root/spec/frontend/ci
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-20 15:20:09 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-20 15:20:09 +0000
commitda23c5d563d68bfa5271b216209a7715c7ce3073 (patch)
treeea829aa79f715b98c440d6bf3767328b4fc4f750 /spec/frontend/ci
parent2366f969a4b3a95e052e551cc7283a2db8d5562e (diff)
downloadgitlab-ce-da23c5d563d68bfa5271b216209a7715c7ce3073.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/ci')
-rw-r--r--spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js76
-rw-r--r--spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js190
-rw-r--r--spec/frontend/ci/pipeline_new/mock_data.js13
-rw-r--r--spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js1
-rw-r--r--spec/frontend/ci/runner/components/runner_create_form_spec.js14
-rw-r--r--spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js1
-rw-r--r--spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js107
7 files changed, 190 insertions, 212 deletions
diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
index 9015031b6c8..a08a01009e2 100644
--- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
@@ -4,7 +4,7 @@ import { GlForm, GlDropdownItem, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import CreditCardValidationRequiredAlert from 'ee_component/billings/components/cc_validation_required_alert.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -30,7 +30,6 @@ import {
mockQueryParams,
mockPostParams,
mockProjectId,
- mockRefs,
mockYamlVariables,
mockPipelineConfigButtonText,
} from '../mock_data';
@@ -41,7 +40,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
}));
-const projectRefsEndpoint = '/root/project/refs';
const pipelinesPath = '/root/project/-/pipelines';
const pipelinesEditorPath = '/root/project/-/ci/editor';
const projectPath = '/root/project/-/pipelines/config_variables';
@@ -91,21 +89,18 @@ describe('Pipeline New Form', () => {
const changeKeyInputValue = async (keyInputIndex, value) => {
const input = findKeyInputs().at(keyInputIndex);
- input.element.value = value;
- input.trigger('change');
+ input.vm.$emit('input', value);
+ input.vm.$emit('change');
await nextTick();
};
- const createComponentWithApollo = ({ method = shallowMountExtended, props = {} } = {}) => {
+ const createComponentWithApollo = ({ props = {} } = {}) => {
const handlers = [[ciConfigVariablesQuery, mockCiConfigVariables]];
mockApollo = createMockApollo(handlers, resolvers);
- wrapper = method(PipelineNewForm, {
+ wrapper = shallowMountExtended(PipelineNewForm, {
apolloProvider: mockApollo,
- provide: {
- projectRefsEndpoint,
- },
propsData: {
projectId: mockProjectId,
pipelinesPath,
@@ -124,7 +119,6 @@ describe('Pipeline New Form', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mockCiConfigVariables = jest.fn();
- mock.onGet(projectRefsEndpoint).reply(HTTP_STATUS_OK, mockRefs);
dummySubmitEvent = {
preventDefault: jest.fn(),
@@ -138,7 +132,7 @@ describe('Pipeline New Form', () => {
describe('Form', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse);
- createComponentWithApollo({ props: mockQueryParams, method: mountExtended });
+ createComponentWithApollo({ props: mockQueryParams });
await waitForPromises();
});
@@ -150,13 +144,13 @@ describe('Pipeline New Form', () => {
});
it('displays a variable from provided query params', () => {
- expect(findKeyInputs().at(0).element.value).toBe('test_var');
- expect(findValueInputs().at(0).element.value).toBe('test_var_val');
+ expect(findKeyInputs().at(0).attributes('value')).toBe('test_var');
+ expect(findValueInputs().at(0).attributes('value')).toBe('test_var_val');
});
it('displays an empty variable for the user to fill out', () => {
- expect(findKeyInputs().at(2).element.value).toBe('');
- expect(findValueInputs().at(2).element.value).toBe('');
+ expect(findKeyInputs().at(2).attributes('value')).toBe('');
+ expect(findValueInputs().at(2).attributes('value')).toBe('');
expect(findVariableTypes().at(2).props('text')).toBe('Variable');
});
@@ -165,7 +159,7 @@ describe('Pipeline New Form', () => {
});
it('removes ci variable row on remove icon button click', async () => {
- findRemoveIcons().at(1).trigger('click');
+ findRemoveIcons().at(1).vm.$emit('click');
await nextTick();
@@ -174,14 +168,15 @@ describe('Pipeline New Form', () => {
it('creates blank variable on input change event', async () => {
const input = findKeyInputs().at(2);
- input.element.value = 'test_var_2';
- input.trigger('change');
+
+ input.vm.$emit('input', 'test_var_2');
+ input.vm.$emit('change');
await nextTick();
expect(findVariableRows()).toHaveLength(4);
- expect(findKeyInputs().at(3).element.value).toBe('');
- expect(findValueInputs().at(3).element.value).toBe('');
+ expect(findKeyInputs().at(3).attributes('value')).toBe('');
+ expect(findValueInputs().at(3).attributes('value')).toBe('');
});
});
@@ -237,7 +232,7 @@ describe('Pipeline New Form', () => {
describe('When the ref has been changed', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
await waitForPromises();
});
@@ -251,12 +246,12 @@ describe('Pipeline New Form', () => {
await selectBranch('main');
- expect(findKeyInputs().at(0).element.value).toBe('build_var');
+ expect(findKeyInputs().at(0).attributes('value')).toBe('build_var');
expect(findVariableRows().length).toBe(2);
await selectBranch('branch-1');
- expect(findKeyInputs().at(0).element.value).toBe('deploy_var');
+ expect(findKeyInputs().at(0).attributes('value')).toBe('deploy_var');
expect(findVariableRows().length).toBe(2);
});
@@ -280,7 +275,7 @@ describe('Pipeline New Form', () => {
describe('When there are no variables in the API cache', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockNoCachedCiConfigVariablesResponse);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
await waitForPromises();
});
@@ -330,7 +325,7 @@ describe('Pipeline New Form', () => {
const testBehaviorWhenCacheIsPopulated = (queryResponse) => {
beforeEach(() => {
mockCiConfigVariables.mockResolvedValue(queryResponse);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
});
it('does not poll for new values', async () => {
@@ -345,6 +340,9 @@ describe('Pipeline New Form', () => {
});
it('loading icon is shown when content is requested and hidden when received', async () => {
+ mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse);
+ createComponentWithApollo({ props: mockQueryParams });
+
expect(findLoadingIcon().exists()).toBe(true);
await waitForPromises();
@@ -358,11 +356,11 @@ describe('Pipeline New Form', () => {
it('displays an empty form', async () => {
mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
await waitForPromises();
- expect(findKeyInputs().at(0).element.value).toBe('');
- expect(findValueInputs().at(0).element.value).toBe('');
+ expect(findKeyInputs().at(0).attributes('value')).toBe('');
+ expect(findValueInputs().at(0).attributes('value')).toBe('');
expect(findVariableTypes().at(0).props('text')).toBe('Variable');
});
});
@@ -373,12 +371,12 @@ describe('Pipeline New Form', () => {
describe('with different predefined values', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponse);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
await waitForPromises();
});
it('multi-line strings are added to the value field without removing line breaks', () => {
- expect(findValueInputs().at(1).element.value).toBe(mockYamlVariables[1].value);
+ expect(findValueInputs().at(1).attributes('value')).toBe(mockYamlVariables[1].value);
});
it('multiple predefined values are rendered as a dropdown', () => {
@@ -402,7 +400,7 @@ describe('Pipeline New Form', () => {
describe('with description', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponse);
- createComponentWithApollo({ props: mockQueryParams, method: mountExtended });
+ createComponentWithApollo({ props: mockQueryParams });
await waitForPromises();
});
@@ -411,15 +409,15 @@ describe('Pipeline New Form', () => {
});
it('displays a variable from yml', () => {
- expect(findKeyInputs().at(0).element.value).toBe(mockYamlVariables[0].key);
- expect(findValueInputs().at(0).element.value).toBe(mockYamlVariables[0].value);
+ expect(findKeyInputs().at(0).attributes('value')).toBe(mockYamlVariables[0].key);
+ expect(findValueInputs().at(0).attributes('value')).toBe(mockYamlVariables[0].value);
});
it('displays a variable from provided query params', () => {
- expect(findKeyInputs().at(3).element.value).toBe(
+ expect(findKeyInputs().at(3).attributes('value')).toBe(
Object.keys(mockQueryParams.variableParams)[0],
);
- expect(findValueInputs().at(3).element.value).toBe(
+ expect(findValueInputs().at(3).attributes('value')).toBe(
Object.values(mockQueryParams.fileParams)[0],
);
});
@@ -429,7 +427,7 @@ describe('Pipeline New Form', () => {
});
it('removes the description when a variable key changes', async () => {
- findKeyInputs().at(0).element.value = 'yml_var_modified';
+ findKeyInputs().at(0).vm.$emit('input', 'yml_var_modified');
findKeyInputs().at(0).trigger('change');
await nextTick();
@@ -441,7 +439,7 @@ describe('Pipeline New Form', () => {
describe('without description', () => {
beforeEach(async () => {
mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponseWithoutDesc);
- createComponentWithApollo({ method: mountExtended });
+ createComponentWithApollo();
await waitForPromises();
});
@@ -460,7 +458,7 @@ describe('Pipeline New Form', () => {
describe('when the refs cannot be loaded', () => {
beforeEach(() => {
mock
- .onGet(projectRefsEndpoint, { params: { search: '' } })
+ .onGet('/api/v4/projects/8/repository/branches', { params: { search: '' } })
.reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
findRefsDropdown().vm.$emit('loadingError');
diff --git a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
index 82dac1358c5..01c7dd7eb84 100644
--- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
+++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
@@ -1,35 +1,22 @@
-import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
-import MockAdapter from 'axios-mock-adapter';
-import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { shallowMount } from '@vue/test-utils';
+import RefSelector from '~/ref/components/ref_selector.vue';
import RefsDropdown from '~/ci/pipeline_new/components/refs_dropdown.vue';
+import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants';
-import { mockBranches, mockRefs, mockFilteredRefs, mockTags } from '../mock_data';
-
-const projectRefsEndpoint = '/root/project/refs';
+const projectId = '8';
const refShortName = 'main';
const refFullName = 'refs/heads/main';
-jest.mock('~/alert');
-
describe('Pipeline New Form', () => {
let wrapper;
- let mock;
- const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
- const findRefsDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
- const findSearchBox = () => wrapper.findByTestId('listbox-search-input');
- const findListboxGroups = () => wrapper.findAll('ul[role="group"]');
+ const findRefSelector = () => wrapper.findComponent(RefSelector);
- const createComponent = (props = {}, mountFn = shallowMountExtended) => {
- wrapper = mountFn(RefsDropdown, {
- provide: {
- projectRefsEndpoint,
- },
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(RefsDropdown, {
propsData: {
+ projectId,
value: {
shortName: refShortName,
fullName: refFullName,
@@ -39,163 +26,54 @@ describe('Pipeline New Form', () => {
});
};
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockRefs);
- });
-
- beforeEach(() => {
- createComponent();
- });
-
- it('displays empty dropdown initially', () => {
- findDropdown().vm.$emit('shown');
-
- expect(findRefsDropdownItems()).toHaveLength(0);
- });
-
- it('does not make requests immediately', () => {
- expect(mock.history.get).toHaveLength(0);
- });
-
describe('when user opens dropdown', () => {
- beforeEach(async () => {
- createComponent({}, mountExtended);
- findDropdown().vm.$emit('shown');
- await waitForPromises();
+ beforeEach(() => {
+ createComponent();
});
- it('requests unfiltered tags and branches', () => {
- expect(mock.history.get).toHaveLength(1);
- expect(mock.history.get[0].url).toBe(projectRefsEndpoint);
- expect(mock.history.get[0].params).toEqual({ search: '' });
+ it('has default selected branch', () => {
+ expect(findRefSelector().props('value')).toBe('main');
});
- it('displays dropdown with branches and tags', () => {
- const refLength = mockRefs.Tags.length + mockRefs.Branches.length;
- expect(findRefsDropdownItems()).toHaveLength(refLength);
- });
-
- it('displays the names of refs', () => {
- // Branches
- expect(findRefsDropdownItems().at(0).text()).toBe(mockRefs.Branches[0]);
-
- // Tags (appear after branches)
- const firstTag = mockRefs.Branches.length;
- expect(findRefsDropdownItems().at(firstTag).text()).toBe(mockRefs.Tags[0]);
- });
-
- it('when user shows dropdown a second time, only one request is done', () => {
- expect(mock.history.get).toHaveLength(1);
+ it('has ref selector for branches and tags', () => {
+ expect(findRefSelector().props('enabledRefTypes')).toEqual([
+ REF_TYPE_BRANCHES,
+ REF_TYPE_TAGS,
+ ]);
});
describe('when user selects a value', () => {
- const selectedIndex = 1;
-
- beforeEach(async () => {
- findRefsDropdownItems().at(selectedIndex).vm.$emit('select', 'refs/heads/branch-1');
- await waitForPromises();
- });
+ const fullName = `refs/heads/conflict-contains-conflict-markers`;
it('component emits @input', () => {
+ findRefSelector().vm.$emit('input', fullName);
+
const inputs = wrapper.emitted('input');
expect(inputs).toHaveLength(1);
- expect(inputs[0]).toEqual([{ shortName: 'branch-1', fullName: 'refs/heads/branch-1' }]);
- });
- });
-
- describe('when user types searches for a tag', () => {
- const mockSearchTerm = 'my-search';
-
- beforeEach(async () => {
- mock
- .onGet(projectRefsEndpoint, { params: { search: mockSearchTerm } })
- .reply(HTTP_STATUS_OK, mockFilteredRefs);
-
- await findSearchBox().vm.$emit('input', mockSearchTerm);
- await waitForPromises();
- });
-
- it('requests filtered tags and branches', () => {
- expect(mock.history.get).toHaveLength(2);
- expect(mock.history.get[1].params).toEqual({
- search: mockSearchTerm,
- });
- });
-
- it('displays dropdown with branches and tags', () => {
- const filteredRefLength = mockFilteredRefs.Tags.length + mockFilteredRefs.Branches.length;
-
- expect(findRefsDropdownItems()).toHaveLength(filteredRefLength);
+ expect(inputs[0]).toEqual([
+ {
+ shortName: 'conflict-contains-conflict-markers',
+ fullName: 'refs/heads/conflict-contains-conflict-markers',
+ },
+ ]);
});
});
});
describe('when user has selected a value', () => {
- const selectedIndex = 1;
- const mockShortName = mockRefs.Branches[selectedIndex];
+ const mockShortName = 'conflict-contains-conflict-markers';
const mockFullName = `refs/heads/${mockShortName}`;
- beforeEach(async () => {
- mock
- .onGet(projectRefsEndpoint, {
- params: { ref: mockFullName },
- })
- .reply(HTTP_STATUS_OK, mockRefs);
-
- createComponent(
- {
- value: {
- shortName: mockShortName,
- fullName: mockFullName,
- },
- },
- mountExtended,
- );
- findDropdown().vm.$emit('shown');
- await waitForPromises();
- });
-
it('branch is checked', () => {
- expect(findRefsDropdownItems().at(selectedIndex).props('isSelected')).toBe(true);
- });
- });
-
- describe('when server returns an error', () => {
- beforeEach(async () => {
- mock
- .onGet(projectRefsEndpoint, { params: { search: '' } })
- .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
-
- findDropdown().vm.$emit('shown');
- await waitForPromises();
- });
+ createComponent({
+ value: {
+ shortName: mockShortName,
+ fullName: mockFullName,
+ },
+ });
- it('loading error event is emitted', () => {
- expect(wrapper.emitted('loadingError')).toHaveLength(1);
- expect(wrapper.emitted('loadingError')[0]).toEqual([expect.any(Error)]);
+ expect(findRefSelector().props('value')).toBe(mockShortName);
});
});
-
- describe('should display branches and tags based on its length', () => {
- it.each`
- mockData | expectedGroupLength | expectedListboxItemsLength
- ${{ ...mockBranches, Tags: [] }} | ${1} | ${mockBranches.Branches.length}
- ${{ Branches: [], ...mockTags }} | ${1} | ${mockTags.Tags.length}
- ${{ ...mockRefs }} | ${2} | ${mockBranches.Branches.length + mockTags.Tags.length}
- ${{ Branches: undefined, Tags: undefined }} | ${0} | ${0}
- `(
- 'should render branches and tags based on presence',
- async ({ mockData, expectedGroupLength, expectedListboxItemsLength }) => {
- mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockData);
- createComponent({}, mountExtended);
- findDropdown().vm.$emit('shown');
- await waitForPromises();
-
- expect(findListboxGroups()).toHaveLength(expectedGroupLength);
- expect(findRefsDropdownItems()).toHaveLength(expectedListboxItemsLength);
- },
- );
- });
});
diff --git a/spec/frontend/ci/pipeline_new/mock_data.js b/spec/frontend/ci/pipeline_new/mock_data.js
index 175f513217b..76a88f63298 100644
--- a/spec/frontend/ci/pipeline_new/mock_data.js
+++ b/spec/frontend/ci/pipeline_new/mock_data.js
@@ -1,16 +1,3 @@
-export const mockBranches = {
- Branches: ['main', 'branch-1', 'branch-2'],
-};
-
-export const mockTags = {
- Tags: ['1.0.0', '1.1.0', '1.2.0'],
-};
-
-export const mockRefs = {
- ...mockBranches,
- ...mockTags,
-};
-
export const mockFilteredRefs = {
Branches: ['branch-1'],
Tags: ['1.0.0', '1.1.0'],
diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
index 58a1c0bc18d..65336edd0d8 100644
--- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
@@ -79,6 +79,7 @@ describe('AdminNewRunnerApp', () => {
expect(findRunnerCreateForm().props()).toEqual({
runnerType: INSTANCE_TYPE,
groupId: null,
+ projectId: null,
});
});
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 a13a19db067..329dd2f73ee 100644
--- a/spec/frontend/ci/runner/components/runner_create_form_spec.js
+++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js
@@ -6,7 +6,12 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
-import { DEFAULT_ACCESS_LEVEL, INSTANCE_TYPE, GROUP_TYPE } from '~/ci/runner/constants';
+import {
+ DEFAULT_ACCESS_LEVEL,
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+} from '~/ci/runner/constants';
import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql';
import { captureException } from '~/ci/runner/sentry_utils';
import { runnerCreateResult } from '../mock_data';
@@ -62,9 +67,10 @@ describe('RunnerCreateForm', () => {
});
describe.each`
- typeName | props | scopeData
- ${'an instance runner'} | ${{ runnerType: INSTANCE_TYPE }} | ${{ runnerType: INSTANCE_TYPE }}
- ${'a group runner'} | ${{ runnerType: GROUP_TYPE, groupId: 'gid://gitlab/Group/72' }} | ${{ runnerType: GROUP_TYPE, groupId: 'gid://gitlab/Group/72' }}
+ typeName | props | scopeData
+ ${'an instance runner'} | ${{ runnerType: INSTANCE_TYPE }} | ${{ runnerType: INSTANCE_TYPE }}
+ ${'a group runner'} | ${{ runnerType: GROUP_TYPE, groupId: 'gid://gitlab/Group/72' }} | ${{ runnerType: GROUP_TYPE, groupId: 'gid://gitlab/Group/72' }}
+ ${'a project runner'} | ${{ runnerType: PROJECT_TYPE, projectId: 'gid://gitlab/Project/42' }} | ${{ runnerType: PROJECT_TYPE, projectId: 'gid://gitlab/Project/42' }}
`('when user submits $typeName', ({ props, scopeData }) => {
let preventDefault;
diff --git a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
index 027196ab004..520c9eed003 100644
--- a/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/group_new_runner_app/group_new_runner_app_spec.js
@@ -82,6 +82,7 @@ describe('GroupRunnerRunnerApp', () => {
expect(findRunnerCreateForm().props()).toEqual({
runnerType: GROUP_TYPE,
groupId: mockGroupId,
+ projectId: null,
});
});
diff --git a/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js
new file mode 100644
index 00000000000..701a93352ef
--- /dev/null
+++ b/spec/frontend/ci/runner/project_new_runner_app/project_new_runner_app_spec.js
@@ -0,0 +1,107 @@
+import { GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+
+import ProjectRunnerRunnerApp from '~/ci/runner/project_new_runner/project_new_runner_app.vue';
+import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
+import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
+import { PROJECT_TYPE, DEFAULT_PLATFORM } from '~/ci/runner/constants';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import { runnerCreateResult, mockRegistrationToken } from '../mock_data';
+
+const mockProjectId = 'gid://gitlab/Project/72';
+
+jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
+jest.mock('~/alert');
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ redirectTo: jest.fn(),
+}));
+
+const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
+
+describe('ProjectRunnerRunnerApp', () => {
+ let wrapper;
+
+ const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link');
+ const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal);
+ const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup);
+ const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm);
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(ProjectRunnerRunnerApp, {
+ propsData: {
+ projectId: mockProjectId,
+ legacyRegistrationToken: mockRegistrationToken,
+ },
+ directives: {
+ GlModal: createMockDirective('gl-modal'),
+ },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('Shows legacy modal', () => {
+ it('passes legacy registration to modal', () => {
+ expect(findRunnerInstructionsModal().props('registrationToken')).toEqual(
+ mockRegistrationToken,
+ );
+ });
+
+ it('opens a modal with the legacy instructions', () => {
+ const modalId = getBinding(findLegacyInstructionsLink().element, 'gl-modal').value;
+
+ expect(findRunnerInstructionsModal().props('modalId')).toBe(modalId);
+ });
+ });
+
+ describe('Platform', () => {
+ it('shows the platforms radio group', () => {
+ expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
+ });
+ });
+
+ describe('Runner form', () => {
+ it('shows the runner create form for an instance runner', () => {
+ expect(findRunnerCreateForm().props()).toEqual({
+ runnerType: PROJECT_TYPE,
+ projectId: mockProjectId,
+ groupId: null,
+ });
+ });
+
+ describe('When a runner is saved', () => {
+ beforeEach(() => {
+ findRunnerCreateForm().vm.$emit('saved', mockCreatedRunner);
+ });
+
+ it('shows an alert', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: s__('Runners|Runner created.'),
+ variant: VARIANT_SUCCESS,
+ });
+ });
+ });
+
+ describe('When runner fails to save', () => {
+ const ERROR_MSG = 'Cannot save!';
+
+ beforeEach(() => {
+ findRunnerCreateForm().vm.$emit('error', new Error(ERROR_MSG));
+ });
+
+ it('shows an error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: ERROR_MSG });
+ });
+ });
+ });
+});