Skip to content
Snippets Groups Projects
Commit 64857a9b authored by Filipa Lacerda's avatar Filipa Lacerda
Browse files

Manage empty states in Pipelines page

Adds i18n
Adds test

Fix broken tests

Fixes empty tab state for external CI
parent 67feb7cd
No related branches found
No related tags found
No related merge requests found
Showing
with 996 additions and 405 deletions
Loading
Loading
@@ -20,10 +20,6 @@
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
Loading
Loading
@@ -45,23 +41,14 @@
},
 
computed: {
/**
* Empty state is only rendered if after the first request we receive no pipelines.
*
* @return {Boolean}
*/
shouldRenderEmptyState() {
return !this.state.pipelines.length &&
!this.isLoading &&
this.hasMadeRequest &&
!this.hasError;
},
shouldRenderTable() {
return !this.isLoading &&
this.state.pipelines.length > 0 &&
!this.hasError;
},
shouldRenderErrorState() {
return this.hasError && !this.isLoading;
},
},
created() {
this.service = new PipelinesService(this.endpoint);
Loading
Loading
@@ -92,25 +79,22 @@
<div class="content-list pipelines">
 
<loading-icon
label="Loading pipelines"
:label="s__('Pipelines|Loading Pipelines')"
size="3"
v-if="isLoading"
class="prepend-top-20"
/>
 
<empty-state
v-if="shouldRenderEmptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
/>
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
<svg-blank-state
v-else-if="shouldRenderErrorState"
:svg-path="errorStateSvgPath"
:message="s__(`Pipelines|There was an error with fetching the pipelines.
Try again in a few moments or contact your support team.`)"
/>
 
<div
class="table-holder"
v-if="shouldRenderTable"
v-else-if="shouldRenderTable"
>
<pipelines-table-component
:pipelines="state.pipelines"
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@ import Vue from 'vue';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils';
 
Vue.use(Translate);
 
Loading
Loading
@@ -15,12 +16,25 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
 
return {
store,
dataset: document.querySelector(this.$options.el).dataset,
};
},
render(createElement) {
return createElement('pipelines-component', {
props: {
store: this.store,
endpoint: this.dataset.endpoint,
helpPagePath: this.dataset.helpPagePath,
emptyStateSvgPath: this.dataset.emptyStateSvgPath,
errorStateSvgPath: this.dataset.errorStateSvgPath,
noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
autoDevopsPath: this.dataset.helpAutoDevopsPath,
newPipelinePath: this.dataset.newPipelinePath,
canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
ciLintPath: this.dataset.ciLintPath,
resetCachePath: this.dataset.resetCachePath,
},
});
},
Loading
Loading
<script>
export default {
props: {
errorStateSvgPath: {
type: String,
required: true,
export default {
name: 'PipelinesSvgState',
props: {
svgPath: {
type: String,
required: true,
},
message: {
type: String,
required: true,
},
},
},
};
};
</script>
 
<template>
<div class="row empty-state js-pipelines-error-state">
<div class="row empty-state">
<div class="col-xs-12">
<div class="svg-content">
<img :src="errorStateSvgPath"/>
<img :src="svgPath" />
</div>
</div>
 
<div class="col-xs-12 text-center">
<div class="text-content">
<h4>The API failed to fetch the pipelines.</h4>
<h4>{{ message }}</h4>
</div>
</div>
</div>
Loading
Loading
<script>
export default {
name: 'PipelinesEmptyState',
props: {
helpPagePath: {
type: String,
Loading
Loading
@@ -9,6 +10,10 @@
type: String,
required: true,
},
canSetCi: {
type: Boolean,
required: true,
},
},
};
</script>
Loading
Loading
@@ -22,22 +27,36 @@
 
<div class="col-xs-12">
<div class="text-content">
<h4 class="text-center">
{{ s__("Pipelines|Build with confidence") }}
</h4>
<p>
{{ s__(`Pipelines|Continous Integration can help
catch bugs by running your tests automatically,
while Continuous Deployment can help you deliver code to your product environment.`) }}
<template v-if="canSetCi">
<h4 class="text-center">
{{ s__('Pipelines|Build with confidence') }}
</h4>
<p>
{{ s__(`Pipelines|Continous Integration can help
catch bugs by running your tests automatically,
while Continuous Deployment can help you deliver
code to your product environment.`) }}
</p>
<div class="text-center">
<a
:href="helpPagePath"
class="btn btn-info js-get-started-pipelines"
>
{{ s__('Pipelines|Get started with Pipelines') }}
</a>
</div>
</template>
<p
v-else
class="text-center"
>
{{ s__('Pipelines|This project is not currently set up to run pipelines.') }}
</p>
<div class="text-center">
<a
:href="helpPagePath"
class="btn btn-info"
>
{{ s__("Pipelines|Get started with Pipelines") }}
</a>
</div>
</div>
</div>
</div>
Loading
Loading
<script>
export default {
name: 'PipelineNavControls',
props: {
newPipelinePath: {
type: String,
required: true,
export default {
name: 'PipelineNavControls',
props: {
newPipelinePath: {
type: String,
required: false,
default: null,
},
resetCachePath: {
type: String,
required: false,
default: null,
},
ciLintPath: {
type: String,
required: false,
default: null,
},
},
hasCiEnabled: {
type: Boolean,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
resetCachePath: {
type: String,
required: true,
},
ciLintPath: {
type: String,
required: true,
},
canCreatePipeline: {
type: Boolean,
required: true,
},
},
};
};
</script>
<template>
<div class="nav-controls">
<a
v-if="canCreatePipeline"
v-if="newPipelinePath"
:href="newPipelinePath"
class="btn btn-create">
Run Pipeline
</a>
<a
v-if="!hasCiEnabled"
:href="helpPagePath"
class="btn btn-info">
Get started with Pipelines
class="btn btn-create js-run-pipeline"
>
{{ s__('Pipelines|Run Pipeline') }}
</a>
 
<a
v-if="resetCachePath"
data-method="post"
rel="nofollow"
:href="resetCachePath"
class="btn btn-default">
Clear runner caches
class="btn btn-default js-clear-cache"
>
{{ s__('Pipelines|Clear Runner Caches') }}
</a>
 
<a
v-if="ciLintPath"
:href="ciLintPath"
class="btn btn-default">
CI Lint
class="btn btn-default js-ci-lint"
>
{{ s__('Pipelines|CI Lint') }}
</a>
</div>
</template>
<script>
import _ from 'underscore';
import { __, sprintf, s__ } from '../../locale';
import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import navigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import navigationControls from './nav_controls.vue';
import TablePagination from '../../vue_shared/components/table_pagination.vue';
import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue';
import {
convertPermissionToBoolean,
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
Loading
Loading
@@ -14,9 +14,9 @@
 
export default {
components: {
tablePagination,
navigationTabs,
navigationControls,
TablePagination,
NavigationTabs,
NavigationControls,
},
mixins: [
pipelinesMixin,
Loading
Loading
@@ -36,111 +36,186 @@
required: false,
default: 'root',
},
endpoint: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
noPipelinesSvgPath: {
type: String,
required: true,
},
autoDevopsPath: {
type: String,
required: true,
},
hasGitlabCi: {
type: Boolean,
required: true,
},
canCreatePipeline: {
type: Boolean,
required: true,
},
ciLintPath: {
type: String,
required: false,
default: null,
},
resetCachePath: {
type: String,
required: false,
default: null,
},
newPipelinePath: {
type: String,
required: false,
default: null,
},
},
data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
return {
endpoint: pipelinesData.endpoint,
helpPagePath: pipelinesData.helpPagePath,
emptyStateSvgPath: pipelinesData.emptyStateSvgPath,
errorStateSvgPath: pipelinesData.errorStateSvgPath,
autoDevopsPath: pipelinesData.helpAutoDevopsPath,
newPipelinePath: pipelinesData.newPipelinePath,
canCreatePipeline: pipelinesData.canCreatePipeline,
hasCi: pipelinesData.hasCi,
ciLintPath: pipelinesData.ciLintPath,
resetCachePath: pipelinesData.resetCachePath,
// Start with loading state to avoid a glitch when the empty state will be rendered
isLoading: true,
state: this.store.state,
scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1',
requestData: {},
};
},
computed: {
canCreatePipelineParsed() {
return convertPermissionToBoolean(this.canCreatePipeline);
},
stateMap: {
// with tabs
loading: 'loading',
tableList: 'tableList',
error: 'error',
emptyTab: 'emptyTab',
 
// without tabs
emptyState: 'emptyState',
},
scopes: {
all: 'all',
pending: 'pending',
running: 'running',
finished: 'finished',
branches: 'branches',
tags: 'tags',
},
computed: {
/**
* The empty state should only be rendered when the request is made to fetch all pipelines
* and none is returned.
*
* @return {Boolean}
*/
shouldRenderEmptyState() {
return !this.isLoading &&
!this.hasError &&
this.hasMadeRequest &&
!this.state.pipelines.length &&
(this.scope === 'all' || this.scope === null);
* `hasGitlabCi` handles both internal and external CI.
* The order on which the checks are made in this method is
* important to guarantee we handle all the corner cases.
*/
stateToRender() {
const { stateMap } = this.$options;
if (this.isLoading) {
return stateMap.loading;
}
if (this.hasError) {
return stateMap.error;
}
if (this.state.pipelines.length) {
return stateMap.tableList;
}
if (this.hasGitlabCi) {
return stateMap.emptyTab;
}
return stateMap.emptyState;
},
/**
* When a specific scope does not have pipelines we render a message.
*
* @return {Boolean}
* Tabs are rendered in all states except empty state.
* They are not rendered before the first request to avoid a flicker on first load.
*/
shouldRenderNoPipelinesMessage() {
return !this.isLoading &&
!this.hasError &&
!this.state.pipelines.length &&
this.scope !== 'all' &&
this.scope !== null;
shouldRenderTabs() {
const { stateMap } = this.$options;
return this.hasMadeRequest &&
[
stateMap.loading,
stateMap.tableList,
stateMap.error,
stateMap.emptyTab,
].includes(this.stateToRender);
},
 
shouldRenderTable() {
return !this.hasError &&
!this.isLoading && this.state.pipelines.length;
shouldRenderButtons() {
return (this.newPipelinePath ||
this.resetCachePath ||
this.ciLintPath) && this.shouldRenderTabs;
},
/**
* Pagination should only be rendered when there is more than one page.
*
* @return {Boolean}
*/
shouldRenderPagination() {
return !this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage;
},
hasCiEnabled() {
return this.hasCi !== undefined;
emptyTabMessage() {
const { scopes } = this.$options;
const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
if (possibleScopes.includes(this.scope)) {
return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
scope: this.scope,
});
}
return s__('Pipelines|There are currently no pipelines.');
},
 
tabs() {
const { count } = this.state;
const { scopes } = this.$options;
return [
{
name: 'All',
scope: 'all',
name: __('All'),
scope: scopes.all,
count: count.all,
isActive: this.scope === 'all',
},
{
name: 'Pending',
scope: 'pending',
name: __('Pending'),
scope: scopes.pending,
count: count.pending,
isActive: this.scope === 'pending',
},
{
name: 'Running',
scope: 'running',
name: __('Running'),
scope: scopes.running,
count: count.running,
isActive: this.scope === 'running',
},
{
name: 'Finished',
scope: 'finished',
name: __('Finished'),
scope: scopes.finished,
count: count.finished,
isActive: this.scope === 'finished',
},
{
name: 'Branches',
scope: 'branches',
name: __('Branches'),
scope: scopes.branches,
isActive: this.scope === 'branches',
},
{
name: 'Tags',
scope: 'tags',
name: __('Tags'),
scope: scopes.tags,
isActive: this.scope === 'tags',
},
];
Loading
Loading
@@ -187,7 +262,7 @@
this.errorCallback();
 
// restart polling
this.poll.restart();
this.poll.restart({ data: this.requestData });
});
},
},
Loading
Loading
@@ -197,69 +272,70 @@
<div class="pipelines-container">
<div
class="top-area scrolling-tabs-container inner-page-scroll-tabs"
v-if="!shouldRenderEmptyState"
v-if="shouldRenderTabs || shouldRenderButtons"
>
<div class="fade-left">
<i
class="fa fa-angle-left"
aria-hidden="true">
aria-hidden="true"
>
</i>
</div>
<div class="fade-right">
<i
class="fa fa-angle-right"
aria-hidden="true">
aria-hidden="true"
>
</i>
</div>
 
<navigation-tabs
v-if="shouldRenderTabs"
:tabs="tabs"
@onChangeTab="onChangeTab"
scope="pipelines"
/>
 
<navigation-controls
v-if="shouldRenderButtons"
:new-pipeline-path="newPipelinePath"
:has-ci-enabled="hasCiEnabled"
:help-page-path="helpPagePath"
:reset-cache-path="resetCachePath"
:ci-lint-path="ciLintPath"
:can-create-pipeline="canCreatePipelineParsed "
/>
</div>
 
<div class="content-list pipelines">
 
<loading-icon
label="Loading Pipelines"
v-if="stateToRender === $options.stateMap.loading"
:label="s__('Pipelines|Loading Pipelines')"
size="3"
v-if="isLoading"
class="prepend-top-20"
/>
 
<empty-state
v-if="shouldRenderEmptyState"
v-else-if="stateToRender === $options.stateMap.emptyState"
:help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
:can-set-ci="canCreatePipeline"
/>
 
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
<svg-blank-state
v-else-if="stateToRender === $options.stateMap.error"
:svg-path="errorStateSvgPath"
:message="s__(`Pipelines|There was an error with fetching the pipelines.
Try again in a few moments or contact your support team.`)"
/>
 
<div
class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage"
>
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
</div>
<svg-blank-state
v-else-if="stateToRender === $options.stateMap.emptyTab"
:svg-path="noPipelinesSvgPath"
:message="emptyTabMessage"
/>
 
<div
class="table-holder"
v-if="shouldRenderTable"
v-else-if="stateToRender === $options.stateMap.tableList"
>
 
<pipelines-table-component
Loading
Loading
import Visibility from 'visibilityjs';
import { __ } from '../../locale';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll';
import emptyState from '../components/empty_state.vue';
import errorState from '../components/error_state.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import pipelinesTableComponent from '../components/pipelines_table.vue';
import EmptyState from '../components/empty_state.vue';
import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
 
export default {
components: {
pipelinesTableComponent,
errorState,
emptyState,
loadingIcon,
},
computed: {
shouldRenderErrorState() {
return this.hasError && !this.isLoading;
},
PipelinesTableComponent,
SvgBlankState,
EmptyState,
LoadingIcon,
},
data() {
return {
Loading
Loading
@@ -85,6 +81,7 @@ export default {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
this.hasMadeRequest = true;
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
Loading
Loading
@@ -96,7 +93,7 @@ export default {
postAction(endpoint) {
this.service.postAction(endpoint)
.then(() => eventHub.$emit('refreshPipelines'))
.catch(() => new Flash('An error occurred while making the request.'));
.catch(() => Flash(__('An error occurred while making the request.')));
},
},
};
Loading
Loading
@@ -5,7 +5,12 @@ export default class PipelinesStore {
this.state = {};
 
this.state.pipelines = [];
this.state.count = {};
this.state.count = {
all: 0,
finished: 0,
pending: 0,
running: 0,
};
this.state.pageInfo = {};
}
 
Loading
Loading
Loading
Loading
@@ -7,8 +7,9 @@
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
"new-pipeline-path" => new_project_pipeline_path(@project),
"no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"has-ci" => @repository.gitlab_ci_yml,
"ci-lint-path" => ci_lint_path,
"reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
"new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project),
"ci-lint-path" => can?(current_user, :create_pipeline, @project) && ci_lint_path,
"reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) ,
"has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } }
---
title: Handle empty state in Pipelines page
merge_request:
author:
type: fixed
Loading
Loading
@@ -86,7 +86,22 @@ describe 'Pipelines', :js do
it 'updates content when tab is clicked' do
page.find('.js-pipelines-tab-pending').click
wait_for_requests
expect(page).to have_content('No pipelines to show.')
expect(page).to have_content('There are currently no pending pipelines.')
end
end
context 'navigation links' do
before do
visit project_pipelines_path(project)
wait_for_requests
end
it 'renders run pipeline link' do
expect(page).to have_link('Run Pipeline')
end
it 'renders ci lint link' do
expect(page).to have_link('CI Lint')
end
end
 
Loading
Loading
@@ -542,7 +557,7 @@ describe 'Pipelines', :js do
end
 
it 'has a clear caches button' do
expect(page).to have_link 'Clear runner caches'
expect(page).to have_link 'Clear Runner Caches'
end
 
describe 'user clicks the button' do
Loading
Loading
@@ -552,19 +567,31 @@ describe 'Pipelines', :js do
end
 
it 'increments jobs_cache_index' do
click_link 'Clear runner caches'
click_link 'Clear Runner Caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
 
context 'when project does not have jobs_cache_index' do
it 'sets jobs_cache_index to 1' do
click_link 'Clear runner caches'
click_link 'Clear Runner Caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
end
end
describe 'Empty State' do
let(:project) { create(:project, :repository) }
before do
visit project_pipelines_path(project)
end
it 'renders empty state' do
expect(page).to have_content 'Build with confidence'
end
end
end
 
context 'when user is not logged in' do
Loading
Loading
@@ -575,7 +602,9 @@ describe 'Pipelines', :js do
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
 
it { expect(page).to have_content 'Build with confidence' }
context 'without pipelines' do
it { expect(page).to have_content 'This project is not currently set up to run pipelines.' }
end
end
 
context 'when project is private' do
Loading
Loading
import Vue from 'vue';
import component from '~/pipelines/components/blank_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Blank State', () => {
let vm;
let Component;
beforeEach(() => {
Component = Vue.extend(component);
vm = mountComponent(Component,
{
svgPath: 'foo',
message: 'Blank State',
},
);
});
it('should render svg', () => {
expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo');
});
it('should render message', () => {
expect(
vm.$el.querySelector('h4').textContent.trim(),
).toEqual('Blank State');
});
});
import Vue from 'vue';
import emptyStateComp from '~/pipelines/components/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
 
describe('Pipelines Empty State', () => {
let component;
Loading
Loading
@@ -8,12 +9,15 @@ describe('Pipelines Empty State', () => {
beforeEach(() => {
EmptyStateComponent = Vue.extend(emptyStateComp);
 
component = new EmptyStateComponent({
propsData: {
helpPagePath: 'foo',
emptyStateSvgPath: 'foo',
},
}).$mount();
component = mountComponent(EmptyStateComponent, {
helpPagePath: 'foo',
emptyStateSvgPath: 'foo',
canSetCi: true,
});
});
afterEach(() => {
component.$destroy();
});
 
it('should render empty state SVG', () => {
Loading
Loading
@@ -24,16 +28,16 @@ describe('Pipelines Empty State', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
 
expect(
component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continous Integration can help catch bugs by running your tests automatically');
component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
).toContain('Continous Integration can help catch bugs by running your tests automatically,');
 
expect(
component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continuous Deployment can help you deliver code to your product environment');
component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
).toContain('while Continuous Deployment can help you deliver code to your product environment');
});
 
it('should render a link with provided help path', () => {
expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual('foo');
expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual('foo');
expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain('Get started with Pipelines');
});
});
import Vue from 'vue';
import errorStateComp from '~/pipelines/components/error_state.vue';
describe('Pipelines Error State', () => {
let component;
let ErrorStateComponent;
beforeEach(() => {
ErrorStateComponent = Vue.extend(errorStateComp);
component = new ErrorStateComponent({
propsData: {
errorStateSvgPath: 'foo',
},
}).$mount();
});
it('should render error state SVG', () => {
expect(component.$el.querySelector('.svg-content svg')).toBeDefined();
});
it('should render emtpy state information', () => {
expect(
component.$el.querySelector('h4').textContent,
).toContain('The API failed to fetch the pipelines');
});
});
import Vue from 'vue';
import navControlsComp from '~/pipelines/components/nav_controls.vue';
import mountComponent from '../helpers/vue_mount_component_helper';
 
describe('Pipelines Nav Controls', () => {
let NavControlsComponent;
let component;
 
beforeEach(() => {
NavControlsComponent = Vue.extend(navControlsComp);
});
 
afterEach(() => {
component.$destroy();
});
it('should render link to create a new pipeline', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true,
};
 
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
component = mountComponent(NavControlsComponent, mockData);
 
expect(component.$el.querySelector('.btn-create').textContent).toContain('Run Pipeline');
expect(component.$el.querySelector('.btn-create').getAttribute('href')).toEqual(mockData.newPipelinePath);
expect(component.$el.querySelector('.js-run-pipeline').textContent).toContain('Run Pipeline');
expect(component.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(mockData.newPipelinePath);
});
 
it('should not render link to create pipeline if no permission is provided', () => {
it('should not render link to create pipeline if no path is provided', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: false,
};
 
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
component = mountComponent(NavControlsComponent, mockData);
 
expect(component.$el.querySelector('.btn-create')).toEqual(null);
expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null);
});
 
it('should render link for resetting runner caches', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: false,
};
 
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
component = mountComponent(NavControlsComponent, mockData);
 
expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
expect(component.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(mockData.resetCachePath);
});
 
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
});
it('should render link to help page when CI is not enabled', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: false,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual(mockData.helpPagePath);
});
it('should not render link to help page when CI is enabled', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true,
};
 
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
component = mountComponent(NavControlsComponent, mockData);
 
expect(component.$el.querySelector('.btn-info')).toEqual(null);
expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint');
expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath);
});
});
This diff is collapsed.
Loading
Loading
@@ -9,7 +9,12 @@ describe('Pipelines Store', () => {
 
it('should be initialized with an empty state', () => {
expect(store.state.pipelines).toEqual([]);
expect(store.state.count).toEqual({});
expect(store.state.count).toEqual({
all: 0,
finished: 0,
pending: 0,
running: 0,
});
expect(store.state.pageInfo).toEqual({});
});
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment