Commit fd1ecb9d authored by Miguel Rincon's avatar Miguel Rincon

Remove group/project params from instructions

This MR cleans up unused parameters from the runner install
instructions.

As runner installation instructions no longer depend on the user,
project ID, or group ID this can be safely removed.

Other additional UX improvement have been added:
- Reduce spacing between modal elements to keep the modal shorter
- Used checkmarks in architecture dropdowns
- Improved the copy of the instructions
parent 38186b3c
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
......@@ -26758,16 +26758,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"
......@@ -26776,7 +26779,7 @@ msgstr ""
msgid "Runners|IP Address"
msgstr ""
msgid "Runners|Install a Runner"
msgid "Runners|Install a runner"
msgstr ""
msgid "Runners|Last contact"
......@@ -26800,9 +26803,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