Commit 446b59dd authored by Filipa Lacerda's avatar Filipa Lacerda

Adds tests to new empty and error states

parent 2c85a204
...@@ -5,6 +5,7 @@ import PipelinesTableComponent from '../../vue_shared/components/pipelines_table ...@@ -5,6 +5,7 @@ import PipelinesTableComponent from '../../vue_shared/components/pipelines_table
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
import eventHub from '../../vue_pipelines_index/event_hub'; import eventHub from '../../vue_pipelines_index/event_hub';
import EmptyState from '../../vue_pipelines_index/components/empty_state';
import ErrorState from '../../vue_pipelines_index/components/error_state'; import ErrorState from '../../vue_pipelines_index/components/error_state';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
...@@ -24,6 +25,7 @@ export default Vue.component('pipelines-table', { ...@@ -24,6 +25,7 @@ export default Vue.component('pipelines-table', {
components: { components: {
'pipelines-table-component': PipelinesTableComponent, 'pipelines-table-component': PipelinesTableComponent,
'error-state': ErrorState, 'error-state': ErrorState,
'empty-state': EmptyState,
}, },
/** /**
...@@ -38,6 +40,7 @@ export default Vue.component('pipelines-table', { ...@@ -38,6 +40,7 @@ export default Vue.component('pipelines-table', {
return { return {
endpoint: pipelinesTableData.endpoint, endpoint: pipelinesTableData.endpoint,
helpPagePath: pipelinesTableData.helpPagePath,
store, store,
state: store.state, state: store.state,
isLoading: false, isLoading: false,
...@@ -49,6 +52,10 @@ export default Vue.component('pipelines-table', { ...@@ -49,6 +52,10 @@ export default Vue.component('pipelines-table', {
shouldRenderErrorState() { shouldRenderErrorState() {
return this.hasError && !this.pageRequest; return this.hasError && !this.pageRequest;
}, },
shouldRenderEmptyState() {
return !this.state.pipelines.length && !this.pageRequest;
},
}, },
/** /**
...@@ -102,6 +109,8 @@ export default Vue.component('pipelines-table', { ...@@ -102,6 +109,8 @@ export default Vue.component('pipelines-table', {
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
</div> </div>
<empty-state v-if="shouldRenderEmptyState" :helpPagePath="helpPagePath" />
<error-state v-if="shouldRenderErrorState" /> <error-state v-if="shouldRenderErrorState" />
<div class="table-holder pipelines" <div class="table-holder pipelines"
......
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
}, },
template: ` template: `
<div class="row empty-state"> <div class="row empty-state js-pipelines-empty-state">
<div class="col-xs-12 pull-right"> <div class="col-xs-12 pull-right">
<div class="svg-content"> <div class="svg-content">
${pipelinesEmptyStateSVG} ${pipelinesEmptyStateSVG}
...@@ -28,10 +28,10 @@ export default { ...@@ -28,10 +28,10 @@ export default {
<p> <p>
Continous Integration can help catch bugs by running your tests automatically, Continous Integration can help catch bugs by running your tests automatically,
while Continuous Deployment can help you deliver code to your product environment. while Continuous Deployment can help you deliver code to your product environment.
<a :href="helpPagePath" class="btn btn-info">
Get started with Pipelines
</a>
</p> </p>
<a :href="helpPagePath" class="btn btn-info">
Get started with Pipelines
</a>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -8,18 +8,18 @@ export default { ...@@ -8,18 +8,18 @@ export default {
}, },
template: ` template: `
<div class="row empty-state"> <div class="row empty-state js-pipelines-error-state">
<div class="col-xs-12 pull-right"> <div class="col-xs-12 pull-right">
<div class="svg-content"> <div class="svg-content">
${pipelinesErrorStateSVG} ${pipelinesErrorStateSVG}
</div>
</div> </div>
</div>
<div class="col-xs-12 center"> <div class="col-xs-12 center">
<div class="text-content"> <div class="text-content">
<h4>The API failed to fetch the pipelines.</h4> <h4>The API failed to fetch the pipelines.</h4>
</div>
</div> </div>
</div> </div>
</div>
`, `,
}; };
...@@ -18,7 +18,9 @@ export default { ...@@ -18,7 +18,9 @@ export default {
template: ` template: `
<ul class="nav-links"> <ul class="nav-links">
<li :class="{ 'active': scope === 'all'}"> <li
class="js-pipelines-tab-all"
:class="{ 'active': scope === 'all'}">
<a :href="paths.allPath"> <a :href="paths.allPath">
All All
<span class="badge js-totalbuilds-count"> <span class="badge js-totalbuilds-count">
......
...@@ -32,7 +32,19 @@ export default { ...@@ -32,7 +32,19 @@ export default {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset; const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
return { return {
...pipelinesData, endpoint: pipelinesData.endpoint,
cssClass: pipelinesData.cssClass,
helpPagePath: pipelinesData.helpPagePath,
newPipelinePath: pipelinesData.newPipelinePath,
canCreatePipeline: pipelinesData.canCreatePipeline,
allPath: pipelinesData.allPath,
pendingPath: pipelinesData.pendingPath,
runningPath: pipelinesData.runningPath,
finishedPath: pipelinesData.finishedPath,
branchesPath: pipelinesData.branchesPath,
tagsPath: pipelinesData.tagsPath,
hasCi: pipelinesData.hasCi,
ciLintPath: pipelinesData.ciLintPath,
state: this.store.state, state: this.store.state,
apiScope: 'all', apiScope: 'all',
pagenum: 1, pagenum: 1,
...@@ -172,8 +184,7 @@ export default { ...@@ -172,8 +184,7 @@ export default {
template: ` template: `
<div <div
:class="cssClass" :class="cssClass">
class="pipelines">
<div <div
class="top-area" class="top-area"
...@@ -191,37 +202,40 @@ export default { ...@@ -191,37 +202,40 @@ export default {
:canCreatePipeline="canCreatePipelineParsed " /> :canCreatePipeline="canCreatePipelineParsed " />
</div> </div>
<div <div class="content-list pipelines">
class="realtime-loading"
v-if="pageRequest">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<empty-state v-if="shouldRenderEmptyState" :helpPagePath="helpPagePath" /> <div
class="realtime-loading"
v-if="pageRequest">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<error-state v-if="shouldRenderErrorState" /> <empty-state v-if="shouldRenderEmptyState" :helpPagePath="helpPagePath" />
<div <error-state v-if="shouldRenderErrorState" />
class="blank-state blank-state-no-icon"
v-if="shouldRenderNoPipelinesMessage">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
<div <div
class="table-holder" class="blank-state blank-state-no-icon"
v-if="shouldRenderTable"> v-if="shouldRenderNoPipelinesMessage">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
<pipelines-table-component <div
:pipelines="state.pipelines" class="table-holder"
:service="service"/> v-if="shouldRenderTable">
</div>
<gl-pagination <pipelines-table-component
v-if="shouldRenderPagination" :pipelines="state.pipelines"
:pagenum="pagenum" :service="service"/>
:change="change" </div>
:count="state.count.all"
:pageInfo="state.pageInfo"/> <gl-pagination
v-if="shouldRenderPagination"
:pagenum="pagenum"
:change="change"
:count="state.count.all"
:pageInfo="state.pageInfo"/>
</div>
</div> </div>
`, `,
}; };
- disable_initialization = local_assigns.fetch(:disable_initialization, false) - disable_initialization = local_assigns.fetch(:disable_initialization, false)
#commit-pipeline-table-view{ data: { disable_initialization: disable_initialization, #commit-pipeline-table-view{ data: { disable_initialization: disable_initialization,
endpoint: endpoint, endpoint: endpoint,
"help-page-path" => help_page_path('ci/quick_start/README')
} } } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
......
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
- page_title "Pipelines" - page_title "Pipelines"
= render "projects/pipelines/head" = render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag("common_vue")
= page_specific_javascript_bundle_tag("vue_pipelines")
#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json), #pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json),
"css-class" => container_class, "css-class" => container_class,
"help-page-path" => help_page_path('ci/quick_start/README'), "help-page-path" => help_page_path('ci/quick_start/README'),
...@@ -19,3 +15,6 @@ ...@@ -19,3 +15,6 @@
"tags-path" => project_pipelines_path(@project, scope: :tags), "tags-path" => project_pipelines_path(@project, scope: :tags),
"has-ci" => @repository.gitlab_ci_yml, "has-ci" => @repository.gitlab_ci_yml,
"ci-lint-path" => ci_lint_path } } "ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('vue_pipelines')
...@@ -442,7 +442,7 @@ describe 'Pipelines', :feature, :js do ...@@ -442,7 +442,7 @@ describe 'Pipelines', :feature, :js do
context 'when project is public' do context 'when project is public' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
it { expect(page).to have_content 'No pipelines to show' } it { expect(page).to have_content 'Build with confidence' }
it { expect(page).to have_http_status(:success) } it { expect(page).to have_http_status(:success) }
end end
......
...@@ -33,7 +33,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -33,7 +33,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
}); });
setTimeout(() => { setTimeout(() => {
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); expect(component.$el.querySelector('.js-pipelines-empty-state')).toBeDefined();
done(); done();
}, 1); }, 1);
}); });
...@@ -92,7 +92,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -92,7 +92,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
}); });
setTimeout(() => { setTimeout(() => {
expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
done(); done();
}, 0); }, 0);
}); });
......
%div
#pipelines-list-vue{ data: { endpoint: 'foo',
"css-class" => 'foo',
"help-page-path" => 'foo',
"new-pipeline-path" => 'foo',
"can-create-pipeline" => 'true',
"all-path" => 'foo',
"pending-path" => 'foo',
"running-path" => 'foo',
"finished-path" => 'foo',
"branches-path" => 'foo',
"tags-path" => 'foo',
"has-ci" => 'foo',
"ci-lint-path" => 'foo' } }
import Vue from 'vue';
import emptyStateComp from '~/vue_pipelines_index/components/empty_state';
describe('Pipelines Empty State', () => {
let component;
let EmptyStateComponent;
beforeEach(() => {
EmptyStateComponent = Vue.extend(emptyStateComp);
component = new EmptyStateComponent({
propsData: {
helpPagePath: 'foo',
},
}).$mount();
});
it('should render empty state SVG', () => {
expect(component.$el.querySelector('.svg-content svg')).toBeDefined();
});
it('should render emtpy state information', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
expect(
component.$el.querySelector('p').textContent,
).toContain('Continous Integration can help catch bugs by running your tests automatically');
expect(
component.$el.querySelector('p').textContent,
).toContain('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');
});
});
import Vue from 'vue';
import errorStateComp from '~/vue_pipelines_index/components/error_state';
describe('Pipelines Error State', () => {
let component;
let ErrorStateComponent;
beforeEach(() => {
ErrorStateComponent = Vue.extend(errorStateComp);
component = new ErrorStateComponent().$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');
});
});
export default {
pipelines: [{
id: 115,
user: {
name: 'Root',
username: 'root',
id: 1,
state: 'active',
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
path: '/root/review-app/pipelines/115',
details: {
status: {
icon: 'icon_status_failed',
text: 'failed',
label: 'failed',
group: 'failed',
has_details: true,
details_path: '/root/review-app/pipelines/115',
},
duration: null,
finished_at: '2017-03-17T19:00:15.996Z',
stages: [{
name: 'build',
title: 'build: failed',
status: {
icon: 'icon_status_failed',
text: 'failed',
label: 'failed',
group: 'failed',
has_details: true,
details_path: '/root/review-app/pipelines/115#build',
},
path: '/root/review-app/pipelines/115#build',
dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=build',
},
{
name: 'review',
title: 'review: skipped',
status: {
icon: 'icon_status_skipped',
text: 'skipped',
label: 'skipped',
group: 'skipped',
has_details: true,
details_path: '/root/review-app/pipelines/115#review',
},
path: '/root/review-app/pipelines/115#review',
dropdown_path: '/root/review-app/pipelines/115/stage.json?stage=review',
}],
artifacts: [],
manual_actions: [{
name: 'stop_review',
path: '/root/review-app/builds/3766/play',
}],
},
flags: {
latest: true,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: false,
},
ref: {
name: 'thisisabranch',
path: '/root/review-app/tree/thisisabranch',
tag: false,
branch: true,
},
commit: {
id: '9e87f87625b26c42c59a2ee0398f81d20cdfe600',
short_id: '9e87f876',
title: 'Update README.md',
created_at: '2017-03-15T22:58:28.000+00:00',
parent_ids: ['3744f9226e699faec2662a8b267e5d3fd0bfff0e'],
message: 'Update README.md',
author_name: 'Root',
author_email: 'admin@example.com',
authored_date: '2017-03-15T22:58:28.000+00:00',
committer_name: 'Root',
committer_email: 'admin@example.com',
committed_date: '2017-03-15T22:58:28.000+00:00',
author: {
name: 'Root',
username: 'root',
id: 1,
state: 'active',
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
commit_url: 'http://localhost:3000/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600',
commit_path: '/root/review-app/commit/9e87f87625b26c42c59a2ee0398f81d20cdfe600',
},
retry_path: '/root/review-app/pipelines/115/retry',
created_at: '2017-03-15T22:58:33.436Z',
updated_at: '2017-03-17T19:00:15.997Z',
}],
count: {
all: 52,
running: 0,
pending: 0,
finished: 52,
},
};
import Vue from 'vue';
import navControlsComp from '~/vue_pipelines_index/components/nav_controls';
describe('Pipelines Nav Controls', () => {
let NavControlsComponent;
beforeEach(() => {
NavControlsComponent = Vue.extend(navControlsComp);
});
it('should render link to create a new pipeline', () => {
const mockData = {
newPipelinePath: 'foo',
hasCIEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
canCreatePipeline: true,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelector('.btn-create').textContent).toContain('Run Pipeline');
expect(component.$el.querySelector('.btn-create').getAttribute('href')).toEqual(mockData.newPipelinePath);
});
it('should not render link to create pipeline if no permission is provided', () => {
const mockData = {
newPipelinePath: 'foo',
hasCIEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
canCreatePipeline: false,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelector('.btn-create')).toEqual(null);
});
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
hasCIEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
canCreatePipeline: true,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint');
expect(component.$el.querySelector('.btn-default').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',
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',
canCreatePipeline: true,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelector('.btn-info')).toEqual(null);
});
});
import Vue from 'vue';
import pipelinesComp from '~/vue_pipelines_index/pipelines';
import Store from '~/vue_pipelines_index/stores/pipelines_store';
import pipelinesData from './mock_data';
describe('Pipelines', () => {
preloadFixtures('static/pipelines.html.raw');
let PipelinesComponent;
beforeEach(() => {
loadFixtures('static/pipelines.html.raw');
PipelinesComponent = Vue.extend(pipelinesComp);
});
describe('successfull request', () => {
describe('with pipelines', () => {
const pipelinesInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify(pipelinesData), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(pipelinesInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, pipelinesInterceptor,
);
});
it('should render table', (done) => {
const component = new PipelinesComponent({
propsData: {
store: new Store(),
},
}).$mount();
setTimeout(() => {
expect(component.$el.querySelector('.table-holder')).toBeDefined();
done();
});
});
});
describe('without pipelines', () => {
const emptyInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify([]), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(emptyInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, emptyInterceptor,
);
});
it('should render empty state', (done) => {
const component = new PipelinesComponent({
propsData: {
store: new Store(),
},
}).$mount();
setTimeout(() => {
expect(component.$el.querySelector('.js-pipelines-empty-state')).toBeDefined();
done();
});
});
});
});
describe('unsuccessfull request', () => {
const errorInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify([]), {
status: 500,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(errorInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, errorInterceptor,
);
});
it('should render error state', (done) => {
const component = new PipelinesComponent({
propsData: {
store: new Store(),
},
}).$mount();
setTimeout(() => {
expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
done();
});
});
});
});
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