summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js6
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js20
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/boards/issue_card_spec.js6
-rw-r--r--spec/javascripts/boards/list_spec.js4
-rw-r--r--spec/javascripts/boards/mock_data.js1
-rw-r--r--spec/javascripts/boards/utils/query_data_spec.js27
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js6
-rw-r--r--spec/javascripts/commit_merge_requests_spec.js60
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js106
-rw-r--r--spec/javascripts/cycle_analytics/banner_spec.js3
-rw-r--r--spec/javascripts/cycle_analytics/total_time_component_spec.js6
-rw-r--r--spec/javascripts/deploy_keys/components/app_spec.js1
-rw-r--r--spec/javascripts/deploy_keys/components/key_spec.js20
-rw-r--r--spec/javascripts/environments/environments_app_spec.js1
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js1
-rw-r--r--spec/javascripts/fixtures/create_item_dropdown.html.haml13
-rw-r--r--spec/javascripts/fixtures/pipelines.html.haml4
-rw-r--r--spec/javascripts/fixtures/search.rb18
-rw-r--r--spec/javascripts/flash_spec.js12
-rw-r--r--spec/javascripts/fly_out_nav_spec.js32
-rw-r--r--spec/javascripts/groups/components/app_spec.js66
-rw-r--r--spec/javascripts/groups/components/item_actions_spec.js46
-rw-r--r--spec/javascripts/helpers/class_spec_helper_spec.js20
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js35
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js2
-rw-r--r--spec/javascripts/job_spec.js5
-rw-r--r--spec/javascripts/jobs/header_spec.js37
-rw-r--r--spec/javascripts/jobs/job_details_mediator_spec.js8
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js10
-rw-r--r--spec/javascripts/merge_request_notes_spec.js1
-rw-r--r--spec/javascripts/merge_request_spec.js22
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js2
-rw-r--r--spec/javascripts/monitoring/graph/deployment_spec.js144
-rw-r--r--spec/javascripts/monitoring/graph/flag_spec.js114
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js12
-rw-r--r--spec/javascripts/notebook/cells/output/html_sanitize_tests.js66
-rw-r--r--spec/javascripts/notebook/cells/output/html_spec.js29
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js10
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js1
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js9
-rw-r--r--spec/javascripts/notes/components/noteable_note_spec.js2
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes_spec.js1
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js2
-rw-r--r--spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js (renamed from spec/javascripts/abuse_reports_spec.js)2
-rw-r--r--spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js63
-rw-r--r--spec/javascripts/pipelines/async_button_spec.js6
-rw-r--r--spec/javascripts/pipelines/empty_state_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js8
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js15
-rw-r--r--spec/javascripts/pipelines/nav_controls_spec.js27
-rw-r--r--spec/javascripts/pipelines/pipeline_details_mediator_spec.js1
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js1
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js7
-rw-r--r--spec/javascripts/pipelines/pipelines_table_spec.js5
-rw-r--r--spec/javascripts/pipelines/stage_spec.js1
-rw-r--r--spec/javascripts/profile/account/components/delete_account_modal_spec.js8
-rw-r--r--spec/javascripts/projects/project_new_spec.js32
-rw-r--r--spec/javascripts/registry/components/app_spec.js3
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js2
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_item_spec.js2
-rw-r--r--spec/javascripts/repo/components/ide_repo_tree_spec.js6
-rw-r--r--spec/javascripts/repo/components/ide_spec.js4
-rw-r--r--spec/javascripts/repo/components/new_dropdown/index_spec.js13
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js2
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js8
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js2
-rw-r--r--spec/javascripts/repo/stores/actions/file_spec.js31
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js27
-rw-r--r--spec/javascripts/repo/stores/mutations/file_spec.js2
-rw-r--r--spec/javascripts/repo/stores/mutations/tree_spec.js2
-rw-r--r--spec/javascripts/search_spec.js40
-rw-r--r--spec/javascripts/sidebar/mock_data.js2
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js1
-rw-r--r--spec/javascripts/sidebar/sidebar_move_issue_spec.js1
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js12
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js9
-rw-r--r--spec/javascripts/smart_interval_spec.js1
-rw-r--r--spec/javascripts/test_bundle.js2
-rw-r--r--spec/javascripts/todos_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js115
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js37
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js57
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js36
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js106
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js138
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js53
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js1
-rw-r--r--spec/javascripts/vue_shared/components/clipboard_button_spec.js31
-rw-r--r--spec/javascripts/vue_shared/components/expand_button_spec.js32
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js81
-rw-r--r--spec/javascripts/vue_shared/components/loading_icon_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/modal_spec.js62
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js9
-rw-r--r--spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js77
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js1
-rw-r--r--spec/javascripts/zen_mode_spec.js2
106 files changed, 1551 insertions, 660 deletions
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index df1b2c9960b..a143fc827d5 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -45,7 +45,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -96,7 +96,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -127,7 +127,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 4e73fa1fe87..80a598e63bd 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -55,7 +55,7 @@ describe('Board card', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('returns false when detailIssue is empty', () => {
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 7c5888b6d82..a5fcb10b9dd 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -60,7 +60,7 @@ describe('Board list component', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('renders component', () => {
@@ -154,6 +154,18 @@ describe('Board list component', () => {
});
});
+ it('sets data attribute with invalid id', (done) => {
+ component.showCount = true;
+
+ Vue.nextTick(() => {
+ expect(
+ component.$el.querySelector('.board-list-count').getAttribute('data-issue-id'),
+ ).toBe('-1');
+
+ done();
+ });
+ });
+
it('shows how many more issues to load', (done) => {
component.showCount = true;
component.list.issuesSize = 20;
@@ -172,9 +184,9 @@ describe('Board list component', () => {
component.$refs.list.style.height = '100px';
component.$refs.list.style.overflow = 'scroll';
- for (let i = 0; i < 19; i += 1) {
- const issue = component.list.issues[0];
- issue.id += 1;
+ for (let i = 1; i < 20; i += 1) {
+ const issue = Object.assign({}, component.list.issues[0]);
+ issue.id += i;
component.list.issues.push(issue);
}
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index c62c537841c..e204985f039 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -58,7 +58,7 @@ describe('Issue boards new issue form', () => {
afterEach(() => {
vm.$destroy();
- mock.reset();
+ mock.restore();
});
it('calls submit if submit button is clicked', (done) => {
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 49fb20f4c84..8411f4dd8a6 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -35,7 +35,7 @@ describe('Store', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('starts with a blank state', () => {
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 8ef221257be..278155c585e 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -45,6 +45,9 @@ describe('Issue card component', () => {
component = new Vue({
el: document.querySelector('.test-container'),
+ components: {
+ 'issue-card': gl.issueBoards.IssueCardInner,
+ },
data() {
return {
list,
@@ -53,9 +56,6 @@ describe('Issue card component', () => {
rootPath: '/',
};
},
- components: {
- 'issue-card': gl.issueBoards.IssueCardInner,
- },
template: `
<issue-card
:issue="issue"
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 645ce831b53..34964b20b05 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -5,7 +5,7 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-
+import _ from 'underscore';
import '~/boards/models/issue';
import '~/boards/models/label';
import '~/boards/models/list';
@@ -30,7 +30,7 @@ describe('List model', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('gets issues when created', (done) => {
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 9ae2d535398..0671facb285 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,5 +1,6 @@
/* global BoardService */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
+import _ from 'underscore';
export const listObj = {
id: 300,
diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js
new file mode 100644
index 00000000000..922215ffc1d
--- /dev/null
+++ b/spec/javascripts/boards/utils/query_data_spec.js
@@ -0,0 +1,27 @@
+import queryData from '~/boards/utils/query_data';
+
+describe('queryData', () => {
+ it('parses path for label with trailing +', () => {
+ expect(
+ queryData('label_name[]=label%2B', {}),
+ ).toEqual({
+ label_name: ['label+'],
+ });
+ });
+
+ it('parses path for milestone with trailing +', () => {
+ expect(
+ queryData('milestone_title=A%2B', {}),
+ ).toEqual({
+ milestone_title: 'A+',
+ });
+ });
+
+ it('parses path for search terms with spaces', () => {
+ expect(
+ queryData('search=two+words', {}),
+ ).toEqual({
+ search: 'two words',
+ });
+ });
+});
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 9fc047b1f5e..0afe09d87bc 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
@@ -9,9 +10,10 @@ describe('Pipelines table in Commits and Merge requests', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTable = Vue.extend(pipelinesTable);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTable = Vue.extend(pipelinesTable);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('successful request', () => {
diff --git a/spec/javascripts/commit_merge_requests_spec.js b/spec/javascripts/commit_merge_requests_spec.js
new file mode 100644
index 00000000000..3466ef51ea8
--- /dev/null
+++ b/spec/javascripts/commit_merge_requests_spec.js
@@ -0,0 +1,60 @@
+import * as CommitMergeRequests from '~/commit_merge_requests';
+
+describe('CommitMergeRequests', () => {
+ describe('createContent', () => {
+ it('should return created content', () => {
+ const content1 = CommitMergeRequests.createContent([{ iid: 1, path: '/path1', title: 'foo' }, { iid: 2, path: '/path2', title: 'baz' }])[0];
+ expect(content1.tagName).toEqual('SPAN');
+ expect(content1.childElementCount).toEqual(4);
+
+ const content2 = CommitMergeRequests.createContent([])[0];
+ expect(content2.tagName).toEqual('SPAN');
+ expect(content2.childElementCount).toEqual(0);
+ expect(content2.innerText).toEqual('No related merge requests found');
+ });
+ });
+
+ describe('getHeaderText', () => {
+ it('should return header text', () => {
+ expect(CommitMergeRequests.getHeaderText(0, 1)).toEqual('1 merge request');
+ expect(CommitMergeRequests.getHeaderText(0, 2)).toEqual('2 merge requests');
+ expect(CommitMergeRequests.getHeaderText(1, 1)).toEqual(',');
+ expect(CommitMergeRequests.getHeaderText(1, 2)).toEqual(',');
+ });
+ });
+
+ describe('createHeader', () => {
+ it('should return created header', () => {
+ const header = CommitMergeRequests.createHeader(0, 1)[0];
+ expect(header.tagName).toEqual('SPAN');
+ expect(header.innerText).toEqual('1 merge request');
+ });
+ });
+
+ describe('createItem', () => {
+ it('should return created item', () => {
+ const item = CommitMergeRequests.createItem({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(item.tagName).toEqual('SPAN');
+ expect(item.childElementCount).toEqual(2);
+ expect(item.children[0].tagName).toEqual('A');
+ expect(item.children[1].tagName).toEqual('SPAN');
+ });
+ });
+
+ describe('createLink', () => {
+ it('should return created link', () => {
+ const link = CommitMergeRequests.createLink({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(link.tagName).toEqual('A');
+ expect(link.href).toMatch(/\/path$/);
+ expect(link.innerText).toEqual('!1');
+ });
+ });
+
+ describe('createTitle', () => {
+ it('should return created title', () => {
+ const title = CommitMergeRequests.createTitle({ iid: 1, path: '/path', title: 'foo' })[0];
+ expect(title.tagName).toEqual('SPAN');
+ expect(title.innerText).toEqual('foo');
+ });
+ });
+});
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
new file mode 100644
index 00000000000..c8b00a4f553
--- /dev/null
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -0,0 +1,106 @@
+import CreateItemDropdown from '~/create_item_dropdown';
+
+const DROPDOWN_ITEM_DATA = [{
+ title: 'one',
+ id: 'one',
+ text: 'one',
+}, {
+ title: 'two',
+ id: 'two',
+ text: 'two',
+}, {
+ title: 'three',
+ id: 'three',
+ text: 'three',
+}];
+
+describe('CreateItemDropdown', () => {
+ preloadFixtures('static/create_item_dropdown.html.raw');
+
+ let $wrapperEl;
+
+ beforeEach(() => {
+ loadFixtures('static/create_item_dropdown.html.raw');
+ $wrapperEl = $('.js-create-item-dropdown-fixture-root');
+
+ // eslint-disable-next-line no-new
+ new CreateItemDropdown({
+ $dropdown: $wrapperEl.find('.js-dropdown-menu-toggle'),
+ defaultToggleLabel: 'All variables',
+ fieldName: 'variable[environment]',
+ getData: (term, callback) => {
+ callback(DROPDOWN_ITEM_DATA);
+ },
+ });
+ });
+
+ afterEach(() => {
+ $wrapperEl.remove();
+ });
+
+ it('should have a dropdown item for each piece of data', () => {
+ // Get the data in the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+
+ describe('created items', () => {
+ const NEW_ITEM_TEXT = 'foobarbaz';
+
+ function createItemAndClearInput(text) {
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(text)
+ .trigger('input');
+
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ // Clear out the filter
+ $wrapperEl.find('.dropdown-input-field')
+ .val('')
+ .trigger('input');
+ }
+
+ beforeEach(() => {
+ // Open the dropdown
+ $('.js-dropdown-menu-toggle').click();
+
+ // Filter for the new item
+ $wrapperEl.find('.dropdown-input-field')
+ .val(NEW_ITEM_TEXT)
+ .trigger('input');
+ });
+
+ it('create new item button should include the filter text', () => {
+ expect($wrapperEl.find('.js-dropdown-create-new-item code').text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should update the dropdown with the newly created item', () => {
+ // Create the new item
+ const $createButton = $wrapperEl.find('.js-dropdown-create-new-item');
+ $createButton.click();
+
+ expect($wrapperEl.find('.dropdown-toggle-text').text()).toEqual(NEW_ITEM_TEXT);
+ expect($wrapperEl.find('input[name="variable[environment]"]').val()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should include newly created item in dropdown list', () => {
+ createItemAndClearInput(NEW_ITEM_TEXT);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(1 + DROPDOWN_ITEM_DATA.length);
+ expect($($itemEls.get(DROPDOWN_ITEM_DATA.length)).text()).toEqual(NEW_ITEM_TEXT);
+ });
+
+ it('should not duplicate an item when trying to create an existing item', () => {
+ createItemAndClearInput(DROPDOWN_ITEM_DATA[0].text);
+
+ const $itemEls = $wrapperEl.find('.js-dropdown-content a');
+ expect($itemEls.length).toEqual(DROPDOWN_ITEM_DATA.length);
+ });
+ });
+});
diff --git a/spec/javascripts/cycle_analytics/banner_spec.js b/spec/javascripts/cycle_analytics/banner_spec.js
index fb6b7fee168..64a76a6ee5f 100644
--- a/spec/javascripts/cycle_analytics/banner_spec.js
+++ b/spec/javascripts/cycle_analytics/banner_spec.js
@@ -20,8 +20,9 @@ describe('Cycle analytics banner', () => {
expect(
vm.$el.querySelector('h4').textContent.trim(),
).toEqual('Introducing Cycle Analytics');
+
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.');
expect(
vm.$el.querySelector('a').textContent.trim(),
diff --git a/spec/javascripts/cycle_analytics/total_time_component_spec.js b/spec/javascripts/cycle_analytics/total_time_component_spec.js
index 31b65fd1cde..ad0fc38a856 100644
--- a/spec/javascripts/cycle_analytics/total_time_component_spec.js
+++ b/spec/javascripts/cycle_analytics/total_time_component_spec.js
@@ -23,7 +23,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('3 days 4 hrs');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('3 days 4 hrs');
});
it('should render information for hours and minutes', () => {
@@ -34,7 +34,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('4 hrs 35 mins');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('4 hrs 35 mins');
});
it('should render information for seconds', () => {
@@ -44,7 +44,7 @@ describe('Total time component', () => {
},
});
- expect(vm.$el.textContent.trim()).toEqual('45 s');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('45 s');
});
});
diff --git a/spec/javascripts/deploy_keys/components/app_spec.js b/spec/javascripts/deploy_keys/components/app_spec.js
index 0ca9290d3d2..b870f87eab9 100644
--- a/spec/javascripts/deploy_keys/components/app_spec.js
+++ b/spec/javascripts/deploy_keys/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import eventHub from '~/deploy_keys/eventhub';
import deployKeysApp from '~/deploy_keys/components/app.vue';
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index 2f28c5bbf01..b7aadf604a4 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -53,18 +53,24 @@ describe('Deploy keys key', () => {
).toBe('Remove');
});
- it('shows write access text when key has write access', (done) => {
- vm.deployKey.can_push = true;
+ it('shows write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = true;
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.write-access-allowed'),
- ).not.toBeNull();
-
- expect(
- vm.$el.querySelector('.write-access-allowed').textContent.trim(),
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
).toBe('Write access allowed');
+ done();
+ });
+ });
+
+ it('does not show write access title when key has write access', (done) => {
+ vm.deployKey.deploy_keys_projects[0].can_push = false;
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
+ ).toBe('Read access only');
done();
});
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index d02adb25b4e..a41a4e5a3f7 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsComponent from '~/environments/components/environments_app.vue';
import { environment, folder } from './mock_data';
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 4ea4d9d7499..a085074d312 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 2ecb64d84b5..0684c3498a2 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
diff --git a/spec/javascripts/fixtures/create_item_dropdown.html.haml b/spec/javascripts/fixtures/create_item_dropdown.html.haml
new file mode 100644
index 00000000000..d4d91b93caf
--- /dev/null
+++ b/spec/javascripts/fixtures/create_item_dropdown.html.haml
@@ -0,0 +1,13 @@
+.js-create-item-dropdown-fixture-root
+ %input{ name: 'variable[environment]', type: 'hidden' }
+ = dropdown_tag('some label',
+ options: { toggle_class: 'js-dropdown-menu-toggle',
+ content_class: 'js-dropdown-content',
+ filter: true,
+ dropdown_class: "dropdown-menu-selectable",
+ footer_content: true }) do
+ %ul.dropdown-footer-list
+ %li
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item" }
+ Create wildcard
+ %code
diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml
index 85ee61f0b54..0161c0550d1 100644
--- a/spec/javascripts/fixtures/pipelines.html.haml
+++ b/spec/javascripts/fixtures/pipelines.html.haml
@@ -7,4 +7,6 @@
"new-pipeline-path" => 'foo',
"can-create-pipeline" => 'true',
"has-ci" => 'foo',
- "ci-lint-path" => 'foo' } }
+ "ci-lint-path" => 'foo',
+ "reset-cache-path" => 'foo' } }
+
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
new file mode 100644
index 00000000000..703cd3d49fa
--- /dev/null
+++ b/spec/javascripts/fixtures/search.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe SearchController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('search/')
+ end
+
+ it 'search/show.html.raw' do |example|
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index 97e3ab682c5..7198dbd4cf2 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -183,11 +183,15 @@ describe('Flash', () => {
});
it('adds flash element into container', () => {
- flash('test');
+ flash('test', 'alert', document, null, false, true);
expect(
document.querySelector('.flash-alert'),
).not.toBeNull();
+
+ expect(
+ document.body.className,
+ ).toContain('flash-shown');
});
it('adds flash into specified parent', () => {
@@ -220,13 +224,17 @@ describe('Flash', () => {
});
it('removes element after clicking', () => {
- flash('test', 'alert', document, null, false);
+ flash('test', 'alert', document, null, false, true);
document.querySelector('.flash-alert').click();
expect(
document.querySelector('.flash-alert'),
).toBeNull();
+
+ expect(
+ document.body.className,
+ ).not.toContain('flash-shown');
});
describe('with actionConfig', () => {
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index a3fa07d5bc2..eb9330f5e5b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -167,30 +167,26 @@ describe('Fly out sidebar navigation', () => {
describe('mouseEnterTopItems', () => {
beforeEach(() => {
- jasmine.clock().install();
-
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
});
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('shows sub-items after 0ms if no menu is open', () => {
+ it('shows sub-items after 0ms if no menu is open', (done) => {
mouseEnterTopItems(el);
expect(
getHideSubItemsInterval(),
).toBe(0);
- jasmine.clock().tick(0);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
- it('shows sub-items after 300ms if a menu is currently open', () => {
+ it('shows sub-items after 300ms if a menu is currently open', (done) => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
@@ -203,17 +199,19 @@ describe('Fly out sidebar navigation', () => {
clientY: el.getBoundingClientRect().top + 10,
});
- mouseEnterTopItems(el);
+ mouseEnterTopItems(el, 0);
expect(
getHideSubItemsInterval(),
).toBe(300);
- jasmine.clock().tick(300);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
});
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 97e39f6411b..8338efe915b 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -256,6 +256,36 @@ describe('AppComponent', () => {
});
});
+ describe('showLeaveGroupModal', () => {
+ it('caches candidate group (as props) which is to be left', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.targetGroup).toBe(null);
+ expect(vm.targetParentGroup).toBe(null);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.targetGroup).not.toBe(null);
+ expect(vm.targetParentGroup).not.toBe(null);
+ });
+
+ it('updates props which show modal confirmation dialog', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.groupLeaveConfirmationMessage).toBe('');
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
+ });
+ });
+
+ describe('hideLeaveGroupModal', () => {
+ it('hides modal confirmation which is shown before leaving the group', () => {
+ const group = Object.assign({}, mockParentGroupItem);
+ vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.showModal).toBeTruthy();
+ vm.hideLeaveGroupModal();
+ expect(vm.showModal).toBeFalsy();
+ });
+ });
+
describe('leaveGroup', () => {
let groupItem;
let childGroupItem;
@@ -265,21 +295,24 @@ describe('AppComponent', () => {
groupItem.children = mockChildren;
childGroupItem = groupItem.children[0];
groupItem.isChildrenLoading = false;
+ vm.targetGroup = childGroupItem;
+ vm.targetParentGroup = groupItem;
});
- it('should leave group and remove group item from tree', (done) => {
+ it('hides modal confirmation leave group and remove group item from tree', (done) => {
const notice = `You left the "${childGroupItem.fullName}" group.`;
spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice }));
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
spyOn($, 'scrollTo');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
- expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
+ vm.leaveGroup();
+ expect(vm.showModal).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
setTimeout(() => {
expect($.scrollTo).toHaveBeenCalledWith(0);
- expect(vm.store.removeGroup).toHaveBeenCalledWith(childGroupItem, groupItem);
+ expect(vm.store.removeGroup).toHaveBeenCalledWith(vm.targetGroup, vm.targetParentGroup);
expect(window.Flash).toHaveBeenCalledWith(notice, 'notice');
done();
}, 0);
@@ -291,13 +324,13 @@ describe('AppComponent', () => {
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
- vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ vm.leaveGroup();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -309,12 +342,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash');
vm.leaveGroup(childGroupItem, groupItem);
- expect(childGroupItem.isBeingRemoved).toBeTruthy();
+ expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(childGroupItem.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
done();
}, 0);
});
@@ -364,7 +397,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
newVm.$destroy();
@@ -404,7 +437,7 @@ describe('AppComponent', () => {
Vue.nextTick(() => {
expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', jasmine.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('leaveGroup', jasmine.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', jasmine.any(Function));
expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', jasmine.any(Function));
done();
@@ -439,5 +472,14 @@ describe('AppComponent', () => {
done();
});
});
+
+ it('renders modal confirmation dialog', () => {
+ vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
+ vm.showModal = true;
+ const modalDialogEl = vm.$el.querySelector('.modal');
+ expect(modalDialogEl).not.toBe(null);
+ expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
+ expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+ });
});
});
diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js
index 7a5c1da4d1d..acccbe639c4 100644
--- a/spec/javascripts/groups/components/item_actions_spec.js
+++ b/spec/javascripts/groups/components/item_actions_spec.js
@@ -26,38 +26,12 @@ describe('ItemActionsComponent', () => {
vm.$destroy();
});
- describe('computed', () => {
- describe('leaveConfirmationMessage', () => {
- it('should return appropriate string for leave group confirmation', () => {
- expect(vm.leaveConfirmationMessage).toBe('Are you sure you want to leave the "platform / hardware" group?');
- });
- });
- });
-
describe('methods', () => {
describe('onLeaveGroup', () => {
- it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => {
- expect(vm.modalStatus).toBeFalsy();
- vm.onLeaveGroup();
- expect(vm.modalStatus).toBeTruthy();
- });
- });
-
- describe('leaveGroup', () => {
- it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
- spyOn(eventHub, '$emit');
- vm.modalStatus = true;
- vm.leaveGroup(true);
- expect(vm.modalStatus).toBeFalsy();
- expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
- });
-
- it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => {
+ it('emits `showLeaveGroupModal` event with `group` and `parentGroup` props', () => {
spyOn(eventHub, '$emit');
- vm.modalStatus = true;
- vm.leaveGroup(false);
- expect(vm.modalStatus).toBeFalsy();
- expect(eventHub.$emit).not.toHaveBeenCalled();
+ vm.onLeaveGroup();
+ expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', vm.group, vm.parentGroup);
});
});
});
@@ -78,7 +52,8 @@ describe('ItemActionsComponent', () => {
expect(editBtn.getAttribute('href')).toBe(group.editPath);
expect(editBtn.getAttribute('aria-label')).toBe('Edit group');
expect(editBtn.dataset.originalTitle).toBe('Edit group');
- expect(editBtn.querySelector('i.fa.fa-cogs')).toBeDefined();
+ expect(editBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(editBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#settings');
newVm.$destroy();
});
@@ -94,17 +69,10 @@ describe('ItemActionsComponent', () => {
expect(leaveBtn.getAttribute('href')).toBe(group.leavePath);
expect(leaveBtn.getAttribute('aria-label')).toBe('Leave this group');
expect(leaveBtn.dataset.originalTitle).toBe('Leave this group');
- expect(leaveBtn.querySelector('i.fa.fa-sign-out')).toBeDefined();
+ expect(leaveBtn.querySelectorAll('svg use').length).not.toBe(0);
+ expect(leaveBtn.querySelector('svg use').getAttribute('xlink:href')).toContain('#leave');
newVm.$destroy();
});
-
- it('should show modal dialog when `modalStatus` is set to `true`', () => {
- vm.modalStatus = true;
- const modalDialogEl = vm.$el.querySelector('.modal');
- expect(modalDialogEl).toBeDefined();
- expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
- expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
- });
});
});
diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js b/spec/javascripts/helpers/class_spec_helper_spec.js
index 686b8eaed31..1415ffb7eb3 100644
--- a/spec/javascripts/helpers/class_spec_helper_spec.js
+++ b/spec/javascripts/helpers/class_spec_helper_spec.js
@@ -3,7 +3,7 @@
import './class_spec_helper';
describe('ClassSpecHelper', () => {
- describe('itShouldBeAStaticMethod', function () {
+ describe('itShouldBeAStaticMethod', () => {
beforeEach(() => {
class TestClass {
instanceMethod() { this.prop = 'val'; }
@@ -14,23 +14,5 @@ describe('ClassSpecHelper', () => {
});
ClassSpecHelper.itShouldBeAStaticMethod(ClassSpecHelper, 'itShouldBeAStaticMethod');
-
- it('should have a defined spec', () => {
- expect(ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod').description).toBe('should be a static method');
- });
-
- it('should pass for a static method', () => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod');
- expect(spec.status()).toBe('passed');
- });
-
- it('should fail for an instance method', (done) => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'instanceMethod');
- spec.resultCallback = (result) => {
- expect(result.status).toBe('failed');
- done();
- };
- spec.execute();
- });
});
});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 1454ca52018..1c9f48028f2 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -59,7 +59,7 @@ describe('Issuable output', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
realtimeRequestCount = 0;
vm.poll.stop();
@@ -218,6 +218,39 @@ describe('Issuable output', () => {
});
});
+ describe('shows dialog when issue has unsaved changed', () => {
+ it('confirms on title change', (done) => {
+ vm.showForm = true;
+ vm.state.titleText = 'title has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('confirms on description change', (done) => {
+ vm.showForm = true;
+ vm.state.descriptionText = 'description has changed';
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).not.toBeNull();
+ done();
+ });
+ });
+
+ it('does nothing when nothing has changed', (done) => {
+ const e = { returnValue: null };
+ vm.handleBeforeUnloadEvent(e);
+ Vue.nextTick(() => {
+ expect(e.returnValue).toBeNull();
+ done();
+ });
+ });
+ });
+
describe('error when updating', () => {
beforeEach(() => {
spyOn(window, 'Flash').and.callThrough();
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
index 2b7ee65094b..30441faf844 100644
--- a/spec/javascripts/issue_show/components/fields/description_template_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Issue description template component', () => {
let vm;
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index 000b53af016..50ce019c32a 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
import formComponent from '~/issue_show/components/form.vue';
-import '~/templates/issuable_template_selector';
-import '~/templates/issuable_template_selectors';
describe('Inline edit form component', () => {
let vm;
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index b740c9ed893..feb341d22e6 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -52,11 +52,6 @@ describe('Job', () => {
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
-
- it('displays the remove date correctly', () => {
- const removeDateElement = document.querySelector('.js-artifacts-remove');
- expect(removeDateElement.innerText.trim()).toBe('1 year remaining');
- });
});
describe('running build', () => {
diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js
index 4a210faa017..a9df0418d5d 100644
--- a/spec/javascripts/jobs/header_spec.js
+++ b/spec/javascripts/jobs/header_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import headerComponent from '~/jobs/components/header.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Job details header', () => {
let HeaderComponent;
@@ -30,27 +31,45 @@ describe('Job details header', () => {
email: 'foo@bar.com',
avatar_url: 'link',
},
+ started: '2018-01-08T09:48:27.319Z',
new_issue_path: 'path',
},
isLoading: false,
};
- vm = new HeaderComponent({ propsData: props }).$mount();
+ vm = mountComponent(HeaderComponent, props);
});
afterEach(() => {
vm.$destroy();
});
- it('should render provided job information', () => {
- expect(
- vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
- ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ describe('triggered job', () => {
+ beforeEach(() => {
+ vm = mountComponent(HeaderComponent, props);
+ });
+
+ it('should render provided job information', () => {
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
+ });
+
+ it('should render new issue link', () => {
+ expect(
+ vm.$el.querySelector('.js-new-issue').getAttribute('href'),
+ ).toEqual(props.job.new_issue_path);
+ });
});
- it('should render new issue link', () => {
- expect(
- vm.$el.querySelector('.js-new-issue').getAttribute('href'),
- ).toEqual(props.job.new_issue_path);
+ describe('created job', () => {
+ it('should render created key', () => {
+ props.job.started = false;
+ vm = mountComponent(HeaderComponent, props);
+
+ expect(
+ vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
+ ).toEqual('failed Job #123 created 3 weeks ago by Foo');
+ });
});
});
diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js
index 3069a0cd60e..ca5c9cf87e4 100644
--- a/spec/javascripts/jobs/job_details_mediator_spec.js
+++ b/spec/javascripts/jobs/job_details_mediator_spec.js
@@ -12,6 +12,10 @@ describe('JobMediator', () => {
mock = new MockAdapter(axios);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should set defaults', () => {
expect(mediator.store).toBeDefined();
expect(mediator.service).toBeDefined();
@@ -24,10 +28,6 @@ describe('JobMediator', () => {
mock.onGet().reply(200, job, {});
});
- afterEach(() => {
- mock.restore();
- });
-
it('should store received data', (done) => {
mediator.fetchJob();
setTimeout(() => {
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 1f46c225071..69a23d7b2f3 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -62,4 +62,14 @@ describe('text_utility', () => {
expect(textUtils.slugify('João')).toEqual('joão');
});
});
+
+ describe('stripHtml', () => {
+ it('replaces html tag with the default replacement', () => {
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual('This is a text with html.');
+ });
+
+ it('replaces html tags with the provided replacement', () => {
+ expect(textUtils.stripHtml('This is a text with <p>html</p>.', ' ')).toEqual('This is a text with html .');
+ });
+ });
});
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
index e983e4de3fc..5d0ee91d977 100644
--- a/spec/javascripts/merge_request_notes_spec.js
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 2f02c11482f..bae3219b043 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -19,17 +19,24 @@ import IssuablesHelper from '~/helpers/issuables_helper';
$('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent);
return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- return it('submits an ajax request on tasklist:changed', function() {
- spyOn(jQuery, 'ajax').and.callFake(function(req) {
+
+ it('submits an ajax request on tasklist:changed', (done) => {
+ spyOn(jQuery, 'ajax').and.callFake((req) => {
expect(req.type).toBe('PATCH');
expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`);
- return expect(req.data.merge_request.description).not.toBe(null);
+ expect(req.data.merge_request.description).not.toBe(null);
+ done();
});
- return $('.js-task-list-field').trigger('tasklist:changed');
+
+ $('.js-task-list-field').trigger('tasklist:changed');
});
});
describe('class constructor', () => {
+ beforeEach(() => {
+ spyOn(jQuery, 'ajax').and.stub();
+ });
+
it('calls .initCloseReopenReport', () => {
spyOn(IssuablesHelper, 'initCloseReopenReport');
@@ -63,8 +70,8 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ new MergeRequest(); // eslint-disable-line no-new
+ MergeRequest.hideCloseButton();
});
it('hides the dropdown close item and selects the next item', () => {
@@ -83,8 +90,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
beforeEach(() => {
loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
this.el = document.querySelector('.js-issuable-actions');
- const merge = new MergeRequest();
- merge.hideCloseButton();
+ MergeRequest.hideCloseButton();
});
it('hides the close button', () => {
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 9885b8a790f..eb8f6bbe50d 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -38,7 +38,7 @@ describe('Dashboard', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('shows up a loading state', (done) => {
diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js
index bf6ada8185e..d07db871d69 100644
--- a/spec/javascripts/monitoring/graph/deployment_spec.js
+++ b/spec/javascripts/monitoring/graph/deployment_spec.js
@@ -11,168 +11,38 @@ const createComponent = (propsData) => {
};
describe('MonitoringDeployment', () => {
- const reducedDeploymentData = [deploymentData[0]];
- reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name;
- reducedDeploymentData[0].xPos = 10;
- reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at);
describe('Methods', () => {
- it('refText shows the ref when a tag is available', () => {
- reducedDeploymentData[0].tag = '1.0';
- const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(
- component.refText(reducedDeploymentData[0]),
- ).toEqual(reducedDeploymentData[0].ref);
- });
-
- it('refText shows the sha when no tag is available', () => {
- reducedDeploymentData[0].tag = null;
- const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.refText(reducedDeploymentData[0]),
- ).toContain('f5bcd1');
- });
-
- it('nameDeploymentClass creates a class with the prefix deploy-info-', () => {
+ it('should contain a hidden gradient', () => {
const component = createComponent({
- showDeployInfo: false,
- deploymentData: reducedDeploymentData,
+ showDeployInfo: true,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
});
- expect(
- component.nameDeploymentClass(reducedDeploymentData[0]),
- ).toContain('deploy-info');
+ expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
});
it('transformDeploymentGroup translates an available deployment', () => {
const component = createComponent({
showDeployInfo: false,
- deploymentData: reducedDeploymentData,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
});
expect(
- component.transformDeploymentGroup(reducedDeploymentData[0]),
+ component.transformDeploymentGroup({ xPos: 16 }),
).toContain('translate(11, 20)');
});
- it('hides the deployment flag', () => {
- reducedDeploymentData[0].showDeploymentFlag = false;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull();
- });
-
- it('positions the flag to the left when the xPos is too far right', () => {
- reducedDeploymentData[0].showDeploymentFlag = false;
- reducedDeploymentData[0].xPos = 250;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphWidth: 440,
- graphHeight: 300,
- graphHeightOffset: 120,
- });
-
- expect(
- component.positionFlag(reducedDeploymentData[0]),
- ).toBeLessThan(0);
- });
-
- it('shows the deployment flag', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelector('.js-deploy-info-box').style.display,
- ).not.toEqual('display: none;');
- });
-
- it('contains date, refs and the "deployed" text', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText('Deployed');
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText('Wed, May 31');
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text'),
- ).toContainText(component.refText(reducedDeploymentData[0]));
- });
-
- it('contains a link to the commit contents', () => {
- reducedDeploymentData[0].showDeploymentFlag = true;
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(
- component.$el.querySelectorAll('.deploy-info-text-link')[0].parentElement.getAttribute('xlink:href'),
- ).not.toEqual('');
- });
-
- it('should contain a hidden gradient', () => {
- const component = createComponent({
- showDeployInfo: true,
- deploymentData: reducedDeploymentData,
- graphHeight: 300,
- graphWidth: 440,
- graphHeightOffset: 120,
- });
-
- expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull();
- });
-
describe('Computed props', () => {
it('calculatedHeight', () => {
const component = createComponent({
showDeployInfo: true,
- deploymentData: reducedDeploymentData,
+ deploymentData,
graphHeight: 300,
graphWidth: 440,
graphHeightOffset: 120,
diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js
index 8ee1171419d..2d474e9092f 100644
--- a/spec/javascripts/monitoring/graph/flag_spec.js
+++ b/spec/javascripts/monitoring/graph/flag_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import GraphFlag from '~/monitoring/components/graph/flag.vue';
+import { deploymentData } from '../mock_data';
const createComponent = (propsData) => {
const Component = Vue.extend(GraphFlag);
@@ -9,11 +10,6 @@ const createComponent = (propsData) => {
}).$mount();
};
-function getCoordinate(component, selector, coordinate) {
- const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate);
- return parseInt(coordinateVal, 10);
-}
-
const defaultValuesComponent = {
currentXCoordinate: 200,
currentYCoordinate: 100,
@@ -25,31 +21,111 @@ const defaultValuesComponent = {
graphHeight: 300,
graphHeightOffset: 120,
showFlagContent: true,
+ realPixelRatio: 1,
+ timeSeries: [{
+ values: [{
+ time: new Date('2017-06-04T18:17:33.501Z'),
+ value: '1.49609375',
+ }],
+ }],
+ unitOfDisplay: 'ms',
+ currentDataIndex: 0,
+ legendTitle: 'Average',
+};
+
+const deploymentFlagData = {
+ ...deploymentData[0],
+ ref: deploymentData[0].ref.name,
+ xPos: 10,
+ time: new Date(deploymentData[0].created_at),
};
describe('GraphFlag', () => {
- it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => {
- const component = createComponent(defaultValuesComponent);
+ let component;
- expect(getCoordinate(component, '.selected-metric-line', 'x1'))
- .toEqual(component.currentXCoordinate);
- expect(getCoordinate(component, '.selected-metric-line', 'x2'))
- .toEqual(component.currentXCoordinate);
+ it('has a line at the currentXCoordinate', () => {
+ component = createComponent(defaultValuesComponent);
+
+ expect(component.$el.style.left)
+ .toEqual(`${70 + component.currentXCoordinate}px`);
});
- it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => {
- const component = createComponent(defaultValuesComponent);
+ describe('Deployment flag', () => {
+ it('shows a deployment flag when deployment data provided', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData,
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.popover-title'),
+ ).toContainText('Deployed');
+ });
+
+ it('contains the ref when a tag is available', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData: {
+ ...deploymentFlagData,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ tag: true,
+ ref: '1.0',
+ },
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('f5bcd1d9');
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('1.0');
+ });
+
+ it('does not contain the ref when a tag is unavailable', () => {
+ const deploymentFlagComponent = createComponent({
+ ...defaultValuesComponent,
+ deploymentFlagData: {
+ ...deploymentFlagData,
+ sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187',
+ tag: false,
+ ref: '1.0',
+ },
+ });
+
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).toContainText('f5bcd1d9');
- const svg = component.$el.querySelector('.rect-text-metric');
- expect(svg.tagName).toEqual('svg');
- expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition);
+ expect(
+ deploymentFlagComponent.$el.querySelector('.deploy-meta-content'),
+ ).not.toContainText('1.0');
+ });
});
describe('Computed props', () => {
- it('calculatedHeight', () => {
- const component = createComponent(defaultValuesComponent);
+ beforeEach(() => {
+ component = createComponent(defaultValuesComponent);
+ });
+
+ it('formatTime', () => {
+ expect(component.formatTime).toMatch(/\d:17PM/);
+ });
+
+ it('formatDate', () => {
+ expect(component.formatDate).toEqual('Sun, Jun 4');
+ });
+
+ it('cursorStyle', () => {
+ expect(component.cursorStyle).toEqual({
+ top: '20px',
+ left: '270px',
+ height: '180px',
+ });
+ });
- expect(component.calculatedHeight).toEqual(180);
+ it('flagOrientation', () => {
+ expect(component.flagOrientation).toEqual('left');
});
});
});
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
index a88e9ed3d99..02304bf5d7d 100644
--- a/spec/javascripts/notebook/cells/markdown_spec.js
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -42,6 +42,18 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
+ it('sanitizes output', (done) => {
+ Object.assign(cell, {
+ source: ['[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n'],
+ });
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('a')).toBeNull();
+
+ done();
+ });
+ });
+
describe('katex', () => {
beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json');
diff --git a/spec/javascripts/notebook/cells/output/html_sanitize_tests.js b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
new file mode 100644
index 00000000000..d587573fc9e
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_sanitize_tests.js
@@ -0,0 +1,66 @@
+export default {
+ 'protocol-based JS injection: simple, no spaces': {
+ input: '<a href="javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before': {
+ input: '<a href="javascript :alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces after': {
+ input: '<a href="javascript: alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: simple, spaces before and after': {
+ input: '<a href="javascript : alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: preceding colon': {
+ input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: UTF-8 encoding': {
+ input: '<a href="javascript&#58;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding': {
+ input: '<a href="javascript&#0058;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long UTF-8 encoding without semicolons': {
+ input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding': {
+ input: '<a href="javascript&#x3A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: long hex encoding': {
+ input: '<a href="javascript&#x003A;">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: hex encoding without semicolons': {
+ input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: null char': {
+ input: '<a href=java\0script:alert("XSS")>foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: invalid URL char': {
+ input: '<img src=java\script:alert("XSS")>', // eslint-disable-line no-useless-escape
+ output: '<img>',
+ },
+ 'protocol-based JS injection: Unicode': {
+ input: '<a href="\u0001java\u0003script:alert(\'XSS\')">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'protocol-based JS injection: spaces and entities': {
+ input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
+ output: '<a>foo</a>',
+ },
+ 'img on error': {
+ input: '<img src="x" onerror="alert(document.domain)" />',
+ output: '<img src="x">',
+ },
+};
diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js
new file mode 100644
index 00000000000..9c5385f2922
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/html_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import htmlOutput from '~/notebook/cells/output/html.vue';
+import sanitizeTests from './html_sanitize_tests';
+
+describe('html output cell', () => {
+ function createComponent(rawCode) {
+ const Component = Vue.extend(htmlOutput);
+
+ return new Component({
+ propsData: {
+ rawCode,
+ },
+ }).$mount();
+ }
+
+ describe('sanitizes output', () => {
+ Object.keys(sanitizeTests).forEach((key) => {
+ it(key, () => {
+ const test = sanitizeTests[key];
+ const vm = createComponent(test.input);
+ const outputEl = [...vm.$el.querySelectorAll('div')].pop();
+
+ expect(outputEl.innerHTML).toEqual(test.output);
+
+ vm.$destroy();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 20e352dd8bd..104d03377b6 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -139,13 +139,21 @@ describe('issue_comment_form component', () => {
});
describe('event enter', () => {
- it('should save note when cmd/ctrl+enter is pressed', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled();
});
+
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleSave').and.callThrough();
+ vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
+ vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleSave).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 7c8d6685ee1..36c56cd3862 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import notesApp from '~/notes/components/notes_app.vue';
import service from '~/notes/services/notes_service';
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 86e9e2a32a9..f841a408d09 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -69,13 +69,20 @@ describe('issue_note_form component', () => {
});
describe('enter', () => {
- it('should submit note', () => {
+ it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
+ it('should save note when ctrl+enter is pressed', () => {
+ spyOn(vm, 'handleUpdate').and.callThrough();
+ vm.$el.querySelector('textarea').value = 'Foo';
+ vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
+
+ expect(vm.handleUpdate).toHaveBeenCalled();
+ });
});
});
diff --git a/spec/javascripts/notes/components/noteable_note_spec.js b/spec/javascripts/notes/components/noteable_note_spec.js
index c8a6cb7e612..cb63b64724d 100644
--- a/spec/javascripts/notes/components/noteable_note_spec.js
+++ b/spec/javascripts/notes/components/noteable_note_spec.js
@@ -1,4 +1,4 @@
-
+import _ from 'underscore';
import Vue from 'vue';
import store from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 6b608adff15..b020a1020df 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -29,7 +29,6 @@ export const noteableDataMock = {
can_create_note: true,
can_update: true,
},
- deleted_at: null,
description: '',
due_date: null,
human_time_estimate: null,
@@ -283,7 +282,6 @@ export const loggedOutnoteableData = {
"updated_by_id": 1,
"created_at": "2017-02-07T10:11:18.395Z",
"updated_at": "2017-08-08T10:22:51.564Z",
- "deleted_at": null,
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 167f074fb9b..a40821a5693 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -1,4 +1,5 @@
/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, max-len */
+import _ from 'underscore';
import * as urlUtils from '~/lib/utils/url_utility';
import 'autosize';
import '~/gl_form';
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index f90e0093d25..b24563f738b 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -1,4 +1,4 @@
-import OAuthRememberMe from '~/oauth_remember_me';
+import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
preloadFixtures('static/oauth_remember_me.html.raw');
diff --git a/spec/javascripts/abuse_reports_spec.js b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
index 7f6b5873011..d2386077aa6 100644
--- a/spec/javascripts/abuse_reports_spec.js
+++ b/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,5 +1,5 @@
import '~/lib/utils/text_utility';
-import AbuseReports from '~/abuse_reports';
+import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
describe('Abuse Reports', () => {
const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
diff --git a/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
new file mode 100644
index 00000000000..440a6585d57
--- /dev/null
+++ b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+import mountComponent from '../../../../../helpers/vue_mount_component_helper';
+
+describe('stop_jobs_modal.vue', () => {
+ const props = {
+ url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ const Component = Vue.extend(stopJobsModal);
+ vm = mountComponent(Component, props);
+ });
+
+ describe('onSubmit', () => {
+ it('stops jobs and redirects to overview page', (done) => {
+ const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectSpy).toHaveBeenCalledWith(responseURL);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if stopping jobs failed', (done) => {
+ const dummyError = new Error('stopping jobs failed');
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(redirectSpy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js
index 48620898357..d010d897642 100644
--- a/spec/javascripts/pipelines/async_button_spec.js
+++ b/spec/javascripts/pipelines/async_button_spec.js
@@ -13,7 +13,7 @@ describe('Pipelines Async Button', () => {
propsData: {
endpoint: '/foo',
title: 'Foo',
- icon: 'fa fa-foo',
+ icon: 'repeat',
cssClass: 'bar',
},
}).$mount();
@@ -23,8 +23,8 @@ describe('Pipelines Async Button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
- it('should render the provided icon', () => {
- expect(component.$el.querySelector('i').getAttribute('class')).toContain('fa fa-foo');
+ it('should render svg icon', () => {
+ expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 6611b74594f..97f04844b3a 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -24,11 +24,11 @@ describe('Pipelines Empty State', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continous Integration can help catch bugs by running your tests automatically');
expect(
- component.$el.querySelector('p').textContent,
+ component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toContain('Continuous Deployment can help you deliver code to your product environment');
});
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 35e36e9c353..c3dc7b53d0f 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -61,14 +61,14 @@ describe('pipeline graph job component', () => {
it('it should render status and name', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4257,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
- details_path: '/root/ci-mock/builds/4256',
+ details_path: '/root/ci-mock/builds/4257',
has_details: false,
},
},
@@ -118,7 +118,7 @@ describe('pipeline graph job component', () => {
it('should not render status label when it is not provided', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4258,
name: 'test',
status: {
icon: 'icon_status_success',
@@ -132,7 +132,7 @@ describe('pipeline graph job component', () => {
it('should not render status label when it is provided', () => {
component = mountComponent(JobComponent, {
job: {
- id: 4256,
+ id: 4259,
name: 'test',
status: {
icon: 'icon_status_success',
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index 063ab53681b..f744f1af5e6 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -4,18 +4,18 @@ import stageColumnComponent from '~/pipelines/components/graph/stage_column_comp
describe('stage column component', () => {
let component;
const mockJob = {
- id: 4256,
+ id: 4250,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
- details_path: '/root/ci-mock/builds/4256',
+ details_path: '/root/ci-mock/builds/4250',
action: {
icon: 'retry',
title: 'Retry',
- path: '/root/ci-mock/builds/4256/retry',
+ path: '/root/ci-mock/builds/4250/retry',
method: 'post',
},
},
@@ -24,10 +24,17 @@ describe('stage column component', () => {
beforeEach(() => {
const StageColumnComponent = Vue.extend(stageColumnComponent);
+ const mockJobs = [];
+ for (let i = 0; i < 3; i += 1) {
+ const mockedJob = Object.assign({}, mockJob);
+ mockedJob.id += i;
+ mockJobs.push(mockedJob);
+ }
+
component = new StageColumnComponent({
propsData: {
title: 'foo',
- jobs: [mockJob, mockJob, mockJob],
+ jobs: mockJobs,
},
}).$mount();
});
diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js
index f1697840fcd..09a0c14d96c 100644
--- a/spec/javascripts/pipelines/nav_controls_spec.js
+++ b/spec/javascripts/pipelines/nav_controls_spec.js
@@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: false,
};
@@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.btn-create')).toEqual(null);
});
+ it('should render link for resetting runner caches', () => {
+ const mockData = {
+ newPipelinePath: 'foo',
+ hasCiEnabled: true,
+ helpPagePath: 'foo',
+ ciLintPath: 'foo',
+ resetCachePath: 'foo',
+ canCreatePipeline: false,
+ };
+
+ const component = new NavControlsComponent({
+ propsData: mockData,
+ }).$mount();
+
+ expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
+ expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
+ });
+
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => {
propsData: mockData,
}).$mount();
- expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint');
- expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath);
+ expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
+ expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
});
it('should render link to help page when CI is not enabled', () => {
@@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: false,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
@@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
+ resetCachePath: 'foo',
canCreatePipeline: true,
};
diff --git a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
index 9fec2f61f78..bc6413a159f 100644
--- a/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
+++ b/spec/javascripts/pipelines/pipeline_details_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import PipelineMediator from '~/pipelines/pipeline_details_mediatior';
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 367b42cefb0..a99ebc4e51a 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index a9126d2f4e9..b3cbf9aba48 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -24,9 +24,10 @@ describe('Pipelines Table Row', () => {
beforeEach(() => {
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
- pipelineWithoutAuthor = pipelines.find(p => p.id === 2);
- pipelineWithoutCommit = pipelines.find(p => p.id === 3);
+
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
+ pipelineWithoutAuthor = pipelines.find(p => p.user == null && p.commit !== null);
+ pipelineWithoutCommit = pipelines.find(p => p.user == null && p.commit == null);
});
afterEach(() => {
diff --git a/spec/javascripts/pipelines/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js
index ca2f9163313..4fc3c08145e 100644
--- a/spec/javascripts/pipelines/pipelines_table_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_spec.js
@@ -11,9 +11,10 @@ describe('Pipelines Table', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTableComponent = Vue.extend(pipelinesTableComp);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTableComponent = Vue.extend(pipelinesTableComp);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('table', () => {
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 1b96b2e3d51..61c2f783acc 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import stage from '~/pipelines/components/stage.vue';
diff --git a/spec/javascripts/profile/account/components/delete_account_modal_spec.js b/spec/javascripts/profile/account/components/delete_account_modal_spec.js
index 2e94948cfb2..588b61196a5 100644
--- a/spec/javascripts/profile/account/components/delete_account_modal_spec.js
+++ b/spec/javascripts/profile/account/components/delete_account_modal_spec.js
@@ -51,7 +51,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
- expect(submitButton).toHaveClass('disabled');
+ expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click();
expect(form.submit).not.toHaveBeenCalled();
})
@@ -68,7 +68,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
- expect(submitButton).not.toHaveClass('disabled');
+ expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click();
expect(form.submit).toHaveBeenCalled();
})
@@ -101,7 +101,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
- expect(submitButton).toHaveClass('disabled');
+ expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click();
expect(form.submit).not.toHaveBeenCalled();
})
@@ -118,7 +118,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
- expect(submitButton).not.toHaveClass('disabled');
+ expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click();
expect(form.submit).toHaveBeenCalled();
})
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index 850768f0e4f..c314ca8ab72 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -6,8 +6,12 @@ describe('New Project', () => {
beforeEach(() => {
setFixtures(`
- <input id="project_import_url" />
- <input id="project_path" />
+ <div class='toggle-import-form'>
+ <div class='import-url-data'>
+ <input id="project_import_url" />
+ <input id="project_path" />
+ </div>
+ </div>
`);
$projectImportUrl = $('#project_import_url');
@@ -25,7 +29,7 @@ describe('New Project', () => {
it('does not change project path for disabled $projectImportUrl', () => {
$projectImportUrl.attr('disabled', true);
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl);
});
@@ -38,7 +42,7 @@ describe('New Project', () => {
it('does not change project path if it is set by user', () => {
$projectPath.keyup();
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl);
});
@@ -46,7 +50,7 @@ describe('New Project', () => {
it('does not change project path for empty $projectImportUrl', () => {
$projectImportUrl.val('');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl);
});
@@ -54,7 +58,7 @@ describe('New Project', () => {
it('does not change project path for whitespace $projectImportUrl', () => {
$projectImportUrl.val(' ');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl);
});
@@ -62,7 +66,7 @@ describe('New Project', () => {
it('does not change project path for $projectImportUrl without slashes', () => {
$projectImportUrl.val('has-no-slash');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl);
});
@@ -70,7 +74,7 @@ describe('New Project', () => {
it('changes project path to last $projectImportUrl component', () => {
$projectImportUrl.val('/this/is/last');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('last');
});
@@ -78,7 +82,7 @@ describe('New Project', () => {
it('ignores trailing slashes in $projectImportUrl', () => {
$projectImportUrl.val('/has/trailing/slash/');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('slash');
});
@@ -86,7 +90,7 @@ describe('New Project', () => {
it('ignores fragment identifier in $projectImportUrl', () => {
$projectImportUrl.val('/this/has/a#fragment-identifier/');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('a');
});
@@ -94,7 +98,7 @@ describe('New Project', () => {
it('ignores query string in $projectImportUrl', () => {
$projectImportUrl.val('/url/with?query=string');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('with');
});
@@ -102,7 +106,7 @@ describe('New Project', () => {
it('ignores trailing .git in $projectImportUrl', () => {
$projectImportUrl.val('/repository.git');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('repository');
});
@@ -110,7 +114,7 @@ describe('New Project', () => {
it('changes project path for HTTPS URL in $projectImportUrl', () => {
$projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('project');
});
@@ -118,7 +122,7 @@ describe('New Project', () => {
it('changes project path for SSH URL in $projectImportUrl', () => {
$projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git');
- projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath);
+ projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('gitlab-ce');
});
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 43e7d9e1224..6a8a85e3dfb 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import registry from '~/registry/components/app.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
@@ -89,7 +90,7 @@ describe('Registry List', () => {
it('should render empty message', (done) => {
setTimeout(() => {
expect(
- vm.$el.querySelector('p').textContent.trim(),
+ vm.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
).toEqual('No container images stored for this project. Add one by following the instructions above.');
done();
}, 0);
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
index c4d3866c922..debde1bb357 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
@@ -12,7 +12,7 @@ describe('Multi-file editor commit sidebar list collapsed', () => {
vm = createComponentWithStore(Component, store);
- vm.$store.state.openFiles.push(file(), file());
+ vm.$store.state.openFiles.push(file('file1'), file('file2'));
vm.$store.state.openFiles[0].tempFile = true;
vm.$store.state.openFiles.forEach((f) => {
Object.assign(f, {
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
index fc7c9ae9dd7..4b20fdf70d6 100644
--- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
@@ -10,7 +10,7 @@ describe('Multi-file editor commit sidebar list item', () => {
beforeEach(() => {
const Component = Vue.extend(listItem);
- f = file();
+ f = file('test-file');
vm = mountComponent(Component, {
file: f,
diff --git a/spec/javascripts/repo/components/ide_repo_tree_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js
index b6f70f585cd..e3bbda514da 100644
--- a/spec/javascripts/repo/components/ide_repo_tree_spec.js
+++ b/spec/javascripts/repo/components/ide_repo_tree_spec.js
@@ -41,11 +41,11 @@ describe('IdeRepoTree', () => {
expect(tbody.querySelector('.file')).toBeTruthy();
});
- it('renders 5 loading files if tree is loading', (done) => {
- vm.$store.state.loading = true;
+ it('renders 3 loading files if tree is loading', (done) => {
+ vm.treeId = '123';
Vue.nextTick(() => {
- expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5);
+ expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3);
done();
});
diff --git a/spec/javascripts/repo/components/ide_spec.js b/spec/javascripts/repo/components/ide_spec.js
index 20b8dc25dcb..acfd63eb8de 100644
--- a/spec/javascripts/repo/components/ide_spec.js
+++ b/spec/javascripts/repo/components/ide_spec.js
@@ -10,7 +10,9 @@ describe('ide component', () => {
beforeEach(() => {
const Component = Vue.extend(ide);
- vm = createComponentWithStore(Component, store).$mount();
+ vm = createComponentWithStore(Component, store, {
+ emptyStateSvgPath: 'svg',
+ }).$mount();
});
afterEach(() => {
diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js
index b001c1655b4..6efbbf6d75e 100644
--- a/spec/javascripts/repo/components/new_dropdown/index_spec.js
+++ b/spec/javascripts/repo/components/new_dropdown/index_spec.js
@@ -57,16 +57,17 @@ describe('new dropdown component', () => {
});
});
- describe('toggleModalOpen', () => {
+ describe('hideModal', () => {
+ beforeAll((done) => {
+ vm.openModal = true;
+ Vue.nextTick(done);
+ });
+
it('closes modal after toggling', (done) => {
- vm.toggleModalOpen();
+ vm.hideModal();
Vue.nextTick()
.then(() => {
- expect(vm.$el.querySelector('.modal')).not.toBeNull();
- })
- .then(vm.toggleModalOpen)
- .then(() => {
expect(vm.$el.querySelector('.modal')).toBeNull();
})
.then(done)
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index cd93fb3ccbf..676ac09f2c9 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -29,7 +29,7 @@ describe('RepoCommitSection', () => {
comp.$store.state.rightPanelCollapsed = false;
comp.$store.state.currentBranch = 'master';
- comp.$store.state.openFiles = [file(), file()];
+ comp.$store.state.openFiles = [file('file1'), file('file2')];
comp.$store.state.openFiles.forEach(f => Object.assign(f, {
changed: true,
content: 'testing',
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index 0810da87e80..27b55ed1f87 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -25,7 +25,7 @@ describe('RepoFile', () => {
vm = new RepoFile({
store,
propsData: {
- file: file(),
+ file: file('t4'),
},
});
spyOn(vm, 'timeFormated').and.returnValue(updated);
@@ -39,7 +39,7 @@ describe('RepoFile', () => {
it('does render if hasFiles is true and is loading tree', () => {
vm = createComponent({
- file: file(),
+ file: file('t1'),
});
expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
@@ -47,7 +47,7 @@ describe('RepoFile', () => {
it('does not render commit message and datetime if mini', (done) => {
vm = createComponent({
- file: file(),
+ file: file('t2'),
});
vm.$store.state.openFiles.push(vm.file);
@@ -61,7 +61,7 @@ describe('RepoFile', () => {
it('fires clickFile when the link is clicked', () => {
vm = createComponent({
- file: file(),
+ file: file('t3'),
});
spyOn(vm, 'clickFile');
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index 507bca983df..933e8d3a06a 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -56,7 +56,7 @@ describe('RepoTab', () => {
});
it('renders an fa-circle icon if tab is changed', () => {
- const tab = file();
+ const tab = file('changedFile');
tab.changed = true;
vm = createComponent({
tab,
@@ -68,7 +68,7 @@ describe('RepoTab', () => {
describe('methods', () => {
describe('closeTab', () => {
it('does not close tab if is changed', (done) => {
- const tab = file();
+ const tab = file('closeFile');
tab.changed = true;
tab.opened = true;
vm = createComponent({
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index 0beaf643793..2c363364d70 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -4,7 +4,7 @@ import repoTabs from '~/ide/components/repo_tabs.vue';
import { file, resetStore } from '../helpers';
describe('RepoTabs', () => {
- const openedFiles = [file(), file()];
+ const openedFiles = [file('open1'), file('open2')];
let vm;
function createComponent() {
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
index 8ce01d3bf12..e2d8f002e27 100644
--- a/spec/javascripts/repo/stores/actions/file_spec.js
+++ b/spec/javascripts/repo/stores/actions/file_spec.js
@@ -18,7 +18,7 @@ describe('Multi-file store file actions', () => {
oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
- localFile = file();
+ localFile = file('testFile');
localFile.active = true;
localFile.opened = true;
localFile.parentTreeUrl = 'parentTreeUrl';
@@ -81,7 +81,7 @@ describe('Multi-file store file actions', () => {
});
it('sets next file as active', (done) => {
- const f = file();
+ const f = file('otherfile');
store.state.openFiles.push(f);
expect(f.active).toBeFalsy();
@@ -119,7 +119,7 @@ describe('Multi-file store file actions', () => {
});
it('calls scrollToTab', (done) => {
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('setThisActive'))
.then(() => {
expect(scrollToTabSpy).toHaveBeenCalled();
@@ -128,7 +128,7 @@ describe('Multi-file store file actions', () => {
});
it('sets the file active', (done) => {
- const localFile = file();
+ const localFile = file('activeFile');
store.dispatch('setFileActive', localFile)
.then(() => {
@@ -139,7 +139,7 @@ describe('Multi-file store file actions', () => {
});
it('returns early if file is already active', (done) => {
- const localFile = file();
+ const localFile = file('earlyActive');
localFile.active = true;
store.dispatch('setFileActive', localFile)
@@ -151,11 +151,11 @@ describe('Multi-file store file actions', () => {
});
it('sets current active file to not active', (done) => {
- const localFile = file();
+ const localFile = file('currentActive');
localFile.active = true;
store.state.openFiles.push(localFile);
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('newActive'))
.then(() => {
expect(localFile.active).toBeFalsy();
@@ -166,7 +166,7 @@ describe('Multi-file store file actions', () => {
it('resets location.hash for line highlighting', (done) => {
location.hash = 'test';
- store.dispatch('setFileActive', file())
+ store.dispatch('setFileActive', file('otherActive'))
.then(() => {
expect(location.hash).not.toBe('test');
@@ -176,7 +176,7 @@ describe('Multi-file store file actions', () => {
});
describe('getFileData', () => {
- let localFile = file();
+ let localFile;
beforeEach(() => {
spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
@@ -194,10 +194,17 @@ describe('Multi-file store file actions', () => {
}),
}));
- localFile = file();
+ localFile = file('newCreate');
localFile.url = 'getFileDataURL';
});
+ afterEach(() => {
+ store.dispatch('closeFile', {
+ file: localFile,
+ force: true,
+ });
+ });
+
it('calls the service', (done) => {
store.dispatch('getFileData', localFile)
.then(() => {
@@ -268,7 +275,7 @@ describe('Multi-file store file actions', () => {
beforeEach(() => {
spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
- tmpFile = file();
+ tmpFile = file('tmpFile');
});
it('calls getRawFileData service method', (done) => {
@@ -294,7 +301,7 @@ describe('Multi-file store file actions', () => {
let tmpFile;
beforeEach(() => {
- tmpFile = file();
+ tmpFile = file('tmpFile');
});
it('updates file content', (done) => {
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
index 0b0d34f072a..8d830c67290 100644
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -48,14 +48,14 @@ describe('Multi-file store actions', () => {
describe('discardAllChanges', () => {
beforeEach(() => {
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('discardAll'));
store.state.openFiles[0].changed = true;
});
});
describe('closeAllFiles', () => {
beforeEach(() => {
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('closeAll'));
store.state.openFiles[0].opened = true;
});
@@ -97,7 +97,7 @@ describe('Multi-file store actions', () => {
it('opens discard popup if there are changed files', (done) => {
store.state.editMode = true;
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('discardChanges'));
store.state.openFiles[0].changed = true;
store.dispatch('toggleEditMode')
@@ -111,7 +111,7 @@ describe('Multi-file store actions', () => {
it('can force closed if there are changed files', (done) => {
store.state.editMode = true;
- store.state.openFiles.push(file());
+ store.state.openFiles.push(file('forceClose'));
store.state.openFiles[0].changed = true;
store.dispatch('toggleEditMode', true)
@@ -124,7 +124,7 @@ describe('Multi-file store actions', () => {
});
it('discards file changes', (done) => {
- const f = file();
+ const f = file('discard');
store.state.editMode = true;
store.state.openFiles.push(f);
f.changed = true;
@@ -285,8 +285,8 @@ describe('Multi-file store actions', () => {
});
it('adds commit data to changed files', (done) => {
- const changedFile = file();
- const f = file();
+ const changedFile = file('changed');
+ const f = file('newfile');
changedFile.changed = true;
store.state.openFiles.push(changedFile, f);
@@ -300,19 +300,6 @@ describe('Multi-file store actions', () => {
}).catch(done.fail);
});
- it('closes all files', (done) => {
- store.state.openFiles.push(file());
- store.state.openFiles[0].opened = true;
-
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(Vue.nextTick)
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
-
it('scrolls to top of page', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
index 947a60587df..6e204ef0404 100644
--- a/spec/javascripts/repo/stores/mutations/file_spec.js
+++ b/spec/javascripts/repo/stores/mutations/file_spec.js
@@ -117,7 +117,7 @@ describe('Multi-file store file mutations', () => {
describe('CREATE_TMP_FILE', () => {
it('adds file into parent tree', () => {
- const f = file();
+ const f = file('tmpFile');
mutations.CREATE_TMP_FILE(localState, {
file: f,
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
index cf1248ba28b..e6ca8ea139e 100644
--- a/spec/javascripts/repo/stores/mutations/tree_spec.js
+++ b/spec/javascripts/repo/stores/mutations/tree_spec.js
@@ -57,7 +57,7 @@ describe('Multi-file store tree mutations', () => {
describe('CREATE_TMP_TREE', () => {
it('adds tree into parent tree', () => {
- const tmpEntry = file();
+ const tmpEntry = file('tmpTree');
mutations.CREATE_TMP_TREE(localState, {
tmpEntry,
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
new file mode 100644
index 00000000000..38e94d45e55
--- /dev/null
+++ b/spec/javascripts/search_spec.js
@@ -0,0 +1,40 @@
+import Api from '~/api';
+import Search from '~/pages/search/show/search';
+
+describe('Search', () => {
+ const fixturePath = 'search/show.html.raw';
+ const searchTerm = 'some search';
+ const fillDropdownInput = (dropdownSelector) => {
+ const dropdownElement = document.querySelector(dropdownSelector).parentNode;
+ const inputElement = dropdownElement.querySelector('.dropdown-input-field');
+ inputElement.value = searchTerm;
+ return inputElement;
+ };
+
+ preloadFixtures(fixturePath);
+
+ beforeEach(() => {
+ loadFixtures(fixturePath);
+ new Search(); // eslint-disable-line no-new
+ });
+
+ it('requests groups from backend when filtering', (done) => {
+ spyOn(Api, 'groups').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-group-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+
+ it('requests projects from backend when filtering', (done) => {
+ spyOn(Api, 'projects').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-project-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+});
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index 3b094d20838..7bc591d2d47 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -15,7 +15,6 @@ const RESPONSE_MAP = {
updated_by_id: 1,
created_at: '2017-02-02T21: 49: 49.664Z',
updated_at: '2017-05-03T22: 26: 03.760Z',
- deleted_at: null,
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
@@ -153,7 +152,6 @@ const RESPONSE_MAP = {
updated_by_id: 1,
created_at: '2017-06-27T19:54:42.437Z',
updated_at: '2017-08-18T03:39:49.222Z',
- deleted_at: null,
time_estimate: 0,
total_time_spent: 0,
human_time_estimate: null,
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index b97e24d9dcf..6bb6d639f24 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 9efd109b996..afa18cc127e 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import SidebarMediator from '~/sidebar/sidebar_mediator';
diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
index 8b0d51bbcc8..97f762d07a7 100644
--- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js
+++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 9b33dd02fb9..79db05f04ed 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -20,23 +20,23 @@ describe('Subscriptions', function () {
subscribed: undefined,
});
- expect(vm.$refs.loadingButton.loading).toBe(true);
- expect(vm.$refs.loadingButton.label).toBeUndefined();
+ expect(vm.$refs.toggleButton.isLoading).toBe(true);
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-loading');
});
- it('has "Subscribe" text when currently not subscribed', () => {
+ it('is toggled "off" when currently not subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: false,
});
- expect(vm.$refs.loadingButton.label).toBe('Subscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).not.toHaveClass('is-checked');
});
- it('has "Unsubscribe" text when currently not subscribed', () => {
+ it('is toggled "on" when currently subscribed', () => {
vm = mountComponent(Subscriptions, {
subscribed: true,
});
- expect(vm.$refs.loadingButton.label).toBe('Unsubscribe');
+ expect(vm.$refs.toggleButton.$el.querySelector('.project-feature-toggle')).toHaveClass('is-checked');
});
});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index a53e8a94d89..b1b03ef1e09 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,5 +1,5 @@
import AccessorUtilities from '~/lib/utils/accessor';
-import SigninTabsMemoizer from '~/signin_tabs_memoizer';
+import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
(() => {
describe('SigninTabsMemoizer', () => {
@@ -53,6 +53,13 @@ import SigninTabsMemoizer from '~/signin_tabs_memoizer';
expect(memo.readData()).toEqual('#standard');
});
+ it('overrides last selected tab with hash tag when given', () => {
+ window.location.hash = '#ldap';
+ createMemoizer();
+
+ expect(memo.readData()).toEqual('#ldap');
+ });
+
describe('class constructor', () => {
beforeEach(() => {
memo = createMemoizer();
diff --git a/spec/javascripts/smart_interval_spec.js b/spec/javascripts/smart_interval_spec.js
index 1c87fcec245..7265e1b6cb5 100644
--- a/spec/javascripts/smart_interval_spec.js
+++ b/spec/javascripts/smart_interval_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import SmartInterval from '~/smart_interval';
describe('SmartInterval', function () {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 6897c991066..2f6691df9cd 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -1,6 +1,5 @@
/* eslint-disable jasmine/no-global-setup */
import $ from 'jquery';
-import _ from 'underscore';
import 'jasmine-jquery';
import '~/commons';
@@ -31,7 +30,6 @@ jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
// globalize common libraries
window.$ = window.jQuery = $;
-window._ = _;
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 59e16f0786e..35871dddf89 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,5 +1,5 @@
import * as urlUtils from '~/lib/utils/url_utility';
-import Todos from '~/todos';
+import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
new file mode 100644
index 00000000000..66ecaa316c8
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -0,0 +1,115 @@
+import Vue from 'vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Merge request widget rebase component', () => {
+ let Component;
+ let vm;
+ beforeEach(() => {
+ Component = Vue.extend(component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('While rebasing', () => {
+ it('should show progress message', () => {
+ vm = mountComponent(Component, {
+ mr: { rebaseInProgress: true },
+ service: {},
+ });
+
+ expect(
+ vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(),
+ ).toContain('Rebase in progress');
+ });
+ });
+
+ describe('With permissions', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ },
+ service: {},
+ });
+ });
+
+ it('it should render rebase button and warning message', () => {
+ const text = vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim();
+ expect(text).toContain('Fast-forward merge is not possible.');
+ expect(text).toContain('Rebase the source branch onto the target branch or merge target');
+ expect(text).toContain('branch into source branch to allow this merge request to be merged.');
+ });
+
+ it('it should render error message when it fails', (done) => {
+ vm.rebasingError = 'Something went wrong!';
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim(),
+ ).toContain('Something went wrong!');
+ done();
+ });
+ });
+ });
+
+ describe('Without permissions', () => {
+ it('should render a message explaining user does not have permissions', () => {
+ vm = mountComponent(Component, {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: false,
+ targetBranch: 'foo',
+ },
+ service: {},
+ });
+
+ const text = vm.$el.querySelector('.rebase-state-find-class-convention span').textContent.trim();
+
+ expect(text).toContain('Fast-forward merge is not possible.');
+ expect(text).toContain('Rebase the source branch onto');
+ expect(text).toContain('foo');
+ expect(text).toContain('to allow this merge request to be merged.');
+ });
+ });
+
+ describe('methods', () => {
+ it('checkRebaseStatus', (done) => {
+ spyOn(eventHub, '$emit');
+ vm = mountComponent(Component, {
+ mr: {},
+ service: {
+ rebase() {
+ return Promise.resolve();
+ },
+ poll() {
+ return Promise.resolve({
+ data: {
+ rebase_in_progress: false,
+ merge_error: null,
+ },
+ });
+ },
+ },
+ });
+
+ vm.rebase();
+
+ // Wait for the rebase request
+ vm.$nextTick()
+ // Wait for the polling request
+ .then(vm.$nextTick())
+ // Wait for the eventHub to be called
+ .then(vm.$nextTick())
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
index 4869fb17d96..f98ebdb38e6 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_archived_spec.js
@@ -1,18 +1,31 @@
import Vue from 'vue';
-import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived';
+import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetArchived', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(archivedComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('This project is archived, write access has been disabled');
- });
+ beforeEach(() => {
+ const Component = Vue.extend(archivedComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a ci status failed icon', () => {
+ expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
+ });
+
+ it('renders a disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Merge');
+ });
+
+ it('renders information', () => {
+ expect(
+ vm.$el.querySelector('.bold').textContent.trim(),
+ ).toEqual('This project is archived, write access has been disabled');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index 6042d7384d5..95c94e95e3a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -1,32 +1,47 @@
import Vue from 'vue';
-import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed';
-
-const mergeError = 'This is the merge error';
+import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetAutoMergeFailed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = autoMergeFailedComponent.props.mr;
+ let vm;
+ const mergeError = 'This is the merge error';
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
+ beforeEach(() => {
+ const Component = Vue.extend(autoMergeFailedComponent);
+ vm = mountComponent(Component, {
+ mr: { mergeError },
});
});
- describe('template', () => {
- const Component = Vue.extend(autoMergeFailedComponent);
- const vm = new Component({
- el: document.createElement('div'),
- propsData: {
- mr: { mergeError },
- },
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders failed message', () => {
+ expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically');
+ });
+
+ it('renders merge error provided', () => {
+ expect(vm.$el.innerText).toContain(mergeError);
+ });
+
+ it('render refresh button', () => {
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh');
+ });
+
+ it('emits event and shows loading icon when button is clicked', (done) => {
+ spyOn(eventHub, '$emit');
+ vm.$el.querySelector('button').click();
+
+ expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested');
- it('should have correct elements', () => {
- expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeFalsy();
- expect(vm.$el.innerText).toContain('This merge request failed to be merged automatically');
- expect(vm.$el.innerText).toContain(mergeError);
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ expect(
+ vm.$el.querySelector('button i').classList,
+ ).toContain('fa-spinner');
+ done();
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 6b7aa935ad3..658cadddb81 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -1,19 +1,29 @@
import Vue from 'vue';
-import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking';
+import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetChecking', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(checkingComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let Component;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('Checking ability to merge automatically');
- expect(el.querySelector('i')).toBeDefined();
- });
+ beforeEach(() => {
+ Component = Vue.extend(checkingComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ });
+
+ it('renders loading icon', () => {
+ expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ });
+
+ it('renders information about merging', () => {
+ expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual('Checking ability to merge automatically');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
index 1bf97bbf093..51a34739ee9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
@@ -1,74 +1,58 @@
import Vue from 'vue';
-import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed';
-
-const mr = {
- targetBranch: 'good-branch',
- targetBranchPath: '/good-branch',
- metrics: {
- mergedBy: {},
- mergedAt: 'mergedUpdatedAt',
- closedBy: {
- name: 'Fatih Acet',
- username: 'fatihacet',
- },
- closedAt: 'closedEventUpdatedAt',
- readableMergedAt: '',
- readableClosedAt: '',
- },
- updatedAt: 'mrUpdatedAt',
- closedAt: '1 day ago',
-};
-
-const createComponent = () => {
- const Component = Vue.extend(closedComponent);
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetClosed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = closedComponent.props.mr;
-
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
- });
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(closedComponent);
+ vm = mountComponent(Component, { mr: {
+ metrics: {
+ mergedBy: {},
+ closedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ closedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ readableMergedAt: '',
+ readableClosedAt: 'less than a minute ago',
+ },
+ targetBranchPath: '/twitter/flight/commits/so_long_jquery',
+ targetBranch: 'so_long_jquery',
+ } });
});
- describe('components', () => {
- it('should have components added', () => {
- expect(closedComponent.components['mr-widget-author-and-time']).toBeDefined();
- });
+ afterEach(() => {
+ vm.$destroy();
});
- describe('template', () => {
- let vm;
- let el;
+ it('renders warning icon', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ });
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
+ it('renders closed by information with author and time', () => {
+ expect(
+ vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain(
+ 'Closed by Administrator less than a minute ago',
+ );
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('links to the user that closed the MR', () => {
+ expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual('http://localhost:3000/root');
+ });
- it('should have correct elements', () => {
- expect(el.querySelector('h4').textContent).toContain('Closed by');
- expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name);
- expect(el.textContent).toContain('The changes were not merged into');
- expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath);
- expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch);
- });
+ it('renders information about the changes not being merged', () => {
+ expect(
+ vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain('The changes were not merged into so_long_jquery');
+ });
- it('should use closedEvent updatedAt as tooltip title', () => {
- expect(
- el.querySelector('time').getAttribute('title'),
- ).toBe('closedEventUpdatedAt');
- });
+ it('renders link for target branch', () => {
+ expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual('/twitter/flight/commits/so_long_jquery');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index 5d4c7ec09dc..a7d69fdcdb9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,105 +1,85 @@
import Vue from 'vue';
-import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts';
+import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
-const ConflictsComponent = Vue.extend(conflictsComponent);
-const path = '/conflicts';
-
describe('MRWidgetConflicts', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = conflictsComponent.props;
+ let Component;
+ let vm;
+ const path = '/conflicts';
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(conflictsComponent);
});
- describe('template', () => {
- describe('when allowed to merge', () => {
- let vm;
-
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: true,
- conflictResolutionPath: path,
- },
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.$el.textContent).toContain('There are merge conflicts');
- expect(vm.$el.textContent).not.toContain('ask someone with write access');
- });
-
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
+ afterEach(() => {
+ vm.$destroy();
+ });
- expect(resolveButton.textContent).toContain('Resolve conflicts');
- expect(resolveButton.getAttribute('href')).toEqual(path);
+ describe('when allowed to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: path,
+ },
});
+ });
- it('should have merge buttons', () => {
- const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
- const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
-
- expect(mergeButton.textContent).toContain('Merge');
- expect(mergeButton.disabled).toBeTruthy();
- expect(mergeButton.classList.contains('btn-success')).toEqual(true);
- expect(mergeLocallyButton.textContent).toContain('Merge locally');
- });
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.$el.textContent).toContain('There are merge conflicts');
+ expect(vm.$el.textContent).not.toContain('ask someone with write access');
});
- describe('when user does not have permission to merge', () => {
- let vm;
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: false,
- },
- });
- });
+ expect(resolveButton.textContent).toContain('Resolve conflicts');
+ expect(resolveButton.getAttribute('href')).toEqual(path);
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('should have merge buttons', () => {
+ const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
+ const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
- it('should show proper message', () => {
- expect(vm.$el.textContent).toContain('ask someone with write access');
- });
+ expect(mergeButton.textContent).toContain('Merge');
+ expect(mergeButton.disabled).toBeTruthy();
+ expect(mergeButton.classList.contains('btn-success')).toEqual(true);
+ expect(mergeLocallyButton.textContent).toContain('Merge locally');
+ });
+ });
- it('should not have action buttons', () => {
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
- expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
- expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ describe('when user does not have permission to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: false,
+ },
});
});
- describe('when fast-forward or semi-linear merge enabled', () => {
- let vm;
+ it('should show proper message', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('ask someone with write access');
+ });
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- shouldBeRebased: true,
- },
- });
- });
+ it('should not have action buttons', () => {
+ expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
+ expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
+ expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ });
+ });
- afterEach(() => {
- vm.$destroy();
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ shouldBeRebased: true,
+ },
});
+ });
- it('should tell you to rebase locally', () => {
- expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.');
- expect(vm.$el.textContent).toContain('To merge this request, first rebase locally');
- });
+ it('should tell you to rebase locally', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 1127576617b..073f26cc78f 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -170,14 +170,14 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.iconClass).toEqual('success');
});
- it('shows x for failed status', () => {
+ it('shows warning icon for failed status', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
- it('shows x for merge not allowed', () => {
+ it('shows warning icon for merge not allowed', () => {
vm.mr.hasCI = true;
- expect(vm.iconClass).toEqual('failed');
+ expect(vm.iconClass).toEqual('warning');
});
});
@@ -371,6 +371,10 @@ describe('MRWidgetReadyToMerge', () => {
});
});
+ beforeEach(() => {
+ loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ });
+
it('should call start and stop polling when MR merged', (done) => {
spyOn(eventHub, '$emit');
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
@@ -392,6 +396,47 @@ describe('MRWidgetReadyToMerge', () => {
}, 333);
});
+ it('updates status box', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ const statusBox = document.querySelector('.status-box');
+ expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy();
+ expect(statusBox.textContent).toContain('Merged');
+
+ done();
+ });
+ });
+
+ it('hides close button', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy();
+
+ done();
+ });
+ });
+
+ it('updates merge request count badge', (done) => {
+ spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
+ spyOn(vm, 'initiateRemoveSourceBranchPolling');
+
+ vm.handleMergePolling(() => {}, () => {});
+
+ setTimeout(() => {
+ expect(document.querySelector('.js-merge-counter').textContent).toBe('0');
+
+ done();
+ });
+ });
+
it('should continue polling until MR is merged', (done) => {
spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state'));
spyOn(vm, 'initiateRemoveSourceBranchPolling');
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index ca29c9fee32..ae494267659 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -14,7 +14,6 @@ export default {
"updated_by_id": null,
"created_at": "2017-04-07T12:27:26.718Z",
"updated_at": "2017-04-07T15:39:25.852Z",
- "deleted_at": null,
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
new file mode 100644
index 00000000000..08e4e1f8337
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('clipboard button', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(clipboardButton);
+ vm = mountComponent(Component, {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a button for clipboard', () => {
+ expect(vm.$el.tagName).toEqual('BUTTON');
+ expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me');
+ expect(vm.$el.querySelector('i').className).toEqual('fa fa-clipboard');
+ });
+
+ it('should have a tooltip with default values', () => {
+ expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!');
+ expect(vm.$el.getAttribute('data-placement')).toEqual('top');
+ expect(vm.$el.getAttribute('data-container')).toEqual(null);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/expand_button_spec.js b/spec/javascripts/vue_shared/components/expand_button_spec.js
new file mode 100644
index 00000000000..a33ab689dd1
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/expand_button_spec.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import expandButton from '~/vue_shared/components/expand_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('expand button', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(expandButton);
+ vm = mountComponent(Component, {
+ slots: {
+ expanded: '<p>Expanded!</p>',
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders a collpased button', () => {
+ expect(vm.$el.textContent.trim()).toEqual('...');
+ });
+
+ it('hides expander on click', (done) => {
+ vm.$el.querySelector('button').click();
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('button').getAttribute('style')).toEqual('display: none;');
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index b4553acb341..b378a0bd896 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import headerCi from '~/vue_shared/components/header_ci_component.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Header CI Component', () => {
let HeaderCi;
@@ -8,7 +9,6 @@ describe('Header CI Component', () => {
beforeEach(() => {
HeaderCi = Vue.extend(headerCi);
-
props = {
status: {
group: 'failed',
@@ -45,54 +45,65 @@ describe('Header CI Component', () => {
],
hasSidebarButton: true,
};
-
- vm = new HeaderCi({
- propsData: props,
- }).$mount();
});
afterEach(() => {
vm.$destroy();
});
- it('should render status badge', () => {
- expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
- expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
- expect(
- vm.$el.querySelector('.ci-failed').getAttribute('href'),
- ).toEqual(props.status.details_path);
- });
+ describe('render', () => {
+ beforeEach(() => {
+ vm = mountComponent(HeaderCi, props);
+ });
- it('should render item name and id', () => {
- expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
- });
+ it('should render status badge', () => {
+ expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
+ expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
+ expect(
+ vm.$el.querySelector('.ci-failed').getAttribute('href'),
+ ).toEqual(props.status.details_path);
+ });
- it('should render timeago date', () => {
- expect(vm.$el.querySelector('time')).toBeDefined();
- });
+ it('should render item name and id', () => {
+ expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
+ });
- it('should render user icon and name', () => {
- expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
- });
+ it('should render timeago date', () => {
+ expect(vm.$el.querySelector('time')).toBeDefined();
+ });
- it('should render provided actions', () => {
- expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
- expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
- expect(vm.$el.querySelector('.link').tagName).toEqual('A');
- expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
- expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
- });
+ it('should render user icon and name', () => {
+ expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
+ });
+
+ it('should render provided actions', () => {
+ expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
+ expect(vm.$el.querySelector('.link').tagName).toEqual('A');
+ expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
+ expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
+ });
- it('should show loading icon', (done) => {
- vm.actions[0].isLoading = true;
+ it('should show loading icon', (done) => {
+ vm.actions[0].isLoading = true;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
- done();
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
+ done();
+ });
+ });
+
+ it('should render sidebar toggle button', () => {
+ expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined();
});
});
- it('should render sidebar toggle button', () => {
- expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined();
+ describe('shouldRenderTriggeredLabel', () => {
+ it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => {
+ vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false });
+
+ expect(vm.$el.textContent).toContain('created');
+ expect(vm.$el.textContent).not.toContain('triggered');
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/loading_icon_spec.js b/spec/javascripts/vue_shared/components/loading_icon_spec.js
index 1baf3537741..5cd3466f501 100644
--- a/spec/javascripts/vue_shared/components/loading_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/loading_icon_spec.js
@@ -16,7 +16,8 @@ describe('Loading Icon Component', () => {
).toEqual('fa fa-spin fa-spinner fa-1x');
expect(component.$el.tagName).toEqual('DIV');
- expect(component.$el.classList.contains('text-center')).toEqual(true);
+ expect(component.$el.classList).toContain('text-center');
+ expect(component.$el.classList).toContain('loading-container');
});
it('should render accessibility attributes', () => {
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 24209be83fe..5f980bbf36c 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -12,14 +12,14 @@ describe('Markdown field component', () => {
beforeEach((done) => {
vm = new Vue({
+ components: {
+ fieldComponent,
+ },
data() {
return {
text: 'testing\n123',
};
},
- components: {
- fieldComponent,
- },
template: `
<field-component
markdown-preview-path="/preview"
diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
index 721f4044659..a5f9c75be4e 100644
--- a/spec/javascripts/vue_shared/components/modal_spec.js
+++ b/spec/javascripts/vue_shared/components/modal_spec.js
@@ -2,11 +2,65 @@ import Vue from 'vue';
import modal from '~/vue_shared/components/modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
+const modalComponent = Vue.extend(modal);
+
describe('Modal', () => {
- it('does not render a primary button if no primaryButtonLabel', () => {
- const modalComponent = Vue.extend(modal);
- const vm = mountComponent(modalComponent);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('props', () => {
+ describe('without primaryButtonLabel', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, {
+ primaryButtonLabel: null,
+ });
+ });
+
+ it('does not render a primary button', () => {
+ expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
+ });
+ });
+
+ describe('with id', () => {
+ describe('does not render a primary button', () => {
+ beforeEach(() => {
+ vm = mountComponent(modalComponent, {
+ id: 'my-modal',
+ });
+ });
+
+ it('assigns the id to the modal', () => {
+ expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull();
+ });
+
+ it('does not show the modal immediately', () => {
+ expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show');
+ });
+
+ it('does not show a backdrop', () => {
+ expect(vm.$el.querySelector('modal-backdrop')).toBeNull();
+ });
+ });
+ });
+
+ it('works with data-toggle="modal"', (done) => {
+ setFixtures(`
+ <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
+ <div id="modal-container"></div>
+ `);
+
+ const modalContainer = document.getElementById('modal-container');
+ const modalButton = document.getElementById('modal-button');
+ vm = mountComponent(modalComponent, {
+ id: 'my-modal',
+ }, modalContainer);
+ const modalElement = vm.$el.querySelector('#my-modal');
+ $(modalElement).on('shown.bs.modal', () => done());
- expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
+ modalButton.click();
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 20363e78094..2de108da2ac 100644
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -21,22 +21,21 @@ describe('collapsedGroupedDatePicker', () => {
});
});
- it('toggleCollapse events', () => {
- const toggleCollapse = jasmine.createSpy();
-
+ describe('toggleCollapse events', () => {
beforeEach((done) => {
+ spyOn(vm, 'toggleSidebar');
vm.minDate = new Date('07/17/2016');
Vue.nextTick(done);
});
it('should emit when sidebar is toggled', () => {
vm.$el.querySelector('.gutter-toggle').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
new file mode 100644
index 00000000000..6940b04573e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
@@ -0,0 +1,77 @@
+import Vue from 'vue';
+
+import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const createComponent = (config) => {
+ const Component = Vue.extend(stackedProgressBarComponent);
+ const defaultConfig = Object.assign({}, {
+ successLabel: 'Synced',
+ failureLabel: 'Failed',
+ neutralLabel: 'Out of sync',
+ successCount: 10,
+ failureCount: 5,
+ totalCount: 20,
+ }, config);
+
+ return mountComponent(Component, defaultConfig);
+};
+
+describe('StackedProgressBarComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('neutralCount', () => {
+ it('returns neutralCount based on totalCount, successCount and failureCount', () => {
+ expect(vm.neutralCount).toBe(5); // 20 - 10 - 5
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('getPercent', () => {
+ it('returns percentage from provided count based on `totalCount`', () => {
+ expect(vm.getPercent(10)).toBe(50);
+ });
+ });
+
+ describe('barStyle', () => {
+ it('returns style string based on percentage provided', () => {
+ expect(vm.barStyle(50)).toBe('width: 50%;');
+ });
+ });
+
+ describe('getTooltip', () => {
+ it('returns label string based on label and count provided', () => {
+ expect(vm.getTooltip('Synced', 10)).toBe('Synced: 10');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders container element', () => {
+ expect(vm.$el.classList.contains('stacked-progress-bar')).toBeTruthy();
+ });
+
+ it('renders empty state when count is unavailable', () => {
+ const vmX = createComponent({ totalCount: 0, successCount: 0, failureCount: 0 });
+ expect(vmX.$el.querySelectorAll('.status-unavailable').length).not.toBe(0);
+ vmX.$destroy();
+ });
+
+ it('renders bar elements when count is available', () => {
+ expect(vm.$el.querySelectorAll('.status-green').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-neutral').length).not.toBe(0);
+ expect(vm.$el.querySelectorAll('.status-red').length).not.toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 1465ef5855f..c63f15e5880 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -32,7 +32,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.innerHTML).not.toBeDefined();
+ expect(component.$el.childNodes.length).toEqual(0);
});
describe('prev button', () => {
@@ -72,7 +72,6 @@ describe('Pagination component', () => {
});
component.$el.querySelector('.js-previous-button a').click();
-
expect(spy).toHaveBeenCalledWith(1);
});
});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
index 8450ad9dbcb..adf80d0c2bb 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import Vue from 'vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 45a0bb0650f..8edba1f47a3 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,4 +1,4 @@
-/* global Mousetrap */
+import Mousetrap from 'mousetrap';
import Dropzone from 'dropzone';
import ZenMode from '~/zen_mode';