summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/api_spec.js21
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_settings_spec.js4
-rw-r--r--spec/javascripts/behaviors/secret_values_spec.js2
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js6
-rw-r--r--spec/javascripts/blob/sketch/index_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js4
-rw-r--r--spec/javascripts/boards/issue_card_spec.js40
-rw-r--r--spec/javascripts/environments/environments_app_spec.js212
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js81
-rw-r--r--spec/javascripts/environments/mock_data.js1
-rw-r--r--spec/javascripts/fixtures/event_filter.html.haml2
-rw-r--r--spec/javascripts/fixtures/issue_sidebar_label.html.haml2
-rw-r--r--spec/javascripts/fixtures/linked_tabs.html.haml10
-rw-r--r--spec/javascripts/gl_dropdown_spec.js12
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js2
-rw-r--r--spec/javascripts/ide/components/ide_spec.js6
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js20
-rw-r--r--spec/javascripts/ide/mock_data.js81
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js159
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js289
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/getters_spec.js71
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js120
-rw-r--r--spec/javascripts/ide/stores/mutations/branch_spec.js36
-rw-r--r--spec/javascripts/notes_spec.js4
-rw-r--r--spec/javascripts/pipelines/empty_state_spec.js2
-rw-r--r--spec/javascripts/pipelines/mock_data.js2
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js11
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js103
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js92
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js88
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/helpers.js49
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js75
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js131
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js65
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js87
-rw-r--r--spec/javascripts/projects_dropdown/components/search_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js69
-rw-r--r--spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js (renamed from spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js)8
-rw-r--r--spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js52
-rw-r--r--spec/javascripts/vue_shared/components/dropdown/mock_data.js11
43 files changed, 1774 insertions, 271 deletions
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 3d7ccf432be..e8435116221 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -341,4 +341,25 @@ describe('Api', () => {
.catch(done.fail);
});
});
+
+ describe('commitPipelines', () => {
+ it('fetches pipelines for a given commit', done => {
+ const projectId = 'example/foobar';
+ const commitSha = 'abc123def';
+ const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.commitPipelines(projectId, commitSha)
+ .then(({ data }) => {
+ expect(data.length).toBe(1);
+ expect(data[0].name).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 9439c578973..02e59ae0843 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -33,7 +33,7 @@ describe('BadgeList component', () => {
});
it('renders a header with the badge count', () => {
- const header = vm.$el.querySelector('.panel-heading');
+ const header = vm.$el.querySelector('.card-header');
expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`));
});
diff --git a/spec/javascripts/badges/components/badge_settings_spec.js b/spec/javascripts/badges/components/badge_settings_spec.js
index 3db02982ad4..59367c85125 100644
--- a/spec/javascripts/badges/components/badge_settings_spec.js
+++ b/spec/javascripts/badges/components/badge_settings_spec.js
@@ -60,7 +60,7 @@ describe('BadgeSettings component', () => {
});
it('displays badge list', () => {
- const badgeListElement = vm.$el.querySelector('.panel');
+ const badgeListElement = vm.$el.querySelector('.card');
expect(badgeListElement).not.toBe(null);
expect(badgeListElement).toBeVisible();
expect(badgeListElement).toContainText('Your badges');
@@ -87,7 +87,7 @@ describe('BadgeSettings component', () => {
});
it('displays no badge list', () => {
- const badgeListElement = vm.$el.querySelector('.panel');
+ const badgeListElement = vm.$el.querySelector('.card');
expect(badgeListElement).toBeHidden();
});
});
diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js
index 38d9bba6868..95122fcf30f 100644
--- a/spec/javascripts/behaviors/secret_values_spec.js
+++ b/spec/javascripts/behaviors/secret_values_spec.js
@@ -9,7 +9,7 @@ function generateValueMarkup(
<div class="${placeholderClass}">
***
</div>
- <div class="hide ${valueClass}">
+ <div class="hidden ${valueClass}">
${secret}
</div>
`;
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
index aa87956109f..cb0f2ba686d 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
@@ -257,9 +257,9 @@ describe('BalsamiqViewer', () => {
name = 'name';
resource = 'resource';
template = `
- <div class="panel panel-default">
- <div class="panel-heading">name</div>
- <div class="panel-body">
+ <div class="card">
+ <div class="card-header">name</div>
+ <div class="card-body">
<img class="img-thumbnail" src="data:image/png;base64,image"/>
</div>
</div>
diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js
index 79f40559817..e062a068a92 100644
--- a/spec/javascripts/blob/sketch/index_spec.js
+++ b/spec/javascripts/blob/sketch/index_spec.js
@@ -82,7 +82,7 @@ describe('Sketch viewer', () => {
const img = document.querySelector('#js-sketch-viewer img');
expect(img).not.toBeNull();
- expect(img.classList.contains('img-responsive')).toBeTruthy();
+ expect(img.classList.contains('img-fluid')).toBeTruthy();
});
it('renders link to image', () => {
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 03df6c06691..c06b2f60813 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -83,13 +83,13 @@ describe('Board list component', () => {
it('renders issues', () => {
expect(
- component.$el.querySelectorAll('.card').length,
+ component.$el.querySelectorAll('.board-card').length,
).toBe(1);
});
it('sets data attribute with issue id', () => {
expect(
- component.$el.querySelector('.card').getAttribute('data-issue-id'),
+ component.$el.querySelector('.board-card').getAttribute('data-issue-id'),
).toBe('1');
});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index be1ea0b57b4..abeef272c68 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -70,19 +70,19 @@ describe('Issue card component', () => {
it('renders issue title', () => {
expect(
- component.$el.querySelector('.card-title').textContent,
+ component.$el.querySelector('.board-card-title').textContent,
).toContain(issue.title);
});
it('includes issue base in link', () => {
expect(
- component.$el.querySelector('.card-title a').getAttribute('href'),
+ component.$el.querySelector('.board-card-title a').getAttribute('href'),
).toContain('/test');
});
it('includes issue title on link', () => {
expect(
- component.$el.querySelector('.card-title a').getAttribute('title'),
+ component.$el.querySelector('.board-card-title a').getAttribute('title'),
).toBe(issue.title);
});
@@ -105,14 +105,14 @@ describe('Issue card component', () => {
it('renders issue ID with #', () => {
expect(
- component.$el.querySelector('.card-number').textContent,
+ component.$el.querySelector('.board-card-number').textContent,
).toContain(`#${issue.id}`);
});
describe('assignee', () => {
it('does not render assignee', () => {
expect(
- component.$el.querySelector('.card-assignee .avatar'),
+ component.$el.querySelector('.board-card-assignee .avatar'),
).toBeNull();
});
@@ -125,25 +125,25 @@ describe('Issue card component', () => {
it('renders assignee', () => {
expect(
- component.$el.querySelector('.card-assignee .avatar'),
+ component.$el.querySelector('.board-card-assignee .avatar'),
).not.toBeNull();
});
it('sets title', () => {
expect(
- component.$el.querySelector('.card-assignee img').getAttribute('data-original-title'),
+ component.$el.querySelector('.board-card-assignee img').getAttribute('data-original-title'),
).toContain(`Assigned to ${user.name}`);
});
it('sets users path', () => {
expect(
- component.$el.querySelector('.card-assignee a').getAttribute('href'),
+ component.$el.querySelector('.board-card-assignee a').getAttribute('href'),
).toBe('/test');
});
it('renders avatar', () => {
expect(
- component.$el.querySelector('.card-assignee img'),
+ component.$el.querySelector('.board-card-assignee img'),
).not.toBeNull();
});
});
@@ -161,10 +161,10 @@ describe('Issue card component', () => {
it('displays defaults avatar if users avatar is null', () => {
expect(
- component.$el.querySelector('.card-assignee img'),
+ component.$el.querySelector('.board-card-assignee img'),
).not.toBeNull();
expect(
- component.$el.querySelector('.card-assignee img').getAttribute('src'),
+ component.$el.querySelector('.board-card-assignee img').getAttribute('src'),
).toBe('default_avatar');
});
});
@@ -197,7 +197,7 @@ describe('Issue card component', () => {
});
it('renders all four assignees', () => {
- expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(4);
+ expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(4);
});
describe('more than four assignees', () => {
@@ -213,11 +213,11 @@ describe('Issue card component', () => {
});
it('renders more avatar counter', () => {
- expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('+2');
+ expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('+2');
});
it('renders three assignees', () => {
- expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(3);
+ expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(3);
});
it('renders 99+ avatar counter', (done) => {
@@ -232,7 +232,7 @@ describe('Issue card component', () => {
}
Vue.nextTick(() => {
- expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('99+');
+ expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('99+');
done();
});
});
@@ -248,13 +248,13 @@ describe('Issue card component', () => {
it('renders list label', () => {
expect(
- component.$el.querySelectorAll('.label').length,
+ component.$el.querySelectorAll('.badge').length,
).toBe(2);
});
it('renders label', () => {
const nodes = [];
- component.$el.querySelectorAll('.label').forEach((label) => {
+ component.$el.querySelectorAll('.badge').forEach((label) => {
nodes.push(label.title);
});
@@ -265,13 +265,13 @@ describe('Issue card component', () => {
it('sets label description as title', () => {
expect(
- component.$el.querySelector('.label').getAttribute('title'),
+ component.$el.querySelector('.badge').getAttribute('title'),
).toContain(label1.description);
});
it('sets background color of button', () => {
const nodes = [];
- component.$el.querySelectorAll('.label').forEach((label) => {
+ component.$el.querySelectorAll('.badge').forEach((label) => {
nodes.push(label.style.backgroundColor);
});
@@ -288,7 +288,7 @@ describe('Issue card component', () => {
Vue.nextTick()
.then(() => {
expect(
- component.$el.querySelectorAll('.label').length,
+ component.$el.querySelectorAll('.badge').length,
).toBe(2);
expect(
component.$el.textContent,
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index e4c3bf2bef1..615168645b4 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -1,8 +1,8 @@
-import _ from 'underscore';
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import environmentsComponent from '~/environments/components/environments_app.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import { environment, folder } from './mock_data';
describe('Environment', () => {
@@ -18,103 +18,76 @@ describe('Environment', () => {
let EnvironmentsComponent;
let component;
+ let mock;
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
EnvironmentsComponent = Vue.extend(environmentsComponent);
});
+ afterEach(() => {
+ component.$destroy();
+ mock.restore();
+ });
+
describe('successfull request', () => {
describe('without environments', () => {
- const environmentsEmptyResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 200,
- }));
- };
-
- beforeEach(() => {
- Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
- Vue.http.interceptors.push(headersInterceptor);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsEmptyResponseInterceptor,
- );
- Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
- });
+ beforeEach((done) => {
+ mock.onGet(mockData.endpoint).reply(200, { environments: [] });
- it('should render the empty state', (done) => {
component = mountComponent(EnvironmentsComponent, mockData);
setTimeout(() => {
- expect(
- component.$el.querySelector('.js-new-environment-button').textContent,
- ).toContain('New environment');
-
- expect(
- component.$el.querySelector('.js-blank-state-title').textContent,
- ).toContain('You don\'t have any environments right now.');
-
done();
}, 0);
});
+
+ it('should render the empty state', () => {
+ expect(
+ component.$el.querySelector('.js-new-environment-button').textContent,
+ ).toContain('New environment');
+
+ expect(
+ component.$el.querySelector('.js-blank-state-title').textContent,
+ ).toContain('You don\'t have any environments right now.');
+ });
});
describe('with paginated environments', () => {
- let backupInterceptors;
- const environmentsResponseInterceptor = (request, next) => {
- next((response) => {
- response.headers.set('X-nExt-pAge', '2');
- });
-
- next(request.respondWith(JSON.stringify({
+ beforeEach((done) => {
+ mock.onGet(mockData.endpoint).reply(200, {
environments: [environment],
stopped_count: 1,
available_count: 0,
- }), {
- status: 200,
- headers: {
- 'X-nExt-pAge': '2',
- 'x-page': '1',
- 'X-Per-Page': '1',
- 'X-Prev-Page': '',
- 'X-TOTAL': '37',
- 'X-Total-Pages': '2',
- },
- }));
- };
-
- beforeEach(() => {
- backupInterceptors = Vue.http.interceptors;
- Vue.http.interceptors = [
- environmentsResponseInterceptor,
- headersInterceptor,
- ];
- component = mountComponent(EnvironmentsComponent, mockData);
- });
+ }, {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '1',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '2',
+ });
- afterEach(() => {
- Vue.http.interceptors = backupInterceptors;
- });
+ component = mountComponent(EnvironmentsComponent, mockData);
- it('should render a table with environments', (done) => {
setTimeout(() => {
- expect(component.$el.querySelectorAll('table')).not.toBeNull();
- expect(
- component.$el.querySelector('.environment-name').textContent.trim(),
- ).toEqual(environment.name);
done();
}, 0);
});
+ it('should render a table with environments', () => {
+ expect(component.$el.querySelectorAll('table')).not.toBeNull();
+ expect(
+ component.$el.querySelector('.environment-name').textContent.trim(),
+ ).toEqual(environment.name);
+ });
+
describe('pagination', () => {
- it('should render pagination', (done) => {
- setTimeout(() => {
- expect(
- component.$el.querySelectorAll('.gl-pagination li').length,
- ).toEqual(5);
- done();
- }, 0);
+ it('should render pagination', () => {
+ expect(
+ component.$el.querySelectorAll('.gl-pagination li').length,
+ ).toEqual(5);
});
it('should make an API request when page is clicked', (done) => {
@@ -133,50 +106,39 @@ describe('Environment', () => {
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
done();
- });
+ }, 0);
});
});
});
});
describe('unsuccessfull request', () => {
- const environmentsErrorResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 500,
- }));
- };
-
- beforeEach(() => {
- Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
- });
+ beforeEach((done) => {
+ mock.onGet(mockData.endpoint).reply(500, {});
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsErrorResponseInterceptor,
- );
- });
-
- it('should render empty state', (done) => {
component = mountComponent(EnvironmentsComponent, mockData);
setTimeout(() => {
- expect(
- component.$el.querySelector('.js-blank-state-title').textContent,
- ).toContain('You don\'t have any environments right now.');
done();
}, 0);
});
+
+ it('should render empty state', () => {
+ expect(
+ component.$el.querySelector('.js-blank-state-title').textContent,
+ ).toContain('You don\'t have any environments right now.');
+ });
});
describe('expandable folders', () => {
- const environmentsResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({
- environments: [folder],
- stopped_count: 0,
- available_count: 1,
- }), {
- status: 200,
- headers: {
+ beforeEach(() => {
+ mock.onGet(mockData.endpoint).reply(200,
+ {
+ environments: [folder],
+ stopped_count: 0,
+ available_count: 1,
+ },
+ {
'X-nExt-pAge': '2',
'x-page': '1',
'X-Per-Page': '1',
@@ -184,18 +146,11 @@ describe('Environment', () => {
'X-TOTAL': '37',
'X-Total-Pages': '2',
},
- }));
- };
+ );
- beforeEach(() => {
- Vue.http.interceptors.push(environmentsResponseInterceptor);
- component = mountComponent(EnvironmentsComponent, mockData);
- });
+ mock.onGet(environment.folder_path).reply(200, { environments: [environment] });
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsResponseInterceptor,
- );
+ component = mountComponent(EnvironmentsComponent, mockData);
});
it('should open a closed folder', (done) => {
@@ -211,7 +166,7 @@ describe('Environment', () => {
).not.toContain('display: none');
done();
});
- });
+ }, 0);
});
it('should close an opened folder', (done) => {
@@ -233,7 +188,7 @@ describe('Environment', () => {
done();
});
});
- });
+ }, 0);
});
it('should show children environments and a button to show all environments', (done) => {
@@ -242,49 +197,32 @@ describe('Environment', () => {
component.$el.querySelector('.folder-name').click();
Vue.nextTick(() => {
- const folderInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({
- environments: [environment],
- }), { status: 200 }));
- };
-
- Vue.http.interceptors.push(folderInterceptor);
-
// wait for next async request
setTimeout(() => {
expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1);
expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain('Show all');
-
- Vue.http.interceptors = _.without(Vue.http.interceptors, folderInterceptor);
done();
});
});
- });
+ }, 0);
});
});
describe('methods', () => {
- const environmentsEmptyResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 200,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
- Vue.http.interceptors.push(headersInterceptor);
+ mock.onGet(mockData.endpoint).reply(200,
+ {
+ environments: [],
+ stopped_count: 0,
+ available_count: 1,
+ },
+ {},
+ );
component = mountComponent(EnvironmentsComponent, mockData);
spyOn(history, 'pushState').and.stub();
});
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsEmptyResponseInterceptor,
- );
- Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
- });
-
describe('updateContent', () => {
it('should set given parameters', (done) => {
component.updateContent({ scope: 'stopped', page: '3' })
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 906a1116974..f5ce4df0bfe 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,13 +1,15 @@
-import _ from 'underscore';
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
-import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
let Component;
let component;
+ let mock;
+
const mockData = {
endpoint: 'environments.json',
folderName: 'review',
@@ -17,46 +19,35 @@ describe('Environments Folder View', () => {
};
beforeEach(() => {
+ mock = new MockAdapter(axios);
+
Component = Vue.extend(environmentsFolderViewComponent);
});
afterEach(() => {
+ mock.restore();
+
component.$destroy();
});
describe('successfull request', () => {
- const environmentsResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify({
+ beforeEach(() => {
+ mock.onGet(mockData.endpoint).reply(200, {
environments: environmentsList,
stopped_count: 1,
available_count: 0,
- }), {
- status: 200,
- headers: {
- 'X-nExt-pAge': '2',
- 'x-page': '1',
- 'X-Per-Page': '2',
- 'X-Prev-Page': '',
- 'X-TOTAL': '20',
- 'X-Total-Pages': '10',
- },
- }));
- };
-
- beforeEach(() => {
- Vue.http.interceptors.push(environmentsResponseInterceptor);
- Vue.http.interceptors.push(headersInterceptor);
+ }, {
+ 'X-nExt-pAge': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '2',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '20',
+ 'X-Total-Pages': '10',
+ });
component = mountComponent(Component, mockData);
});
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsResponseInterceptor,
- );
- Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
- });
-
it('should render a table with environments', (done) => {
setTimeout(() => {
expect(component.$el.querySelectorAll('table')).not.toBeNull();
@@ -135,25 +126,15 @@ describe('Environments Folder View', () => {
});
describe('unsuccessfull request', () => {
- const environmentsErrorResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 500,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(environmentsErrorResponseInterceptor);
- });
+ mock.onGet(mockData.endpoint).reply(500, {
+ environments: [],
+ });
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsErrorResponseInterceptor,
- );
+ component = mountComponent(Component, mockData);
});
it('should not render a table', (done) => {
- component = mountComponent(Component, mockData);
-
setTimeout(() => {
expect(
component.$el.querySelector('table'),
@@ -190,27 +171,15 @@ describe('Environments Folder View', () => {
});
describe('methods', () => {
- const environmentsEmptyResponseInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 200,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
- Vue.http.interceptors.push(headersInterceptor);
+ mock.onGet(mockData.endpoint).reply(200, {
+ environments: [],
+ });
component = mountComponent(Component, mockData);
spyOn(history, 'pushState').and.stub();
});
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, environmentsEmptyResponseInterceptor,
- );
- Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
- });
-
describe('updateContent', () => {
it('should set given parameters', (done) => {
component.updateContent({ scope: 'stopped', page: '4' })
diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js
index 15e11aa686b..8a1e26935d9 100644
--- a/spec/javascripts/environments/mock_data.js
+++ b/spec/javascripts/environments/mock_data.js
@@ -82,6 +82,7 @@ export const environment = {
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
updated_at: '2017-01-31T10:53:46.894Z',
+ folder_path: '/root/review-app/environments/7',
},
};
diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml
index 5477c6075f0..aa7af61c7eb 100644
--- a/spec/javascripts/fixtures/event_filter.html.haml
+++ b/spec/javascripts/fixtures/event_filter.html.haml
@@ -1,4 +1,4 @@
-%ul.nav-links.event-filter.scrolling-tabs
+%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
%li.active
%a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"}
%span
diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml
index 397bdc85c67..06ce248dc9c 100644
--- a/spec/javascripts/fixtures/issue_sidebar_label.html.haml
+++ b/spec/javascripts/fixtures/issue_sidebar_label.html.haml
@@ -1,7 +1,7 @@
.block.labels
.sidebar-collapsed-icon.js-sidebar-labels-tooltip
.title.hide-collapsed
- %a.edit-link.pull-right{ href: "#" }
+ %a.edit-link.float-right{ href: "#" }
Edit
.selectbox.hide-collapsed{ style: "display: none;" }
.dropdown
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
index c38fe8b1f25..632606e0536 100644
--- a/spec/javascripts/fixtures/linked_tabs.html.haml
+++ b/spec/javascripts/fixtures/linked_tabs.html.haml
@@ -1,9 +1,9 @@
-%ul.nav-links.new-session-tabs.linked-tabs
- %li
- %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
+%ul.nav.nav-tabs.new-session-tabs.linked-tabs
+ %li.nav-item
+ %a.nav-link{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
Tab 1
- %li
- %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
+ %li.nav-item
+ %a.nav-link{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
Tab 2
.tab-content
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 7f9c4811fba..175f386b60e 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -70,9 +70,9 @@ describe('glDropdown', function describeDropdown() {
it('should open on click', () => {
initDropDown.call(this, false);
- expect(this.dropdownContainerElement).not.toHaveClass('open');
+ expect(this.dropdownContainerElement).not.toHaveClass('show');
this.dropdownButtonElement.click();
- expect(this.dropdownContainerElement).toHaveClass('open');
+ expect(this.dropdownContainerElement).toHaveClass('show');
});
it('escapes HTML as text', () => {
@@ -134,12 +134,12 @@ describe('glDropdown', function describeDropdown() {
});
it('should click the selected item on ENTER keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
+ expect(this.dropdownContainerElement).toHaveClass('show');
const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0;
navigateWithKeys('down', randomIndex, () => {
const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub();
navigateWithKeys('enter', null, () => {
- expect(this.dropdownContainerElement).not.toHaveClass('open');
+ expect(this.dropdownContainerElement).not.toHaveClass('show');
const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement);
expect(link).toHaveClass('is-active');
const linkedLocation = link.attr('href');
@@ -149,13 +149,13 @@ describe('glDropdown', function describeDropdown() {
});
it('should close on ESC keypress', () => {
- expect(this.dropdownContainerElement).toHaveClass('open');
+ expect(this.dropdownContainerElement).toHaveClass('show');
this.dropdownContainerElement.trigger({
type: 'keyup',
which: ARROW_KEYS.ESC,
keyCode: ARROW_KEYS.ESC
});
- expect(this.dropdownContainerElement).not.toHaveClass('open');
+ expect(this.dropdownContainerElement).not.toHaveClass('show');
});
});
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 83f29d1b0c2..d6ab0aeeed7 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -55,7 +55,7 @@ export default (action, payload, state, expectedMutations, expectedActions, done
};
// call the action with mocked store and arguments
- action({ commit, state, dispatch }, payload);
+ action({ commit, state, dispatch, rootState: state }, payload);
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index 6f580e1f7af..045a60e56a0 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -107,5 +107,11 @@ describe('ide component', () => {
vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'),
).toBe(true);
});
+
+ it('stops callback in monaco editor', () => {
+ setFixtures('<div class="inputarea"></div>');
+
+ expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true);
+ });
});
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index ff500acd849..d3f80e6f9c0 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -346,4 +346,24 @@ describe('RepoEditor', () => {
});
});
});
+
+ it('calls removePendingTab when old file is pending', done => {
+ spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
+ spyOn(vm, 'removePendingTab');
+
+ vm.file.pending = true;
+
+ vm
+ .$nextTick()
+ .then(() => {
+ vm.file = file('testing');
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.removePendingTab).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index c059862b9d1..c68ae050641 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -1,4 +1,3 @@
-// eslint-disable-next-line import/prefer-default-export
export const projectData = {
id: 1,
name: 'abcproject',
@@ -14,3 +13,83 @@ export const projectData = {
mergeRequests: {},
merge_requests_enabled: true,
};
+
+export const pipelines = [
+ {
+ id: 1,
+ ref: 'master',
+ sha: '123',
+ status: 'failed',
+ },
+ {
+ id: 2,
+ ref: 'master',
+ sha: '213',
+ status: 'success',
+ },
+];
+
+export const jobs = [
+ {
+ id: 1,
+ name: 'test',
+ status: 'failed',
+ stage: 'test',
+ duration: 1,
+ },
+ {
+ id: 2,
+ name: 'test 2',
+ status: 'failed',
+ stage: 'test',
+ duration: 1,
+ },
+ {
+ id: 3,
+ name: 'test 3',
+ status: 'failed',
+ stage: 'test',
+ duration: 1,
+ },
+ {
+ id: 4,
+ name: 'test 3',
+ status: 'failed',
+ stage: 'build',
+ duration: 1,
+ },
+];
+
+export const fullPipelinesResponse = {
+ data: {
+ count: {
+ all: 2,
+ },
+ pipelines: [
+ {
+ id: '51',
+ commit: {
+ id: 'xxxxxxxxxxxxxxxxxxxx',
+ },
+ details: {
+ status: {
+ icon: 'status_failed',
+ text: 'failed',
+ },
+ },
+ },
+ {
+ id: '50',
+ commit: {
+ id: 'abc123def456ghi789jkl',
+ },
+ details: {
+ status: {
+ icon: 'status_passed',
+ text: 'passed',
+ },
+ },
+ },
+ ],
+ },
+};
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index ebd08d95810..8e078ae7138 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -1,14 +1,33 @@
-import {
- refreshLastCommitData,
-} from '~/ide/stores/actions';
+import Visibility from 'visibilityjs';
+import MockAdapter from 'axios-mock-adapter';
+import { refreshLastCommitData, pollSuccessCallBack } 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.abcproject = {};
+ store.state.projects['abc/def'] = {};
});
afterEach(() => {
@@ -17,18 +36,16 @@ describe('IDE store project actions', () => {
describe('refreshLastCommitData', () => {
beforeEach(() => {
- store.state.currentProjectId = 'abcproject';
+ store.state.currentProjectId = 'abc/def';
store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
+ store.state.projects['abc/def'] = {
+ id: 4,
branches: {
master: {
commit: null,
},
},
};
- });
-
- it('calls the service', done => {
spyOn(service, 'getBranchData').and.returnValue(
Promise.resolve({
data: {
@@ -36,14 +53,16 @@ describe('IDE store project actions', () => {
},
}),
);
+ });
+ it('calls the service', done => {
store
.dispatch('refreshLastCommitData', {
projectId: store.state.currentProjectId,
branchId: store.state.currentBranchId,
})
.then(() => {
- expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
+ expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'master');
done();
})
@@ -53,16 +72,118 @@ describe('IDE store project actions', () => {
it('commits getBranchData', done => {
testAction(
refreshLastCommitData,
- {},
- {},
- [{
- type: 'SET_BRANCH_COMMIT',
- payload: {
- projectId: 'abcproject',
- branchId: 'master',
- commit: { id: '123' },
+ {
+ projectId: store.state.currentProjectId,
+ branchId: store.state.currentBranchId,
+ },
+ store.state,
+ [
+ {
+ type: 'SET_BRANCH_COMMIT',
+ payload: {
+ projectId: 'abc/def',
+ branchId: 'master',
+ commit: { id: '123' },
+ },
+ },
+ ], // mutations
+ [
+ {
+ type: 'getLastCommitPipeline',
+ payload: {
+ projectId: 'abc/def',
+ projectIdNumber: store.state.projects['abc/def'].id,
+ branchId: 'master',
+ },
+ },
+ ], // action
+ done,
+ );
+ });
+ });
+
+ 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
+ ], // 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
new file mode 100644
index 00000000000..85fbcf8084b
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -0,0 +1,289 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import actions, {
+ requestLatestPipeline,
+ receiveLatestPipelineError,
+ receiveLatestPipelineSuccess,
+ fetchLatestPipeline,
+ requestJobs,
+ receiveJobsError,
+ receiveJobsSuccess,
+ fetchJobs,
+} from '~/ide/stores/modules/pipelines/actions';
+import state from '~/ide/stores/modules/pipelines/state';
+import * as types from '~/ide/stores/modules/pipelines/mutation_types';
+import testAction from '../../../../helpers/vuex_action_helper';
+import { pipelines, jobs } from '../../../mock_data';
+
+describe('IDE pipelines actions', () => {
+ let mockedState;
+ let mock;
+
+ beforeEach(() => {
+ mockedState = state();
+ mock = new MockAdapter(axios);
+
+ gon.api_version = 'v4';
+ mockedState.currentProjectId = 'test/project';
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('requestLatestPipeline', () => {
+ it('commits request', done => {
+ testAction(
+ requestLatestPipeline,
+ null,
+ mockedState,
+ [{ type: types.REQUEST_LATEST_PIPELINE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveLatestPipelineError', () => {
+ it('commits error', done => {
+ testAction(
+ receiveLatestPipelineError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }],
+ [],
+ done,
+ );
+ });
+
+ it('creates flash message', () => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+
+ receiveLatestPipelineError({ commit() {} });
+
+ expect(flashSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('receiveLatestPipelineSuccess', () => {
+ it('commits pipeline', done => {
+ testAction(
+ receiveLatestPipelineSuccess,
+ pipelines[0],
+ mockedState,
+ [{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchLatestPipeline', () => {
+ describe('success', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(200, pipelines);
+ });
+
+ 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',
+ },
+ });
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500);
+ });
+
+ it('dispatches error', done => {
+ testAction(
+ fetchLatestPipeline,
+ '123',
+ mockedState,
+ [],
+ [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineError' }],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('requestJobs', () => {
+ it('commits request', done => {
+ testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done);
+ });
+ });
+
+ describe('receiveJobsError', () => {
+ it('commits error', done => {
+ testAction(
+ receiveJobsError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_ERROR }],
+ [],
+ done,
+ );
+ });
+
+ it('creates flash message', () => {
+ const flashSpy = spyOnDependency(actions, 'flash');
+
+ receiveJobsError({ commit() {} });
+
+ expect(flashSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('receiveJobsSuccess', () => {
+ it('commits jobs', done => {
+ testAction(
+ receiveJobsSuccess,
+ jobs,
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchJobs', () => {
+ let page = '';
+
+ beforeEach(() => {
+ mockedState.latestPipeline = pipelines[0];
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [
+ 200,
+ jobs,
+ {
+ 'x-next-page': page,
+ },
+ ]);
+ });
+
+ 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,
+ mockedState,
+ [],
+ [
+ { type: 'requestJobs' },
+ { type: 'receiveJobsSuccess', payload: jobs },
+ { type: 'fetchJobs', payload: '2' },
+ { type: 'requestJobs' },
+ { type: 'receiveJobsSuccess', payload: 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);
+ });
+
+ it('dispatches error', done => {
+ testAction(
+ fetchJobs,
+ null,
+ mockedState,
+ [],
+ [{ type: 'requestJobs' }, { type: 'receiveJobsError' }],
+ done,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
new file mode 100644
index 00000000000..b2a7e8a9025
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
@@ -0,0 +1,71 @@
+import * as getters from '~/ide/stores/modules/pipelines/getters';
+import state from '~/ide/stores/modules/pipelines/state';
+
+describe('IDE pipeline getters', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('hasLatestPipeline', () => {
+ it('returns false when loading is true', () => {
+ mockedState.isLoadingPipeline = true;
+
+ expect(getters.hasLatestPipeline(mockedState)).toBe(false);
+ });
+
+ it('returns false when pipelines is null', () => {
+ mockedState.latestPipeline = null;
+
+ expect(getters.hasLatestPipeline(mockedState)).toBe(false);
+ });
+
+ it('returns false when loading is true & pipelines is null', () => {
+ mockedState.latestPipeline = null;
+ mockedState.isLoadingPipeline = true;
+
+ expect(getters.hasLatestPipeline(mockedState)).toBe(false);
+ });
+
+ it('returns true when loading is false & pipelines is an object', () => {
+ mockedState.latestPipeline = {
+ id: 1,
+ };
+ mockedState.isLoadingPipeline = false;
+
+ 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
new file mode 100644
index 00000000000..8262e916243
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js
@@ -0,0 +1,120 @@
+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';
+
+describe('IDE pipelines mutations', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe(types.REQUEST_LATEST_PIPELINE, () => {
+ it('sets loading to true', () => {
+ mutations[types.REQUEST_LATEST_PIPELINE](mockedState);
+
+ expect(mockedState.isLoadingPipeline).toBe(true);
+ });
+ });
+
+ describe(types.RECEIVE_LASTEST_PIPELINE_ERROR, () => {
+ it('sets loading to false', () => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_ERROR](mockedState);
+
+ expect(mockedState.isLoadingPipeline).toBe(false);
+ });
+ });
+
+ describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => {
+ it('sets loading to false on success', () => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]);
+
+ expect(mockedState.isLoadingPipeline).toBe(false);
+ });
+
+ it('sets latestPipeline', () => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]);
+
+ expect(mockedState.latestPipeline).toEqual({
+ id: pipelines[0].id,
+ status: pipelines[0].status,
+ });
+ });
+
+ it('does not set latest pipeline if pipeline is null', () => {
+ mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null);
+
+ expect(mockedState.latestPipeline).toEqual(null);
+ });
+ });
+
+ describe(types.REQUEST_JOBS, () => {
+ it('sets jobs loading to true', () => {
+ mutations[types.REQUEST_JOBS](mockedState);
+
+ expect(mockedState.isLoadingJobs).toBe(true);
+ });
+ });
+
+ describe(types.RECEIVE_JOBS_ERROR, () => {
+ it('sets jobs loading to false', () => {
+ mutations[types.RECEIVE_JOBS_ERROR](mockedState);
+
+ expect(mockedState.isLoadingJobs).toBe(false);
+ });
+ });
+
+ describe(types.RECEIVE_JOBS_SUCCESS, () => {
+ it('sets jobs loading to false on success', () => {
+ mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+
+ expect(mockedState.isLoadingJobs).toBe(false);
+ });
+
+ it('sets stages', () => {
+ mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+
+ expect(mockedState.stages.length).toBe(2);
+ expect(mockedState.stages).toEqual([
+ {
+ title: 'test',
+ jobs: jasmine.anything(),
+ },
+ {
+ title: 'build',
+ jobs: jasmine.anything(),
+ },
+ ]);
+ });
+
+ it('sets jobs in stages', () => {
+ mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs);
+
+ 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,
+ })),
+ },
+ ]);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js
index 29eb859ddaf..f2f1f2a9a2e 100644
--- a/spec/javascripts/ide/stores/mutations/branch_spec.js
+++ b/spec/javascripts/ide/stores/mutations/branch_spec.js
@@ -37,4 +37,40 @@ 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/notes_spec.js b/spec/javascripts/notes_spec.js
index 0952356c2f4..648fb3e9bd3 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -974,7 +974,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
).toBeFalsy();
expect(
$tempNoteHeader
- .find('.hidden-xs')
+ .find('.d-none.d-sm-block')
.text()
.trim(),
).toEqual(currentUserFullname);
@@ -1020,7 +1020,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
const $tempNoteHeader = $tempNote.find('.note-header');
expect(
$tempNoteHeader
- .find('.hidden-xs')
+ .find('.d-none.d-sm-block')
.text()
.trim(),
).toEqual('Foo &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;');
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 71f77e5f42e..1e41a7bfe7c 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -29,7 +29,7 @@ describe('Pipelines Empty State', () => {
expect(
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,');
+ ).toContain('Continuous Integration can help catch bugs by running your tests automatically,');
expect(
component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
index a5a200973d7..03ead6cd8ba 100644
--- a/spec/javascripts/pipelines/mock_data.js
+++ b/spec/javascripts/pipelines/mock_data.js
@@ -217,7 +217,7 @@ export const pipelineWithStages = {
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse',
},
{
- name: 'codequality',
+ name: 'code_quality',
expired: false,
expire_at: '2018-04-18T14:16:24.484Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download',
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index 05ca4cb9044..68043b91bd0 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -182,7 +182,16 @@ describe('Pipelines Table Row', () => {
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
- expect(component.isCancelling).toEqual(true);
+ });
+
+ it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => {
+ component.cancelingPipeline = pipeline.id;
+ component.$nextTick()
+ .then(() => {
+ expect(component.isCancelling).toEqual(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
new file mode 100644
index 00000000000..21805ef0b28
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
@@ -0,0 +1,103 @@
+import Vue from 'vue';
+import GkeMachineTypeDropdown from '~/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue';
+import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import {
+ SET_PROJECT,
+ SET_PROJECT_BILLING_STATUS,
+ SET_ZONE,
+ SET_MACHINE_TYPES,
+} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import {
+ selectedZoneMock,
+ selectedProjectMock,
+ selectedMachineTypeMock,
+ gapiMachineTypesResponseMock,
+} from '../mock_data';
+
+const componentConfig = {
+ fieldId: 'cluster_provider_gcp_attributes_gcp_machine_type',
+ fieldName: 'cluster[provider_gcp_attributes][gcp_machine_type]',
+};
+
+const LABELS = {
+ LOADING: 'Fetching machine types',
+ DISABLED_NO_PROJECT: 'Select project and zone to choose machine type',
+ DISABLED_NO_ZONE: 'Select zone to choose machine type',
+ DEFAULT: 'Select machine type',
+};
+
+const createComponent = (store, props = componentConfig) => {
+ const Component = Vue.extend(GkeMachineTypeDropdown);
+
+ return mountComponentWithStore(Component, {
+ el: null,
+ props,
+ store,
+ });
+};
+
+describe('GkeMachineTypeDropdown', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ vm = createComponent(store);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('shows various toggle text depending on state', () => {
+ it('returns disabled state toggle text when no project and zone are selected', () => {
+ expect(vm.toggleText).toBe(LABELS.DISABLED_NO_PROJECT);
+ });
+
+ it('returns disabled state toggle text when no zone is selected', () => {
+ vm.$store.commit(SET_PROJECT, selectedProjectMock);
+ vm.$store.commit(SET_PROJECT_BILLING_STATUS, true);
+
+ expect(vm.toggleText).toBe(LABELS.DISABLED_NO_ZONE);
+ });
+
+ it('returns loading toggle text', () => {
+ vm.isLoading = true;
+
+ expect(vm.toggleText).toBe(LABELS.LOADING);
+ });
+
+ it('returns default toggle text', () => {
+ expect(vm.toggleText).toBe(LABELS.DISABLED_NO_PROJECT);
+
+ vm.$store.commit(SET_PROJECT, selectedProjectMock);
+ vm.$store.commit(SET_PROJECT_BILLING_STATUS, true);
+ vm.$store.commit(SET_ZONE, selectedZoneMock);
+
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+ });
+
+ it('returns machine type name if machine type selected', () => {
+ vm.setItem(selectedMachineTypeMock);
+
+ expect(vm.toggleText).toBe(selectedMachineTypeMock);
+ });
+ });
+
+ describe('form input', () => {
+ it('reflects new value when dropdown item is clicked', done => {
+ expect(vm.$el.querySelector('input').value).toBe('');
+ vm.$store.commit(SET_MACHINE_TYPES, gapiMachineTypesResponseMock.items);
+
+ return vm.$nextTick().then(() => {
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('input').value).toBe(selectedMachineTypeMock);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
new file mode 100644
index 00000000000..d4fcb2dc8ff
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
@@ -0,0 +1,92 @@
+import Vue from 'vue';
+import GkeProjectIdDropdown from '~/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue';
+import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import { SET_PROJECTS } from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { emptyProjectMock, selectedProjectMock } from '../mock_data';
+
+const componentConfig = {
+ docsUrl: 'https://console.cloud.google.com/home/dashboard',
+ fieldId: 'cluster_provider_gcp_attributes_gcp_project_id',
+ fieldName: 'cluster[provider_gcp_attributes][gcp_project_id]',
+};
+
+const LABELS = {
+ LOADING: 'Fetching projects',
+ VALIDATING_PROJECT_BILLING: 'Validating project billing status',
+ DEFAULT: 'Select project',
+ EMPTY: 'No projects found',
+};
+
+const createComponent = (store, props = componentConfig) => {
+ const Component = Vue.extend(GkeProjectIdDropdown);
+
+ return mountComponentWithStore(Component, {
+ el: null,
+ props,
+ store,
+ });
+};
+
+describe('GkeProjectIdDropdown', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ vm = createComponent(store);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('toggleText', () => {
+ it('returns loading toggle text', () => {
+ expect(vm.toggleText).toBe(LABELS.LOADING);
+ });
+
+ it('returns project billing validation text', () => {
+ vm.setIsValidatingProjectBilling(true);
+ expect(vm.toggleText).toBe(LABELS.VALIDATING_PROJECT_BILLING);
+ });
+
+ it('returns default toggle text', done =>
+ vm.$nextTick().then(() => {
+ vm.setItem(emptyProjectMock);
+
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+ done();
+ }));
+
+ it('returns project name if project selected', done =>
+ vm.$nextTick().then(() => {
+ expect(vm.toggleText).toBe(selectedProjectMock.name);
+ done();
+ }));
+
+ it('returns empty toggle text', done =>
+ vm.$nextTick().then(() => {
+ vm.$store.commit(SET_PROJECTS, null);
+ vm.setItem(emptyProjectMock);
+
+ expect(vm.toggleText).toBe(LABELS.EMPTY);
+ done();
+ }));
+ });
+
+ describe('selectItem', () => {
+ it('reflects new value when dropdown item is clicked', done => {
+ expect(vm.$el.querySelector('input').value).toBe('');
+
+ return vm.$nextTick().then(() => {
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('input').value).toBe(selectedProjectMock.projectId);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
new file mode 100644
index 00000000000..89a4a7ea2ce
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
@@ -0,0 +1,88 @@
+import Vue from 'vue';
+import GkeZoneDropdown from '~/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue';
+import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import {
+ SET_PROJECT,
+ SET_ZONES,
+ SET_PROJECT_BILLING_STATUS,
+} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { selectedZoneMock, selectedProjectMock, gapiZonesResponseMock } from '../mock_data';
+
+const componentConfig = {
+ fieldId: 'cluster_provider_gcp_attributes_gcp_zone',
+ fieldName: 'cluster[provider_gcp_attributes][gcp_zone]',
+};
+
+const LABELS = {
+ LOADING: 'Fetching zones',
+ DISABLED: 'Select project to choose zone',
+ DEFAULT: 'Select zone',
+};
+
+const createComponent = (store, props = componentConfig) => {
+ const Component = Vue.extend(GkeZoneDropdown);
+
+ return mountComponentWithStore(Component, {
+ el: null,
+ props,
+ store,
+ });
+};
+
+describe('GkeZoneDropdown', () => {
+ let vm;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ vm = createComponent(store);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('toggleText', () => {
+ it('returns disabled state toggle text', () => {
+ expect(vm.toggleText).toBe(LABELS.DISABLED);
+ });
+
+ it('returns loading toggle text', () => {
+ vm.isLoading = true;
+
+ expect(vm.toggleText).toBe(LABELS.LOADING);
+ });
+
+ it('returns default toggle text', () => {
+ expect(vm.toggleText).toBe(LABELS.DISABLED);
+
+ vm.$store.commit(SET_PROJECT, selectedProjectMock);
+ vm.$store.commit(SET_PROJECT_BILLING_STATUS, true);
+
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+ });
+
+ it('returns project name if project selected', () => {
+ vm.setItem(selectedZoneMock);
+
+ expect(vm.toggleText).toBe(selectedZoneMock);
+ });
+ });
+
+ describe('selectItem', () => {
+ it('reflects new value when dropdown item is clicked', done => {
+ expect(vm.$el.querySelector('input').value).toBe('');
+ vm.$store.commit(SET_ZONES, gapiZonesResponseMock.items);
+
+ return vm.$nextTick().then(() => {
+ vm.$el.querySelector('.dropdown-content button').click();
+
+ return vm.$nextTick().then(() => {
+ expect(vm.$el.querySelector('input').value).toBe(selectedZoneMock);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js b/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js
new file mode 100644
index 00000000000..6df511e9157
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js
@@ -0,0 +1,49 @@
+import {
+ gapiProjectsResponseMock,
+ gapiZonesResponseMock,
+ gapiMachineTypesResponseMock,
+} from './mock_data';
+
+// eslint-disable-next-line import/prefer-default-export
+export const gapi = () => ({
+ client: {
+ cloudbilling: {
+ projects: {
+ getBillingInfo: () =>
+ new Promise(resolve => {
+ resolve({
+ result: { billingEnabled: true },
+ });
+ }),
+ },
+ },
+ cloudresourcemanager: {
+ projects: {
+ list: () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiProjectsResponseMock },
+ });
+ }),
+ },
+ },
+ compute: {
+ zones: {
+ list: () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiZonesResponseMock },
+ });
+ }),
+ },
+ machineTypes: {
+ list: () =>
+ new Promise(resolve => {
+ resolve({
+ result: { ...gapiMachineTypesResponseMock },
+ });
+ }),
+ },
+ },
+ },
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js b/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js
new file mode 100644
index 00000000000..d9f5dbc636f
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js
@@ -0,0 +1,75 @@
+export const emptyProjectMock = {
+ projectId: '',
+ name: '',
+};
+
+export const selectedProjectMock = {
+ projectId: 'gcp-project-123',
+ name: 'gcp-project',
+};
+
+export const selectedZoneMock = 'us-central1-a';
+
+export const selectedMachineTypeMock = 'n1-standard-2';
+
+export const gapiProjectsResponseMock = {
+ projects: [
+ {
+ projectNumber: '1234',
+ projectId: 'gcp-project-123',
+ lifecycleState: 'ACTIVE',
+ name: 'gcp-project',
+ createTime: '2017-12-16T01:48:29.129Z',
+ parent: {
+ type: 'organization',
+ id: '12345',
+ },
+ },
+ ],
+};
+
+export const gapiZonesResponseMock = {
+ kind: 'compute#zoneList',
+ id: 'projects/gitlab-internal-153318/zones',
+ items: [
+ {
+ kind: 'compute#zone',
+ id: '2000',
+ creationTimestamp: '1969-12-31T16:00:00.000-08:00',
+ name: 'us-central1-a',
+ description: 'us-central1-a',
+ status: 'UP',
+ region:
+ 'https://www.googleapis.com/compute/v1/projects/gitlab-internal-153318/regions/us-central1',
+ selfLink:
+ 'https://www.googleapis.com/compute/v1/projects/gitlab-internal-153318/zones/us-central1-a',
+ availableCpuPlatforms: ['Intel Skylake', 'Intel Broadwell', 'Intel Sandy Bridge'],
+ },
+ ],
+ selfLink: 'https://www.googleapis.com/compute/v1/projects/gitlab-internal-153318/zones',
+};
+
+export const gapiMachineTypesResponseMock = {
+ kind: 'compute#machineTypeList',
+ id: 'projects/gitlab-internal-153318/zones/us-central1-a/machineTypes',
+ items: [
+ {
+ kind: 'compute#machineType',
+ id: '3002',
+ creationTimestamp: '1969-12-31T16:00:00.000-08:00',
+ name: 'n1-standard-2',
+ description: '2 vCPUs, 7.5 GB RAM',
+ guestCpus: 2,
+ memoryMb: 7680,
+ imageSpaceGb: 10,
+ maximumPersistentDisks: 64,
+ maximumPersistentDisksSizeGb: '65536',
+ zone: 'us-central1-a',
+ selfLink:
+ 'https://www.googleapis.com/compute/v1/projects/gitlab-internal-153318/zones/us-central1-a/machineTypes/n1-standard-2',
+ isSharedCpu: false,
+ },
+ ],
+ selfLink:
+ 'https://www.googleapis.com/compute/v1/projects/gitlab-internal-153318/zones/us-central1-a/machineTypes',
+};
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js
new file mode 100644
index 00000000000..9d892b8185b
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js
@@ -0,0 +1,131 @@
+import testAction from 'spec/helpers/vuex_action_helper';
+import * as actions from '~/projects/gke_cluster_dropdowns/store/actions';
+import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import { gapi } from '../helpers';
+import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
+
+describe('GCP Cluster Dropdown Store Actions', () => {
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ describe('setProject', () => {
+ it('should set project', done => {
+ testAction(
+ actions.setProject,
+ selectedProjectMock,
+ { selectedProject: {} },
+ [{ type: 'SET_PROJECT', payload: selectedProjectMock }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setZone', () => {
+ it('should set zone', done => {
+ testAction(
+ actions.setZone,
+ selectedZoneMock,
+ { selectedZone: '' },
+ [{ type: 'SET_ZONE', payload: selectedZoneMock }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setMachineType', () => {
+ it('should set machine type', done => {
+ testAction(
+ actions.setMachineType,
+ selectedMachineTypeMock,
+ { selectedMachineType: '' },
+ [{ type: 'SET_MACHINE_TYPE', payload: selectedMachineTypeMock }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setIsValidatingProjectBilling', () => {
+ it('should set machine type', done => {
+ testAction(
+ actions.setIsValidatingProjectBilling,
+ true,
+ { isValidatingProjectBilling: null },
+ [{ type: 'SET_IS_VALIDATING_PROJECT_BILLING', payload: true }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('async fetch methods', () => {
+ window.gapi = gapi();
+
+ describe('fetchProjects', () => {
+ it('fetches projects from Google API', done => {
+ store
+ .dispatch('fetchProjects')
+ .then(() => {
+ expect(store.state.projects[0].projectId).toEqual(selectedProjectMock.projectId);
+ expect(store.state.projects[0].name).toEqual(selectedProjectMock.name);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('validateProjectBilling', () => {
+ it('checks project billing status from Google API', done => {
+ testAction(
+ actions.validateProjectBilling,
+ true,
+ {
+ selectedProject: selectedProjectMock,
+ selectedZone: '',
+ selectedMachineType: '',
+ projectHasBillingEnabled: null,
+ },
+ [
+ { type: 'SET_ZONE', payload: '' },
+ { type: 'SET_MACHINE_TYPE', payload: '' },
+ { type: 'SET_PROJECT_BILLING_STATUS', payload: true },
+ ],
+ [{ type: 'setIsValidatingProjectBilling', payload: false }],
+ done,
+ );
+ });
+ });
+
+ describe('fetchZones', () => {
+ it('fetches zones from Google API', done => {
+ store
+ .dispatch('fetchZones')
+ .then(() => {
+ expect(store.state.zones[0].name).toEqual(selectedZoneMock);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('fetchMachineTypes', () => {
+ it('fetches machine types from Google API', done => {
+ store
+ .dispatch('fetchMachineTypes')
+ .then(() => {
+ expect(store.state.machineTypes[0].name).toEqual(selectedMachineTypeMock);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js
new file mode 100644
index 00000000000..6f89158f807
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js
@@ -0,0 +1,65 @@
+import * as getters from '~/projects/gke_cluster_dropdowns/store/getters';
+import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
+
+describe('GCP Cluster Dropdown Store Getters', () => {
+ let state;
+
+ describe('valid states', () => {
+ beforeEach(() => {
+ state = {
+ selectedProject: selectedProjectMock,
+ selectedZone: selectedZoneMock,
+ selectedMachineType: selectedMachineTypeMock,
+ };
+ });
+
+ describe('hasProject', () => {
+ it('should return true when project is selected', () => {
+ expect(getters.hasProject(state)).toEqual(true);
+ });
+ });
+
+ describe('hasZone', () => {
+ it('should return true when zone is selected', () => {
+ expect(getters.hasZone(state)).toEqual(true);
+ });
+ });
+
+ describe('hasMachineType', () => {
+ it('should return true when machine type is selected', () => {
+ expect(getters.hasMachineType(state)).toEqual(true);
+ });
+ });
+ });
+
+ describe('invalid states', () => {
+ beforeEach(() => {
+ state = {
+ selectedProject: {
+ projectId: '',
+ name: '',
+ },
+ selectedZone: '',
+ selectedMachineType: '',
+ };
+ });
+
+ describe('hasProject', () => {
+ it('should return false when project is not selected', () => {
+ expect(getters.hasProject(state)).toEqual(false);
+ });
+ });
+
+ describe('hasZone', () => {
+ it('should return false when zone is not selected', () => {
+ expect(getters.hasZone(state)).toEqual(false);
+ });
+ });
+
+ describe('hasMachineType', () => {
+ it('should return false when machine type is not selected', () => {
+ expect(getters.hasMachineType(state)).toEqual(false);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js
new file mode 100644
index 00000000000..7f8c4f314e4
--- /dev/null
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js
@@ -0,0 +1,87 @@
+import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import * as types from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import {
+ selectedProjectMock,
+ selectedZoneMock,
+ selectedMachineTypeMock,
+ gapiProjectsResponseMock,
+ gapiZonesResponseMock,
+ gapiMachineTypesResponseMock,
+} from '../mock_data';
+
+describe('GCP Cluster Dropdown Store Mutations', () => {
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ describe('SET_PROJECT', () => {
+ it('should set GCP project as selectedProject', () => {
+ const projectToSelect = gapiProjectsResponseMock.projects[0];
+
+ store.commit(types.SET_PROJECT, projectToSelect);
+
+ expect(store.state.selectedProject.projectId).toEqual(selectedProjectMock.projectId);
+ expect(store.state.selectedProject.name).toEqual(selectedProjectMock.name);
+ });
+ });
+
+ describe('SET_PROJECT_BILLING_STATUS', () => {
+ it('should set project billing status', () => {
+ store.commit(types.SET_PROJECT_BILLING_STATUS, true);
+
+ expect(store.state.projectHasBillingEnabled).toBeTruthy();
+ });
+ });
+
+ describe('SET_ZONE', () => {
+ it('should set GCP zone as selectedZone', () => {
+ const zoneToSelect = gapiZonesResponseMock.items[0].name;
+
+ store.commit(types.SET_ZONE, zoneToSelect);
+
+ expect(store.state.selectedZone).toEqual(selectedZoneMock);
+ });
+ });
+
+ describe('SET_MACHINE_TYPE', () => {
+ it('should set GCP machine type as selectedMachineType', () => {
+ const machineTypeToSelect = gapiMachineTypesResponseMock.items[0].name;
+
+ store.commit(types.SET_MACHINE_TYPE, machineTypeToSelect);
+
+ expect(store.state.selectedMachineType).toEqual(selectedMachineTypeMock);
+ });
+ });
+
+ describe('SET_PROJECTS', () => {
+ it('should set Google API Projects response as projects', () => {
+ expect(store.state.projects.length).toEqual(0);
+
+ store.commit(types.SET_PROJECTS, gapiProjectsResponseMock.projects);
+
+ expect(store.state.projects.length).toEqual(gapiProjectsResponseMock.projects.length);
+ });
+ });
+
+ describe('SET_ZONES', () => {
+ it('should set Google API Zones response as zones', () => {
+ expect(store.state.zones.length).toEqual(0);
+
+ store.commit(types.SET_ZONES, gapiZonesResponseMock.items);
+
+ expect(store.state.zones.length).toEqual(gapiZonesResponseMock.items.length);
+ });
+ });
+
+ describe('SET_MACHINE_TYPES', () => {
+ it('should set Google API Machine Types response as machineTypes', () => {
+ expect(store.state.machineTypes.length).toEqual(0);
+
+ store.commit(types.SET_MACHINE_TYPES, gapiMachineTypesResponseMock.items);
+
+ expect(store.state.machineTypes.length).toEqual(gapiMachineTypesResponseMock.items.length);
+ });
+ });
+});
diff --git a/spec/javascripts/projects_dropdown/components/search_spec.js b/spec/javascripts/projects_dropdown/components/search_spec.js
index 601264258c2..427f5024e3a 100644
--- a/spec/javascripts/projects_dropdown/components/search_spec.js
+++ b/spec/javascripts/projects_dropdown/components/search_spec.js
@@ -92,7 +92,6 @@ describe('SearchComponent', () => {
const inputEl = vm.$el.querySelector('input.form-control');
expect(vm.$el.classList.contains('search-input-container')).toBeTruthy();
- expect(vm.$el.classList.contains('hidden-xs')).toBeTruthy();
expect(inputEl).not.toBe(null);
expect(inputEl.getAttribute('placeholder')).toBe('Search your projects');
expect(vm.$el.querySelector('.search-icon')).toBeDefined();
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 00847df4b60..8f35b9ca437 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -14,7 +14,9 @@ describe('SidebarMoveIssue', function () {
this.$content = $(`
<div class="dropdown">
<div class="js-toggle"></div>
- <div class="dropdown-content"></div>
+ <div class="dropdown-menu">
+ <div class="dropdown-content"></div>
+ </div>
<div class="js-confirm-button"></div>
</div>
`);
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
index db27aa144d6..00f4f2d7c39 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author.vue';
+import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-describe('MRWidgetAuthor', () => {
+describe('MrWidgetAuthor', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(authorComponent);
+ const Component = Vue.extend(MrWidgetAuthor);
vm = mountComponent(Component, {
author: {
diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
new file mode 100644
index 00000000000..ba897f4660d
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
@@ -0,0 +1,69 @@
+import Vue from 'vue';
+
+import dropdownButtonComponent from '~/vue_shared/components/dropdown/dropdown_button.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const defaultLabel = 'Select';
+const customLabel = 'Select project';
+
+const createComponent = config => {
+ const Component = Vue.extend(dropdownButtonComponent);
+
+ return mountComponent(Component, config);
+};
+
+describe('DropdownButtonComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('dropdownToggleText', () => {
+ it('returns default toggle text', () => {
+ expect(vm.toggleText).toBe(defaultLabel);
+ });
+
+ it('returns custom toggle text when provided via props', () => {
+ const vmEmptyLabels = createComponent({ toggleText: customLabel });
+
+ expect(vmEmptyLabels.toggleText).toBe(customLabel);
+ vmEmptyLabels.$destroy();
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element of type `button`', () => {
+ expect(vm.$el.nodeName).toBe('BUTTON');
+ });
+
+ it('renders component container element with required data attributes', () => {
+ expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
+ expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
+ expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
+ expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
+ expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
+ expect(vm.$el.dataset.showAny).not.toBeDefined();
+ });
+
+ it('renders dropdown toggle text element', () => {
+ const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
+ expect(dropdownToggleTextEl).not.toBeNull();
+ expect(dropdownToggleTextEl.innerText.trim()).toBe(defaultLabel);
+ });
+
+ it('renders dropdown button icon', () => {
+ const dropdownIconEl = vm.$el.querySelector('.dropdown-toggle-icon i.fa');
+
+ expect(dropdownIconEl).not.toBeNull();
+ expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js b/spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js
index 88733922a59..445ab0cb40e 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js
+++ b/spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js
@@ -1,17 +1,17 @@
import Vue from 'vue';
-import dropdownHiddenInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue';
+import dropdownHiddenInputComponent from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockLabels } from './mock_data';
-const createComponent = (name = 'label_id[]', label = mockLabels[0]) => {
+const createComponent = (name = 'label_id[]', value = mockLabels[0].id) => {
const Component = Vue.extend(dropdownHiddenInputComponent);
return mountComponent(Component, {
name,
- label,
+ value,
});
};
@@ -31,7 +31,7 @@ describe('DropdownHiddenInputComponent', () => {
expect(vm.$el.nodeName).toBe('INPUT');
expect(vm.$el.getAttribute('type')).toBe('hidden');
expect(vm.$el.getAttribute('name')).toBe(vm.name);
- expect(vm.$el.getAttribute('value')).toBe(`${vm.label.id}`);
+ expect(vm.$el.getAttribute('value')).toBe(`${vm.value}`);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js
new file mode 100644
index 00000000000..551520721e5
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/dropdown/dropdown_search_input_spec.js
@@ -0,0 +1,52 @@
+import Vue from 'vue';
+
+import dropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const componentConfig = {
+ placeholderText: 'Search something',
+};
+
+const createComponent = (config = componentConfig) => {
+ const Component = Vue.extend(dropdownSearchInputComponent);
+
+ return mountComponent(Component, config);
+};
+
+describe('DropdownSearchInputComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders input element with type `search`', () => {
+ const inputEl = vm.$el.querySelector('input.dropdown-input-field');
+
+ expect(inputEl).not.toBeNull();
+ expect(inputEl.getAttribute('type')).toBe('search');
+ });
+
+ it('renders search icon element', () => {
+ expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+ });
+
+ it('renders clear search icon element', () => {
+ expect(
+ vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear'),
+ ).not.toBeNull();
+ });
+
+ it('displays custom placeholder text', () => {
+ const inputEl = vm.$el.querySelector('input.dropdown-input-field');
+
+ expect(inputEl.getAttribute('placeholder')).toBe(componentConfig.placeholderText);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/dropdown/mock_data.js b/spec/javascripts/vue_shared/components/dropdown/mock_data.js
new file mode 100644
index 00000000000..b09d42da401
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/dropdown/mock_data.js
@@ -0,0 +1,11 @@
+export const mockLabels = [
+ {
+ id: 26,
+ title: 'Foo Label',
+ description: 'Foobar',
+ color: '#BADA55',
+ text_color: '#FFFFFF',
+ },
+];
+
+export default mockLabels;