Commit 08022572 authored by David O'Regan's avatar David O'Regan

Merge branch '326102-fe-cleanup-runner-instructions-project-group-parameters' into 'master'

Remove unused group/project parameters from runner instructions modal

See merge request gitlab-org/gitlab!58055
parents b5fd7238 fd1ecb9d
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
Vue.use(VueApollo);
......@@ -10,7 +10,6 @@ export function initInstallRunner(componentId = 'js-install-runner') {
if (installRunnerEl) {
const defaultClient = createDefaultClient();
const { projectPath, groupPath } = installRunnerEl.dataset;
const apolloProvider = new VueApollo({
defaultClient,
......@@ -20,12 +19,8 @@ export function initInstallRunner(componentId = 'js-install-runner') {
new Vue({
el: installRunnerEl,
apolloProvider,
provide: {
projectPath,
groupPath,
},
render(createElement) {
return createElement(InstallRunnerInstructions);
return createElement(RunnerInstructions);
},
});
}
......
query getRunnerPlatforms($projectPath: ID!, $groupPath: ID!) {
query getRunnerPlatforms {
runnerPlatforms {
nodes {
name
......@@ -11,10 +11,4 @@ query getRunnerPlatforms($projectPath: ID!, $groupPath: ID!) {
}
}
}
project(fullPath: $projectPath) {
id
}
group(fullPath: $groupPath) {
id
}
}
query runnerSetupInstructions(
$platform: String!
$architecture: String!
$projectId: ID!
$groupId: ID!
) {
runnerSetup(
platform: $platform
architecture: $architecture
projectId: $projectId
groupId: $groupId
) {
query runnerSetupInstructions($platform: String!, $architecture: String!) {
runnerSetup(platform: $platform, architecture: $architecture) {
installInstructions
registerInstructions
}
......
<script>
import {
GlAlert,
GlButton,
GlModal,
GlModalDirective,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlIcon,
} from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { __, s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import {
PLATFORMS_WITHOUT_ARCHITECTURES,
INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES,
} from './constants';
import getRunnerPlatforms from './graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructions from './graphql/queries/get_runner_setup.query.graphql';
import { GlButton, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import RunnerInstructionsModal from './runner_instructions_modal.vue';
export default {
components: {
GlAlert,
GlButton,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlModal,
GlIcon,
ModalCopyButton,
RunnerInstructionsModal,
},
directives: {
GlModalDirective,
},
inject: {
projectPath: {
default: '',
},
groupPath: {
default: '',
},
},
apollo: {
runnerPlatforms: {
query: getRunnerPlatforms,
variables() {
return {
projectPath: this.projectPath,
groupPath: this.groupPath,
};
},
error() {
this.showAlert = true;
},
result({ data }) {
this.project = data?.project;
this.group = data?.group;
this.selectPlatform(this.platforms[0].name);
},
},
modalId: 'runner-instructions-modal',
i18n: {
buttonText: s__('Runners|Show Runner installation instructions'),
},
data() {
return {
showAlert: false,
selectedPlatformArchitectures: [],
selectedPlatform: {
name: '',
},
selectedArchitecture: {},
runnerPlatforms: {},
instructions: {},
project: {},
group: {},
opened: false,
};
},
computed: {
isPlatformSelected() {
return Object.keys(this.selectedPlatform).length > 0;
},
instructionsEmpty() {
return isEmpty(this.instructions);
},
groupId() {
return this.group?.id ?? '';
},
projectId() {
return this.project?.id ?? '';
},
platforms() {
return this.runnerPlatforms?.nodes;
},
hasArchitecureList() {
return !PLATFORMS_WITHOUT_ARCHITECTURES.includes(this.selectedPlatform?.name);
},
instructionsWithoutArchitecture() {
return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform.name]?.instructions;
},
runnerInstallationLink() {
return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform.name]?.link;
},
},
methods: {
selectPlatform(name) {
this.selectedPlatform = this.platforms.find((platform) => platform.name === name);
if (this.hasArchitecureList) {
this.selectedPlatformArchitectures = this.selectedPlatform?.architectures?.nodes;
[this.selectedArchitecture] = this.selectedPlatformArchitectures;
this.selectArchitecture(this.selectedArchitecture);
}
},
selectArchitecture(architecture) {
this.selectedArchitecture = architecture;
this.$apollo.addSmartQuery('instructions', {
variables() {
return {
platform: this.selectedPlatform.name,
architecture: this.selectedArchitecture.name,
projectId: this.projectId,
groupId: this.groupId,
};
},
query: getRunnerSetupInstructions,
update(data) {
return data?.runnerSetup;
},
error() {
this.showAlert = true;
},
});
},
toggleAlert(state) {
this.showAlert = state;
onClick() {
// lazily mount modal to prevent premature instructions requests
this.opened = true;
},
},
modalId: 'installation-instructions-modal',
i18n: {
installARunner: s__('Runners|Install a Runner'),
architecture: s__('Runners|Architecture'),
downloadInstallBinary: s__('Runners|Download and Install Binary'),
downloadLatestBinary: s__('Runners|Download Latest Binary'),
registerRunner: s__('Runners|Register Runner'),
method: __('Method'),
fetchError: s__('Runners|An error has occurred fetching instructions'),
instructions: s__('Runners|Show Runner installation instructions'),
copyInstructions: s__('Runners|Copy instructions'),
},
closeButton: {
text: __('Close'),
attributes: [{ variant: 'default' }],
},
};
</script>
<template>
......@@ -159,101 +34,10 @@ export default {
v-gl-modal-directive="$options.modalId"
class="gl-mt-4"
data-testid="show-modal-button"
@click="onClick"
>
{{ $options.i18n.instructions }}
{{ $options.i18n.buttonText }}
</gl-button>
<gl-modal
:modal-id="$options.modalId"
:title="$options.i18n.installARunner"
:action-secondary="$options.closeButton"
>
<gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)">
{{ $options.i18n.fetchError }}
</gl-alert>
<h5>{{ __('Environment') }}</h5>
<gl-button-group class="gl-mb-5">
<gl-button
v-for="platform in platforms"
:key="platform.name"
data-testid="platform-button"
@click="selectPlatform(platform.name)"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
<template v-if="hasArchitecureList">
<template v-if="isPlatformSelected">
<h5>
{{ $options.i18n.architecture }}
</h5>
<gl-dropdown class="gl-mb-5" :text="selectedArchitecture.name">
<gl-dropdown-item
v-for="architecture in selectedPlatformArchitectures"
:key="architecture.name"
data-testid="architecture-dropdown-item"
@click="selectArchitecture(architecture)"
>
{{ architecture.name }}
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-display-flex gl-align-items-center gl-mb-5">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button
class="gl-ml-auto"
:href="selectedArchitecture.downloadLocation"
download
data-testid="binary-download-button"
>
{{ $options.i18n.downloadLatestBinary }}
</gl-button>
</div>
</template>
<template v-if="!instructionsEmpty">
<div class="gl-display-flex">
<pre
class="gl-bg-gray gl-flex-fill-1 gl-white-space-pre-line"
data-testid="binary-instructions"
>{{ instructions.installInstructions }}</pre
>
<modal-copy-button
:title="$options.i18n.copyInstructions"
:text="instructions.installInstructions"
:modal-id="$options.modalId"
css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
/>
</div>
<hr />
<h5 class="gl-mb-5">{{ $options.i18n.registerRunner }}</h5>
<h5 class="gl-mb-5">{{ $options.i18n.method }}</h5>
<div class="gl-display-flex">
<pre
class="gl-bg-gray gl-flex-fill-1 gl-white-space-pre-line"
data-testid="runner-instructions"
>
{{ instructions.registerInstructions }}
</pre
>
<modal-copy-button
:title="$options.i18n.copyInstructions"
:text="instructions.registerInstructions"
:modal-id="$options.modalId"
css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
/>
</div>
</template>
</template>
<template v-else>
<div>
<p>{{ instructionsWithoutArchitecture }}</p>
<gl-button :href="runnerInstallationLink">
<gl-icon name="external-link" />
{{ s__('Runners|View installation instructions') }}
</gl-button>
</div>
</template>
</gl-modal>
<runner-instructions-modal v-if="opened" :modal-id="$options.modalId" />
</div>
</template>
<script>
import {
GlAlert,
GlButton,
GlModal,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlIcon,
GlLoadingIcon,
GlSkeletonLoader,
} from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { __, s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import {
PLATFORMS_WITHOUT_ARCHITECTURES,
INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES,
} from './constants';
import getRunnerPlatformsQuery from './graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructionsQuery from './graphql/queries/get_runner_setup.query.graphql';
export default {
components: {
GlAlert,
GlButton,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlModal,
GlIcon,
GlLoadingIcon,
GlSkeletonLoader,
ModalCopyButton,
},
props: {
modalId: {
type: String,
required: true,
},
},
apollo: {
platforms: {
query: getRunnerPlatformsQuery,
update(data) {
return data?.runnerPlatforms?.nodes.map(({ name, humanReadableName, architectures }) => {
return {
name,
humanReadableName,
architectures: architectures?.nodes || [],
};
});
},
result() {
// Select first platform by default
if (this.platforms?.[0]) {
this.selectPlatform(this.platforms[0]);
}
},
error() {
this.toggleAlert(true);
},
},
instructions: {
query: getRunnerSetupInstructionsQuery,
skip() {
return !this.selectedPlatform;
},
variables() {
return {
platform: this.selectedPlatformName,
architecture: this.selectedArchitectureName || '',
};
},
update(data) {
return data?.runnerSetup;
},
error() {
this.toggleAlert(true);
},
},
},
data() {
return {
platforms: [],
selectedPlatform: null,
selectedArchitecture: null,
showAlert: false,
instructions: {},
};
},
computed: {
platformsEmpty() {
return isEmpty(this.platforms);
},
instructionsEmpty() {
return isEmpty(this.instructions);
},
selectedPlatformName() {
return this.selectedPlatform?.name;
},
selectedArchitectureName() {
return this.selectedArchitecture?.name;
},
hasArchitecureList() {
return !PLATFORMS_WITHOUT_ARCHITECTURES.includes(this.selectedPlatformName);
},
instructionsWithoutArchitecture() {
return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatformName]?.instructions;
},
runnerInstallationLink() {
return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatformName]?.link;
},
},
methods: {
selectPlatform(platform) {
this.selectedPlatform = platform;
if (!platform.architectures?.some(({ name }) => name === this.selectedArchitectureName)) {
// Select first architecture when current value is not available
this.selectArchitecture(platform.architectures[0]);
}
},
selectArchitecture(architecture) {
this.selectedArchitecture = architecture;
},
toggleAlert(state) {
this.showAlert = state;
},
},
i18n: {
installARunner: s__('Runners|Install a runner'),
architecture: s__('Runners|Architecture'),
downloadInstallBinary: s__('Runners|Download and install binary'),
downloadLatestBinary: s__('Runners|Download latest binary'),
registerRunnerCommand: s__('Runners|Command to register runner'),
fetchError: s__('Runners|An error has occurred fetching instructions'),
copyInstructions: s__('Runners|Copy instructions'),
},
closeButton: {
text: __('Close'),
attributes: [{ variant: 'default' }],
},
};
</script>
<template>
<gl-modal
:modal-id="modalId"
:title="$options.i18n.installARunner"
:action-secondary="$options.closeButton"
>
<gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)">
{{ $options.i18n.fetchError }}
</gl-alert>
<gl-skeleton-loader v-if="platformsEmpty && $apollo.loading" />
<template v-if="!platformsEmpty">
<h5>
{{ __('Environment') }}
</h5>
<gl-button-group class="gl-mb-3">
<gl-button
v-for="platform in platforms"
:key="platform.name"
:selected="selectedPlatform && selectedPlatform.name === platform.name"
data-testid="platform-button"
@click="selectPlatform(platform)"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
</template>
<template v-if="hasArchitecureList">
<template v-if="selectedPlatform">
<h5>
{{ $options.i18n.architecture }}
<gl-loading-icon v-if="$apollo.loading" inline />
</h5>
<gl-dropdown class="gl-mb-3" :text="selectedArchitectureName">
<gl-dropdown-item
v-for="architecture in selectedPlatform.architectures"
:key="architecture.name"
:is-check-item="true"
:is-checked="selectedArchitectureName === architecture.name"
data-testid="architecture-dropdown-item"
@click="selectArchitecture(architecture)"
>
{{ architecture.name }}
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-display-flex gl-align-items-center gl-mb-3">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button
class="gl-ml-auto"
:href="selectedArchitecture.downloadLocation"
download
icon="download"
data-testid="binary-download-button"
>
{{ $options.i18n.downloadLatestBinary }}
</gl-button>
</div>
</template>
<template v-if="!instructionsEmpty">
<div class="gl-display-flex">
<pre
class="gl-bg-gray gl-flex-fill-1 gl-white-space-pre-line"
data-testid="binary-instructions"
>{{ instructions.installInstructions }}</pre
>
<modal-copy-button
:title="$options.i18n.copyInstructions"
:text="instructions.installInstructions"
:modal-id="$options.modalId"
css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
/>
</div>
<h5 class="gl-mb-3">{{ $options.i18n.registerRunnerCommand }}</h5>
<div class="gl-display-flex">
<pre
class="gl-bg-gray gl-flex-fill-1 gl-white-space-pre-line"
data-testid="register-command"
>{{ instructions.registerInstructions }}</pre
>
<modal-copy-button
:title="$options.i18n.copyInstructions"
:text="instructions.registerInstructions"
:modal-id="$options.modalId"
css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
/>
</div>
</template>
</template>
<template v-else>
<div>
<p>{{ instructionsWithoutArchitecture }}</p>
<gl-button :href="runnerInstallationLink">
<gl-icon name="external-link" />
{{ s__('Runners|View installation instructions') }}
</gl-button>
</div>
</template>
</gl-modal>
</template>
......@@ -22,4 +22,4 @@
method: :put, class: 'gl-button btn btn-default',
data: { confirm: _("Are you sure you want to reset the registration token?") }
#js-install-runner{ data: { project_path: project_path, group_path: group_path } }
#js-install-runner
---
title: 'Improve UI of Runner Installation instructions: add a loading indicator, use
checkmark on selected options, reduce height of modal'
merge_request: 58055
author:
type: changed
......@@ -26755,16 +26755,19 @@ msgstr ""
msgid "Runners|Can run untagged jobs"
msgstr ""
msgid "Runners|Command to register runner"
msgstr ""
msgid "Runners|Copy instructions"
msgstr ""
msgid "Runners|Description"
msgstr ""
msgid "Runners|Download Latest Binary"
msgid "Runners|Download and install binary"
msgstr ""
msgid "Runners|Download and Install Binary"
msgid "Runners|Download latest binary"
msgstr ""
msgid "Runners|Group"
......@@ -26773,7 +26776,7 @@ msgstr ""
msgid "Runners|IP Address"
msgstr ""
msgid "Runners|Install a Runner"
msgid "Runners|Install a runner"
msgstr ""
msgid "Runners|Last contact"
......@@ -26797,9 +26800,6 @@ msgstr ""
msgid "Runners|Protected"
msgstr ""
msgid "Runners|Register Runner"
msgstr ""
msgid "Runners|Revision"
msgstr ""
......
......@@ -98,9 +98,21 @@ export const mockGraphqlInstructions = {
data: {
runnerSetup: {
installInstructions:
"# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n",
'# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start',
registerInstructions:
'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz',
'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token $REGISTRATION_TOKEN',
__typename: 'RunnerSetup',
},
},
};
export const mockGraphqlInstructionsWindows = {
data: {
runnerSetup: {
installInstructions:
'# Windows runner, then run\n.gitlab-runner.exe install\n.gitlab-runner.exe start',
registerInstructions:
'./gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token $REGISTRATION_TOKEN',
__typename: 'RunnerSetup',
},
},
......
import { GlAlert, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import {
mockGraphqlRunnerPlatforms,
mockGraphqlInstructions,
mockGraphqlInstructionsWindows,
} from './mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('RunnerInstructionsModal component', () => {
let wrapper;
let fakeApollo;
let runnerPlatformsHandler;
let runnerSetupInstructionsHandler;
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
const findPlatformButtons = () => wrapper.findAllByTestId('platform-button');
const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item');
const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions');
const findRegisterCommand = () => wrapper.findByTestId('register-command');
const createComponent = () => {
const requestHandlers = [
[getRunnerPlatformsQuery, runnerPlatformsHandler],
[getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = extendedWrapper(
shallowMount(RunnerInstructionsModal, {
propsData: {
modalId: 'runner-instructions-modal',
},
localVue,
apolloProvider: fakeApollo,
}),
);
};
beforeEach(async () => {
runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms);
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
createComponent();
await nextTick();
});
afterEach(() => {
wrapper.destroy();
});
it('should not show alert', () => {
expect(findAlert().exists()).toBe(false);
});
it('should contain a number of platforms buttons', () => {
expect(runnerPlatformsHandler).toHaveBeenCalledWith({});
const buttons = findPlatformButtons();
expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
});
it('should contain a number of dropdown items for the architecture options', () => {
expect(findArchitectureDropdownItems()).toHaveLength(
mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
);
});
describe('should display default instructions', () => {
const { installInstructions, registerInstructions } = mockGraphqlInstructions.data.runnerSetup;
it('runner instructions are requested', () => {
expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
platform: 'linux',
architecture: 'amd64',
});
});
it('binary instructions are shown', () => {
const instructions = findBinaryInstructions().text();
expect(instructions).toBe(installInstructions);
});
it('register command is shown', () => {
const instructions = findRegisterCommand().text();
expect(instructions).toBe(registerInstructions);
});
});
describe('after a platform and architecture are selected', () => {
const {
installInstructions,
registerInstructions,
} = mockGraphqlInstructionsWindows.data.runnerSetup;
beforeEach(async () => {
runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
findPlatformButtons().at(2).vm.$emit('click'); // another option, happens to be windows
await nextTick();
findArchitectureDropdownItems().at(1).vm.$emit('click'); // another option
await nextTick();
});
it('runner instructions are requested', () => {
expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
platform: 'windows',
architecture: '386',
});
});
it('other binary instructions are shown', () => {
const instructions = findBinaryInstructions().text();
expect(instructions).toBe(installInstructions);
});
it('register command is shown', () => {
const command = findRegisterCommand().text();
expect(command).toBe(registerInstructions);
});
});
describe('when apollo is loading', () => {
it('should show a skeleton loader', async () => {
createComponent();
expect(findSkeletonLoader().exists()).toBe(true);
expect(findGlLoadingIcon().exists()).toBe(false);
await nextTick(); // wait for platforms
expect(findGlLoadingIcon().exists()).toBe(true);
});
it('once loaded, should not show a loading state', async () => {
createComponent();
await nextTick(); // wait for platforms
await nextTick(); // wait for architectures
expect(findSkeletonLoader().exists()).toBe(false);
expect(findGlLoadingIcon().exists()).toBe(false);
});
});
describe('when instructions cannot be loaded', () => {
beforeEach(async () => {
runnerSetupInstructionsHandler.mockRejectedValue();
createComponent();
await waitForPromises();
});
it('should show alert', () => {
expect(findAlert().exists()).toBe(true);
});
it('should not show instructions', () => {
expect(findBinaryInstructions().exists()).toBe(false);
expect(findRegisterCommand().exists()).toBe(false);
});
});
});
import { GlAlert } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data';
const projectPath = 'gitlab-org/gitlab';
const localVue = createLocalVue();
localVue.use(VueApollo);
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
describe('RunnerInstructions component', () => {
let wrapper;
let fakeApollo;
let runnerPlatformsHandler;
let runnerSetupInstructionsHandler;
const findAlert = () => wrapper.findComponent(GlAlert);
const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]');
const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]');
const findArchitectureDropdownItems = () =>
wrapper.findAll('[data-testid="architecture-dropdown-item"]');
const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]');
const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]');
const findModalButton = () => wrapper.findByTestId('show-modal-button');
const findModal = () => wrapper.findComponent(RunnerInstructionsModal);
const createComponent = () => {
const requestHandlers = [
[getRunnerPlatforms, runnerPlatformsHandler],
[getRunnerSetupInstructions, runnerSetupInstructionsHandler],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = shallowMount(RunnerInstructions, {
provide: {
projectPath,
},
localVue,
apolloProvider: fakeApollo,
});
wrapper = extendedWrapper(shallowMount(RunnerInstructions));
};
beforeEach(async () => {
runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms);
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
beforeEach(() => {
createComponent();
await wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should not show alert', () => {
expect(findAlert().exists()).toBe(false);
});
it('should show the "Show Runner installation instructions" button', () => {
const button = findModalButton();
expect(button.exists()).toBe(true);
expect(button.text()).toBe('Show Runner installation instructions');
});
it('should contain a number of platforms buttons', () => {
const buttons = findPlatformButtons();
expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
});
it('should contain a number of dropdown items for the architecture options', () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
return wrapper.vm.$nextTick(() => {
const dropdownItems = findArchitectureDropdownItems();
expect(dropdownItems).toHaveLength(
mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
);
});
expect(findModalButton().exists()).toBe(true);
expect(findModalButton().text()).toBe('Show Runner installation instructions');
});
it('should display the binary installation instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findBinaryInstructionsSection();
expect(runner.text()).toMatch('sudo chmod +x /usr/local/bin/gitlab-runner');
expect(runner.text()).toMatch(
`sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`,
);
expect(runner.text()).toMatch(
'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner',
);
expect(runner.text()).toMatch('sudo gitlab-runner start');
it('should not render the modal once mounted', () => {
expect(findModal().exists()).toBe(false);
});
it('should display the runner register instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findRunnerInstructionsSection();
expect(runner.text()).toMatch(mockGraphqlInstructions.data.runnerSetup.registerInstructions);
});
describe('when instructions cannot be loaded', () => {
beforeEach(async () => {
runnerSetupInstructionsHandler.mockRejectedValue();
createComponent();
await wrapper.vm.$nextTick();
});
it('should render the modal once clicked', async () => {
findModalButton().vm.$emit('click');
it('should show alert', () => {
expect(findAlert().exists()).toBe(true);
});
await nextTick();
it('should not show instructions', () => {
expect(findBinaryInstructionsSection().exists()).toBe(false);
expect(findRunnerInstructionsSection().exists()).toBe(false);
});
expect(findModal().exists()).toBe(true);
});
});
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