summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/.eslintrc33
-rw-r--r--spec/javascripts/.eslintrc.yml34
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js9
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js1
-rw-r--r--spec/javascripts/boards/board_card_spec.js1
-rw-r--r--spec/javascripts/boards/board_list_spec.js3
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js1
-rw-r--r--spec/javascripts/boards/boards_store_spec.js1
-rw-r--r--spec/javascripts/boards/issue_spec.js1
-rw-r--r--spec/javascripts/boards/list_spec.js1
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js32
-rw-r--r--spec/javascripts/clusters/components/application_row_spec.js22
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js94
-rw-r--r--spec/javascripts/clusters/services/mock_data.js35
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js18
-rw-r--r--spec/javascripts/commit/commit_pipeline_status_component_spec.js5
-rw-r--r--spec/javascripts/helpers/vue_mount_component_helper.js30
-rw-r--r--spec/javascripts/ide/components/external_link_spec.js35
-rw-r--r--spec/javascripts/ide/components/ide_file_buttons_spec.js61
-rw-r--r--spec/javascripts/ide/components/jobs/item_spec.js29
-rw-r--r--spec/javascripts/ide/components/jobs/list_spec.js67
-rw-r--r--spec/javascripts/ide/components/jobs/stage_spec.js95
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js117
-rw-r--r--spec/javascripts/ide/helpers.js2
-rw-r--r--spec/javascripts/ide/mock_data.js70
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js111
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js265
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/getters_spec.js31
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js162
-rw-r--r--spec/javascripts/ide/stores/mutations/branch_spec.js36
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js5
-rw-r--r--spec/javascripts/u2f/mock_u2f_device.js2
-rw-r--r--spec/javascripts/vue_shared/components/tabs/tab_spec.js32
-rw-r--r--spec/javascripts/vue_shared/components/tabs/tabs_spec.js68
34 files changed, 987 insertions, 522 deletions
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
deleted file mode 100644
index 9eb0e732572..00000000000
--- a/spec/javascripts/.eslintrc
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "env": {
- "jasmine": true
- },
- "extends": "plugin:jasmine/recommended",
- "globals": {
- "appendLoadFixtures": false,
- "appendLoadStyleFixtures": false,
- "appendSetFixtures": false,
- "appendSetStyleFixtures": false,
- "getJSONFixture": false,
- "loadFixtures": false,
- "loadJSONFixtures": false,
- "loadStyleFixtures": false,
- "preloadFixtures": false,
- "preloadStyleFixtures": false,
- "readFixtures": false,
- "sandbox": false,
- "setFixtures": false,
- "setStyleFixtures": false,
- "spyOnDependency": false,
- "spyOnEvent": false,
- "ClassSpecHelper": false
- },
- "plugins": ["jasmine"],
- "rules": {
- "func-names": 0,
- "jasmine/no-suite-dupes": [1, "branch"],
- "jasmine/no-spec-dupes": [1, "branch"],
- "no-console": 0,
- "prefer-arrow-callback": 0
- }
-}
diff --git a/spec/javascripts/.eslintrc.yml b/spec/javascripts/.eslintrc.yml
new file mode 100644
index 00000000000..8bceb2c50fc
--- /dev/null
+++ b/spec/javascripts/.eslintrc.yml
@@ -0,0 +1,34 @@
+---
+env:
+ jasmine: true
+extends: plugin:jasmine/recommended
+globals:
+ appendLoadFixtures: false
+ appendLoadStyleFixtures: false
+ appendSetFixtures: false
+ appendSetStyleFixtures: false
+ getJSONFixture: false
+ loadFixtures: false
+ loadJSONFixtures: false
+ loadStyleFixtures: false
+ preloadFixtures: false
+ preloadStyleFixtures: false
+ readFixtures: false
+ sandbox: false
+ setFixtures: false
+ setStyleFixtures: false
+ spyOnDependency: false
+ spyOnEvent: false
+ ClassSpecHelper: false
+plugins:
+ - jasmine
+rules:
+ func-names: off
+ jasmine/no-suite-dupes:
+ - warn
+ - branch
+ jasmine/no-spec-dupes:
+ - warn
+ - branch
+ no-console: off
+ prefer-arrow-callback: off
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index a143fc827d5..80c09a544d6 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -84,9 +84,14 @@ describe('iPython notebook renderer', () => {
describe('error in JSON response', () => {
let mock;
- beforeEach((done) => {
+ beforeEach(done => {
mock = new MockAdapter(axios);
- mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }));
+ mock
+ .onGet('/test')
+ .reply(() =>
+ // eslint-disable-next-line prefer-promise-reject-errors
+ Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }),
+ );
renderNotebook();
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 664ea202e93..89a4fae4b59 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,4 +1,3 @@
-/* global BoardService */
import Vue from 'vue';
import '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 13d607a06d2..9b4db774b63 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -1,7 +1,6 @@
/* global List */
/* global ListAssignee */
/* global ListLabel */
-/* global BoardService */
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index c06b2f60813..de261d36c61 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -1,10 +1,9 @@
-/* global BoardService */
/* global List */
/* global ListIssue */
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import Sortable from 'vendor/Sortable';
+import Sortable from 'sortablejs';
import BoardList from '~/boards/components/board_list.vue';
import eventHub from '~/boards/eventhub';
import '~/boards/mixins/sortable_default_options';
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index d5fbfdeaa91..ee37821ad08 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -1,4 +1,3 @@
-/* global BoardService */
/* global List */
import Vue from 'vue';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 0cf9e4c9ba1..46fa10e1789 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -1,5 +1,4 @@
/* eslint-disable comma-dangle, one-var, no-unused-vars */
-/* global BoardService */
/* global ListIssue */
import Vue from 'vue';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 4a11131b55c..d90f9a41231 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,5 +1,4 @@
/* eslint-disable comma-dangle */
-/* global BoardService */
/* global ListIssue */
import Vue from 'vue';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index d9a1d692949..d5d1139de15 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -1,5 +1,4 @@
/* eslint-disable comma-dangle */
-/* global BoardService */
/* global List */
/* global ListIssue */
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index a5cd247b689..abe2954d506 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -207,11 +207,11 @@ describe('Clusters', () => {
spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
- cluster.installApplication('helm');
+ cluster.installApplication({ id: 'helm' });
expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING);
expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('helm');
+ expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined);
getSetTimeoutPromise()
.then(() => {
@@ -226,11 +226,11 @@ describe('Clusters', () => {
spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null);
- cluster.installApplication('ingress');
+ cluster.installApplication({ id: 'ingress' });
expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_LOADING);
expect(cluster.store.state.applications.ingress.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress');
+ expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined);
getSetTimeoutPromise()
.then(() => {
@@ -245,11 +245,11 @@ describe('Clusters', () => {
spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
expect(cluster.store.state.applications.runner.requestStatus).toEqual(null);
- cluster.installApplication('runner');
+ cluster.installApplication({ id: 'runner' });
expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_LOADING);
expect(cluster.store.state.applications.runner.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('runner');
+ expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined);
getSetTimeoutPromise()
.then(() => {
@@ -260,11 +260,29 @@ describe('Clusters', () => {
.catch(done.fail);
});
+ it('tries to install jupyter', (done) => {
+ spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
+ expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null);
+ cluster.installApplication({ id: 'jupyter', params: { hostname: cluster.store.state.applications.jupyter.hostname } });
+
+ expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_LOADING);
+ expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null);
+ expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', { hostname: cluster.store.state.applications.jupyter.hostname });
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUCCESS);
+ expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
it('sets error request status when the request fails', (done) => {
spyOn(cluster.service, 'installApplication').and.returnValue(Promise.reject(new Error('STUBBED ERROR')));
expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
- cluster.installApplication('helm');
+ cluster.installApplication({ id: 'helm' });
expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_LOADING);
expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js
index 2c4707bb856..c83cbe90a57 100644
--- a/spec/javascripts/clusters/components/application_row_spec.js
+++ b/spec/javascripts/clusters/components/application_row_spec.js
@@ -174,7 +174,27 @@ describe('Application Row', () => {
installButton.click();
- expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', DEFAULT_APPLICATION_STATE.id);
+ expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: {},
+ });
+ });
+
+ it('clicking install button when installApplicationRequestParams are provided emits event', () => {
+ spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_INSTALLABLE,
+ installApplicationRequestParams: { hostname: 'jupyter' },
+ });
+ const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
+
+ installButton.click();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: { hostname: 'jupyter' },
+ });
});
it('clicking disabled install button emits nothing', () => {
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index d546543d273..a70138c7eee 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -22,6 +22,7 @@ describe('Applications', () => {
ingress: { title: 'Ingress' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub' },
},
});
});
@@ -41,6 +42,10 @@ describe('Applications', () => {
it('renders a row for GitLab Runner', () => {
expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined();
});
+
+ it('renders a row for Jupyter', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBe(null);
+ });
});
describe('Ingress application', () => {
@@ -57,12 +62,11 @@ describe('Applications', () => {
helm: { title: 'Helm Tiller' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '' },
},
});
- expect(
- vm.$el.querySelector('.js-ip-address').value,
- ).toEqual('0.0.0.0');
+ expect(vm.$el.querySelector('.js-ip-address').value).toEqual('0.0.0.0');
expect(
vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
@@ -81,12 +85,11 @@ describe('Applications', () => {
helm: { title: 'Helm Tiller' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '' },
},
});
- expect(
- vm.$el.querySelector('.js-ip-address').value,
- ).toEqual('?');
+ expect(vm.$el.querySelector('.js-ip-address').value).toEqual('?');
expect(vm.$el.querySelector('.js-no-ip-message')).not.toBe(null);
});
@@ -101,6 +104,7 @@ describe('Applications', () => {
ingress: { title: 'Ingress' },
runner: { title: 'GitLab Runner' },
prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '' },
},
});
@@ -108,5 +112,83 @@ describe('Applications', () => {
expect(vm.$el.querySelector('.js-ip-address')).toBe(null);
});
});
+
+ describe('Jupyter application', () => {
+ describe('with ingress installed with ip & jupyter installable', () => {
+ it('renders hostname active input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller', status: 'installed' },
+ ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null);
+ });
+ });
+
+ describe('with ingress installed without external ip', () => {
+ it('does not render hostname input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller', status: 'installed' },
+ ingress: { title: 'Ingress', status: 'installed' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname')).toBe(null);
+ });
+ });
+
+ describe('with ingress & jupyter installed', () => {
+ it('renders readonly input', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller', status: 'installed' },
+ ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly');
+ });
+ });
+
+ describe('without ingress installed', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller' },
+ ingress: { title: 'Ingress' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', status: 'not_installable' },
+ },
+ });
+ });
+
+ it('does not render input', () => {
+ expect(vm.$el.querySelector('.js-hostname')).toBe(null);
+ });
+
+ it('renders disabled install button', () => {
+ expect(
+ vm.$el
+ .querySelector(
+ '.js-cluster-application-row-jupyter .js-cluster-application-install-button',
+ )
+ .getAttribute('disabled'),
+ ).toEqual('disabled');
+ });
+ });
+ });
});
});
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
index 6ae7a792329..b2b0ebf840b 100644
--- a/spec/javascripts/clusters/services/mock_data.js
+++ b/spec/javascripts/clusters/services/mock_data.js
@@ -1,4 +1,5 @@
import {
+ APPLICATION_INSTALLED,
APPLICATION_INSTALLABLE,
APPLICATION_INSTALLING,
APPLICATION_ERROR,
@@ -28,6 +29,39 @@ const CLUSTERS_MOCK_DATA = {
name: 'prometheus',
status: APPLICATION_ERROR,
status_reason: 'Cannot connect',
+ }, {
+ name: 'jupyter',
+ status: APPLICATION_INSTALLING,
+ status_reason: 'Cannot connect',
+ }],
+ },
+ },
+ '/gitlab-org/gitlab-shell/clusters/2/status.json': {
+ data: {
+ status: 'errored',
+ status_reason: 'Failed to request to CloudPlatform.',
+ applications: [{
+ name: 'helm',
+ status: APPLICATION_INSTALLED,
+ status_reason: null,
+ }, {
+ name: 'ingress',
+ status: APPLICATION_INSTALLED,
+ status_reason: 'Cannot connect',
+ external_ip: '1.1.1.1',
+ }, {
+ name: 'runner',
+ status: APPLICATION_INSTALLING,
+ status_reason: null,
+ },
+ {
+ name: 'prometheus',
+ status: APPLICATION_ERROR,
+ status_reason: 'Cannot connect',
+ }, {
+ name: 'jupyter',
+ status: APPLICATION_INSTALLABLE,
+ status_reason: 'Cannot connect',
}],
},
},
@@ -37,6 +71,7 @@ const CLUSTERS_MOCK_DATA = {
'/gitlab-org/gitlab-shell/clusters/1/applications/ingress': { },
'/gitlab-org/gitlab-shell/clusters/1/applications/runner': { },
'/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': { },
+ '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': { },
},
};
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 8028faf2f02..6854b016852 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -91,8 +91,26 @@ describe('Clusters Store', () => {
requestStatus: null,
requestReason: null,
},
+ jupyter: {
+ title: 'JupyterHub',
+ status: mockResponseData.applications[4].status,
+ statusReason: mockResponseData.applications[4].status_reason,
+ requestStatus: null,
+ requestReason: null,
+ hostname: '',
+ },
},
});
});
+
+ it('sets default hostname for jupyter when ingress has a ip address', () => {
+ const mockResponseData = CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
+
+ store.updateStateFromServer(mockResponseData);
+
+ expect(
+ store.state.applications.jupyter.hostname,
+ ).toEqual(`jupyter.${store.state.applications.ingress.externalIp}.xip.io`);
+ });
});
});
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
index 421fe62a1e7..d3776d0c3cf 100644
--- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js
+++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
@@ -75,10 +75,7 @@ describe('Commit pipeline status component', () => {
describe('When polling data was not succesful', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- mock.onGet('/dummy/endpoint').reply(() => {
- const res = Promise.reject([502, { }]);
- return res;
- });
+ mock.onGet('/dummy/endpoint').reply(502, {});
vm = new Component({
props: {
endpoint: '/dummy/endpoint',
diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js
index a34a1add4e0..5ba17ecf5b5 100644
--- a/spec/javascripts/helpers/vue_mount_component_helper.js
+++ b/spec/javascripts/helpers/vue_mount_component_helper.js
@@ -1,30 +1,18 @@
-import Vue from 'vue';
-
-const mountComponent = (Component, props = {}, el = null) => new Component({
- propsData: props,
-}).$mount(el);
-
-export const createComponentWithStore = (Component, store, propsData = {}) => new Component({
- store,
- propsData,
-});
+const mountComponent = (Component, props = {}, el = null) =>
+ new Component({
+ propsData: props,
+ }).$mount(el);
-export const createComponentWithMixin = (mixins = [], state = {}, props = {}, template = '<div></div>') => {
- const Component = Vue.extend({
- template,
- mixins,
- data() {
- return props;
- },
+export const createComponentWithStore = (Component, store, propsData = {}) =>
+ new Component({
+ store,
+ propsData,
});
- return mountComponent(Component, props);
-};
-
export const mountComponentWithStore = (Component, { el, props, store }) =>
new Component({
store,
- propsData: props || { },
+ propsData: props || {},
}).$mount(el);
export default mountComponent;
diff --git a/spec/javascripts/ide/components/external_link_spec.js b/spec/javascripts/ide/components/external_link_spec.js
new file mode 100644
index 00000000000..b3d94c041fa
--- /dev/null
+++ b/spec/javascripts/ide/components/external_link_spec.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import externalLink from '~/ide/components/external_link.vue';
+import createVueComponent from '../../helpers/vue_mount_component_helper';
+import { file } from '../helpers';
+
+describe('ExternalLink', () => {
+ const activeFile = file();
+ let vm;
+
+ function createComponent() {
+ const ExternalLink = Vue.extend(externalLink);
+
+ activeFile.permalink = 'test';
+
+ return createVueComponent(ExternalLink, {
+ file: activeFile,
+ });
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders the external link with the correct href', done => {
+ activeFile.binary = true;
+ vm = createComponent();
+
+ vm.$nextTick(() => {
+ const openLink = vm.$el.querySelector('a');
+
+ expect(openLink.href).toMatch(`/${activeFile.permalink}`);
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/ide_file_buttons_spec.js b/spec/javascripts/ide/components/ide_file_buttons_spec.js
deleted file mode 100644
index 8ac8d1b2acf..00000000000
--- a/spec/javascripts/ide/components/ide_file_buttons_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import Vue from 'vue';
-import repoFileButtons from '~/ide/components/ide_file_buttons.vue';
-import createVueComponent from '../../helpers/vue_mount_component_helper';
-import { file } from '../helpers';
-
-describe('RepoFileButtons', () => {
- const activeFile = file();
- let vm;
-
- function createComponent() {
- const RepoFileButtons = Vue.extend(repoFileButtons);
-
- activeFile.rawPath = 'test';
- activeFile.blamePath = 'test';
- activeFile.commitsPath = 'test';
-
- return createVueComponent(RepoFileButtons, {
- file: activeFile,
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders Raw, Blame, History and Permalink', done => {
- vm = createComponent();
-
- vm.$nextTick(() => {
- const raw = vm.$el.querySelector('.raw');
- const blame = vm.$el.querySelector('.blame');
- const history = vm.$el.querySelector('.history');
-
- expect(raw.href).toMatch(`/${activeFile.rawPath}`);
- expect(raw.getAttribute('data-original-title')).toEqual('Raw');
- expect(blame.href).toMatch(`/${activeFile.blamePath}`);
- expect(blame.getAttribute('data-original-title')).toEqual('Blame');
- expect(history.href).toMatch(`/${activeFile.commitsPath}`);
- expect(history.getAttribute('data-original-title')).toEqual('History');
- expect(vm.$el.querySelector('.permalink').getAttribute('data-original-title')).toEqual(
- 'Permalink',
- );
-
- done();
- });
- });
-
- it('renders Download', done => {
- activeFile.binary = true;
- vm = createComponent();
-
- vm.$nextTick(() => {
- const raw = vm.$el.querySelector('.raw');
-
- expect(raw.href).toMatch(`/${activeFile.rawPath}`);
- expect(raw.getAttribute('data-original-title')).toEqual('Download');
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/ide/components/jobs/item_spec.js b/spec/javascripts/ide/components/jobs/item_spec.js
new file mode 100644
index 00000000000..7c1dd4e475c
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/item_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import JobItem from '~/ide/components/jobs/item.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+import { jobs } from '../../mock_data';
+
+describe('IDE jobs item', () => {
+ const Component = Vue.extend(JobItem);
+ const job = jobs[0];
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ job,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders job details', () => {
+ expect(vm.$el.textContent).toContain(job.name);
+ expect(vm.$el.textContent).toContain(`#${job.id}`);
+ });
+
+ it('renders CI icon', () => {
+ expect(vm.$el.querySelector('.ic-status_passed_borderless')).not.toBe(null);
+ });
+});
diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js
new file mode 100644
index 00000000000..b24853c56fa
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/list_spec.js
@@ -0,0 +1,67 @@
+import Vue from 'vue';
+import StageList from '~/ide/components/jobs/list.vue';
+import { createStore } from '~/ide/stores';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { stages, jobs } from '../../mock_data';
+
+describe('IDE stages list', () => {
+ const Component = Vue.extend(StageList);
+ let vm;
+
+ beforeEach(() => {
+ const store = createStore();
+
+ vm = createComponentWithStore(Component, store, {
+ stages: stages.map((mappedState, i) => ({
+ ...mappedState,
+ id: i,
+ dropdownPath: mappedState.dropdown_path,
+ jobs: [...jobs],
+ isLoading: false,
+ isCollapsed: false,
+ })),
+ loading: false,
+ });
+
+ spyOn(vm, 'fetchJobs');
+ spyOn(vm, 'toggleStageCollapsed');
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders list of stages', () => {
+ expect(vm.$el.querySelectorAll('.card').length).toBe(2);
+ });
+
+ it('renders loading icon when no stages & is loading', done => {
+ vm.stages = [];
+ vm.loading = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('calls toggleStageCollapsed when clicking stage header', done => {
+ vm.$el.querySelector('.card-header').click();
+
+ vm.$nextTick(() => {
+ expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0);
+
+ done();
+ });
+ });
+
+ it('calls fetchJobs when stage is mounted', () => {
+ expect(vm.fetchJobs.calls.count()).toBe(stages.length);
+
+ expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]);
+ expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]);
+ });
+});
diff --git a/spec/javascripts/ide/components/jobs/stage_spec.js b/spec/javascripts/ide/components/jobs/stage_spec.js
new file mode 100644
index 00000000000..fc3831f2d05
--- /dev/null
+++ b/spec/javascripts/ide/components/jobs/stage_spec.js
@@ -0,0 +1,95 @@
+import Vue from 'vue';
+import Stage from '~/ide/components/jobs/stage.vue';
+import { stages, jobs } from '../../mock_data';
+
+describe('IDE pipeline stage', () => {
+ const Component = Vue.extend(Stage);
+ let vm;
+ let stage;
+
+ beforeEach(() => {
+ stage = {
+ ...stages[0],
+ id: 0,
+ dropdownPath: stages[0].dropdown_path,
+ jobs: [...jobs],
+ isLoading: false,
+ isCollapsed: false,
+ };
+
+ vm = new Component({
+ propsData: { stage },
+ });
+
+ spyOn(vm, '$emit');
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('emits fetch event when mounted', () => {
+ expect(vm.$emit).toHaveBeenCalledWith('fetch', vm.stage);
+ });
+
+ it('renders stages details', () => {
+ expect(vm.$el.textContent).toContain(vm.stage.name);
+ });
+
+ it('renders CI icon', () => {
+ expect(vm.$el.querySelector('.ic-status_failed')).not.toBe(null);
+ });
+
+ describe('collapsed', () => {
+ it('emits event when clicking header', done => {
+ vm.$el.querySelector('.card-header').click();
+
+ vm.$nextTick(() => {
+ expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed', vm.stage.id);
+
+ done();
+ });
+ });
+
+ it('toggles collapse status when collapsed', done => {
+ vm.stage.isCollapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.card-body').style.display).toBe('none');
+
+ done();
+ });
+ });
+
+ it('sets border bottom class when collapsed', done => {
+ vm.stage.isCollapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.card-header').classList).toContain('border-bottom-0');
+
+ done();
+ });
+ });
+ });
+
+ it('renders jobs count', () => {
+ expect(vm.$el.querySelector('.badge').textContent).toContain('4');
+ });
+
+ it('renders loading icon when no jobs and isLoading is true', done => {
+ vm.stage.isLoading = true;
+ vm.stage.jobs = [];
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('renders list of jobs', () => {
+ expect(vm.$el.querySelectorAll('.ide-job-item').length).toBe(4);
+ });
+});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
new file mode 100644
index 00000000000..2bb5aa08c3b
--- /dev/null
+++ b/spec/javascripts/ide/components/pipelines/list_spec.js
@@ -0,0 +1,117 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { createStore } from '~/ide/stores';
+import List from '~/ide/components/pipelines/list.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { pipelines, projectData, stages, jobs } from '../../mock_data';
+
+describe('IDE pipelines list', () => {
+ const Component = Vue.extend(List);
+ let vm;
+ let mock;
+
+ beforeEach(done => {
+ const store = createStore();
+
+ mock = new MockAdapter(axios);
+
+ store.state.currentProjectId = 'abc/def';
+ store.state.currentBranchId = 'master';
+ store.state.projects['abc/def'] = {
+ ...projectData,
+ path_with_namespace: 'abc/def',
+ branches: {
+ master: { commit: { id: '123' } },
+ },
+ };
+ store.state.links = { ciHelpPagePath: gl.TEST_HOST };
+ store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST;
+ store.state.pipelines.stages = stages.map((mappedState, i) => ({
+ ...mappedState,
+ id: i,
+ dropdownPath: mappedState.dropdown_path,
+ jobs: [...jobs],
+ isLoading: false,
+ isCollapsed: false,
+ }));
+
+ mock
+ .onGet('/abc/def/commit/123/pipelines')
+ .replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' });
+
+ vm = createComponentWithStore(Component, store).$mount();
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ vm.$store.dispatch('pipelines/stopPipelinePolling');
+ vm.$store.dispatch('pipelines/clearEtagPoll');
+
+ vm.$destroy();
+ mock.restore();
+ });
+
+ it('renders pipeline data', () => {
+ expect(vm.$el.textContent).toContain('#1');
+ });
+
+ it('renders CI icon', () => {
+ expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null);
+ });
+
+ it('renders list of jobs', () => {
+ expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe(
+ jobs.length * stages.length,
+ );
+ });
+
+ it('renders list of failed jobs on failed jobs tab', done => {
+ vm.$el.querySelectorAll('.tab-links a')[1].click();
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2);
+
+ done();
+ });
+ });
+
+ describe('YAML error', () => {
+ it('renders YAML error', done => {
+ vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:');
+ expect(vm.$el.textContent).toContain('test yaml error');
+
+ done();
+ });
+ });
+ });
+
+ describe('empty state', () => {
+ it('renders pipelines empty state', done => {
+ vm.$store.state.pipelines.latestPipeline = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.empty-state')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('loading state', () => {
+ it('renders loading state when there is no latest pipeline', done => {
+ vm.$store.state.pipelines.latestPipeline = null;
+ vm.$store.state.pipelines.isLoadingPipeline = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js
index bc9a7f97b37..9312e17704e 100644
--- a/spec/javascripts/ide/helpers.js
+++ b/spec/javascripts/ide/helpers.js
@@ -2,12 +2,14 @@ import { decorateData } from '~/ide/stores/utils';
import state from '~/ide/stores/state';
import commitState from '~/ide/stores/modules/commit/state';
import mergeRequestsState from '~/ide/stores/modules/merge_requests/state';
+import pipelinesState from '~/ide/stores/modules/pipelines/state';
export const resetStore = store => {
const newState = {
...state(),
commit: commitState(),
mergeRequests: mergeRequestsState(),
+ pipelines: pipelinesState(),
};
store.replaceState(newState);
};
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 8f24114de26..dcf857f7e04 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -19,13 +19,48 @@ export const pipelines = [
id: 1,
ref: 'master',
sha: '123',
- status: 'failed',
+ details: {
+ status: {
+ icon: 'status_failed',
+ group: 'failed',
+ text: 'Failed',
+ },
+ },
+ commit: { id: '123' },
},
{
id: 2,
ref: 'master',
sha: '213',
- status: 'success',
+ details: {
+ status: {
+ icon: 'status_failed',
+ group: 'failed',
+ text: 'Failed',
+ },
+ },
+ commit: { id: '213' },
+ },
+];
+
+export const stages = [
+ {
+ dropdown_path: `${gl.TEST_HOST}/testing`,
+ name: 'build',
+ status: {
+ icon: 'status_failed',
+ group: 'failed',
+ text: 'failed',
+ },
+ },
+ {
+ dropdown_path: 'testing',
+ name: 'test',
+ status: {
+ icon: 'status_failed',
+ group: 'failed',
+ text: 'failed',
+ },
},
];
@@ -33,28 +68,44 @@ export const jobs = [
{
id: 1,
name: 'test',
- status: 'failed',
+ path: 'testing',
+ status: {
+ icon: 'status_passed',
+ text: 'passed',
+ },
stage: 'test',
duration: 1,
},
{
id: 2,
name: 'test 2',
- status: 'failed',
+ path: 'testing2',
+ status: {
+ icon: 'status_passed',
+ text: 'passed',
+ },
stage: 'test',
duration: 1,
},
{
id: 3,
name: 'test 3',
- status: 'failed',
+ path: 'testing3',
+ status: {
+ icon: 'status_passed',
+ text: 'passed',
+ },
stage: 'test',
duration: 1,
},
{
id: 4,
- name: 'test 3',
- status: 'failed',
+ name: 'test 4',
+ path: 'testing4',
+ status: {
+ icon: 'status_failed',
+ text: 'failed',
+ },
stage: 'build',
duration: 1,
},
@@ -68,14 +119,16 @@ export const fullPipelinesResponse = {
pipelines: [
{
id: '51',
+ path: 'test',
commit: {
- id: 'xxxxxxxxxxxxxxxxxxxx',
+ id: '123',
},
details: {
status: {
icon: 'status_failed',
text: 'failed',
},
+ stages: [...stages],
},
},
{
@@ -88,6 +141,7 @@ export const fullPipelinesResponse = {
icon: 'status_passed',
text: 'passed',
},
+ stages: [...stages],
},
},
],
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 8e078ae7138..d71fc0e035e 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -1,31 +1,10 @@
-import Visibility from 'visibilityjs';
-import MockAdapter from 'axios-mock-adapter';
-import { refreshLastCommitData, pollSuccessCallBack } from '~/ide/stores/actions';
+import { refreshLastCommitData } from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
-import axios from '~/lib/utils/axios_utils';
-import { fullPipelinesResponse } from '../../mock_data';
import { resetStore } from '../../helpers';
import testAction from '../../../helpers/vuex_action_helper';
describe('IDE store project actions', () => {
- const setProjectState = () => {
- store.state.currentProjectId = 'abc/def';
- store.state.currentBranchId = 'master';
- store.state.projects['abc/def'] = {
- id: 4,
- path_with_namespace: 'abc/def',
- branches: {
- master: {
- commit: {
- id: 'abc123def456ghi789jkl',
- title: 'example',
- },
- },
- },
- };
- };
-
beforeEach(() => {
store.state.projects['abc/def'] = {};
});
@@ -101,92 +80,4 @@ describe('IDE store project actions', () => {
);
});
});
-
- describe('pipelinePoll', () => {
- let mock;
-
- beforeEach(() => {
- setProjectState();
- jasmine.clock().install();
- mock = new MockAdapter(axios);
- mock
- .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines')
- .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' });
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- mock.restore();
- store.dispatch('stopPipelinePolling');
- });
-
- it('calls service periodically', done => {
- spyOn(axios, 'get').and.callThrough();
- spyOn(Visibility, 'hidden').and.returnValue(false);
-
- store
- .dispatch('pipelinePoll')
- .then(() => {
- jasmine.clock().tick(1000);
-
- expect(axios.get).toHaveBeenCalled();
- expect(axios.get.calls.count()).toBe(1);
- })
- .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
- .then(() => {
- jasmine.clock().tick(10000);
- expect(axios.get.calls.count()).toBe(2);
- })
- .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
- .then(() => {
- jasmine.clock().tick(10000);
- expect(axios.get.calls.count()).toBe(3);
- })
- .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
- .then(() => {
- jasmine.clock().tick(10000);
- expect(axios.get.calls.count()).toBe(4);
- })
-
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('pollSuccessCallBack', () => {
- beforeEach(() => {
- setProjectState();
- });
-
- it('commits correct pipeline', done => {
- testAction(
- pollSuccessCallBack,
- fullPipelinesResponse,
- store.state,
- [
- {
- type: 'SET_LAST_COMMIT_PIPELINE',
- payload: {
- projectId: 'abc/def',
- branchId: 'master',
- pipeline: {
- id: '50',
- commit: {
- id: 'abc123def456ghi789jkl',
- },
- details: {
- status: {
- icon: 'status_passed',
- text: 'passed',
- },
- },
- },
- },
- },
- ], // mutations
- [], // action
- done,
- );
- });
- });
});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
index 85fbcf8084b..f26eaf9c81f 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -1,3 +1,4 @@
+import Visibility from 'visibilityjs';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import actions, {
@@ -5,10 +6,13 @@ import actions, {
receiveLatestPipelineError,
receiveLatestPipelineSuccess,
fetchLatestPipeline,
+ stopPipelinePolling,
+ clearEtagPoll,
requestJobs,
receiveJobsError,
receiveJobsSuccess,
fetchJobs,
+ toggleStageCollapsed,
} from '~/ide/stores/modules/pipelines/actions';
import state from '~/ide/stores/modules/pipelines/state';
import * as types from '~/ide/stores/modules/pipelines/mutation_types';
@@ -51,7 +55,7 @@ describe('IDE pipelines actions', () => {
null,
mockedState,
[{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }],
- [],
+ [{ type: 'stopPipelinePolling' }],
done,
);
});
@@ -59,91 +63,128 @@ describe('IDE pipelines actions', () => {
it('creates flash message', () => {
const flashSpy = spyOnDependency(actions, 'flash');
- receiveLatestPipelineError({ commit() {} });
+ receiveLatestPipelineError({ commit() {}, dispatch() {} });
expect(flashSpy).toHaveBeenCalled();
});
});
describe('receiveLatestPipelineSuccess', () => {
- it('commits pipeline', done => {
- testAction(
- receiveLatestPipelineSuccess,
+ const rootGetters = {
+ lastCommit: { id: '123' },
+ };
+ let commit;
+
+ beforeEach(() => {
+ commit = jasmine.createSpy('commit');
+ });
+
+ it('commits pipeline', () => {
+ receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines });
+
+ expect(commit.calls.argsFor(0)).toEqual([
+ types.RECEIVE_LASTEST_PIPELINE_SUCCESS,
pipelines[0],
- mockedState,
- [{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }],
- [],
- done,
- );
+ ]);
+ });
+
+ it('commits false when there are no pipelines', () => {
+ receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines: [] });
+
+ expect(commit.calls.argsFor(0)).toEqual([types.RECEIVE_LASTEST_PIPELINE_SUCCESS, false]);
});
});
describe('fetchLatestPipeline', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ stopPipelinePolling();
+ clearEtagPoll();
+ });
+
describe('success', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(200, pipelines);
+ mock
+ .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines')
+ .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' });
});
it('dispatches request', done => {
- testAction(
- fetchLatestPipeline,
- '123',
- mockedState,
- [],
- [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineSuccess' }],
- done,
- );
- });
-
- it('dispatches success with latest pipeline', done => {
- testAction(
- fetchLatestPipeline,
- '123',
- mockedState,
- [],
- [
- { type: 'requestLatestPipeline' },
- { type: 'receiveLatestPipelineSuccess', payload: pipelines[0] },
- ],
- done,
- );
- });
-
- it('calls axios with correct params', () => {
- const apiSpy = spyOn(axios, 'get').and.callThrough();
-
- fetchLatestPipeline({ dispatch() {}, rootState: state }, '123');
-
- expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
- params: {
- sha: '123',
- per_page: '1',
- },
- });
+ spyOn(axios, 'get').and.callThrough();
+ spyOn(Visibility, 'hidden').and.returnValue(false);
+
+ const dispatch = jasmine.createSpy('dispatch');
+ const rootGetters = {
+ lastCommit: { id: 'abc123def456ghi789jkl' },
+ currentProject: { path_with_namespace: 'abc/def' },
+ };
+
+ fetchLatestPipeline({ dispatch, rootGetters });
+
+ expect(dispatch.calls.argsFor(0)).toEqual(['requestLatestPipeline']);
+
+ jasmine.clock().tick(1000);
+
+ new Promise(resolve => requestAnimationFrame(resolve))
+ .then(() => {
+ expect(axios.get).toHaveBeenCalled();
+ expect(axios.get.calls.count()).toBe(1);
+
+ expect(dispatch.calls.argsFor(1)).toEqual([
+ 'receiveLatestPipelineSuccess',
+ jasmine.anything(),
+ ]);
+
+ jasmine.clock().tick(10000);
+ })
+ .then(() => new Promise(resolve => requestAnimationFrame(resolve)))
+ .then(() => {
+ expect(axios.get).toHaveBeenCalled();
+ expect(axios.get.calls.count()).toBe(2);
+
+ expect(dispatch.calls.argsFor(2)).toEqual([
+ 'receiveLatestPipelineSuccess',
+ jasmine.anything(),
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('error', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500);
+ mock.onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines').reply(500);
});
it('dispatches error', done => {
- testAction(
- fetchLatestPipeline,
- '123',
- mockedState,
- [],
- [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineError' }],
- done,
- );
+ const dispatch = jasmine.createSpy('dispatch');
+ const rootGetters = {
+ lastCommit: { id: 'abc123def456ghi789jkl' },
+ currentProject: { path_with_namespace: 'abc/def' },
+ };
+
+ fetchLatestPipeline({ dispatch, rootGetters });
+
+ jasmine.clock().tick(1500);
+
+ new Promise(resolve => requestAnimationFrame(resolve))
+ .then(() => {
+ expect(dispatch.calls.argsFor(1)).toEqual(['receiveLatestPipelineError']);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('requestJobs', () => {
it('commits request', done => {
- testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done);
+ testAction(requestJobs, 1, mockedState, [{ type: types.REQUEST_JOBS, payload: 1 }], [], done);
});
});
@@ -151,9 +192,9 @@ describe('IDE pipelines actions', () => {
it('commits error', done => {
testAction(
receiveJobsError,
- null,
+ 1,
mockedState,
- [{ type: types.RECEIVE_JOBS_ERROR }],
+ [{ type: types.RECEIVE_JOBS_ERROR, payload: 1 }],
[],
done,
);
@@ -162,19 +203,19 @@ describe('IDE pipelines actions', () => {
it('creates flash message', () => {
const flashSpy = spyOnDependency(actions, 'flash');
- receiveJobsError({ commit() {} });
+ receiveJobsError({ commit() {} }, 1);
expect(flashSpy).toHaveBeenCalled();
});
});
describe('receiveJobsSuccess', () => {
- it('commits jobs', done => {
+ it('commits data', done => {
testAction(
receiveJobsSuccess,
- jobs,
+ { id: 1, data: jobs },
mockedState,
- [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }],
+ [{ type: types.RECEIVE_JOBS_SUCCESS, payload: { id: 1, data: jobs } }],
[],
done,
);
@@ -182,108 +223,62 @@ describe('IDE pipelines actions', () => {
});
describe('fetchJobs', () => {
- let page = '';
-
- beforeEach(() => {
- mockedState.latestPipeline = pipelines[0];
- });
+ const stage = {
+ id: 1,
+ dropdownPath: `${gl.TEST_HOST}/jobs`,
+ };
describe('success', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [
- 200,
- jobs,
- {
- 'x-next-page': page,
- },
- ]);
+ mock.onGet(stage.dropdownPath).replyOnce(200, jobs);
});
it('dispatches request', done => {
testAction(
fetchJobs,
- null,
- mockedState,
- [],
- [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess' }],
- done,
- );
- });
-
- it('dispatches success with latest pipeline', done => {
- testAction(
- fetchJobs,
- null,
- mockedState,
- [],
- [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess', payload: jobs }],
- done,
- );
- });
-
- it('dispatches twice for both pages', done => {
- page = '2';
-
- testAction(
- fetchJobs,
- null,
+ stage,
mockedState,
[],
[
- { type: 'requestJobs' },
- { type: 'receiveJobsSuccess', payload: jobs },
- { type: 'fetchJobs', payload: '2' },
- { type: 'requestJobs' },
- { type: 'receiveJobsSuccess', payload: jobs },
+ { type: 'requestJobs', payload: stage.id },
+ { type: 'receiveJobsSuccess', payload: { id: stage.id, data: jobs } },
],
done,
);
});
-
- it('calls axios with correct URL', () => {
- const apiSpy = spyOn(axios, 'get').and.callThrough();
-
- fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState });
-
- expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', {
- params: { page: '1' },
- });
- });
-
- it('calls axios with page next page', () => {
- const apiSpy = spyOn(axios, 'get').and.callThrough();
-
- fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState });
-
- expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', {
- params: { page: '1' },
- });
-
- page = '2';
-
- fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }, page);
-
- expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', {
- params: { page: '2' },
- });
- });
});
describe('error', () => {
beforeEach(() => {
- mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500);
+ mock.onGet(stage.dropdownPath).replyOnce(500);
});
it('dispatches error', done => {
testAction(
fetchJobs,
- null,
+ stage,
mockedState,
[],
- [{ type: 'requestJobs' }, { type: 'receiveJobsError' }],
+ [
+ { type: 'requestJobs', payload: stage.id },
+ { type: 'receiveJobsError', payload: stage.id },
+ ],
done,
);
});
});
});
+
+ describe('toggleStageCollapsed', () => {
+ it('commits collapse', done => {
+ testAction(
+ toggleStageCollapsed,
+ 1,
+ mockedState,
+ [{ type: types.TOGGLE_STAGE_COLLAPSE, payload: 1 }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
index b2a7e8a9025..4514896b5ea 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
@@ -37,35 +37,4 @@ describe('IDE pipeline getters', () => {
expect(getters.hasLatestPipeline(mockedState)).toBe(true);
});
});
-
- describe('failedJobs', () => {
- it('returns array of failed jobs', () => {
- mockedState.stages = [
- {
- title: 'test',
- jobs: [{ id: 1, status: 'failed' }, { id: 2, status: 'success' }],
- },
- {
- title: 'build',
- jobs: [{ id: 3, status: 'failed' }, { id: 4, status: 'failed' }],
- },
- ];
-
- expect(getters.failedJobs(mockedState).length).toBe(3);
- expect(getters.failedJobs(mockedState)).toEqual([
- {
- id: 1,
- status: jasmine.anything(),
- },
- {
- id: 3,
- status: jasmine.anything(),
- },
- {
- id: 4,
- status: jasmine.anything(),
- },
- ]);
- });
- });
});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
index 8262e916243..6285c01d483 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
@@ -1,7 +1,7 @@
import mutations from '~/ide/stores/modules/pipelines/mutations';
import state from '~/ide/stores/modules/pipelines/state';
import * as types from '~/ide/stores/modules/pipelines/mutation_types';
-import { pipelines, jobs } from '../../../mock_data';
+import { fullPipelinesResponse, stages, jobs } from '../../../mock_data';
describe('IDE pipelines mutations', () => {
let mockedState;
@@ -28,93 +28,147 @@ describe('IDE pipelines mutations', () => {
describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => {
it('sets loading to false on success', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]);
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
+ mockedState,
+ fullPipelinesResponse.data.pipelines[0],
+ );
expect(mockedState.isLoadingPipeline).toBe(false);
});
it('sets latestPipeline', () => {
- mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]);
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
+ mockedState,
+ fullPipelinesResponse.data.pipelines[0],
+ );
expect(mockedState.latestPipeline).toEqual({
- id: pipelines[0].id,
- status: pipelines[0].status,
+ id: '51',
+ path: 'test',
+ commit: { id: '123' },
+ details: { status: jasmine.any(Object) },
+ yamlError: undefined,
});
});
it('does not set latest pipeline if pipeline is null', () => {
mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null);
- expect(mockedState.latestPipeline).toEqual(null);
+ expect(mockedState.latestPipeline).toEqual(false);
+ });
+
+ it('sets stages', () => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](
+ mockedState,
+ fullPipelinesResponse.data.pipelines[0],
+ );
+
+ expect(mockedState.stages.length).toBe(2);
+ expect(mockedState.stages).toEqual([
+ {
+ id: 0,
+ dropdownPath: stages[0].dropdown_path,
+ name: stages[0].name,
+ status: stages[0].status,
+ isCollapsed: false,
+ isLoading: false,
+ jobs: [],
+ },
+ {
+ id: 1,
+ dropdownPath: stages[1].dropdown_path,
+ name: stages[1].name,
+ status: stages[1].status,
+ isCollapsed: false,
+ isLoading: false,
+ jobs: [],
+ },
+ ]);
});
});
describe(types.REQUEST_JOBS, () => {
- it('sets jobs loading to true', () => {
- mutations[types.REQUEST_JOBS](mockedState);
+ beforeEach(() => {
+ mockedState.stages = stages.map((stage, i) => ({
+ ...stage,
+ id: i,
+ }));
+ });
+
+ it('sets isLoading on stage', () => {
+ mutations[types.REQUEST_JOBS](mockedState, mockedState.stages[0].id);
- expect(mockedState.isLoadingJobs).toBe(true);
+ expect(mockedState.stages[0].isLoading).toBe(true);
});
});
describe(types.RECEIVE_JOBS_ERROR, () => {
- it('sets jobs loading to false', () => {
- mutations[types.RECEIVE_JOBS_ERROR](mockedState);
+ beforeEach(() => {
+ mockedState.stages = stages.map((stage, i) => ({
+ ...stage,
+ id: i,
+ }));
+ });
+
+ it('sets isLoading on stage after error', () => {
+ mutations[types.RECEIVE_JOBS_ERROR](mockedState, mockedState.stages[0].id);
- expect(mockedState.isLoadingJobs).toBe(false);
+ expect(mockedState.stages[0].isLoading).toBe(false);
});
});
describe(types.RECEIVE_JOBS_SUCCESS, () => {
- it('sets jobs loading to false on success', () => {
- mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+ let data;
- expect(mockedState.isLoadingJobs).toBe(false);
+ beforeEach(() => {
+ mockedState.stages = stages.map((stage, i) => ({
+ ...stage,
+ id: i,
+ }));
+
+ data = {
+ latest_statuses: [...jobs],
+ };
});
- it('sets stages', () => {
- mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+ it('updates loading', () => {
+ mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, { id: mockedState.stages[0].id, data });
- expect(mockedState.stages.length).toBe(2);
- expect(mockedState.stages).toEqual([
- {
- title: 'test',
- jobs: jasmine.anything(),
- },
- {
- title: 'build',
- jobs: jasmine.anything(),
- },
- ]);
+ expect(mockedState.stages[0].isLoading).toBe(false);
});
- it('sets jobs in stages', () => {
- mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+ it('sets jobs on stage', () => {
+ mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, { id: mockedState.stages[0].id, data });
+
+ expect(mockedState.stages[0].jobs.length).toBe(jobs.length);
+ expect(mockedState.stages[0].jobs).toEqual(
+ jobs.map(job => ({
+ id: job.id,
+ name: job.name,
+ status: job.status,
+ path: job.build_path,
+ })),
+ );
+ });
+ });
- expect(mockedState.stages[0].jobs.length).toBe(3);
- expect(mockedState.stages[1].jobs.length).toBe(1);
- expect(mockedState.stages).toEqual([
- {
- title: jasmine.anything(),
- jobs: jobs.filter(job => job.stage === 'test').map(job => ({
- id: job.id,
- name: job.name,
- status: job.status,
- stage: job.stage,
- duration: job.duration,
- })),
- },
- {
- title: jasmine.anything(),
- jobs: jobs.filter(job => job.stage === 'build').map(job => ({
- id: job.id,
- name: job.name,
- status: job.status,
- stage: job.stage,
- duration: job.duration,
- })),
- },
- ]);
+ describe(types.TOGGLE_STAGE_COLLAPSE, () => {
+ beforeEach(() => {
+ mockedState.stages = stages.map((stage, i) => ({
+ ...stage,
+ id: i,
+ isCollapsed: false,
+ }));
+ });
+
+ it('toggles collapsed state', () => {
+ mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id);
+
+ expect(mockedState.stages[0].isCollapsed).toBe(true);
+
+ mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id);
+
+ expect(mockedState.stages[0].isCollapsed).toBe(false);
});
});
});
diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js
index f2f1f2a9a2e..29eb859ddaf 100644
--- a/spec/javascripts/ide/stores/mutations/branch_spec.js
+++ b/spec/javascripts/ide/stores/mutations/branch_spec.js
@@ -37,40 +37,4 @@ describe('Multi-file store branch mutations', () => {
expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
});
});
-
- describe('SET_LAST_COMMIT_PIPELINE', () => {
- it('sets the pipeline for the last commit on current project', () => {
- localState.projects = {
- Example: {
- branches: {
- master: {
- commit: {},
- },
- },
- },
- };
-
- mutations.SET_LAST_COMMIT_PIPELINE(localState, {
- projectId: 'Example',
- branchId: 'master',
- pipeline: {
- id: '50',
- details: {
- status: {
- icon: 'status_passed',
- text: 'passed',
- },
- },
- },
- });
-
- expect(localState.projects.Example.branches.master.commit.pipeline.id).toBe('50');
- expect(localState.projects.Example.branches.master.commit.pipeline.details.status.text).toBe(
- 'passed',
- );
- expect(localState.projects.Example.branches.master.commit.pipeline.details.status.icon).toBe(
- 'status_passed',
- );
- });
- });
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 0c173062835..6110d5d89ac 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -8,10 +8,7 @@ describe('Confidential Issue Sidebar Block', () => {
beforeEach(() => {
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
- update: () => new Promise((resolve, reject) => {
- resolve(true);
- reject('failed!');
- }),
+ update: () => Promise.resolve(true),
};
vm1 = new Component({
diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js
index 5a1ace2b4d6..8fec6ae3fa4 100644
--- a/spec/javascripts/u2f/mock_u2f_device.js
+++ b/spec/javascripts/u2f/mock_u2f_device.js
@@ -1,5 +1,5 @@
/* eslint-disable prefer-rest-params, wrap-iife,
-no-unused-expressions, no-return-assign, no-param-reassign*/
+no-unused-expressions, no-return-assign, no-param-reassign */
export default class MockU2FDevice {
constructor() {
diff --git a/spec/javascripts/vue_shared/components/tabs/tab_spec.js b/spec/javascripts/vue_shared/components/tabs/tab_spec.js
new file mode 100644
index 00000000000..8437fe37738
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/tabs/tab_spec.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import Tab from '~/vue_shared/components/tabs/tab.vue';
+
+describe('Tab component', () => {
+ const Component = Vue.extend(Tab);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component);
+ });
+
+ it('sets localActive to equal active', done => {
+ vm.active = true;
+
+ vm.$nextTick(() => {
+ expect(vm.localActive).toBe(true);
+
+ done();
+ });
+ });
+
+ it('sets active class', done => {
+ vm.active = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.classList).toContain('active');
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js
new file mode 100644
index 00000000000..50ba18cd338
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import Tabs from '~/vue_shared/components/tabs/tabs';
+import Tab from '~/vue_shared/components/tabs/tab.vue';
+
+describe('Tabs component', () => {
+ let vm;
+
+ beforeEach(done => {
+ vm = new Vue({
+ components: {
+ Tabs,
+ Tab,
+ },
+ template: `
+ <div>
+ <tabs>
+ <tab title="Testing" active>
+ First tab
+ </tab>
+ <tab>
+ <template slot="title">Test slot</template>
+ Second tab
+ </tab>
+ </tabs>
+ </div>
+ `,
+ }).$mount();
+
+ setTimeout(done);
+ });
+
+ describe('tab links', () => {
+ it('renders links for tabs', () => {
+ expect(vm.$el.querySelectorAll('a').length).toBe(2);
+ });
+
+ it('renders link titles from props', () => {
+ expect(vm.$el.querySelector('a').textContent).toContain('Testing');
+ });
+
+ it('renders link titles from slot', () => {
+ expect(vm.$el.querySelectorAll('a')[1].textContent).toContain('Test slot');
+ });
+
+ it('renders active class', () => {
+ expect(vm.$el.querySelector('a').classList).toContain('active');
+ });
+
+ it('updates active class on click', done => {
+ vm.$el.querySelectorAll('a')[1].click();
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('a').classList).not.toContain('active');
+ expect(vm.$el.querySelectorAll('a')[1].classList).toContain('active');
+
+ done();
+ });
+ });
+ });
+
+ describe('content', () => {
+ it('renders content panes', () => {
+ expect(vm.$el.querySelectorAll('.tab-pane').length).toBe(2);
+ expect(vm.$el.querySelectorAll('.tab-pane')[0].textContent).toContain('First tab');
+ expect(vm.$el.querySelectorAll('.tab-pane')[1].textContent).toContain('Second tab');
+ });
+ });
+});