summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
authorAchilleas Pipinellis <axil@gitlab.com>2019-08-20 20:22:43 +0200
committerAchilleas Pipinellis <axil@gitlab.com>2019-08-20 20:22:43 +0200
commite61308ce1d82e12e5087371469baea4a452875d1 (patch)
tree62551a3ae4eab75e5af7e3b35358c07a51b2132f /spec/javascripts
parent4f323bb62fbe71a4352de25cab141f361a3fe1a6 (diff)
parent2989ed078c1d45b0959dcecb1bc3c8f4740a3c0d (diff)
downloadgitlab-ce-docs-patch-71.tar.gz
Merge branch 'master' into docs-patch-71docs-patch-71
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js19
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js23
-rw-r--r--spec/javascripts/boards/components/board_form_spec.js56
-rw-r--r--spec/javascripts/boards/components/board_spec.js61
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js206
-rw-r--r--spec/javascripts/boards/components/issue_time_estimate_spec.js70
-rw-r--r--spec/javascripts/boards/mock_data.js5
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js2
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js2
-rw-r--r--spec/javascripts/collapsed_sidebar_todo_spec.js16
-rw-r--r--spec/javascripts/create_merge_request_dropdown_spec.js68
-rw-r--r--spec/javascripts/diffs/components/app_spec.js2
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_expansion_cell_spec.js64
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js6
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js20
-rw-r--r--spec/javascripts/diffs/components/diff_gutter_avatars_spec.js146
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js15
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_table_cell_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/inline_diff_table_row_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js12
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js4
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js2
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js47
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js15
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js11
-rw-r--r--spec/javascripts/environments/confirm_rollback_modal_spec.js70
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js61
-rw-r--r--spec/javascripts/environments/environment_terminal_button_spec.js40
-rw-r--r--spec/javascripts/error_tracking_settings/components/app_spec.js63
-rw-r--r--spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js91
-rw-r--r--spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js109
-rw-r--r--spec/javascripts/error_tracking_settings/mock.js92
-rw-r--r--spec/javascripts/error_tracking_settings/store/actions_spec.js191
-rw-r--r--spec/javascripts/error_tracking_settings/store/getters_spec.js93
-rw-r--r--spec/javascripts/error_tracking_settings/store/mutation_spec.js82
-rw-r--r--spec/javascripts/error_tracking_settings/utils_spec.js29
-rw-r--r--spec/javascripts/filtered_search/visual_token_value_spec.js2
-rw-r--r--spec/javascripts/fixtures/.gitignore7
-rw-r--r--spec/javascripts/fixtures/abuse_reports.rb26
-rw-r--r--spec/javascripts/fixtures/admin_users.rb28
-rw-r--r--spec/javascripts/fixtures/application_settings.rb33
-rw-r--r--spec/javascripts/fixtures/autocomplete_sources.rb39
-rw-r--r--spec/javascripts/fixtures/blob.rb34
-rw-r--r--spec/javascripts/fixtures/boards.rb28
-rw-r--r--spec/javascripts/fixtures/branches.rb32
-rw-r--r--spec/javascripts/fixtures/clusters.rb34
-rw-r--r--spec/javascripts/fixtures/commit.rb33
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb43
-rw-r--r--spec/javascripts/fixtures/groups.rb35
-rw-r--r--spec/javascripts/fixtures/issues.rb122
-rw-r--r--spec/javascripts/fixtures/jobs.rb54
-rw-r--r--spec/javascripts/fixtures/labels.rb58
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb134
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb70
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb43
-rw-r--r--spec/javascripts/fixtures/pipelines.rb34
-rw-r--r--spec/javascripts/fixtures/projects.rb81
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb34
-rw-r--r--spec/javascripts/fixtures/raw.rb39
-rw-r--r--spec/javascripts/fixtures/search.rb17
-rw-r--r--spec/javascripts/fixtures/services.rb34
-rw-r--r--spec/javascripts/fixtures/sessions.rb25
-rw-r--r--spec/javascripts/fixtures/snippet.rb33
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/fixtures/static/ajax_loading_spinner.html3
-rw-r--r--spec/javascripts/fixtures/static/balsamiq_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/create_item_dropdown.html11
-rw-r--r--spec/javascripts/fixtures/static/environments/table.html15
-rw-r--r--spec/javascripts/fixtures/static/event_filter.html44
-rw-r--r--spec/javascripts/fixtures/static/gl_dropdown.html26
-rw-r--r--spec/javascripts/fixtures/static/gl_field_errors.html22
-rw-r--r--spec/javascripts/fixtures/static/images/green_box.pngbin1306 -> 0 bytes
-rw-r--r--spec/javascripts/fixtures/static/images/one_white_pixel.pngbin68 -> 0 bytes
-rw-r--r--spec/javascripts/fixtures/static/images/red_box.pngbin1305 -> 0 bytes
-rw-r--r--spec/javascripts/fixtures/static/issuable_filter.html9
-rw-r--r--spec/javascripts/fixtures/static/issue_sidebar_label.html26
-rw-r--r--spec/javascripts/fixtures/static/line_highlighter.html107
-rw-r--r--spec/javascripts/fixtures/static/linked_tabs.html20
-rw-r--r--spec/javascripts/fixtures/static/merge_requests_show.html15
-rw-r--r--spec/javascripts/fixtures/static/mini_dropdown_graph.html13
-rw-r--r--spec/javascripts/fixtures/static/notebook_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/oauth_remember_me.html6
-rw-r--r--spec/javascripts/fixtures/static/pdf_viewer.html1
-rw-r--r--spec/javascripts/fixtures/static/pipeline_graph.html24
-rw-r--r--spec/javascripts/fixtures/static/pipelines.html3
-rw-r--r--spec/javascripts/fixtures/static/project_select_combo_button.html9
-rw-r--r--spec/javascripts/fixtures/static/projects.json445
-rw-r--r--spec/javascripts/fixtures/static/search_autocomplete.html15
-rw-r--r--spec/javascripts/fixtures/static/signin_tabs.html8
-rw-r--r--spec/javascripts/fixtures/static/sketch_viewer.html3
-rw-r--r--spec/javascripts/fixtures/todos.rb54
-rw-r--r--spec/javascripts/fixtures/u2f.rb44
-rw-r--r--spec/javascripts/groups/components/group_item_spec.js7
-rw-r--r--spec/javascripts/groups/components/item_stats_spec.js12
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js24
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper_spec.js48
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js4
-rw-r--r--spec/javascripts/ide/components/ide_tree_list_spec.js14
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js105
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js37
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js78
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js20
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js75
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js79
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js65
-rw-r--r--spec/javascripts/issuable_spec.js12
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js111
-rw-r--r--spec/javascripts/jobs/components/empty_state_spec.js141
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js684
-rw-r--r--spec/javascripts/jobs/components/job_log_spec.js61
-rw-r--r--spec/javascripts/jobs/components/manual_variables_form_spec.js88
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js17
-rw-r--r--spec/javascripts/jobs/mock_data.js16
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js46
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js22
-rw-r--r--spec/javascripts/monitoring/charts/column_spec.js58
-rw-r--r--spec/javascripts/monitoring/charts/empty_chart_spec.js29
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js9
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js221
-rw-r--r--spec/javascripts/monitoring/dashboard_state_spec.js101
-rw-r--r--spec/javascripts/monitoring/mock_data.js85
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js78
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js29
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js17
-rw-r--r--spec/javascripts/monitoring/store/utils_spec.js37
-rw-r--r--spec/javascripts/monitoring/utils_spec.js55
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js15
-rw-r--r--spec/javascripts/notes/components/diff_with_note_spec.js13
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js52
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js71
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js68
-rw-r--r--spec/javascripts/pages/labels/components/promote_label_modal_spec.js4
-rw-r--r--spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js4
-rw-r--r--spec/javascripts/pdf/page_spec.js5
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js27
-rw-r--r--spec/javascripts/performance_bar/components/simple_metric_spec.js47
-rw-r--r--spec/javascripts/persistent_user_callout_spec.js87
-rw-r--r--spec/javascripts/pipelines/mock_data.js1
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js5
-rw-r--r--spec/javascripts/pipelines/pipelines_actions_spec.js42
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js8
-rw-r--r--spec/javascripts/registry/components/app_spec.js35
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js16
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js178
-rw-r--r--spec/javascripts/registry/mock_data.js11
-rw-r--r--spec/javascripts/registry/stores/actions_spec.js24
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js18
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js12
-rw-r--r--spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js25
-rw-r--r--spec/javascripts/sidebar/todo_spec.js8
-rw-r--r--spec/javascripts/test_bundle.js7
-rw-r--r--spec/javascripts/test_constants.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js27
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js8
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js10
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js63
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js111
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js180
-rw-r--r--spec/javascripts/vue_shared/directives/autofocusonshow_spec.js38
179 files changed, 3209 insertions, 4521 deletions
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 2f72c9ed89d..2fa807657de 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.spinner');
+ const loadingIcon = vm.$el.querySelector('.gl-spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 4e4d1ae2e99..c82a03a628a 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.spinner'),
+ loadingIcon: vm.$el.querySelector('.gl-spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 8e2a947b0dd..b64859ec32a 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import boardsStore from '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
-import { mockBoardService } from './mock_data';
describe('Boards blank state', () => {
let vm;
@@ -11,9 +10,10 @@ describe('Boards blank state', () => {
const Comp = Vue.extend(BoardBlankState);
boardsStore.create();
- gl.boardService = mockBoardService();
- spyOn(gl.boardService, 'generateDefaultLists').and.callFake(
+ spyOn(boardsStore, 'addList').and.stub();
+ spyOn(boardsStore, 'removeList').and.stub();
+ spyOn(boardsStore, 'generateDefaultLists').and.callFake(
() =>
new Promise((resolve, reject) => {
if (fail) {
@@ -71,9 +71,14 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-success').click();
setTimeout(() => {
- expect(boardsStore.state.lists.length).toBe(2);
- expect(boardsStore.state.lists[0].title).toEqual('To Do');
- expect(boardsStore.state.lists[1].title).toEqual('Doing');
+ expect(boardsStore.addList).toHaveBeenCalledTimes(2);
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'To Do' }),
+ );
+
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'Doing' }),
+ );
done();
});
@@ -86,7 +91,7 @@ describe('Boards blank state', () => {
setTimeout(() => {
expect(boardsStore.welcomeIsHidden()).toBeFalsy();
- expect(boardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label');
done();
});
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 9c9b435d7fd..6774a46ed58 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -148,7 +148,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index e81115e10c9..36bd7ada4f0 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -355,4 +355,27 @@ describe('Store', () => {
expect(boardsStore.moving.list).toEqual(dummyList);
});
});
+
+ describe('setTimeTrackingLimitToHours', () => {
+ it('sets the timeTracking.LimitToHours option', () => {
+ boardsStore.timeTracking.limitToHours = false;
+
+ boardsStore.setTimeTrackingLimitToHours('true');
+
+ expect(boardsStore.timeTracking.limitToHours).toEqual(true);
+ });
+ });
+
+ describe('setCurrentBoard', () => {
+ const dummyBoard = 'hoverboard';
+
+ it('sets the current board', () => {
+ const { state } = boardsStore;
+ state.currentBoard = null;
+
+ boardsStore.setCurrentBoard(dummyBoard);
+
+ expect(state.currentBoard).toEqual(dummyBoard);
+ });
+ });
});
diff --git a/spec/javascripts/boards/components/board_form_spec.js b/spec/javascripts/boards/components/board_form_spec.js
new file mode 100644
index 00000000000..e9014156a98
--- /dev/null
+++ b/spec/javascripts/boards/components/board_form_spec.js
@@ -0,0 +1,56 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import boardsStore from '~/boards/stores/boards_store';
+import boardForm from '~/boards/components/board_form.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('board_form.vue', () => {
+ const props = {
+ canAdminBoard: false,
+ labelsPath: `${gl.TEST_HOST}/labels/path`,
+ milestonePath: `${gl.TEST_HOST}/milestone/path`,
+ };
+ let vm;
+
+ beforeEach(() => {
+ spyOn($, 'ajax');
+ boardsStore.state.currentPage = 'edit';
+ const Component = Vue.extend(boardForm);
+ vm = mountComponent(Component, props);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('methods', () => {
+ describe('cancel', () => {
+ it('resets currentPage', done => {
+ vm.cancel();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(boardsStore.state.currentPage).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('buttons', () => {
+ it('cancel button triggers cancel()', done => {
+ spyOn(vm, 'cancel');
+
+ Vue.nextTick()
+ .then(() => {
+ const cancelButton = vm.$el.querySelector('button[data-dismiss="modal"]');
+ cancelButton.click();
+
+ expect(vm.cancel).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index d08ee41802b..683783334c6 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
-import '~/boards/services/board_service';
import Board from '~/boards/components/board';
-import '~/boards/models/list';
+import List from '~/boards/models/list';
import { mockBoardService } from '../mock_data';
describe('Board component', () => {
@@ -27,7 +26,6 @@ describe('Board component', () => {
disabled: false,
issueLinkBase: '/',
rootPath: '/',
- // eslint-disable-next-line no-undef
list: new List({
id: 1,
position: 0,
@@ -53,57 +51,62 @@ describe('Board component', () => {
expect(vm.$el.classList.contains('is-expandable')).toBe(true);
});
- it('board is expandable when list type is closed', done => {
- vm.list.type = 'closed';
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(true);
-
- done();
- });
+ it('board is expandable when list type is closed', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
});
- it('board is not expandable when list type is label', done => {
- vm.list.type = 'label';
- vm.list.isExpandable = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(false);
+ it('board is expandable when list type is label', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
+ });
- done();
- });
+ it('board is not expandable when list type is blank', () => {
+ expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false);
});
- it('collapses when clicking header', done => {
+ it('does not collapse when clicking header', done => {
+ vm.list.isExpanded = true;
vm.$el.querySelector('.board-header').click();
Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
});
});
- it('created sets isExpanded to true from localStorage', done => {
- vm.$el.querySelector('.board-header').click();
+ it('collapses when clicking the collapse icon', done => {
+ vm.list.isExpanded = true;
- return Vue.nextTick()
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
+ })
.then(() => {
expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
- // call created manually
- vm.$options.created[0].call(vm);
+ it('expands when clicking the expand icon', done => {
+ vm.list.isExpanded = false;
- return Vue.nextTick();
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
})
.then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
-
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
})
.catch(done.fail);
});
+ it('is expanded when created', () => {
+ expect(vm.list.isExpanded).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
+ });
+
it('does render add issue button', () => {
expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
});
diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js
new file mode 100644
index 00000000000..473cc0612ea
--- /dev/null
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -0,0 +1,206 @@
+import Vue from 'vue';
+import BoardService from '~/boards/services/board_service';
+import BoardsSelector from '~/boards/components/boards_selector.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import boardsStore from '~/boards/stores/boards_store';
+
+const throttleDuration = 1;
+
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, id) => {
+ const name = `board${id}`;
+
+ return {
+ id,
+ name,
+ };
+ });
+}
+
+describe('BoardsSelector', () => {
+ let vm;
+ let allBoardsResponse;
+ let recentBoardsResponse;
+ let fillSearchBox;
+ const boards = boardGenerator(20);
+ const recentBoards = boardGenerator(5);
+
+ beforeEach(done => {
+ setFixtures('<div class="js-boards-selector"></div>');
+ window.gl = window.gl || {};
+
+ boardsStore.setEndpoints({
+ boardsEndpoint: '',
+ recentBoardsEndpoint: '',
+ listsEndpoint: '',
+ bulkUpdatePath: '',
+ boardId: '',
+ });
+ window.gl.boardService = new BoardService();
+
+ allBoardsResponse = Promise.resolve({
+ data: boards,
+ });
+ recentBoardsResponse = Promise.resolve({
+ data: recentBoards,
+ });
+
+ spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse);
+ spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse);
+
+ const Component = Vue.extend(BoardsSelector);
+ vm = mountComponent(
+ Component,
+ {
+ throttleDuration,
+ currentBoard: {
+ id: 1,
+ name: 'Development',
+ milestone_id: null,
+ weight: null,
+ assignee_id: null,
+ labels: [],
+ },
+ milestonePath: `${TEST_HOST}/milestone/path`,
+ boardBaseUrl: `${TEST_HOST}/board/base/url`,
+ hasMissingBoards: false,
+ canAdminBoard: true,
+ multipleIssueBoardsAvailable: true,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ projectId: 42,
+ groupId: 19,
+ scopedIssueBoardFeatureEnabled: true,
+ weights: [],
+ },
+ document.querySelector('.js-boards-selector'),
+ );
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ vm.$children[0].$emit('show');
+
+ Promise.all([allBoardsResponse, recentBoardsResponse])
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+
+ fillSearchBox = filterTerm => {
+ const { searchBox } = vm.$refs;
+ const searchBoxInput = searchBox.$el.querySelector('input');
+ searchBoxInput.value = filterTerm;
+ searchBoxInput.dispatchEvent(new Event('input'));
+ };
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ window.gl.boardService = undefined;
+ });
+
+ describe('filtering', () => {
+ it('shows all boards without filtering', done => {
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItem = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItem.length).toBe(boards.length + recentBoards.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows only matching boards when filtering', done => {
+ const filterTerm = 'board1';
+ const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
+
+ fillSearchBox(filterTerm);
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(expectedCount);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows message if there are no matching boards', done => {
+ fillSearchBox('does not exist');
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(0);
+ expect(vm.$el).toContainText('No matching boards found');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('recent boards section', () => {
+ it('shows only when boards are greater than 10', done => {
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+
+ const expectedCount = 2; // Recent + All
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when boards are less than 10', done => {
+ spyOn(vm, 'initScrollFade');
+ spyOn(vm, 'setScrollFade');
+
+ vm.$nextTick()
+ .then(() => {
+ vm.boards = vm.boards.slice(0, 5);
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when recentBoards api returns empty array', done => {
+ vm.$nextTick()
+ .then(() => {
+ vm.recentBoards = [];
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when search is active', done => {
+ fillSearchBox('Random string');
+
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/components/issue_time_estimate_spec.js b/spec/javascripts/boards/components/issue_time_estimate_spec.js
index ba65d3287da..de48e3f6091 100644
--- a/spec/javascripts/boards/components/issue_time_estimate_spec.js
+++ b/spec/javascripts/boards/components/issue_time_estimate_spec.js
@@ -1,40 +1,70 @@
import Vue from 'vue';
import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
+import boardsStore from '~/boards/stores/boards_store';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('Issue Tine Estimate component', () => {
+describe('Issue Time Estimate component', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(IssueTimeEstimate);
- vm = mountComponent(Component, {
- estimate: 374460,
- });
+ boardsStore.create();
});
afterEach(() => {
vm.$destroy();
});
- it('renders the correct time estimate', () => {
- expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m');
- });
+ describe('when limitToHours is false', () => {
+ beforeEach(() => {
+ boardsStore.timeTracking.limitToHours = false;
+
+ const Component = Vue.extend(IssueTimeEstimate);
+ vm = mountComponent(Component, {
+ estimate: 374460,
+ });
+ });
+
+ it('renders the correct time estimate', () => {
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m');
+ });
+
+ it('renders expanded time estimate in tooltip', () => {
+ expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain(
+ '2 weeks 3 days 1 minute',
+ );
+ });
+
+ it('prevents tooltip xss', done => {
+ const alertSpy = spyOn(window, 'alert');
+ vm.estimate = 'Foo <script>alert("XSS")</script>';
- it('renders expanded time estimate in tooltip', () => {
- expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain(
- '2 weeks 3 days 1 minute',
- );
+ vm.$nextTick(() => {
+ expect(alertSpy).not.toHaveBeenCalled();
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m');
+ expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m');
+ done();
+ });
+ });
});
- it('prevents tooltip xss', done => {
- const alertSpy = spyOn(window, 'alert');
- vm.estimate = 'Foo <script>alert("XSS")</script>';
+ describe('when limitToHours is true', () => {
+ beforeEach(() => {
+ boardsStore.timeTracking.limitToHours = true;
+
+ const Component = Vue.extend(IssueTimeEstimate);
+ vm = mountComponent(Component, {
+ estimate: 374460,
+ });
+ });
+
+ it('renders the correct time estimate', () => {
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual('104h 1m');
+ });
- vm.$nextTick(() => {
- expect(alertSpy).not.toHaveBeenCalled();
- expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m');
- expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m');
- done();
+ it('renders expanded time estimate in tooltip', () => {
+ expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain(
+ '104 hours 1 minute',
+ );
});
});
});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 9854cf49e97..ea22ae5c4e7 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,4 +1,5 @@
import BoardService from '~/boards/services/board_service';
+import boardsStore from '~/boards/stores/boards_store';
export const boardObj = {
id: 1,
@@ -76,12 +77,14 @@ export const mockBoardService = (opts = {}) => {
const bulkUpdatePath = opts.bulkUpdatePath || '';
const boardId = opts.boardId || '1';
- return new BoardService({
+ boardsStore.setEndpoints({
boardsEndpoint,
listsEndpoint,
bulkUpdatePath,
boardId,
});
+
+ return new BoardService();
};
export const mockAssigneesList = [
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index e6a969bd855..b2fe315f6c6 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -224,7 +224,7 @@ describe('AjaxFormVariableList', () => {
describe('maskableRegex', () => {
it('takes in the regex provided by the data attribute', () => {
- expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
});
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 064113e879a..c8d6f789ed0 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -162,7 +162,7 @@ describe('VariableList', () => {
});
it('has a regex provided via a data attribute', () => {
- expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
});
it('allows values that are 8 characters long', done => {
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index bb90e53e525..f75d63c8f57 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -58,7 +58,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
it('sets default tooltip title', () => {
expect(
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').getAttribute('title'),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
});
it('toggle todo state', done => {
@@ -85,7 +85,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
setTimeout(() => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -99,7 +99,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('data-original-title'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -124,13 +124,13 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
expect(
document.querySelector('.issuable-sidebar-header .js-issuable-todo').textContent.trim(),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
});
- it('updates aria-label to mark todo as done', done => {
+ it('updates aria-label to Mark as done', done => {
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
setTimeout(() => {
@@ -138,7 +138,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
done();
});
@@ -153,7 +153,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Mark todo as done');
+ ).toBe('Mark as done');
document.querySelector('.js-issuable-todo.sidebar-collapsed-icon').click();
})
@@ -163,7 +163,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
document
.querySelector('.js-issuable-todo.sidebar-collapsed-icon')
.getAttribute('aria-label'),
- ).toBe('Add todo');
+ ).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/create_merge_request_dropdown_spec.js b/spec/javascripts/create_merge_request_dropdown_spec.js
deleted file mode 100644
index 00fe3f451f5..00000000000
--- a/spec/javascripts/create_merge_request_dropdown_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-import MockAdapter from 'axios-mock-adapter';
-import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
-import { TEST_HOST } from 'spec/test_constants';
-
-describe('CreateMergeRequestDropdown', () => {
- let axiosMock;
- let dropdown;
-
- beforeEach(() => {
- axiosMock = new MockAdapter(axios);
-
- setFixtures(`
- <div id="dummy-wrapper-element">
- <div class="available"></div>
- <div class="unavailable">
- <div class="fa"></div>
- <div class="text"></div>
- </div>
- <div class="js-ref"></div>
- <div class="js-create-merge-request"></div>
- <div class="js-create-target"></div>
- <div class="js-dropdown-toggle"></div>
- </div>
- `);
-
- const dummyElement = document.getElementById('dummy-wrapper-element');
- dropdown = new CreateMergeRequestDropdown(dummyElement);
- dropdown.refsPath = `${TEST_HOST}/dummy/refs?search=`;
- });
-
- afterEach(() => {
- axiosMock.restore();
- });
-
- describe('getRef', () => {
- it('escapes branch names correctly', done => {
- const endpoint = `${dropdown.refsPath}contains%23hash`;
- spyOn(axios, 'get').and.callThrough();
- axiosMock.onGet(endpoint).replyOnce({});
-
- dropdown
- .getRef('contains#hash')
- .then(() => {
- expect(axios.get).toHaveBeenCalledWith(endpoint);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('updateCreatePaths', () => {
- it('escapes branch names correctly', () => {
- dropdown.createBranchPath = `${TEST_HOST}/branches?branch_name=some-branch&issue=42`;
- dropdown.createMrPath = `${TEST_HOST}/create_merge_request?branch_name=some-branch&ref=master`;
-
- dropdown.updateCreatePaths('branch', 'contains#hash');
-
- expect(dropdown.createBranchPath).toBe(
- `${TEST_HOST}/branches?branch_name=contains%23hash&issue=42`,
- );
-
- expect(dropdown.createMrPath).toBe(
- `${TEST_HOST}/create_merge_request?branch_name=contains%23hash&ref=master`,
- );
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 1aabf3c2132..fdf8bcee756 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -37,6 +37,8 @@ describe('diffs/components/app', () => {
projectPath: 'namespace/project',
currentUser: {},
changesEmptyStateIllustration: '',
+ dismissEndpoint: '',
+ showSuggestPopover: true,
...props,
},
store,
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 77f8352047c..ef4bb470734 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffsMockData from '../mock_data/merge_request_diffs';
import getDiffWithCommit from '../mock_data/diff_with_commit';
@@ -10,6 +10,8 @@ describe('CompareVersions', () => {
const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
beforeEach(() => {
+ const store = createStore();
+
store.state.diffs.addedLines = 10;
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
diff --git a/spec/javascripts/diffs/components/diff_expansion_cell_spec.js b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
new file mode 100644
index 00000000000..63c50c09fce
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+const EXPAND_UP_CLASS = '.js-unfold';
+const EXPAND_DOWN_CLASS = '.js-unfold-down';
+const EXPAND_ALL_CLASS = '.js-unfold-all';
+
+describe('DiffExpansionCell', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(DiffExpansionCell);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('top row', () => {
+ it('should have "expand up" and "show all" option', () => {
+ const vm = createComponent({
+ isTop: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('middle row', () => {
+ it('should have "expand down", "show all", "expand up" option', () => {
+ const vm = createComponent();
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('bottom row', () => {
+ it('should have "expand down" and "show all" option', () => {
+ const vm = createComponent({
+ isBottom: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 596a1ba5ad2..d4280d3ec2c 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -521,7 +521,7 @@ describe('diff_file_header', () => {
});
describe('with discussions', () => {
- it('dispatches toggleFileDiscussions when user clicks on toggle discussions button', () => {
+ it('dispatches toggleFileDiscussionWrappers when user clicks on toggle discussions button', () => {
const propsCopy = Object.assign({}, props);
propsCopy.diffFile.submodule = false;
propsCopy.diffFile.blob = {
@@ -552,11 +552,11 @@ describe('diff_file_header', () => {
}),
});
- spyOn(vm, 'toggleFileDiscussions');
+ spyOn(vm, 'toggleFileDiscussionWrappers');
vm.$el.querySelector('.js-btn-vue-toggle-comments').click();
- expect(vm.toggleFileDiscussions).toHaveBeenCalled();
+ expect(vm.toggleFileDiscussionWrappers).toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index ef4589ada48..3ca2d1dc934 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,14 +9,18 @@ describe('DiffFile', () => {
let vm;
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
+ vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockData)),
canCurrentUserFork: false,
}).$mount();
});
+ afterEach(() => {
+ vm.$destroy();
+ });
+
describe('template', () => {
- it('should render component with file header, file content components', () => {
+ it('should render component with file header, file content components', done => {
const el = vm.$el;
const { file_hash, file_path } = vm.file;
@@ -30,9 +34,13 @@ describe('DiffFile', () => {
vm.file.renderIt = true;
- vm.$nextTick(() => {
- expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5);
- });
+ vm.$nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.line_content').length).toBe(5);
+ expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('collapsed', () => {
diff --git a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
deleted file mode 100644
index cdd30919b09..00000000000
--- a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import Vue from 'vue';
-import DiffGutterAvatarsComponent from '~/diffs/components/diff_gutter_avatars.vue';
-import { COUNT_OF_AVATARS_IN_GUTTER } from '~/diffs/constants';
-import store from '~/mr_notes/stores';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import discussionsMockData from '../mock_data/diff_discussions';
-
-describe('DiffGutterAvatars', () => {
- let component;
- const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
-
- beforeEach(() => {
- component = createComponentWithStore(Vue.extend(DiffGutterAvatarsComponent), store, {
- discussions: getDiscussionsMockData(),
- }).$mount();
- });
-
- describe('computed', () => {
- describe('discussionsExpanded', () => {
- it('should return true when all discussions are expanded', () => {
- expect(component.discussionsExpanded).toEqual(true);
- });
-
- it('should return false when all discussions are not expanded', () => {
- component.discussions[0].expanded = false;
-
- expect(component.discussionsExpanded).toEqual(false);
- });
- });
-
- describe('allDiscussions', () => {
- it('should return an array of notes', () => {
- expect(component.allDiscussions).toEqual([...component.discussions[0].notes]);
- });
- });
-
- describe('notesInGutter', () => {
- it('should return a subset of discussions to show in gutter', () => {
- expect(component.notesInGutter.length).toEqual(COUNT_OF_AVATARS_IN_GUTTER);
- expect(component.notesInGutter[0]).toEqual({
- note: component.discussions[0].notes[0].note,
- author: component.discussions[0].notes[0].author,
- });
- });
- });
-
- describe('moreCount', () => {
- it('should return count of remaining discussions from gutter', () => {
- expect(component.moreCount).toEqual(2);
- });
- });
-
- describe('moreText', () => {
- it('should return proper text if moreCount > 0', () => {
- expect(component.moreText).toEqual('2 more comments');
- });
-
- it('should return empty string if there is no discussion', () => {
- component.discussions = [];
-
- expect(component.moreText).toEqual('');
- });
- });
- });
-
- describe('methods', () => {
- describe('getTooltipText', () => {
- it('should return original comment if it is shorter than max length', () => {
- const note = component.discussions[0].notes[0];
-
- expect(component.getTooltipText(note)).toEqual('Administrator: comment 1');
- });
-
- it('should return truncated version of comment', () => {
- const note = component.discussions[0].notes[1];
-
- expect(component.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
- });
- });
-
- describe('toggleDiscussions', () => {
- it('should toggle all discussions', () => {
- expect(component.discussions[0].expanded).toEqual(true);
-
- component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
- component.discussions = component.$store.state.notes.discussions;
- component.toggleDiscussions();
-
- expect(component.discussions[0].expanded).toEqual(false);
- component.$store.dispatch('setInitialNotes', []);
- });
-
- it('forces expansion of all discussions', () => {
- spyOn(component.$store, 'dispatch');
-
- component.discussions[0].expanded = true;
- component.discussions.push({
- ...component.discussions[0],
- id: '123test',
- expanded: false,
- });
-
- component.toggleDiscussions();
-
- expect(component.$store.dispatch.calls.argsFor(0)).toEqual([
- 'toggleDiscussion',
- {
- discussionId: component.discussions[0].id,
- forceExpanded: true,
- },
- ]);
-
- expect(component.$store.dispatch.calls.argsFor(1)).toEqual([
- 'toggleDiscussion',
- {
- discussionId: component.discussions[1].id,
- forceExpanded: true,
- },
- ]);
- });
- });
- });
-
- describe('template', () => {
- const buttonSelector = '.js-diff-comment-button';
- const svgSelector = `${buttonSelector} svg`;
- const avatarSelector = '.js-diff-comment-avatar';
- const plusCountSelector = '.js-diff-comment-plus';
-
- it('should have button to collapse discussions when the discussions expanded', () => {
- expect(component.$el.querySelector(buttonSelector)).toBeDefined();
- expect(component.$el.querySelector(svgSelector)).toBeDefined();
- });
-
- it('should have user avatars when discussions collapsed', () => {
- component.discussions[0].expanded = false;
-
- Vue.nextTick(() => {
- expect(component.$el.querySelector(buttonSelector)).toBeNull();
- expect(component.$el.querySelectorAll(avatarSelector).length).toEqual(4);
- expect(component.$el.querySelector(plusCountSelector)).toBeDefined();
- expect(component.$el.querySelector(plusCountSelector).textContent).toEqual('+2');
- });
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index 038db8eaa7c..6bb704658fb 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
@@ -23,7 +23,7 @@ describe('DiffLineGutterContent', () => {
props.fileHash = getDiffFileMock().file_hash;
props.contextLinesPath = '/context/lines/path';
- return createComponentWithStore(cmp, store, props).$mount();
+ return createComponentWithStore(cmp, createStore(), props).$mount();
};
describe('computed', () => {
@@ -61,7 +61,7 @@ describe('DiffLineGutterContent', () => {
contextLinesPath: '/context/lines/path',
};
props.line.discussions = [Object.assign({}, discussionsMockData)];
- const component = createComponentWithStore(cmp, store, props).$mount();
+ const component = createComponentWithStore(cmp, createStore(), props).$mount();
expect(component.hasDiscussions).toEqual(true);
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
@@ -70,15 +70,6 @@ describe('DiffLineGutterContent', () => {
});
describe('template', () => {
- it('should render three dots for context lines', () => {
- const component = createComponent({
- isMatchLine: true,
- });
-
- expect(component.$el.querySelector('span').classList.contains('context-cell')).toEqual(true);
- expect(component.$el.innerText).toEqual('...');
- });
-
it('should render comment button', () => {
const component = createComponent({
showCommentButton: true,
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
index b983dc35a57..237cfccfa29 100644
--- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
@@ -15,7 +15,7 @@ describe('DiffLineNoteForm', () => {
diffFile = getDiffFileMock();
diffLines = diffFile.highlighted_diff_lines;
- component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, {
+ component = createComponentWithStore(Vue.extend(DiffLineNoteForm), createStore(), {
diffFileHash: diffFile.file_hash,
diffLines,
line: diffLines[0],
diff --git a/spec/javascripts/diffs/components/diff_table_cell_spec.js b/spec/javascripts/diffs/components/diff_table_cell_spec.js
index 170e661beea..a5a042c577c 100644
--- a/spec/javascripts/diffs/components/diff_table_cell_spec.js
+++ b/spec/javascripts/diffs/components/diff_table_cell_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import DiffTableCell from '~/diffs/components/diff_table_cell.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
describe('DiffTableCell', () => {
const createComponent = options =>
- createComponentWithStore(Vue.extend(DiffTableCell), store, {
+ createComponentWithStore(Vue.extend(DiffTableCell), createStore(), {
line: diffFileMockData.highlighted_diff_lines[0],
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..290b3d7c803
--- /dev/null
+++ b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('InlineDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(InlineDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
index 97926f6625e..0ddffe926d9 100644
--- a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,7 +9,7 @@ describe('InlineDiffTableRow', () => {
const thisLine = diffFileMockData.highlighted_diff_lines[0];
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), store, {
+ vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), createStore(), {
line: thisLine,
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
index 4452106580a..486d9629e26 100644
--- a/spec/javascripts/diffs/components/inline_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
@@ -10,10 +10,13 @@ describe('InlineDiffView', () => {
let component;
const getDiffFileMock = () => Object.assign({}, diffFileMockData);
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
+ const notesLength = getDiscussionsMockData()[0].notes.length;
beforeEach(done => {
const diffFile = getDiffFileMock();
+ const store = createStore();
+
store.dispatch('diffs/setInlineDiffViewType');
component = createComponentWithStore(Vue.extend(InlineDiffView), store, {
diffFile,
@@ -27,19 +30,20 @@ describe('InlineDiffView', () => {
it('should have rendered diff lines', () => {
const el = component.$el;
- expect(el.querySelectorAll('tr.line_holder').length).toEqual(6);
+ expect(el.querySelectorAll('tr.line_holder').length).toEqual(5);
expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2);
- expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1);
+ expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1);
expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1);
});
it('should render discussions', done => {
const el = component.$el;
component.diffLines[1].discussions = getDiscussionsMockData();
+ component.diffLines[1].discussionsExpanded = true;
Vue.nextTick(() => {
expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
- expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5);
+ expect(el.querySelectorAll('.notes_holder .note').length).toEqual(notesLength + 1);
expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1);
component.$store.dispatch('setInitialNotes', []);
diff --git a/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..a766ebb5efb
--- /dev/null
+++ b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('ParallelDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(ParallelDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
index 236bda96145..191313bf487 100644
--- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import * as constants from '~/diffs/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -12,7 +12,7 @@ describe('ParallelDiffView', () => {
beforeEach(() => {
const diffFile = getDiffFileMock();
- component = createComponentWithStore(Vue.extend(ParallelDiffView), store, {
+ component = createComponentWithStore(Vue.extend(ParallelDiffView), createStore(), {
diffFile,
diffLines: diffFile.parallel_diff_lines,
}).$mount();
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 27428197c1c..531686efff1 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/diffs/mock_data/diff_file.js
+
export default {
submodule: false,
submodule_link: null,
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index f129fbb57a3..5806cb47034 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -37,6 +37,7 @@ import actions, {
toggleFullDiff,
setFileCollapsed,
setExpandedDiffLines,
+ setSuggestPopoverDismissed,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -68,12 +69,19 @@ describe('DiffsStoreActions', () => {
it('should set given endpoint and project path', done => {
const endpoint = '/diffs/set/endpoint';
const projectPath = '/root/project';
+ const dismissEndpoint = '/-/user_callouts';
+ const showSuggestPopover = false;
testAction(
setBaseConfig,
- { endpoint, projectPath },
- { endpoint: '', projectPath: '' },
- [{ type: types.SET_BASE_CONFIG, payload: { endpoint, projectPath } }],
+ { endpoint, projectPath, dismissEndpoint, showSuggestPopover },
+ { endpoint: '', projectPath: '', dismissEndpoint: '', showSuggestPopover: true },
+ [
+ {
+ type: types.SET_BASE_CONFIG,
+ payload: { endpoint, projectPath, dismissEndpoint, showSuggestPopover },
+ },
+ ],
[],
done,
);
@@ -198,6 +206,7 @@ describe('DiffsStoreActions', () => {
position_type: 'text',
},
},
+ hash: 'diff-content-1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a',
},
},
],
@@ -371,7 +380,9 @@ describe('DiffsStoreActions', () => {
const params = { since: 6, to: 26 };
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
const fileHash = 'ff9200';
- const options = { endpoint, params, lineNumbers, fileHash };
+ const isExpandDown = false;
+ const nextLineNumbers = {};
+ const options = { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers };
const mock = new MockAdapter(axios);
const contextLines = { contextLines: [{ lineCode: 6 }] };
mock.onGet(endpoint).reply(200, contextLines);
@@ -383,7 +394,7 @@ describe('DiffsStoreActions', () => {
[
{
type: types.ADD_CONTEXT_LINES,
- payload: { lineNumbers, contextLines, params, fileHash },
+ payload: { lineNumbers, contextLines, params, fileHash, isExpandDown, nextLineNumbers },
},
],
[],
@@ -1080,4 +1091,30 @@ describe('DiffsStoreActions', () => {
);
});
});
+
+ describe('setSuggestPopoverDismissed', () => {
+ it('commits SET_SHOW_SUGGEST_POPOVER', done => {
+ const state = { dismissEndpoint: `${gl.TEST_HOST}/-/user_callouts` };
+ const mock = new MockAdapter(axios);
+ mock.onPost(state.dismissEndpoint).reply(200, {});
+
+ spyOn(axios, 'post').and.callThrough();
+
+ testAction(
+ setSuggestPopoverDismissed,
+ null,
+ state,
+ [{ type: types.SET_SHOW_SUGGEST_POPOVER }],
+ [],
+ () => {
+ expect(axios.post).toHaveBeenCalledWith(state.dismissEndpoint, {
+ feature_name: 'suggest_popover_dismissed',
+ });
+
+ mock.restore();
+ done();
+ },
+ );
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index fa193e1d3b9..3e033b6c9dc 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -81,6 +81,8 @@ describe('DiffsStoreMutations', () => {
params: {
bottom: true,
},
+ isExpandDown: false,
+ nextLineNumbers: {},
};
const diffFile = {
file_hash: options.fileHash,
@@ -108,6 +110,8 @@ describe('DiffsStoreMutations', () => {
options.contextLines,
options.lineNumbers,
options.params.bottom,
+ options.isExpandDown,
+ options.nextLineNumbers,
);
expect(addContextLinesSpy).toHaveBeenCalledWith({
@@ -116,6 +120,7 @@ describe('DiffsStoreMutations', () => {
contextLines: options.contextLines,
bottom: options.params.bottom,
lineNumbers: options.lineNumbers,
+ isExpandDown: false,
});
});
});
@@ -850,4 +855,14 @@ describe('DiffsStoreMutations', () => {
expect(file.renderingLines).toBe(false);
});
});
+
+ describe('SET_SHOW_SUGGEST_POPOVER', () => {
+ it('sets showSuggestPopover to false', () => {
+ const state = { showSuggestPopover: true };
+
+ mutations[types.SET_SHOW_SUGGEST_POPOVER](state);
+
+ expect(state.showSuggestPopover).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 1f877910125..65eb4c9d2a3 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -260,6 +260,17 @@ describe('DiffsStoreUtils', () => {
expect(linesWithReferences[1].meta_data.old_pos).toEqual(2);
expect(linesWithReferences[1].meta_data.new_pos).toEqual(3);
});
+
+ it('should add correct line references when isExpandDown is true', () => {
+ const lines = [{ type: null }, { type: MATCH_LINE_TYPE }];
+ const linesWithReferences = utils.addLineReferences(lines, lineNumbers, false, true, {
+ old_line: 10,
+ new_line: 11,
+ });
+
+ expect(linesWithReferences[1].meta_data.old_pos).toEqual(10);
+ expect(linesWithReferences[1].meta_data.new_pos).toEqual(11);
+ });
});
describe('trimFirstCharOfLineContent', () => {
diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/javascripts/environments/confirm_rollback_modal_spec.js
deleted file mode 100644
index 05715bce38f..00000000000
--- a/spec/javascripts/environments/confirm_rollback_modal_spec.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
-import eventHub from '~/environments/event_hub';
-
-describe('Confirm Rollback Modal Component', () => {
- let environment;
-
- beforeEach(() => {
- environment = {
- name: 'test',
- last_deployment: {
- commit: {
- short_id: 'abc0123',
- },
- },
- modalId: 'test',
- };
- });
-
- it('should show "Rollback" when isLastDeployment is false', () => {
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment: {
- ...environment,
- isLastDeployment: false,
- },
- },
- });
- const modal = component.find(GlModal);
-
- expect(modal.attributes('title')).toContain('Rollback');
- expect(modal.attributes('title')).toContain('test');
- expect(modal.attributes('ok-title')).toBe('Rollback');
- expect(modal.text()).toContain('commit abc0123');
- expect(modal.text()).toContain('Are you sure you want to continue?');
- });
-
- it('should show "Re-deploy" when isLastDeployment is true', () => {
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment: {
- ...environment,
- isLastDeployment: true,
- },
- },
- });
- const modal = component.find(GlModal);
-
- expect(modal.attributes('title')).toContain('Re-deploy');
- expect(modal.attributes('title')).toContain('test');
- expect(modal.attributes('ok-title')).toBe('Re-deploy');
- expect(modal.text()).toContain('commit abc0123');
- expect(modal.text()).toContain('Are you sure you want to continue?');
- });
-
- it('should emit the "rollback" event when "ok" is clicked', () => {
- environment = { ...environment, isLastDeployment: true };
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment,
- },
- });
- const eventHubSpy = spyOn(eventHub, '$emit');
- const modal = component.find(GlModal);
- modal.vm.$emit('ok');
-
- expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment);
- });
-});
diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js
deleted file mode 100644
index 8c47f6a12c0..00000000000
--- a/spec/javascripts/environments/environment_rollback_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
-import { GlButton } from '@gitlab/ui';
-import eventHub from '~/environments/event_hub';
-import rollbackComp from '~/environments/components/environment_rollback.vue';
-
-describe('Rollback Component', () => {
- const retryUrl = 'https://gitlab.com/retry';
- let RollbackComponent;
-
- beforeEach(() => {
- RollbackComponent = Vue.extend(rollbackComp);
- });
-
- it('Should render Re-deploy label when isLastDeployment is true', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
- propsData: {
- retryUrl,
- isLastDeployment: true,
- environment: {},
- },
- }).$mount();
-
- expect(component.$el).toHaveSpriteIcon('repeat');
- });
-
- it('Should render Rollback label when isLastDeployment is false', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
- propsData: {
- retryUrl,
- isLastDeployment: false,
- environment: {},
- },
- }).$mount();
-
- expect(component.$el).toHaveSpriteIcon('redo');
- });
-
- it('should emit a "rollback" event on button click', () => {
- const eventHubSpy = spyOn(eventHub, '$emit');
- const component = shallowMount(RollbackComponent, {
- propsData: {
- retryUrl,
- environment: {
- name: 'test',
- },
- },
- });
- const button = component.find(GlButton);
-
- button.vm.$emit('click');
-
- expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
- retryUrl,
- isLastDeployment: true,
- name: 'test',
- });
- });
-});
diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js
index 56e18db59c5..fc98e656efe 100644
--- a/spec/javascripts/environments/environment_terminal_button_spec.js
+++ b/spec/javascripts/environments/environment_terminal_button_spec.js
@@ -12,36 +12,24 @@ describe('Stop Component', () => {
}).$mount();
};
- describe('enabled', () => {
- beforeEach(() => {
- mountWithProps({ terminalPath });
- });
-
- describe('computed', () => {
- it('title', () => {
- expect(component.title).toEqual('Terminal');
- });
- });
-
- it('should render a link to open a web terminal with the provided path', () => {
- expect(component.$el.tagName).toEqual('A');
- expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
- expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
- expect(component.$el.getAttribute('href')).toEqual(terminalPath);
- });
+ beforeEach(() => {
+ mountWithProps({ terminalPath });
+ });
- it('should render a non-disabled button', () => {
- expect(component.$el.classList).not.toContain('disabled');
+ describe('computed', () => {
+ it('title', () => {
+ expect(component.title).toEqual('Terminal');
});
});
- describe('disabled', () => {
- beforeEach(() => {
- mountWithProps({ terminalPath, disabled: true });
- });
+ it('should render a link to open a web terminal with the provided path', () => {
+ expect(component.$el.tagName).toEqual('A');
+ expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
+ expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
+ expect(component.$el.getAttribute('href')).toEqual(terminalPath);
+ });
- it('should render a disabled button', () => {
- expect(component.$el.classList).toContain('disabled');
- });
+ it('should render a non-disabled button', () => {
+ expect(component.$el.classList).not.toContain('disabled');
});
});
diff --git a/spec/javascripts/error_tracking_settings/components/app_spec.js b/spec/javascripts/error_tracking_settings/components/app_spec.js
deleted file mode 100644
index 2e52a45fd34..00000000000
--- a/spec/javascripts/error_tracking_settings/components/app_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
-import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
-import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
-import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from 'spec/test_constants';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('error tracking settings app', () => {
- let store;
- let wrapper;
-
- function mountComponent() {
- wrapper = shallowMount(ErrorTrackingSettings, {
- localVue,
- store, // Override the imported store
- propsData: {
- initialEnabled: 'true',
- initialApiHost: TEST_HOST,
- initialToken: 'someToken',
- initialProject: null,
- listProjectsEndpoint: TEST_HOST,
- operationsSettingsEndpoint: TEST_HOST,
- },
- });
- }
-
- beforeEach(() => {
- store = createStore();
-
- mountComponent();
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('section', () => {
- it('renders the form and dropdown', () => {
- expect(wrapper.find(ErrorTrackingForm).exists()).toBeTruthy();
- expect(wrapper.find(ProjectDropdown).exists()).toBeTruthy();
- });
-
- it('renders the Save Changes button', () => {
- expect(wrapper.find('.js-error-tracking-button').exists()).toBeTruthy();
- });
-
- it('enables the button by default', () => {
- expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy();
- });
-
- it('disables the button when saving', () => {
- store.state.settingsLoading = true;
-
- expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js b/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
deleted file mode 100644
index 23e57c4bbf1..00000000000
--- a/spec/javascripts/error_tracking_settings/components/error_tracking_form_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlButton, GlFormInput } from '@gitlab/ui';
-import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
-import { defaultProps } from '../mock';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('error tracking settings form', () => {
- let wrapper;
-
- function mountComponent() {
- wrapper = shallowMount(ErrorTrackingForm, {
- localVue,
- propsData: defaultProps,
- });
- }
-
- beforeEach(() => {
- mountComponent();
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('an empty form', () => {
- it('is rendered', () => {
- expect(wrapper.findAll(GlFormInput).length).toBe(2);
- expect(wrapper.find(GlFormInput).attributes('id')).toBe('error-tracking-api-host');
- expect(
- wrapper
- .findAll(GlFormInput)
- .at(1)
- .attributes('id'),
- ).toBe('error-tracking-token');
-
- expect(wrapper.findAll(GlButton).exists()).toBe(true);
- });
-
- it('is rendered with labels and placeholders', () => {
- const pageText = wrapper.text();
-
- expect(pageText).toContain('Find your hostname in your Sentry account settings page');
- expect(pageText).toContain(
- "After adding your Auth Token, use the 'Connect' button to load projects",
- );
-
- expect(pageText).not.toContain('Connection has failed. Re-check Auth Token and try again');
- expect(
- wrapper
- .findAll(GlFormInput)
- .at(0)
- .attributes('placeholder'),
- ).toContain('https://mysentryserver.com');
- });
- });
-
- describe('after a successful connection', () => {
- beforeEach(() => {
- wrapper.setProps({ connectSuccessful: true });
- });
-
- it('shows the success checkmark', () => {
- expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(true);
- });
-
- it('does not show an error', () => {
- expect(wrapper.text()).not.toContain(
- 'Connection has failed. Re-check Auth Token and try again',
- );
- });
- });
-
- describe('after an unsuccessful connection', () => {
- beforeEach(() => {
- wrapper.setProps({ connectError: true });
- });
-
- it('does not show the check mark', () => {
- expect(wrapper.find('.js-error-tracking-connect-success').isVisible()).toBe(false);
- });
-
- it('shows an error', () => {
- expect(wrapper.text()).toContain('Connection has failed. Re-check Auth Token and try again');
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js b/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
deleted file mode 100644
index 8e5dbe28452..00000000000
--- a/spec/javascripts/error_tracking_settings/components/project_dropdown_spec.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import _ from 'underscore';
-import Vuex from 'vuex';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
-import { defaultProps, projectList, staleProject } from '../mock';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('error tracking settings project dropdown', () => {
- let wrapper;
-
- function mountComponent() {
- wrapper = shallowMount(ProjectDropdown, {
- localVue,
- propsData: {
- ..._.pick(
- defaultProps,
- 'dropdownLabel',
- 'invalidProjectLabel',
- 'projects',
- 'projectSelectionLabel',
- 'selectedProject',
- 'token',
- ),
- hasProjects: false,
- isProjectInvalid: false,
- },
- });
- }
-
- beforeEach(() => {
- mountComponent();
- });
-
- afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
- });
-
- describe('empty project list', () => {
- it('renders the dropdown', () => {
- expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
- expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
- });
-
- it('shows helper text', () => {
- expect(wrapper.find('.js-project-dropdown-label').exists()).toBeTruthy();
- expect(wrapper.find('.js-project-dropdown-label').text()).toContain(
- 'To enable project selection',
- );
- });
-
- it('does not show an error', () => {
- expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
- });
-
- it('does not contain any dropdown items', () => {
- expect(wrapper.find(GlDropdownItem).exists()).toBeFalsy();
- expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available');
- });
- });
-
- describe('populated project list', () => {
- beforeEach(() => {
- wrapper.setProps({ projects: _.clone(projectList), hasProjects: true });
- });
-
- it('renders the dropdown', () => {
- expect(wrapper.find('#project-dropdown').exists()).toBeTruthy();
- expect(wrapper.find(GlDropdown).exists()).toBeTruthy();
- });
-
- it('contains a number of dropdown items', () => {
- expect(wrapper.find(GlDropdownItem).exists()).toBeTruthy();
- expect(wrapper.findAll(GlDropdownItem).length).toBe(2);
- });
- });
-
- describe('selected project', () => {
- const selectedProject = _.clone(projectList[0]);
-
- beforeEach(() => {
- wrapper.setProps({ projects: _.clone(projectList), selectedProject, hasProjects: true });
- });
-
- it('does not show helper text', () => {
- expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
- expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy();
- });
- });
-
- describe('invalid project selected', () => {
- beforeEach(() => {
- wrapper.setProps({
- projects: _.clone(projectList),
- selectedProject: staleProject,
- isProjectInvalid: true,
- });
- });
-
- it('displays a error', () => {
- expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy();
- expect(wrapper.find('.js-project-dropdown-error').exists()).toBeTruthy();
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/mock.js b/spec/javascripts/error_tracking_settings/mock.js
deleted file mode 100644
index 32cdba33c14..00000000000
--- a/spec/javascripts/error_tracking_settings/mock.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from 'spec/test_constants';
-
-const defaultStore = createStore();
-
-export const projectList = [
- {
- name: 'name',
- slug: 'slug',
- organizationName: 'organizationName',
- organizationSlug: 'organizationSlug',
- },
- {
- name: 'name2',
- slug: 'slug2',
- organizationName: 'organizationName2',
- organizationSlug: 'organizationSlug2',
- },
-];
-
-export const staleProject = {
- name: 'staleName',
- slug: 'staleSlug',
- organizationName: 'staleOrganizationName',
- organizationSlug: 'staleOrganizationSlug',
-};
-
-export const normalizedProject = {
- name: 'name',
- slug: 'slug',
- organizationName: 'organization_name',
- organizationSlug: 'organization_slug',
-};
-
-export const sampleBackendProject = {
- name: normalizedProject.name,
- slug: normalizedProject.slug,
- organization_name: normalizedProject.organizationName,
- organization_slug: normalizedProject.organizationSlug,
-};
-
-export const sampleFrontendSettings = {
- apiHost: 'apiHost',
- enabled: false,
- token: 'token',
- selectedProject: {
- slug: normalizedProject.slug,
- name: normalizedProject.name,
- organizationName: normalizedProject.organizationName,
- organizationSlug: normalizedProject.organizationSlug,
- },
-};
-
-export const transformedSettings = {
- api_host: 'apiHost',
- enabled: false,
- token: 'token',
- project: {
- slug: normalizedProject.slug,
- name: normalizedProject.name,
- organization_name: normalizedProject.organizationName,
- organization_slug: normalizedProject.organizationSlug,
- },
-};
-
-export const defaultProps = {
- ...defaultStore.state,
- ...defaultStore.getters,
-};
-
-export const initialEmptyState = {
- apiHost: '',
- enabled: false,
- project: null,
- token: '',
- listProjectsEndpoint: TEST_HOST,
- operationsSettingsEndpoint: TEST_HOST,
-};
-
-export const initialPopulatedState = {
- apiHost: 'apiHost',
- enabled: true,
- project: JSON.stringify(projectList[0]),
- token: 'token',
- listProjectsEndpoint: TEST_HOST,
- operationsSettingsEndpoint: TEST_HOST,
-};
-
-export const projectWithHtmlTemplate = {
- ...projectList[0],
- name: '<strong>bold</strong>',
-};
diff --git a/spec/javascripts/error_tracking_settings/store/actions_spec.js b/spec/javascripts/error_tracking_settings/store/actions_spec.js
deleted file mode 100644
index 0255b3a7aa4..00000000000
--- a/spec/javascripts/error_tracking_settings/store/actions_spec.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'spec/helpers/vuex_action_helper';
-import { TEST_HOST } from 'spec/test_constants';
-import axios from '~/lib/utils/axios_utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import actionsDefaultExport, * as actions from '~/error_tracking_settings/store/actions';
-import * as types from '~/error_tracking_settings/store/mutation_types';
-import defaultState from '~/error_tracking_settings/store/state';
-import { projectList } from '../mock';
-
-describe('error tracking settings actions', () => {
- let state;
-
- describe('project list actions', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state = { ...defaultState(), listProjectsEndpoint: TEST_HOST };
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should request and transform the project list', done => {
- mock.onPost(TEST_HOST).reply(() => [200, { projects: projectList }]);
- testAction(
- actions.fetchProjects,
- null,
- state,
- [],
- [
- { type: 'requestProjects' },
- {
- type: 'receiveProjectsSuccess',
- payload: projectList.map(convertObjectPropsToCamelCase),
- },
- ],
- () => {
- expect(mock.history.post.length).toBe(1);
- done();
- },
- );
- });
-
- it('should handle a server error', done => {
- mock.onPost(`${TEST_HOST}.json`).reply(() => [400]);
- testAction(
- actions.fetchProjects,
- null,
- state,
- [],
- [
- { type: 'requestProjects' },
- {
- type: 'receiveProjectsError',
- },
- ],
- () => {
- expect(mock.history.post.length).toBe(1);
- done();
- },
- );
- });
-
- it('should request projects correctly', done => {
- testAction(actions.requestProjects, null, state, [{ type: types.RESET_CONNECT }], [], done);
- });
-
- it('should receive projects correctly', done => {
- const testPayload = [];
- testAction(
- actions.receiveProjectsSuccess,
- testPayload,
- state,
- [
- { type: types.UPDATE_CONNECT_SUCCESS },
- { type: types.RECEIVE_PROJECTS, payload: testPayload },
- ],
- [],
- done,
- );
- });
-
- it('should handle errors when receiving projects', done => {
- const testPayload = [];
- testAction(
- actions.receiveProjectsError,
- testPayload,
- state,
- [{ type: types.UPDATE_CONNECT_ERROR }, { type: types.CLEAR_PROJECTS }],
- [],
- done,
- );
- });
- });
-
- describe('save changes actions', () => {
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- state = {
- operationsSettingsEndpoint: TEST_HOST,
- };
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- it('should save the page', done => {
- const refreshCurrentPage = spyOnDependency(actionsDefaultExport, 'refreshCurrentPage');
- mock.onPatch(TEST_HOST).reply(200);
- testAction(actions.updateSettings, null, state, [], [{ type: 'requestSettings' }], () => {
- expect(mock.history.patch.length).toBe(1);
- expect(refreshCurrentPage).toHaveBeenCalled();
- done();
- });
- });
-
- it('should handle a server error', done => {
- mock.onPatch(TEST_HOST).reply(400);
- testAction(
- actions.updateSettings,
- null,
- state,
- [],
- [
- { type: 'requestSettings' },
- {
- type: 'receiveSettingsError',
- payload: new Error('Request failed with status code 400'),
- },
- ],
- () => {
- expect(mock.history.patch.length).toBe(1);
- done();
- },
- );
- });
-
- it('should request to save the page', done => {
- testAction(
- actions.requestSettings,
- null,
- state,
- [{ type: types.UPDATE_SETTINGS_LOADING, payload: true }],
- [],
- done,
- );
- });
-
- it('should handle errors when requesting to save the page', done => {
- testAction(
- actions.receiveSettingsError,
- {},
- state,
- [{ type: types.UPDATE_SETTINGS_LOADING, payload: false }],
- [],
- done,
- );
- });
- });
-
- describe('generic actions to update the store', () => {
- const testData = 'test';
- it('should reset the `connect success` flag when updating the api host', done => {
- testAction(
- actions.updateApiHost,
- testData,
- state,
- [{ type: types.UPDATE_API_HOST, payload: testData }, { type: types.RESET_CONNECT }],
- [],
- done,
- );
- });
-
- it('should reset the `connect success` flag when updating the token', done => {
- testAction(
- actions.updateToken,
- testData,
- state,
- [{ type: types.UPDATE_TOKEN, payload: testData }, { type: types.RESET_CONNECT }],
- [],
- done,
- );
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/store/getters_spec.js b/spec/javascripts/error_tracking_settings/store/getters_spec.js
deleted file mode 100644
index 2c5ff084b8a..00000000000
--- a/spec/javascripts/error_tracking_settings/store/getters_spec.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import * as getters from '~/error_tracking_settings/store/getters';
-import defaultState from '~/error_tracking_settings/store/state';
-import { projectList, projectWithHtmlTemplate, staleProject } from '../mock';
-
-describe('Error Tracking Settings - Getters', () => {
- let state;
-
- beforeEach(() => {
- state = defaultState();
- });
-
- describe('hasProjects', () => {
- it('should reflect when no projects exist', () => {
- expect(getters.hasProjects(state)).toEqual(false);
- });
-
- it('should reflect when projects exist', () => {
- state.projects = projectList;
-
- expect(getters.hasProjects(state)).toEqual(true);
- });
- });
-
- describe('isProjectInvalid', () => {
- const mockGetters = { hasProjects: true };
- it('should show when a project is valid', () => {
- state.projects = projectList;
- [state.selectedProject] = projectList;
-
- expect(getters.isProjectInvalid(state, mockGetters)).toEqual(false);
- });
-
- it('should show when a project is invalid', () => {
- state.projects = projectList;
- state.selectedProject = staleProject;
-
- expect(getters.isProjectInvalid(state, mockGetters)).toEqual(true);
- });
- });
-
- describe('dropdownLabel', () => {
- const mockGetters = { hasProjects: false };
- it('should display correctly when there are no projects available', () => {
- expect(getters.dropdownLabel(state, mockGetters)).toEqual('No projects available');
- });
-
- it('should display correctly when a project is selected', () => {
- [state.selectedProject] = projectList;
-
- expect(getters.dropdownLabel(state, mockGetters)).toEqual('organizationName | name');
- });
-
- it('should display correctly when no project is selected', () => {
- state.projects = projectList;
-
- expect(getters.dropdownLabel(state, { hasProjects: true })).toEqual('Select project');
- });
- });
-
- describe('invalidProjectLabel', () => {
- it('should display an error containing the project name', () => {
- [state.selectedProject] = projectList;
-
- expect(getters.invalidProjectLabel(state)).toEqual(
- 'Project "name" is no longer available. Select another project to continue.',
- );
- });
-
- it('should properly escape the label text', () => {
- state.selectedProject = projectWithHtmlTemplate;
-
- expect(getters.invalidProjectLabel(state)).toEqual(
- 'Project "&lt;strong&gt;bold&lt;/strong&gt;" is no longer available. Select another project to continue.',
- );
- });
- });
-
- describe('projectSelectionLabel', () => {
- it('should show the correct message when the token is empty', () => {
- expect(getters.projectSelectionLabel(state)).toEqual(
- 'To enable project selection, enter a valid Auth Token',
- );
- });
-
- it('should show the correct message when token exists', () => {
- state.token = 'test-token';
-
- expect(getters.projectSelectionLabel(state)).toEqual(
- "Click 'Connect' to re-establish the connection to Sentry and activate the dropdown.",
- );
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/store/mutation_spec.js b/spec/javascripts/error_tracking_settings/store/mutation_spec.js
deleted file mode 100644
index bb1f1da784e..00000000000
--- a/spec/javascripts/error_tracking_settings/store/mutation_spec.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { TEST_HOST } from 'spec/test_constants';
-import mutations from '~/error_tracking_settings/store/mutations';
-import defaultState from '~/error_tracking_settings/store/state';
-import * as types from '~/error_tracking_settings/store/mutation_types';
-import {
- initialEmptyState,
- initialPopulatedState,
- projectList,
- sampleBackendProject,
- normalizedProject,
-} from '../mock';
-
-describe('error tracking settings mutations', () => {
- describe('mutations', () => {
- let state;
-
- beforeEach(() => {
- state = defaultState();
- });
-
- it('should create an empty initial state correctly', () => {
- mutations[types.SET_INITIAL_STATE](state, {
- ...initialEmptyState,
- });
-
- expect(state.apiHost).toEqual('');
- expect(state.enabled).toEqual(false);
- expect(state.selectedProject).toEqual(null);
- expect(state.token).toEqual('');
- expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
- expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
- });
-
- it('should populate the initial state correctly', () => {
- mutations[types.SET_INITIAL_STATE](state, {
- ...initialPopulatedState,
- });
-
- expect(state.apiHost).toEqual('apiHost');
- expect(state.enabled).toEqual(true);
- expect(state.selectedProject).toEqual(projectList[0]);
- expect(state.token).toEqual('token');
- expect(state.listProjectsEndpoint).toEqual(TEST_HOST);
- expect(state.operationsSettingsEndpoint).toEqual(TEST_HOST);
- });
-
- it('should receive projects successfully', () => {
- mutations[types.RECEIVE_PROJECTS](state, [sampleBackendProject]);
-
- expect(state.projects).toEqual([normalizedProject]);
- });
-
- it('should strip out unnecessary project properties', () => {
- mutations[types.RECEIVE_PROJECTS](state, [
- { ...sampleBackendProject, extra_property: 'extra_property' },
- ]);
-
- expect(state.projects).toEqual([normalizedProject]);
- });
-
- it('should update state when connect is successful', () => {
- mutations[types.UPDATE_CONNECT_SUCCESS](state);
-
- expect(state.connectSuccessful).toBe(true);
- expect(state.connectError).toBe(false);
- });
-
- it('should update state when connect fails', () => {
- mutations[types.UPDATE_CONNECT_ERROR](state);
-
- expect(state.connectSuccessful).toBe(false);
- expect(state.connectError).toBe(true);
- });
-
- it('should update state when connect is reset', () => {
- mutations[types.RESET_CONNECT](state);
-
- expect(state.connectSuccessful).toBe(false);
- expect(state.connectError).toBe(false);
- });
- });
-});
diff --git a/spec/javascripts/error_tracking_settings/utils_spec.js b/spec/javascripts/error_tracking_settings/utils_spec.js
deleted file mode 100644
index 4b144f7daf1..00000000000
--- a/spec/javascripts/error_tracking_settings/utils_spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { transformFrontendSettings } from '~/error_tracking_settings/utils';
-import { sampleFrontendSettings, transformedSettings } from './mock';
-
-describe('error tracking settings utils', () => {
- describe('data transform functions', () => {
- it('should transform settings successfully for the backend', () => {
- expect(transformFrontendSettings(sampleFrontendSettings)).toEqual(transformedSettings);
- });
-
- it('should transform empty values in the settings object to null', () => {
- const emptyFrontendSettingsObject = {
- apiHost: '',
- enabled: false,
- token: '',
- selectedProject: null,
- };
- const transformedEmptySettingsObject = {
- api_host: null,
- enabled: false,
- token: null,
- project: null,
- };
-
- expect(transformFrontendSettings(emptyFrontendSettingsObject)).toEqual(
- transformedEmptySettingsObject,
- );
- });
- });
-});
diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js
index d1d16afc977..10d844fd94b 100644
--- a/spec/javascripts/filtered_search/visual_token_value_spec.js
+++ b/spec/javascripts/filtered_search/visual_token_value_spec.js
@@ -155,7 +155,7 @@ describe('Filtered Search Visual Tokens', () => {
`);
const filteredSearchInput = document.querySelector('.filtered-search');
- filteredSearchInput.dataset.baseEndpoint = dummyEndpoint;
+ filteredSearchInput.dataset.runnerTagsEndpoint = `${dummyEndpoint}/admin/runners/tag_list`;
filteredSearchInput.dataset.labelsEndpoint = `${dummyEndpoint}/-/labels`;
filteredSearchInput.dataset.milestonesEndpoint = `${dummyEndpoint}/-/milestones`;
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index bed020f5b0a..d6b7ef32c84 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1,5 +1,2 @@
-*.html.raw
-*.html
-*.json
-*.pdf
-*.bmpr
+*
+!.gitignore
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb
deleted file mode 100644
index e0aaecf626a..00000000000
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let!(:abuse_report) { create(:abuse_report) }
- let!(:abuse_report_with_short_message) { create(:abuse_report, message: 'SHORT MESSAGE') }
- let!(:abuse_report_with_long_message) { create(:abuse_report, message: "LONG MESSAGE\n" * 50) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('abuse_reports/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'abuse_reports/abuse_reports_list.html' do
- get :index
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
deleted file mode 100644
index 22a5de66577..00000000000
--- a/spec/javascripts/fixtures/admin_users.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
- include StubENV
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
-
- before do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(admin)
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('admin/users')
- end
-
- it 'admin/users/new_with_internal_user_regex.html' do
- stub_application_setting(user_default_external: true)
- stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
-
- get :new
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
deleted file mode 100644
index d4651fa6ece..00000000000
--- a/spec/javascripts/fixtures/application_settings.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
- include StubENV
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
-
- before do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(admin)
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('application_settings/')
- end
-
- after do
- remove_repository(project)
- end
-
- it 'application_settings/accounts_and_limit.html' do
- stub_application_setting(user_default_external: false)
-
- get :show
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb
deleted file mode 100644
index b20a0159d7d..00000000000
--- a/spec/javascripts/fixtures/autocomplete_sources.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- set(:admin) { create(:admin) }
- set(:group) { create(:group, name: 'frontend-fixtures') }
- set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
- set(:issue) { create(:issue, project: project) }
-
- before(:all) do
- clean_frontend_fixtures('autocomplete_sources/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'autocomplete_sources/labels.json' do
- issue.labels << create(:label, project: project, title: 'bug')
- issue.labels << create(:label, project: project, title: 'critical')
-
- create(:label, project: project, title: 'feature')
- create(:label, project: project, title: 'documentation')
-
- get :labels,
- format: :json,
- params: {
- namespace_id: group.path,
- project_id: project.path,
- type: issue.class.name,
- type_id: issue.id
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
deleted file mode 100644
index 07670552cd5..00000000000
--- a/spec/javascripts/fixtures/blob.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('blob/')
- end
-
- before do
- sign_in(admin)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- after do
- remove_repository(project)
- end
-
- it 'blob/show.html' do
- get(:show, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: 'add-ipython-files/files/ipython/basic.ipynb'
- })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
deleted file mode 100644
index 5835721d3d5..00000000000
--- a/spec/javascripts/fixtures/boards.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('boards/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'boards/show.html' do
- get(:index, params: {
- namespace_id: project.namespace,
- project_id: project
- })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
deleted file mode 100644
index 204aa9b7c7a..00000000000
--- a/spec/javascripts/fixtures/branches.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('branches/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'branches/new_branch.html' do
- get :new, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
deleted file mode 100644
index 1076404e0e3..00000000000
--- a/spec/javascripts/fixtures/clusters.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace) }
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('clusters/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'clusters/show_cluster.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: cluster
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
deleted file mode 100644
index ff9a4bc1adc..00000000000
--- a/spec/javascripts/fixtures/commit.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
- let(:commit) { project.commit("master") }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('commit/')
- end
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- it 'commit/show.html' do
- params = {
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id
- }
-
- get :show, params: params
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
deleted file mode 100644
index 38eab853da2..00000000000
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:project2) { create(:project, :internal)}
- let(:project3) { create(:project, :internal)}
- let(:project4) { create(:project, :internal)}
-
- before(:all) do
- clean_frontend_fixtures('deploy_keys/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- render_views
-
- it 'deploy_keys/keys.json' do
- create(:rsa_deploy_key_2048, public: true)
- project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
- internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
- create(:deploy_keys_project, project: project, deploy_key: project_key)
- create(:deploy_keys_project, project: project2, deploy_key: internal_key)
- create(:deploy_keys_project, project: project3, deploy_key: project_key)
- create(:deploy_keys_project, project: project4, deploy_key: project_key)
-
- get :index, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
deleted file mode 100644
index 4d0afc3ce1a..00000000000
--- a/spec/javascripts/fixtures/groups.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe 'Groups (JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('groups/')
- end
-
- before do
- group.add_maintainer(admin)
- sign_in(admin)
- end
-
- describe GroupsController, '(JavaScript fixtures)', type: :controller do
- it 'groups/edit.html' do
- get :edit, params: { id: group }
-
- expect(response).to be_success
- end
- end
-
- describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'groups/ci_cd_settings.html' do
- get :show, params: { group_id: group }
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
deleted file mode 100644
index d8d77f767de..00000000000
--- a/spec/javascripts/fixtures/issues.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'spec_helper'
-
-describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('issues/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'issues/open-issue.html' do
- render_issue(create(:issue, project: project))
- end
-
- it 'issues/closed-issue.html' do
- render_issue(create(:closed_issue, project: project))
- end
-
- it 'issues/issue-with-task-list.html' do
- issue = create(:issue, project: project, description: '- [ ] Task List Item')
- render_issue(issue)
- end
-
- it 'issues/issue_with_comment.html' do
- issue = create(:issue, project: project)
- create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
- render_issue(issue)
- end
-
- it 'issues/issue_list.html' do
- create(:issue, project: project)
-
- get :index, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-
- private
-
- def render_issue(issue)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.to_param
- }
-
- expect(response).to be_success
- end
-end
-
-describe API::Issues, '(JavaScript fixtures)', type: :request do
- include ApiHelpers
- include JavaScriptFixturesHelpers
-
- def get_related_merge_requests(project_id, issue_iid, user = nil)
- get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
- end
-
- def create_referencing_mr(user, project, issue)
- attributes = {
- author: user,
- source_project: project,
- target_project: project,
- source_branch: "master",
- target_branch: "test",
- assignee: user,
- description: "See #{issue.to_reference}"
- }
- create(:merge_request, attributes).tap do |merge_request|
- create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
- end
- end
-
- it 'issues/related_merge_requests.json' do
- user = create(:user)
- project = create(:project, :public, creator_id: user.id, namespace: user.namespace)
- issue_title = 'foo'
- issue_description = 'closed'
- milestone = create(:milestone, title: '1.0.0', project: project)
- issue = create :issue,
- author: user,
- assignees: [user],
- project: project,
- milestone: milestone,
- created_at: generate(:past_time),
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
-
- project.add_reporter(user)
- create_referencing_mr(user, project, issue)
-
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: "Some description")
- project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
- create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline))
-
- get_related_merge_requests(project.id, issue.iid, user)
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
deleted file mode 100644
index 46ccd6f8c8a..00000000000
--- a/spec/javascripts/fixtures/jobs.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
- let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
- let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
- let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
- let!(:delayed_job) do
- create(:ci_build, :scheduled,
- pipeline: pipeline,
- name: 'delayed job',
- stage: 'test')
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('builds/')
- clean_frontend_fixtures('jobs/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'builds/build-with-artifacts.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: build_with_artifacts.to_param
- }
-
- expect(response).to be_success
- end
-
- it 'jobs/delayed.json' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: delayed_job.to_param
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb
deleted file mode 100644
index 4d1b7317274..00000000000
--- a/spec/javascripts/fixtures/labels.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'spec_helper'
-
-describe 'Labels (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:group) { create(:group, name: 'frontend-fixtures-group' )}
- let(:project) { create(:project_empty_repo, namespace: group, path: 'labels-project') }
-
- let!(:project_label_bug) { create(:label, project: project, title: 'bug', color: '#FF0000') }
- let!(:project_label_enhancement) { create(:label, project: project, title: 'enhancement', color: '#00FF00') }
- let!(:project_label_feature) { create(:label, project: project, title: 'feature', color: '#0000FF') }
-
- let!(:group_label_roses) { create(:group_label, group: group, title: 'roses', color: '#FF0000') }
- let!(:groub_label_space) { create(:group_label, group: group, title: 'some space', color: '#FFFFFF') }
- let!(:groub_label_violets) { create(:group_label, group: group, title: 'violets', color: '#0000FF') }
-
- before(:all) do
- clean_frontend_fixtures('labels/')
- end
-
- after do
- remove_repository(project)
- end
-
- describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'labels/group_labels.json' do
- get :index, params: {
- group_id: group
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-
- describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'labels/project_labels.json' do
- get :index, params: {
- namespace_id: group,
- project_id: project
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
deleted file mode 100644
index 05860be2291..00000000000
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
- let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
- let(:pipeline) do
- create(
- :ci_pipeline,
- project: merge_request.source_project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha
- )
- end
- let(:path) { "files/ruby/popen.rb" }
- let(:position) do
- Gitlab::Diff::Position.new(
- old_path: path,
- new_path: path,
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs
- )
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('merge_requests/')
- end
-
- before do
- sign_in(admin)
- allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
- end
-
- after do
- remove_repository(project)
- end
-
- it 'merge_requests/merge_request_of_current_user.html' do
- merge_request.update(author: admin)
-
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merge_request_with_task_list.html' do
- create(:ci_build, :pending, pipeline: pipeline)
-
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merged_merge_request.html' do
- expect_next_instance_of(MergeRequest) do |merge_request|
- allow(merge_request).to receive(:source_branch_exists?).and_return(true)
- allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
- end
- render_merge_request(merged_merge_request)
- end
-
- it 'merge_requests/diff_comment.html' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merge_request_with_comment.html' do
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/discussions.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request)
- end
-
- it 'merge_requests/diff_discussion.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request)
- end
-
- it 'merge_requests/resolved_diff_discussion.json' do
- note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
- create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
-
- render_discussions_json(merge_request)
- end
-
- context 'with image diff' do
- let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") }
- let(:image_path) { "files/images/ee_repo_logo.png" }
- let(:image_position) do
- Gitlab::Diff::Position.new(
- old_path: image_path,
- new_path: image_path,
- width: 100,
- height: 100,
- x: 1,
- y: 1,
- position_type: "image",
- diff_refs: merge_request2.diff_refs
- )
- end
-
- it 'merge_requests/image_diff_discussion.json' do
- create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position)
- render_discussions_json(merge_request2)
- end
- end
-
- private
-
- def render_discussions_json(merge_request)
- get :discussions, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param
- }, format: :json
- end
-
- def render_merge_request(merge_request)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param
- }, format: :html
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
deleted file mode 100644
index 03b9b713fd8..00000000000
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-
-require 'spec_helper'
-
-describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
- let(:path) { "files/ruby/popen.rb" }
- let(:selected_commit) { merge_request.all_commits[0] }
- let(:position) do
- Gitlab::Diff::Position.new(
- old_path: path,
- new_path: path,
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs
- )
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('merge_request_diffs/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'merge_request_diffs/with_commit.json' do
- # Create a user that matches the selected commit author
- # This is so that the "author" information will be populated
- create(:user, email: selected_commit.author_email, name: selected_commit.author_name)
-
- render_merge_request(merge_request, commit_id: selected_commit.sha)
- end
-
- it 'merge_request_diffs/inline_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request)
- end
-
- it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request, view: 'parallel')
- end
-
- private
-
- def render_merge_request(merge_request, view: 'inline', **extra_params)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param,
- view: view,
- **extra_params
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
deleted file mode 100644
index aecd56e6198..00000000000
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :public, :repository) }
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
- let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
- let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
- let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('pipeline_schedules/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'pipeline_schedules/edit.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule.id
- }
-
- expect(response).to be_success
- end
-
- it 'pipeline_schedules/edit_with_variables.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule_populated.id
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
deleted file mode 100644
index de6fcfe10f4..00000000000
--- a/spec/javascripts/fixtures/pipelines.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
- let(:commit) { create(:commit, project: project) }
- let(:commit_without_author) { RepoHelpers.another_sample_commit }
- let!(:user) { create(:user, email: commit.author_email) }
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
- let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
- let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('pipelines/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'pipelines/pipelines.json' do
- get :index, params: {
- namespace_id: namespace,
- project_id: project
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
deleted file mode 100644
index 94c59207898..00000000000
--- a/spec/javascripts/fixtures/projects.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'spec_helper'
-
-describe 'Projects (JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- runners_token = 'runnerstoken:intabulasreferre'
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) }
- let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
- let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('projects/')
- end
-
- before do
- project.add_maintainer(admin)
- sign_in(admin)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- after do
- remove_repository(project)
- end
-
- describe ProjectsController, '(JavaScript fixtures)', type: :controller do
- it 'projects/dashboard.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- id: project
- }
-
- expect(response).to be_success
- end
-
- it 'projects/overview.html' do
- get :show, params: {
- namespace_id: project_with_repo.namespace.to_param,
- id: project_with_repo
- }
-
- expect(response).to be_success
- end
-
- it 'projects/edit.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- id: project
- }
-
- expect(response).to be_success
- end
- end
-
- describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'projects/ci_cd_settings.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-
- it 'projects/ci_cd_settings_with_variables.html' do
- create(:ci_variable, project: project_variable_populated)
- create(:ci_variable, project: project_variable_populated)
-
- get :show, params: {
- namespace_id: project_variable_populated.namespace.to_param,
- project_id: project_variable_populated
- }
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
deleted file mode 100644
index f3171fdd97b..00000000000
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:prometheus_service, project: project) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('services/prometheus')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'services/prometheus/prometheus_service.html' do
- get :edit, params: {
- namespace_id: namespace,
- project_id: project,
- id: service.to_param
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/javascripts/fixtures/raw.rb
deleted file mode 100644
index 801c80a0112..00000000000
--- a/spec/javascripts/fixtures/raw.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe 'Raw files', '(JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'raw-project') }
- let(:response) { @blob.data.force_encoding('UTF-8') }
-
- before(:all) do
- clean_frontend_fixtures('blob/balsamiq/')
- clean_frontend_fixtures('blob/notebook/')
- clean_frontend_fixtures('blob/pdf/')
- end
-
- after do
- remove_repository(project)
- end
-
- it 'blob/balsamiq/test.bmpr' do
- @blob = project.repository.blob_at('b89b56d79', 'files/images/balsamiq.bmpr')
- end
-
- it 'blob/notebook/basic.json' do
- @blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
- end
-
- it 'blob/notebook/worksheets.json' do
- @blob = project.repository.blob_at('6d85bb69', 'files/ipython/worksheets.ipynb')
- end
-
- it 'blob/notebook/math.json' do
- @blob = project.repository.blob_at('93ee732', 'files/ipython/math.ipynb')
- end
-
- it 'blob/pdf/test.pdf' do
- @blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
- end
-end
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
deleted file mode 100644
index 22fc546d761..00000000000
--- a/spec/javascripts/fixtures/search.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-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' do
- get :show
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
deleted file mode 100644
index 2237702ccca..00000000000
--- a/spec/javascripts/fixtures/services.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('services/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'services/edit_service.html' do
- get :edit, params: {
- namespace_id: namespace,
- project_id: project,
- id: service.to_param
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb
deleted file mode 100644
index 92b74c01c89..00000000000
--- a/spec/javascripts/fixtures/sessions.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-describe 'Sessions (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- before(:all) do
- clean_frontend_fixtures('sessions/')
- end
-
- describe SessionsController, '(JavaScript fixtures)', type: :controller do
- include DeviseHelpers
-
- render_views
-
- before do
- set_devise_mapping(context: @request)
- end
-
- it 'sessions/new.html' do
- get :new
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
deleted file mode 100644
index ace84b14eb7..00000000000
--- a/spec/javascripts/fixtures/snippet.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe SnippetsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('snippets/')
- end
-
- before do
- sign_in(admin)
- allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
- end
-
- after do
- remove_repository(project)
- end
-
- it 'snippets/show.html' do
- create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
-
- get(:show, params: { id: snippet.to_param })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/static/README.md b/spec/javascripts/fixtures/static/README.md
deleted file mode 100644
index b5c2f8233bf..00000000000
--- a/spec/javascripts/fixtures/static/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not add new files here!
-
-Instead use a Ruby file in the fixtures root directory (`spec/javascripts/fixtures/`).
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/javascripts/fixtures/static/ajax_loading_spinner.html
deleted file mode 100644
index 0e1ebb32b1c..00000000000
--- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<a class="js-ajax-loading-spinner" data-remote href="http://goesnowhere.nothing/whereami">
-<i class="fa fa-trash-o"></i>
-</a>
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/javascripts/fixtures/static/balsamiq_viewer.html
deleted file mode 100644
index cdd723d1a84..00000000000
--- a/spec/javascripts/fixtures/static/balsamiq_viewer.html
+++ /dev/null
@@ -1 +0,0 @@
-<div class="file-content balsamiq-viewer" data-endpoint="/test" id="js-balsamiq-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/javascripts/fixtures/static/create_item_dropdown.html
deleted file mode 100644
index d2d38370092..00000000000
--- a/spec/javascripts/fixtures/static/create_item_dropdown.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="js-create-item-dropdown-fixture-root">
-<input name="variable[environment]" type="hidden">
-<div class="dropdown "><button class="dropdown-menu-toggle js-dropdown-menu-toggle" type="button" data-toggle="dropdown"><span class="dropdown-toggle-text ">some label</span><i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i></button><div class="dropdown-menu dropdown-select dropdown-menu-selectable"><div class="dropdown-input"><input type="search" id="" class="dropdown-input-field" autocomplete="off" /><i aria-hidden="true" data-hidden="true" class="fa fa-search dropdown-input-search"></i><i aria-hidden="true" data-hidden="true" role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i></div><div class="dropdown-content js-dropdown-content"></div><div class="dropdown-footer"><ul class="dropdown-footer-list">
-<li>
-<button class="dropdown-create-new-item-button js-dropdown-create-new-item">
-Create wildcard
-<code></code>
-</button>
-</li>
-</ul>
-</div><div class="dropdown-loading"><i aria-hidden="true" data-hidden="true" class="fa fa-spinner fa-spin"></i></div></div></div></div>
diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/javascripts/fixtures/static/environments/table.html
deleted file mode 100644
index 417af564ff1..00000000000
--- a/spec/javascripts/fixtures/static/environments/table.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<table>
-<thead>
-<tr>
-<th>Environment</th>
-<th>Last deployment</th>
-<th>Job</th>
-<th>Commit</th>
-<th></th>
-<th></th>
-</tr>
-</thead>
-<tbody>
-<tr id="environment-row"></tr>
-</tbody>
-</table>
diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/javascripts/fixtures/static/event_filter.html
deleted file mode 100644
index 8e9b6fb1b5c..00000000000
--- a/spec/javascripts/fixtures/static/event_filter.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<ul class="nav-links event-filter scrolling-tabs nav nav-tabs">
-<li class="active">
-<a class="event-filter-link" href="/dashboard/activity" id="all_event_filter" title="Filter by all">
-<span>
-All
-</span>
-</a>
-</li>
-<li>
-<a class="event-filter-link" href="/dashboard/activity" id="push_event_filter" title="Filter by push events">
-<span>
-Push events
-</span>
-</a>
-</li>
-<li>
-<a class="event-filter-link" href="/dashboard/activity" id="merged_event_filter" title="Filter by merge events">
-<span>
-Merge events
-</span>
-</a>
-</li>
-<li>
-<a class="event-filter-link" href="/dashboard/activity" id="issue_event_filter" title="Filter by issue events">
-<span>
-Issue events
-</span>
-</a>
-</li>
-<li>
-<a class="event-filter-link" href="/dashboard/activity" id="comments_event_filter" title="Filter by comments">
-<span>
-Comments
-</span>
-</a>
-</li>
-<li>
-<a class="event-filter-link" href="/dashboard/activity" id="team_event_filter" title="Filter by team">
-<span>
-Team
-</span>
-</a>
-</li>
-</ul>
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/javascripts/fixtures/static/gl_dropdown.html
deleted file mode 100644
index 08f6738414e..00000000000
--- a/spec/javascripts/fixtures/static/gl_dropdown.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<div>
-<div class="dropdown inline">
-<button class="dropdown-menu-toggle" data-toggle="dropdown" id="js-project-dropdown" type="button">
-<div class="dropdown-toggle-text">
-Projects
-</div>
-<i class="fa fa-chevron-down dropdown-toggle-caret js-projects-dropdown-toggle"></i>
-</button>
-<div class="dropdown-menu dropdown-select dropdown-menu-selectable">
-<div class="dropdown-title">
-<span>Go to project</span>
-<button aria="{:label=&gt;&quot;Close&quot;}" class="dropdown-title-button dropdown-menu-close">
-<i class="fa fa-times dropdown-menu-close-icon"></i>
-</button>
-</div>
-<div class="dropdown-input">
-<input class="dropdown-input-field" placeholder="Filter results" type="search">
-<i class="fa fa-search dropdown-input-search"></i>
-</div>
-<div class="dropdown-content"></div>
-<div class="dropdown-loading">
-<i class="fa fa-spinner fa-spin"></i>
-</div>
-</div>
-</div>
-</div>
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/javascripts/fixtures/static/gl_field_errors.html
deleted file mode 100644
index f8470e02b7c..00000000000
--- a/spec/javascripts/fixtures/static/gl_field_errors.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<form action="submit" class="gl-show-field-errors" method="post">
-<div class="form-group">
-<input class="required-text" required type="text">Text</input>
-</div>
-<div class="form-group">
-<input class="email" required title="Please provide a valid email address." type="email">Email</input>
-</div>
-<div class="form-group">
-<input class="password" required type="password">Password</input>
-</div>
-<div class="form-group">
-<input class="alphanumeric" pattern="[a-zA-Z0-9]" required type="text">Alphanumeric</input>
-</div>
-<div class="form-group">
-<input class="hidden" type="hidden">
-</div>
-<div class="form-group">
-<input class="custom gl-field-error-ignore" type="text">Custom, do not validate</input>
-</div>
-<div class="form-group"></div>
-<input class="submit" type="submit">Submit</input>
-</form>
diff --git a/spec/javascripts/fixtures/static/images/green_box.png b/spec/javascripts/fixtures/static/images/green_box.png
deleted file mode 100644
index cd1ff9f9ade..00000000000
--- a/spec/javascripts/fixtures/static/images/green_box.png
+++ /dev/null
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/one_white_pixel.png b/spec/javascripts/fixtures/static/images/one_white_pixel.png
deleted file mode 100644
index 073fcf40a18..00000000000
--- a/spec/javascripts/fixtures/static/images/one_white_pixel.png
+++ /dev/null
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/red_box.png b/spec/javascripts/fixtures/static/images/red_box.png
deleted file mode 100644
index 73b2927da0f..00000000000
--- a/spec/javascripts/fixtures/static/images/red_box.png
+++ /dev/null
Binary files differ
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/javascripts/fixtures/static/issuable_filter.html
deleted file mode 100644
index 06b70fb43f1..00000000000
--- a/spec/javascripts/fixtures/static/issuable_filter.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<form action="/user/project/issues?scope=all&amp;state=closed" class="js-filter-form">
-<input id="utf8" name="utf8" value="✓">
-<input id="check-all-issues" name="check-all-issues">
-<input id="search" name="search">
-<input id="author_id" name="author_id">
-<input id="assignee_id" name="assignee_id">
-<input id="milestone_title" name="milestone_title">
-<input id="label_name" name="label_name">
-</form>
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/javascripts/fixtures/static/issue_sidebar_label.html
deleted file mode 100644
index ec8fb30f219..00000000000
--- a/spec/javascripts/fixtures/static/issue_sidebar_label.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<div class="block labels">
-<div class="sidebar-collapsed-icon js-sidebar-labels-tooltip"></div>
-<div class="title hide-collapsed">
-<a class="edit-link float-right" href="#">
-Edit
-</a>
-</div>
-<div class="selectbox hide-collapsed" style="display: none;">
-<div class="dropdown">
-<button class="dropdown-menu-toggle js-label-select js-multiselect" data-ability-name="issue" data-field-name="issue[label_names][]" data-issue-update="/root/test/issues/2.json" data-labels="/root/test/labels.json" data-project-id="12" data-show-any="true" data-show-no="true" data-toggle="dropdown" type="button">
-<span class="dropdown-toggle-text">
-Label
-</span>
-<i class="fa fa-chevron-down"></i>
-</button>
-<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
-<div class="dropdown-page-one">
-<div class="dropdown-content"></div>
-<div class="dropdown-loading">
-<i class="fa fa-spinner fa-spin"></i>
-</div>
-</div>
-</div>
-</div>
-</div>
-</div>
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/javascripts/fixtures/static/line_highlighter.html
deleted file mode 100644
index 897a25d6760..00000000000
--- a/spec/javascripts/fixtures/static/line_highlighter.html
+++ /dev/null
@@ -1,107 +0,0 @@
-<div class="file-holder">
-<div class="file-content">
-<div class="line-numbers">
-<a data-line-number="1" href="#L1" id="L1">
-<i class="fa fa-link"></i>
-1
-</a>
-<a data-line-number="2" href="#L2" id="L2">
-<i class="fa fa-link"></i>
-2
-</a>
-<a data-line-number="3" href="#L3" id="L3">
-<i class="fa fa-link"></i>
-3
-</a>
-<a data-line-number="4" href="#L4" id="L4">
-<i class="fa fa-link"></i>
-4
-</a>
-<a data-line-number="5" href="#L5" id="L5">
-<i class="fa fa-link"></i>
-5
-</a>
-<a data-line-number="6" href="#L6" id="L6">
-<i class="fa fa-link"></i>
-6
-</a>
-<a data-line-number="7" href="#L7" id="L7">
-<i class="fa fa-link"></i>
-7
-</a>
-<a data-line-number="8" href="#L8" id="L8">
-<i class="fa fa-link"></i>
-8
-</a>
-<a data-line-number="9" href="#L9" id="L9">
-<i class="fa fa-link"></i>
-9
-</a>
-<a data-line-number="10" href="#L10" id="L10">
-<i class="fa fa-link"></i>
-10
-</a>
-<a data-line-number="11" href="#L11" id="L11">
-<i class="fa fa-link"></i>
-11
-</a>
-<a data-line-number="12" href="#L12" id="L12">
-<i class="fa fa-link"></i>
-12
-</a>
-<a data-line-number="13" href="#L13" id="L13">
-<i class="fa fa-link"></i>
-13
-</a>
-<a data-line-number="14" href="#L14" id="L14">
-<i class="fa fa-link"></i>
-14
-</a>
-<a data-line-number="15" href="#L15" id="L15">
-<i class="fa fa-link"></i>
-15
-</a>
-<a data-line-number="16" href="#L16" id="L16">
-<i class="fa fa-link"></i>
-16
-</a>
-<a data-line-number="17" href="#L17" id="L17">
-<i class="fa fa-link"></i>
-17
-</a>
-<a data-line-number="18" href="#L18" id="L18">
-<i class="fa fa-link"></i>
-18
-</a>
-<a data-line-number="19" href="#L19" id="L19">
-<i class="fa fa-link"></i>
-19
-</a>
-<a data-line-number="20" href="#L20" id="L20">
-<i class="fa fa-link"></i>
-20
-</a>
-<a data-line-number="21" href="#L21" id="L21">
-<i class="fa fa-link"></i>
-21
-</a>
-<a data-line-number="22" href="#L22" id="L22">
-<i class="fa fa-link"></i>
-22
-</a>
-<a data-line-number="23" href="#L23" id="L23">
-<i class="fa fa-link"></i>
-23
-</a>
-<a data-line-number="24" href="#L24" id="L24">
-<i class="fa fa-link"></i>
-24
-</a>
-<a data-line-number="25" href="#L25" id="L25">
-<i class="fa fa-link"></i>
-25
-</a>
-</div>
-<pre class="code highlight"><code><span class="line" id="LC1">Line 1</span><span class="line" id="LC2">Line 2</span><span class="line" id="LC3">Line 3</span><span class="line" id="LC4">Line 4</span><span class="line" id="LC5">Line 5</span><span class="line" id="LC6">Line 6</span><span class="line" id="LC7">Line 7</span><span class="line" id="LC8">Line 8</span><span class="line" id="LC9">Line 9</span><span class="line" id="LC10">Line 10</span><span class="line" id="LC11">Line 11</span><span class="line" id="LC12">Line 12</span><span class="line" id="LC13">Line 13</span><span class="line" id="LC14">Line 14</span><span class="line" id="LC15">Line 15</span><span class="line" id="LC16">Line 16</span><span class="line" id="LC17">Line 17</span><span class="line" id="LC18">Line 18</span><span class="line" id="LC19">Line 19</span><span class="line" id="LC20">Line 20</span><span class="line" id="LC21">Line 21</span><span class="line" id="LC22">Line 22</span><span class="line" id="LC23">Line 23</span><span class="line" id="LC24">Line 24</span><span class="line" id="LC25">Line 25</span></code></pre>
-</div>
-</div>
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/javascripts/fixtures/static/linked_tabs.html
deleted file mode 100644
index c25463bf1db..00000000000
--- a/spec/javascripts/fixtures/static/linked_tabs.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<ul class="nav nav-tabs new-session-tabs linked-tabs">
-<li class="nav-item">
-<a class="nav-link" data-action="tab1" data-target="div#tab1" data-toggle="tab" href="foo/bar/1">
-Tab 1
-</a>
-</li>
-<li class="nav-item">
-<a class="nav-link" data-action="tab2" data-target="div#tab2" data-toggle="tab" href="foo/bar/1/context">
-Tab 2
-</a>
-</li>
-</ul>
-<div class="tab-content">
-<div class="tab-pane" id="tab1">
-Tab 1 Content
-</div>
-<div class="tab-pane" id="tab2">
-Tab 2 Content
-</div>
-</div>
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/javascripts/fixtures/static/merge_requests_show.html
deleted file mode 100644
index 87e36c9f315..00000000000
--- a/spec/javascripts/fixtures/static/merge_requests_show.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<a class="btn-close"></a>
-<div class="detail-page-description">
-<div class="description js-task-list-container">
-<div class="md">
-<ul class="task-list">
-<li class="task-list-item">
-<input class="task-list-item-checkbox" type="checkbox">
-Task List Item
-</li>
-</ul>
-<textarea class="js-task-list-field">- [ ] Task List Item</textarea>
-</div>
-</div>
-</div>
-<form action="/foo" class="js-issuable-update"></form>
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/javascripts/fixtures/static/mini_dropdown_graph.html
deleted file mode 100644
index cd0b8dec3fc..00000000000
--- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="js-builds-dropdown-tests dropdown dropdown js-mini-pipeline-graph">
-<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
-Dropdown
-</button>
-<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
-<li class="js-builds-dropdown-list scrollable-menu">
-<ul></ul>
-</li>
-<li class="js-builds-dropdown-loading hidden">
-<span class="fa fa-spinner"></span>
-</li>
-</ul>
-</div>
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/javascripts/fixtures/static/notebook_viewer.html
deleted file mode 100644
index 4bbb7bf1094..00000000000
--- a/spec/javascripts/fixtures/static/notebook_viewer.html
+++ /dev/null
@@ -1 +0,0 @@
-<div class="file-content" data-endpoint="/test" id="js-notebook-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/javascripts/fixtures/static/oauth_remember_me.html
deleted file mode 100644
index 9ba1ffc72fe..00000000000
--- a/spec/javascripts/fixtures/static/oauth_remember_me.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<div id="oauth-container">
-<input id="remember_me" type="checkbox">
-<a class="oauth-login twitter" href="http://example.com/"></a>
-<a class="oauth-login github" href="http://example.com/"></a>
-<a class="oauth-login facebook" href="http://example.com/?redirect_fragment=L1"></a>
-</div>
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/javascripts/fixtures/static/pdf_viewer.html
deleted file mode 100644
index 350d35a262f..00000000000
--- a/spec/javascripts/fixtures/static/pdf_viewer.html
+++ /dev/null
@@ -1 +0,0 @@
-<div class="file-content" data-endpoint="/test" id="js-pdf-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/javascripts/fixtures/static/pipeline_graph.html
deleted file mode 100644
index 422372bb7d5..00000000000
--- a/spec/javascripts/fixtures/static/pipeline_graph.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<div class="pipeline-visualization js-pipeline-graph">
-<ul class="stage-column-list">
-<li class="stage-column">
-<div class="stage-name">
-<a href="/">
-Test
-<div class="builds-container">
-<ul>
-<li class="build">
-<div class="curve"></div>
-<a>
-<svg></svg>
-<div class="ci-status-text">
-stop_review
-</div>
-</a>
-</li>
-</ul>
-</div>
-</a>
-</div>
-</li>
-</ul>
-</div>
diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/javascripts/fixtures/static/pipelines.html
deleted file mode 100644
index 42333f94f2f..00000000000
--- a/spec/javascripts/fixtures/static/pipelines.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
-<div data-can-create-pipeline="true" data-ci-lint-path="foo" data-empty-state-svg-path="foo" data-endpoint="foo" data-error-state-svg-path="foo" data-has-ci="foo" data-help-auto-devops-path="foo" data-help-page-path="foo" data-new-pipeline-path="foo" data-reset-cache-path="foo" id="pipelines-list-vue"></div>
-</div>
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/javascripts/fixtures/static/project_select_combo_button.html
deleted file mode 100644
index 50c826051c0..00000000000
--- a/spec/javascripts/fixtures/static/project_select_combo_button.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="project-item-select-holder">
-<input class="project-item-select" data-group-id="12345" data-relative-path="issues/new">
-<a class="new-project-item-link" data-label="New issue" data-type="issues" href="">
-<i class="fa fa-spinner spin"></i>
-</a>
-<a class="new-project-item-select-button">
-<i class="fa fa-caret-down"></i>
-</a>
-</div>
diff --git a/spec/javascripts/fixtures/static/projects.json b/spec/javascripts/fixtures/static/projects.json
deleted file mode 100644
index 68a150f602a..00000000000
--- a/spec/javascripts/fixtures/static/projects.json
+++ /dev/null
@@ -1,445 +0,0 @@
-[{
- "id": 9,
- "description": "",
- "default_branch": null,
- "tag_list": [],
- "public": true,
- "archived": false,
- "visibility_level": 20,
- "ssh_url_to_repo": "phil@localhost:root/test.git",
- "http_url_to_repo": "http://localhost:3000/root/test.git",
- "web_url": "http://localhost:3000/root/test",
- "owner": {
- "name": "Administrator",
- "username": "root",
- "id": 1,
- "state": "active",
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url": "http://localhost:3000/u/root"
- },
- "name": "test",
- "name_with_namespace": "Administrator / test",
- "path": "test",
- "path_with_namespace": "root/test",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-14T19:08:05.364Z",
- "last_activity_at": "2016-01-14T19:08:07.418Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 1,
- "name": "root",
- "path": "root",
- "owner_id": 1,
- "created_at": "2016-01-13T20:19:44.439Z",
- "updated_at": "2016-01-13T20:19:44.439Z",
- "description": "",
- "avatar": null
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 0,
- "permissions": {
- "project_access": null,
- "group_access": null
- }
-}, {
- "id": 8,
- "description": "Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "phil@localhost:h5bp/html5-boilerplate.git",
- "http_url_to_repo": "http://localhost:3000/h5bp/html5-boilerplate.git",
- "web_url": "http://localhost:3000/h5bp/html5-boilerplate",
- "name": "Html5 Boilerplate",
- "name_with_namespace": "H5bp / Html5 Boilerplate",
- "path": "html5-boilerplate",
- "path_with_namespace": "h5bp/html5-boilerplate",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:57.525Z",
- "last_activity_at": "2016-01-13T20:27:57.280Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 5,
- "name": "H5bp",
- "path": "h5bp",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:57.239Z",
- "updated_at": "2016-01-13T20:19:57.239Z",
- "description": "Tempore accusantium possimus aut libero.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 5,
- "permissions": {
- "project_access": {
- "access_level": 10,
- "notification_level": 3
- },
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}, {
- "id": 7,
- "description": "Modi odio mollitia dolorem qui.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "phil@localhost:twitter/typeahead-js.git",
- "http_url_to_repo": "http://localhost:3000/twitter/typeahead-js.git",
- "web_url": "http://localhost:3000/twitter/typeahead-js",
- "name": "Typeahead.Js",
- "name_with_namespace": "Twitter / Typeahead.Js",
- "path": "typeahead-js",
- "path_with_namespace": "twitter/typeahead-js",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:56.212Z",
- "last_activity_at": "2016-01-13T20:27:51.496Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 4,
- "name": "Twitter",
- "path": "twitter",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:54.480Z",
- "updated_at": "2016-01-13T20:19:54.480Z",
- "description": "Id voluptatem ipsa maiores omnis repudiandae et et.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": true,
- "open_issues_count": 4,
- "permissions": {
- "project_access": null,
- "group_access": {
- "access_level": 10,
- "notification_level": 3
- }
- }
-}, {
- "id": 6,
- "description": "Omnis asperiores ipsa et beatae quidem necessitatibus quia.",
- "default_branch": "master",
- "tag_list": [],
- "public": true,
- "archived": false,
- "visibility_level": 20,
- "ssh_url_to_repo": "phil@localhost:twitter/flight.git",
- "http_url_to_repo": "http://localhost:3000/twitter/flight.git",
- "web_url": "http://localhost:3000/twitter/flight",
- "name": "Flight",
- "name_with_namespace": "Twitter / Flight",
- "path": "flight",
- "path_with_namespace": "twitter/flight",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:54.754Z",
- "last_activity_at": "2016-01-13T20:27:50.502Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 4,
- "name": "Twitter",
- "path": "twitter",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:54.480Z",
- "updated_at": "2016-01-13T20:19:54.480Z",
- "description": "Id voluptatem ipsa maiores omnis repudiandae et et.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": true,
- "open_issues_count": 4,
- "permissions": {
- "project_access": null,
- "group_access": {
- "access_level": 10,
- "notification_level": 3
- }
- }
-}, {
- "id": 5,
- "description": "Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-test.git",
- "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-test.git",
- "web_url": "http://localhost:3000/gitlab-org/gitlab-test",
- "name": "Gitlab Test",
- "name_with_namespace": "Gitlab Org / Gitlab Test",
- "path": "gitlab-test",
- "path_with_namespace": "gitlab-org/gitlab-test",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:53.202Z",
- "last_activity_at": "2016-01-13T20:27:41.626Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 3,
- "name": "Gitlab Org",
- "path": "gitlab-org",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:48.851Z",
- "updated_at": "2016-01-13T20:19:48.851Z",
- "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 5,
- "permissions": {
- "project_access": null,
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}, {
- "id": 4,
- "description": "Aut molestias quas est ut aperiam officia quod libero.",
- "default_branch": "master",
- "tag_list": [],
- "public": true,
- "archived": false,
- "visibility_level": 20,
- "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-shell.git",
- "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-shell.git",
- "web_url": "http://localhost:3000/gitlab-org/gitlab-shell",
- "name": "Gitlab Shell",
- "name_with_namespace": "Gitlab Org / Gitlab Shell",
- "path": "gitlab-shell",
- "path_with_namespace": "gitlab-org/gitlab-shell",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:51.882Z",
- "last_activity_at": "2016-01-13T20:27:35.678Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 3,
- "name": "Gitlab Org",
- "path": "gitlab-org",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:48.851Z",
- "updated_at": "2016-01-13T20:19:48.851Z",
- "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 5,
- "permissions": {
- "project_access": {
- "access_level": 20,
- "notification_level": 3
- },
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}, {
- "id": 3,
- "description": "Excepturi molestiae quia repellendus omnis est illo illum eligendi.",
- "default_branch": "master",
- "tag_list": [],
- "public": true,
- "archived": false,
- "visibility_level": 20,
- "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ci.git",
- "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ci.git",
- "web_url": "http://localhost:3000/gitlab-org/gitlab-ci",
- "name": "Gitlab Ci",
- "name_with_namespace": "Gitlab Org / Gitlab Ci",
- "path": "gitlab-ci",
- "path_with_namespace": "gitlab-org/gitlab-ci",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:50.346Z",
- "last_activity_at": "2016-01-13T20:27:30.115Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 3,
- "name": "Gitlab Org",
- "path": "gitlab-org",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:48.851Z",
- "updated_at": "2016-01-13T20:19:48.851Z",
- "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 3,
- "permissions": {
- "project_access": null,
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}, {
- "id": 2,
- "description": "Adipisci quaerat dignissimos enim sed ipsam dolorem quia.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 10,
- "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ce.git",
- "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ce.git",
- "web_url": "http://localhost:3000/gitlab-org/gitlab-ce",
- "name": "Gitlab Ce",
- "name_with_namespace": "Gitlab Org / Gitlab Ce",
- "path": "gitlab-ce",
- "path_with_namespace": "gitlab-org/gitlab-ce",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:49.065Z",
- "last_activity_at": "2016-01-13T20:26:58.454Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 3,
- "name": "Gitlab Org",
- "path": "gitlab-org",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:48.851Z",
- "updated_at": "2016-01-13T20:19:48.851Z",
- "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 5,
- "permissions": {
- "project_access": {
- "access_level": 30,
- "notification_level": 3
- },
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}, {
- "id": 1,
- "description": "Vel voluptatem maxime saepe ex quia.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "phil@localhost:documentcloud/underscore.git",
- "http_url_to_repo": "http://localhost:3000/documentcloud/underscore.git",
- "web_url": "http://localhost:3000/documentcloud/underscore",
- "name": "Underscore",
- "name_with_namespace": "Documentcloud / Underscore",
- "path": "underscore",
- "path_with_namespace": "documentcloud/underscore",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "created_at": "2016-01-13T20:19:45.862Z",
- "last_activity_at": "2016-01-13T20:25:03.106Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 2,
- "name": "Documentcloud",
- "path": "documentcloud",
- "owner_id": null,
- "created_at": "2016-01-13T20:19:44.464Z",
- "updated_at": "2016-01-13T20:19:44.464Z",
- "description": "Aut impedit perferendis fuga et ipsa repellat cupiditate et.",
- "avatar": {
- "url": null
- }
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "only_allow_merge_if_pipeline_succeeds": false,
- "open_issues_count": 5,
- "permissions": {
- "project_access": null,
- "group_access": {
- "access_level": 50,
- "notification_level": 3
- }
- }
-}]
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/javascripts/fixtures/static/search_autocomplete.html
deleted file mode 100644
index 29db9020424..00000000000
--- a/spec/javascripts/fixtures/static/search_autocomplete.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<div class="search search-form">
-<form class="form-inline">
-<div class="search-input-container">
-<div class="search-input-wrap">
-<div class="dropdown">
-<input class="search-input dropdown-menu-toggle" id="search">
-<div class="dropdown-menu dropdown-select">
-<div class="dropdown-content"></div>
-</div>
-</div>
-</div>
-</div>
-<input class="js-search-project-options" type="hidden">
-</form>
-</div>
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/javascripts/fixtures/static/signin_tabs.html
deleted file mode 100644
index 7e66ab9394b..00000000000
--- a/spec/javascripts/fixtures/static/signin_tabs.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<ul class="nav-links new-session-tabs">
-<li class="active">
-<a href="#ldap">LDAP</a>
-</li>
-<li>
-<a href="#login-pane">Standard</a>
-</li>
-</ul>
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/javascripts/fixtures/static/sketch_viewer.html
deleted file mode 100644
index e25e554e568..00000000000
--- a/spec/javascripts/fixtures/static/sketch_viewer.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="file-content" data-endpoint="/test_sketch_file.sketch" id="js-sketch-viewer">
-<div class="js-loading-icon"></div>
-</div>
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
deleted file mode 100644
index d0c8a6eca01..00000000000
--- a/spec/javascripts/fixtures/todos.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe 'Todos (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
- let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
- let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
- let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
-
- before(:all) do
- clean_frontend_fixtures('todos/')
- end
-
- after do
- remove_repository(project)
- end
-
- describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'todos/todos.html' do
- get :index
-
- expect(response).to be_success
- end
- end
-
- describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'todos/todos.json' do
- post :create, params: {
- namespace_id: namespace,
- project_id: project,
- issuable_type: 'issue',
- issuable_id: issue_2.id
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
deleted file mode 100644
index f52832b6efb..00000000000
--- a/spec/javascripts/fixtures/u2f.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-context 'U2F' do
- include JavaScriptFixturesHelpers
-
- let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') }
-
- before(:all) do
- clean_frontend_fixtures('u2f/')
- end
-
- describe SessionsController, '(JavaScript fixtures)', type: :controller do
- include DeviseHelpers
-
- render_views
-
- before do
- set_devise_mapping(context: @request)
- end
-
- it 'u2f/authenticate.html' do
- allow(controller).to receive(:find_user).and_return(user)
-
- post :create, params: { user: { login: user.username, password: user.password } }
-
- expect(response).to be_success
- end
- end
-
- describe Profiles::TwoFactorAuthsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(user)
- allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
- end
-
- it 'u2f/register.html' do
- get :show
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js
index 4d6d0c895b6..cc88a7ac6c1 100644
--- a/spec/javascripts/groups/components/group_item_spec.js
+++ b/spec/javascripts/groups/components/group_item_spec.js
@@ -156,6 +156,8 @@ describe('GroupItemComponent', () => {
describe('template', () => {
it('should render component template correctly', () => {
+ const visibilityIconEl = vm.$el.querySelector('.item-visibility');
+
expect(vm.$el.getAttribute('id')).toBe('group-55');
expect(vm.$el.classList.contains('group-row')).toBeTruthy();
@@ -173,6 +175,11 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.title')).toBeDefined();
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
+
+ expect(visibilityIconEl).not.toBe(null);
+ expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
+ expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
+
expect(vm.$el.querySelector('.access-type')).toBeDefined();
expect(vm.$el.querySelector('.description')).toBeDefined();
diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js
index 00d6a4817d7..b2441babf3f 100644
--- a/spec/javascripts/groups/components/item_stats_spec.js
+++ b/spec/javascripts/groups/components/item_stats_spec.js
@@ -108,18 +108,6 @@ describe('ItemStatsComponent', () => {
vm.$destroy();
});
- it('renders item visibility icon and tooltip correctly', () => {
- const vm = createComponent();
-
- const visibilityIconEl = vm.$el.querySelector('.item-visibility');
-
- expect(visibilityIconEl).not.toBe(null);
- expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
- expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
-
- vm.$destroy();
- });
-
it('renders start count and last updated information for project item correctly', () => {
const item = Object.assign({}, mockParentGroupItem, {
type: ITEM_TYPE.PROJECT,
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 121e99c9783..5b749b11246 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -1,21 +1,5 @@
-/* eslint-disable import/prefer-default-export */
+// No new code should be added to this file. Instead, modify the
+// file this one re-exports from. For more detail about why, see:
+// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31349
-const vNodeContainsText = (vnode, text) =>
- (vnode.text && vnode.text.includes(text)) ||
- (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
-
-/**
- * Determines whether a `shallowMount` Wrapper contains text
- * within one of it's slots. This will also work on Wrappers
- * acquired with `find()`, but only if it's parent Wrapper
- * was shallowMounted.
- * NOTE: Prefer checking the rendered output of a component
- * wherever possible using something like `text()` instead.
- * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
- * @param {String} slotName
- * @param {String} text
- */
-export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- Boolean(
- shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
- );
+export * from '../../frontend/helpers/vue_test_utils_helper';
diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/javascripts/helpers/vue_test_utils_helper_spec.js
deleted file mode 100644
index 41714066da5..00000000000
--- a/spec/javascripts/helpers/vue_test_utils_helper_spec.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { shallowWrapperContainsSlotText } from './vue_test_utils_helper';
-
-describe('Vue test utils helpers', () => {
- describe('shallowWrapperContainsSlotText', () => {
- const mockText = 'text';
- const mockSlot = `<div>${mockText}</div>`;
- let mockComponent;
-
- beforeEach(() => {
- mockComponent = shallowMount(
- {
- render(h) {
- h(`<div>mockedComponent</div>`);
- },
- },
- {
- slots: {
- default: mockText,
- namedSlot: mockSlot,
- },
- },
- );
- });
-
- it('finds text within shallowWrapper default slot', () => {
- expect(shallowWrapperContainsSlotText(mockComponent, 'default', mockText)).toBe(true);
- });
-
- it('finds text within shallowWrapper named slot', () => {
- expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', mockText)).toBe(true);
- });
-
- it('returns false when text is not present', () => {
- const searchText = 'absent';
-
- expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false);
- expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false);
- });
-
- it('searches with case-sensitivity', () => {
- const searchText = mockText.toUpperCase();
-
- expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false);
- expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false);
- });
- });
-});
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 88652202a8e..c5de31a4138 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -89,9 +89,7 @@ export default (
payload,
);
- return new Promise(resolve => {
- setImmediate(resolve);
- })
+ return new Promise(setImmediate)
.then(() => result)
.catch(error => {
validateResults();
diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js
index f63007c7dd2..554bd1ae3b5 100644
--- a/spec/javascripts/ide/components/ide_tree_list_spec.js
+++ b/spec/javascripts/ide/components/ide_tree_list_spec.js
@@ -58,6 +58,20 @@ describe('IDE tree list', () => {
it('renders list of files', () => {
expect(vm.$el.textContent).toContain('fileName');
});
+
+ it('does not render moved entries', done => {
+ const tree = [file('moved entry'), file('normal entry')];
+ tree[0].moved = true;
+ store.state.trees['abcproject/master'].tree = tree;
+ const container = vm.$el.querySelector('.ide-tree-body');
+
+ vm.$nextTick(() => {
+ expect(container.children.length).toBe(1);
+ expect(vm.$el.textContent).not.toContain('moved entry');
+ expect(vm.$el.textContent).toContain('normal entry');
+ done();
+ });
+ });
});
describe('empty-branch state', () => {
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 002b5a005b8..0701b773e17 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
import Editor from '~/ide/lib/editor';
-import { activityBarViews } from '~/ide/constants';
+import { activityBarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
@@ -14,7 +14,10 @@ describe('RepoEditor', () => {
let vm;
beforeEach(done => {
- const f = file();
+ const f = {
+ ...file(),
+ viewMode: FILE_VIEW_MODE_EDITOR,
+ };
const RepoEditor = Vue.extend(repoEditor);
vm = createComponentWithStore(RepoEditor, store, {
@@ -27,6 +30,7 @@ describe('RepoEditor', () => {
Vue.set(vm.$store.state.entries, f.path, f);
spyOn(vm, 'getFileData').and.returnValue(Promise.resolve());
+ spyOn(vm, 'getRawFileData').and.returnValue(Promise.resolve());
vm.$mount();
@@ -41,12 +45,17 @@ describe('RepoEditor', () => {
Editor.editorInstance.dispose();
});
- it('renders an ide container', done => {
- Vue.nextTick(() => {
- expect(vm.shouldHideEditor).toBeFalsy();
+ const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder');
+ const changeRightPanelCollapsed = () => {
+ const { state } = vm.$store;
- done();
- });
+ state.rightPanelCollapsed = !state.rightPanelCollapsed;
+ };
+
+ it('renders an ide container', () => {
+ expect(vm.shouldHideEditor).toBeFalsy();
+ expect(vm.showEditor).toBe(true);
+ expect(findEditor()).not.toHaveCss({ display: 'none' });
});
it('renders only an edit tab', done => {
@@ -283,7 +292,7 @@ describe('RepoEditor', () => {
});
it('calls updateDimensions when rightPanelCollapsed is changed', done => {
- vm.$store.state.rightPanelCollapsed = true;
+ changeRightPanelCollapsed();
vm.$nextTick(() => {
expect(vm.editor.updateDimensions).toHaveBeenCalled();
@@ -358,6 +367,85 @@ describe('RepoEditor', () => {
});
});
+ describe('when files view mode is preview', () => {
+ beforeEach(done => {
+ spyOn(vm.editor, 'updateDimensions');
+ vm.file.viewMode = FILE_VIEW_MODE_PREVIEW;
+ vm.$nextTick(done);
+ });
+
+ it('should hide editor', () => {
+ expect(vm.showEditor).toBe(false);
+ expect(findEditor()).toHaveCss({ display: 'none' });
+ });
+
+ it('should not update dimensions', done => {
+ changeRightPanelCollapsed();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.editor.updateDimensions).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('when file view mode changes to editor', () => {
+ beforeEach(done => {
+ vm.file.viewMode = FILE_VIEW_MODE_EDITOR;
+
+ // one tick to trigger watch
+ vm.$nextTick()
+ // another tick needed until we can update dimensions
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('should update dimensions', () => {
+ expect(vm.editor.updateDimensions).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('initEditor', () => {
+ beforeEach(() => {
+ spyOn(vm.editor, 'createInstance');
+ spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
+ });
+
+ it('is being initialised for files without content even if shouldHideEditor is `true`', done => {
+ vm.file.content = '';
+ vm.file.raw = '';
+
+ vm.initEditor();
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.getFileData).toHaveBeenCalled();
+ expect(vm.getRawFileData).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not initialize editor for files already with content', done => {
+ expect(vm.getFileData.calls.count()).toEqual(1);
+ expect(vm.getRawFileData.calls.count()).toEqual(1);
+
+ vm.file.content = 'foo';
+
+ vm.initEditor();
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.getFileData.calls.count()).toEqual(1);
+ expect(vm.getRawFileData.calls.count()).toEqual(1);
+ expect(vm.editor.createInstance).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
it('calls removePendingTab when old file is pending', done => {
spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
spyOn(vm, 'removePendingTab');
@@ -367,6 +455,7 @@ describe('RepoEditor', () => {
vm.$nextTick()
.then(() => {
vm.file = file('testing');
+ vm.file.content = 'foo'; // need to prevent full cycle of initEditor
return vm.$nextTick();
})
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index dd2313dc800..021c3076094 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -275,6 +275,43 @@ describe('IDE store file actions', () => {
});
});
+ describe('Re-named success', () => {
+ beforeEach(() => {
+ localFile = file(`newCreate-${Math.random()}`);
+ localFile.url = `project/getFileDataURL`;
+ localFile.prevPath = 'old-dull-file';
+ localFile.path = 'new-shiny-file';
+ store.state.entries[localFile.path] = localFile;
+
+ mock.onGet(`${RELATIVE_URL_ROOT}/project/getFileDataURL`).replyOnce(
+ 200,
+ {
+ blame_path: 'blame_path',
+ commits_path: 'commits_path',
+ permalink: 'permalink',
+ raw_path: 'raw_path',
+ binary: false,
+ html: '123',
+ render_error: '',
+ },
+ {
+ 'page-title': 'testing old-dull-file',
+ },
+ );
+ });
+
+ it('sets document title considering `prevPath` on a file', done => {
+ store
+ .dispatch('getFileData', { path: localFile.path })
+ .then(() => {
+ expect(document.title).toBe('testing new-shiny-file');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
describe('error', () => {
beforeEach(() => {
mock.onGet(`project/getFileDataURL`).networkError();
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 37354283cab..8504fb3f42b 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -10,6 +10,7 @@ import actions, {
deleteEntry,
renameEntry,
getBranchData,
+ createTempEntry,
} from '~/ide/stores/actions';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
@@ -247,18 +248,30 @@ describe('Multi-file store actions', () => {
});
it('sets tmp file as active', done => {
- store
- .dispatch('createTempEntry', {
+ testAction(
+ createTempEntry,
+ {
name: 'test',
branchId: 'mybranch',
type: 'blob',
- })
- .then(f => {
- expect(f.active).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
+ },
+ store.state,
+ [
+ { type: types.CREATE_TMP_ENTRY, payload: jasmine.any(Object) },
+ { type: types.TOGGLE_FILE_OPEN, payload: 'test' },
+ { type: types.ADD_FILE_TO_CHANGED, payload: 'test' },
+ ],
+ [
+ {
+ type: 'setFileActive',
+ payload: 'test',
+ },
+ {
+ type: 'triggerFilesChange',
+ },
+ ],
+ done,
+ );
});
it('creates flash message if file already exists', done => {
@@ -488,7 +501,42 @@ describe('Multi-file store actions', () => {
'path',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }],
- [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
+ [
+ { type: 'burstUnusedSeal' },
+ { type: 'stageChange', payload: 'path' },
+ { type: 'triggerFilesChange' },
+ ],
+ done,
+ );
+ });
+
+ it('does not delete a folder after it is emptied', done => {
+ const testFolder = {
+ type: 'tree',
+ tree: [],
+ };
+ const testEntry = {
+ path: 'testFolder/entry-to-delete',
+ parentPath: 'testFolder',
+ opened: false,
+ tree: [],
+ };
+ testFolder.tree.push(testEntry);
+ store.state.entries = {
+ testFolder,
+ 'testFolder/entry-to-delete': testEntry,
+ };
+
+ testAction(
+ deleteEntry,
+ 'testFolder/entry-to-delete',
+ store.state,
+ [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }],
+ [
+ { type: 'burstUnusedSeal' },
+ { type: 'stageChange', payload: 'testFolder/entry-to-delete' },
+ { type: 'triggerFilesChange' },
+ ],
done,
);
});
@@ -509,8 +557,15 @@ describe('Multi-file store actions', () => {
type: types.RENAME_ENTRY,
payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
},
+ {
+ type: types.TOGGLE_FILE_CHANGED,
+ payload: {
+ file: store.state.entries['parent-path/new-name'],
+ changed: true,
+ },
+ },
],
- [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }],
+ [{ type: 'triggerFilesChange' }],
done,
);
});
@@ -557,7 +612,6 @@ describe('Multi-file store actions', () => {
parentPath: 'parent-path/new-name',
},
},
- { type: 'deleteEntry', payload: 'test' },
{ type: 'triggerFilesChange' },
],
done,
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 5f7272311c8..14d861f21d2 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -6,9 +6,11 @@ import eventHub from '~/ide/eventhub';
import consts from '~/ide/stores/modules/commit/constants';
import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
import * as actions from '~/ide/stores/modules/commit/actions';
-import testAction from '../../../../helpers/vuex_action_helper';
import { commitActionTypes } from '~/ide/constants';
import { resetStore, file } from 'spec/ide/helpers';
+import testAction from '../../../../helpers/vuex_action_helper';
+
+const TEST_COMMIT_SHA = '123456789';
describe('IDE commit module actions', () => {
beforeEach(() => {
@@ -139,6 +141,9 @@ describe('IDE commit module actions', () => {
branches: {
master: {
workingReference: '',
+ commit: {
+ short_id: TEST_COMMIT_SHA,
+ },
},
},
};
@@ -239,6 +244,9 @@ describe('IDE commit module actions', () => {
branches: {
master: {
workingReference: '1',
+ commit: {
+ id: TEST_COMMIT_SHA,
+ },
},
},
};
@@ -247,7 +255,7 @@ describe('IDE commit module actions', () => {
...file('changed'),
type: 'blob',
active: true,
- lastCommitSha: '123456789',
+ lastCommitSha: TEST_COMMIT_SHA,
};
store.state.stagedFiles.push(f);
store.state.changedFiles = [
@@ -307,7 +315,7 @@ describe('IDE commit module actions', () => {
previous_path: undefined,
},
],
- start_branch: 'master',
+ start_sha: TEST_COMMIT_SHA,
});
done();
@@ -330,11 +338,11 @@ describe('IDE commit module actions', () => {
file_path: jasmine.anything(),
content: undefined,
encoding: jasmine.anything(),
- last_commit_id: '123456789',
+ last_commit_id: TEST_COMMIT_SHA,
previous_path: undefined,
},
],
- start_branch: undefined,
+ start_sha: undefined,
});
done();
@@ -403,7 +411,7 @@ describe('IDE commit module actions', () => {
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/placeholderBranchName']
- }&merge_request[target_branch]=master`,
+ }&merge_request[target_branch]=master&nav_source=webide`,
);
done();
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index efd0d86552b..064e66cef64 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -1,5 +1,6 @@
import mutations from '~/ide/stores/mutations/file';
import state from '~/ide/stores/state';
+import { FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { file } from '../../helpers';
describe('IDE store file mutations', () => {
@@ -83,6 +84,63 @@ describe('IDE store file mutations', () => {
expect(localFile.raw).toBeNull();
expect(localFile.baseRaw).toBeNull();
});
+
+ it('sets extra file data to all arrays concerned', () => {
+ localState.stagedFiles = [localFile];
+ localState.changedFiles = [localFile];
+ localState.openFiles = [localFile];
+
+ const rawPath = 'foo/bar/blah.md';
+
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ raw_path: rawPath,
+ },
+ file: localFile,
+ });
+
+ expect(localState.stagedFiles[0].rawPath).toEqual(rawPath);
+ expect(localState.changedFiles[0].rawPath).toEqual(rawPath);
+ expect(localState.openFiles[0].rawPath).toEqual(rawPath);
+ expect(localFile.rawPath).toEqual(rawPath);
+ });
+
+ it('does not mutate certain props on the file', () => {
+ const path = 'New Path';
+ const name = 'New Name';
+ localFile.path = path;
+ localFile.name = name;
+
+ localState.stagedFiles = [localFile];
+ localState.changedFiles = [localFile];
+ localState.openFiles = [localFile];
+
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ path: 'Old Path',
+ name: 'Old Name',
+ raw: 'Old Raw',
+ base_raw: 'Old Base Raw',
+ },
+ file: localFile,
+ });
+
+ [
+ localState.stagedFiles[0],
+ localState.changedFiles[0],
+ localState.openFiles[0],
+ localFile,
+ ].forEach(f => {
+ expect(f).toEqual(
+ jasmine.objectContaining({
+ path,
+ name,
+ raw: null,
+ baseRaw: null,
+ }),
+ );
+ });
+ });
});
describe('SET_FILE_RAW_DATA', () => {
@@ -315,6 +373,19 @@ describe('IDE store file mutations', () => {
expect(localState.stagedFiles.length).toBe(1);
expect(localState.stagedFiles[0].raw).toEqual('testing 123');
});
+
+ it('adds already-staged file to `replacedFiles`', () => {
+ localFile.raw = 'already-staged';
+
+ mutations.STAGE_CHANGE(localState, localFile.path);
+
+ localFile.raw = 'testing 123';
+
+ mutations.STAGE_CHANGE(localState, localFile.path);
+
+ expect(localState.replacedFiles.length).toBe(1);
+ expect(localState.replacedFiles[0].raw).toEqual('already-staged');
+ });
});
describe('UNSTAGE_CHANGE', () => {
@@ -355,10 +426,10 @@ describe('IDE store file mutations', () => {
it('updates file view mode', () => {
mutations.SET_FILE_VIEWMODE(localState, {
file: localFile,
- viewMode: 'preview',
+ viewMode: FILE_VIEW_MODE_PREVIEW,
});
- expect(localFile.viewMode).toBe('preview');
+ expect(localFile.viewMode).toBe(FILE_VIEW_MODE_PREVIEW);
});
});
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 5ee098bf17f..2470c99e300 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -79,6 +79,16 @@ describe('Multi-file store mutations', () => {
});
});
+ describe('CLEAR_REPLACED_FILES', () => {
+ it('clears replacedFiles array', () => {
+ localState.replacedFiles.push('a');
+
+ mutations.CLEAR_REPLACED_FILES(localState);
+
+ expect(localState.replacedFiles.length).toBe(0);
+ });
+ });
+
describe('UPDATE_VIEWER', () => {
it('sets viewer state', () => {
mutations.UPDATE_VIEWER(localState, 'diff');
@@ -109,6 +119,62 @@ describe('Multi-file store mutations', () => {
});
});
+ describe('CREATE_TMP_ENTRY', () => {
+ beforeEach(() => {
+ localState.currentProjectId = 'gitlab-ce';
+ localState.currentBranchId = 'master';
+ localState.trees['gitlab-ce/master'] = {
+ tree: [],
+ };
+ });
+
+ it('creates temp entry in the tree', () => {
+ const tmpFile = file('test');
+ mutations.CREATE_TMP_ENTRY(localState, {
+ data: {
+ entries: {
+ test: {
+ ...tmpFile,
+ tempFile: true,
+ changed: true,
+ },
+ },
+ treeList: [tmpFile],
+ },
+ projectId: 'gitlab-ce',
+ branchId: 'master',
+ });
+
+ expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
+ expect(localState.entries.test.tempFile).toEqual(true);
+ });
+
+ it('marks entry as replacing previous entry if the old one has been deleted', () => {
+ const tmpFile = file('test');
+ localState.entries.test = {
+ ...tmpFile,
+ deleted: true,
+ };
+ mutations.CREATE_TMP_ENTRY(localState, {
+ data: {
+ entries: {
+ test: {
+ ...tmpFile,
+ tempFile: true,
+ changed: true,
+ },
+ },
+ treeList: [tmpFile],
+ },
+ projectId: 'gitlab-ce',
+ branchId: 'master',
+ });
+
+ expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
+ expect(localState.entries.test.replaces).toEqual(true);
+ });
+ });
+
describe('UPDATE_TEMP_FLAG', () => {
beforeEach(() => {
localState.entries.test = {
@@ -252,6 +318,7 @@ describe('Multi-file store mutations', () => {
permalink: `${gl.TEST_HOST}/testing-123`,
commitsPath: `${gl.TEST_HOST}/testing-123`,
blamePath: `${gl.TEST_HOST}/testing-123`,
+ replaces: true,
};
localState.entries.test = f;
localState.changedFiles.push(f);
@@ -262,6 +329,7 @@ describe('Multi-file store mutations', () => {
expect(f.permalink).toBe(`${gl.TEST_HOST}/test`);
expect(f.commitsPath).toBe(`${gl.TEST_HOST}/test`);
expect(f.blamePath).toBe(`${gl.TEST_HOST}/test`);
+ expect(f.replaces).toBe(false);
});
});
@@ -309,7 +377,7 @@ describe('Multi-file store mutations', () => {
...localState.entries.oldPath,
id: 'newPath',
name: 'newPath',
- key: 'newPath-blob-name',
+ key: 'newPath-blob-oldPath',
path: 'newPath',
tempFile: true,
prevPath: 'oldPath',
@@ -318,6 +386,7 @@ describe('Multi-file store mutations', () => {
url: `${gl.TEST_HOST}/newPath`,
moved: jasmine.anything(),
movedPath: jasmine.anything(),
+ opened: false,
});
});
@@ -349,13 +418,5 @@ describe('Multi-file store mutations', () => {
expect(localState.entries.parentPath.tree.length).toBe(1);
});
-
- it('adds to openFiles if previously opened', () => {
- localState.entries.oldPath.opened = true;
-
- mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
-
- expect(localState.openFiles).toEqual([localState.entries.newPath]);
- });
});
});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index debe1c4acee..0fc9519a6bf 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -92,6 +92,16 @@ describe('Multi-file store utils', () => {
path: 'deletedFile',
deleted: true,
},
+ {
+ ...file('renamedFile'),
+ path: 'renamedFile',
+ prevPath: 'prevPath',
+ },
+ {
+ ...file('replacingFile'),
+ path: 'replacingFile',
+ replaces: true,
+ },
],
currentBranchId: 'master',
};
@@ -131,8 +141,24 @@ describe('Multi-file store utils', () => {
last_commit_id: undefined,
previous_path: undefined,
},
+ {
+ action: commitActionTypes.move,
+ file_path: 'renamedFile',
+ content: null,
+ encoding: 'text',
+ last_commit_id: undefined,
+ previous_path: 'prevPath',
+ },
+ {
+ action: commitActionTypes.update,
+ file_path: 'replacingFile',
+ content: undefined,
+ encoding: 'text',
+ last_commit_id: undefined,
+ previous_path: undefined,
+ },
],
- start_branch: undefined,
+ start_sha: undefined,
});
});
@@ -187,7 +213,7 @@ describe('Multi-file store utils', () => {
previous_path: undefined,
},
],
- start_branch: undefined,
+ start_sha: undefined,
});
});
});
@@ -235,6 +261,41 @@ describe('Multi-file store utils', () => {
},
]);
});
+
+ it('filters out folders from the list', () => {
+ const files = [
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c',
+ type: 'tree',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ];
+
+ const flattendFiles = utils.getCommitFiles(files);
+
+ expect(flattendFiles).toEqual([
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ]);
+ });
});
describe('mergeTrees', () => {
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 25543053eba..4d57bfb1b33 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => {
- let Issuable;
describe('initBulkUpdate', () => {
it('should not set bulkUpdateSidebar', () => {
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).not.toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeNull();
});
it('should set bulkUpdateSidebar', () => {
@@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update');
document.body.appendChild(element);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
@@ -36,7 +36,7 @@ describe('Issuable', () => {
input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios);
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index b0f4ab2b12d..a111333ac80 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,81 +1,98 @@
import Vue from 'vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
import formComponent from '~/issue_show/components/form.vue';
import eventHub from '~/issue_show/event_hub';
describe('Inline edit form component', () => {
let vm;
- let autosave;
- let autosaveObj;
-
- beforeEach(done => {
- autosaveObj = { reset: jasmine.createSpy() };
-
- autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ const defaultProps = {
+ canDestroy: true,
+ formState: {
+ title: 'b',
+ description: 'a',
+ lockedWarningVisible: false,
+ },
+ issuableType: 'issue',
+ markdownPreviewPath: '/',
+ markdownDocsPath: '/',
+ projectPath: '/',
+ projectNamespace: '/',
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+ const createComponent = props => {
const Component = Vue.extend(formComponent);
- vm = new Component({
- propsData: {
- canDestroy: true,
- formState: {
- title: 'b',
- description: 'a',
- lockedWarningVisible: false,
- },
- issuableType: 'issue',
- markdownPreviewPath: '/',
- markdownDocsPath: '/',
- projectPath: '/',
- projectNamespace: '/',
- },
- }).$mount();
-
- Vue.nextTick(done);
- });
+ vm = mountComponent(Component, {
+ ...defaultProps,
+ ...props,
+ });
+ };
it('does not render template selector if no templates exist', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull();
});
- it('renders template selector when templates exists', done => {
- vm.issuableTemplates = ['test'];
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
+ it('renders template selector when templates exists', () => {
+ createComponent({ issuableTemplates: ['test'] });
- done();
- });
+ expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
});
it('hides locked warning by default', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('shows locked warning if formState is different', done => {
- vm.formState.lockedWarningVisible = true;
+ it('shows locked warning if formState is different', () => {
+ createComponent({ formState: { ...defaultProps.formState, lockedWarningVisible: true } });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ });
- done();
+ it('hides locked warning when currently saving', () => {
+ createComponent({
+ formState: { ...defaultProps.formState, updateLoading: true, lockedWarningVisible: true },
});
- });
- it('initialized Autosave on mount', () => {
- expect(autosave).toHaveBeenCalledTimes(2);
+ expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('calls reset on autosave when eventHub emits appropriate events', () => {
- eventHub.$emit('close.form');
+ describe('autosave', () => {
+ let autosaveObj;
+ let autosave;
+
+ beforeEach(() => {
+ autosaveObj = { reset: jasmine.createSpy() };
+ autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ });
+
+ it('initialized Autosave on mount', () => {
+ createComponent();
- expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
+ expect(autosave).toHaveBeenCalledTimes(2);
+ });
+
+ it('calls reset on autosave when eventHub emits appropriate events', () => {
+ createComponent();
+
+ eventHub.$emit('close.form');
- eventHub.$emit('delete.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
+ eventHub.$emit('delete.issuable');
- eventHub.$emit('update.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ eventHub.$emit('update.issuable');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ });
});
});
diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js
new file mode 100644
index 00000000000..c6eac4e27b3
--- /dev/null
+++ b/spec/javascripts/jobs/components/empty_state_spec.js
@@ -0,0 +1,141 @@
+import Vue from 'vue';
+import component from '~/jobs/components/empty_state.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Empty State', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const props = {
+ illustrationPath: 'illustrations/pending_job_empty.svg',
+ illustrationSizeClass: 'svg-430',
+ title: 'This job has not started yet',
+ playable: false,
+ variablesSettingsUrl: '',
+ };
+
+ const content = 'This job is in pending state and is waiting to be picked by a runner';
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('renders image and title', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+ });
+
+ it('renders img with provided path and size', () => {
+ expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath);
+ expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass);
+ });
+
+ it('renders provided title', () => {
+ expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual(
+ props.title,
+ );
+ });
+ });
+
+ describe('with content', () => {
+ it('renders content', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual(
+ content,
+ );
+ });
+ });
+
+ describe('without content', () => {
+ it('does not render content', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull();
+ });
+ });
+
+ describe('with action', () => {
+ it('renders action', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ action: {
+ path: 'runner',
+ button_title: 'Check runner',
+ method: 'post',
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual(
+ 'runner',
+ );
+ });
+ });
+
+ describe('without action', () => {
+ it('does not render action', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ action: null,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+ });
+ });
+
+ describe('without playbale action', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and not scheduled job', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ playable: true,
+ scheduled: false,
+ action: {
+ path: 'runner',
+ button_title: 'Check runner',
+ method: 'post',
+ },
+ });
+ });
+
+ it('renders manual variables form', () => {
+ expect(vm.$el.querySelector('.js-manual-vars-form')).not.toBeNull();
+ });
+
+ it('does not render the empty state action', () => {
+ expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and scheduled job', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index f28d2c2a882..d3c1cf831bb 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import jobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
+import * as types from '~/jobs/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { waitForMutation } from 'spec/helpers/vue_test_utils_helper';
import { resetStore } from '../store/helpers';
import job from '../mock_data';
@@ -19,12 +21,24 @@ describe('Job App ', () => {
runnerHelpUrl: 'help/runner',
deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
+ variablesSettingsUrl: 'settings/ci-cd/variables',
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
+ projectPath: 'user-name/project-name',
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
+ const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS);
+ const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => {
+ mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData });
+ mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData);
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ return waitForJobReceived();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
store = createStore();
@@ -38,103 +52,81 @@ describe('Job App ', () => {
describe('while loading', () => {
beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {});
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount();
});
- it('renders loading icon', done => {
+ it('renders loading icon', () => {
expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull();
expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull();
expect(vm.$el.querySelector('.js-job-content')).toBeNull();
-
- setTimeout(() => {
- done();
- }, 0);
});
});
describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {});
- });
-
describe('Header section', () => {
describe('job callout message', () => {
it('should not render the reason when reason is absent', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(false);
-
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should render the reason when reason is present', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
- callout_message: 'There is an unknown failure, please try again',
- }),
- );
-
- vm = mountComponentWithStore(Component, { props, store });
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(true);
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ callout_message: 'There is an unkown failure, please try again',
+ },
+ })
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('triggered job', () => {
- beforeEach(() => {
+ beforeEach(done => {
const aYearAgo = new Date();
aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
- mock
- .onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount({ jobData: { started: aYearAgo.toISOString() } })
+ .then(done)
+ .catch(done.fail);
});
- it('should render provided job information', done => {
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 triggered 1 year ago by Root');
- done();
- }, 0);
+ it('should render provided job information', () => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 triggered 1 year ago by Root');
});
- it('should render new issue link', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
- done();
- }, 0);
+ it('should render new issue link', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
});
});
describe('created job', () => {
it('should render created key', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 created 3 weeks ago by Root');
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 created 3 weeks ago by Root');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -142,9 +134,8 @@ describe('Job App ', () => {
describe('stuck block', () => {
describe('without active runners availabl', () => {
it('renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -158,23 +149,23 @@ describe('Job App ', () => {
online: false,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
+ expect(
+ vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'),
+ ).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when available runners can not run specified tag', () => {
it('renders tags in stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -187,27 +178,21 @@ describe('Job App ', () => {
available: false,
online: false,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when runners are offline and build has tags', () => {
it('renders message about job being stuck because of no runners with the specified tags', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -220,48 +205,35 @@ describe('Job App ', () => {
available: true,
online: true,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
it('does not renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
runners: { available: true },
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('unmet prerequisites block', () => {
it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'failed',
icon: 'status_failed',
@@ -281,104 +253,81 @@ describe('Job App ', () => {
available: true,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('environments block', () => {
it('renders environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
deployment_status: {
environment: {
environment_path: '/path',
name: 'foo',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('erased block', () => {
it('renders erased block when `erased` is true', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
erased_by: {
username: 'root',
web_url: 'gitlab.com/root',
},
erased_at: '2016-11-07T11:11:16.525Z',
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render erased block when `erased` is false', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ erased_at: null,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('empty states block', () => {
it('renders empty state when job does not have trace and is not running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'pending',
@@ -398,25 +347,18 @@ describe('Job App ', () => {
path: '/path',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job does not have trace but it is running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'running',
@@ -425,34 +367,23 @@ describe('Job App ', () => {
text: 'running',
details_path: 'path',
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job has trace but it is not running', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({ jobData: { has_trace: true } })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ done();
});
it('displays remaining time for a delayed job', done => {
@@ -460,120 +391,114 @@ describe('Job App ', () => {
spyOn(Date, 'now').and.callFake(
() => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds,
);
- mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture });
+ setupAndMount({ jobData: delayedJobFixture })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- store.subscribeAction(action => {
- if (action.type !== 'receiveJobSuccess') {
- return;
- }
+ const title = vm.$el.querySelector('.js-job-empty-state-title');
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- const title = vm.$el.querySelector('.js-job-empty-state-title');
+ expect(title).toContainText('01:00:00');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
- expect(title).toContainText('01:00:00');
- done();
- })
- .catch(done.fail);
- });
+ describe('sidebar', () => {
+ it('has no blank blocks', done => {
+ setupAndMount({
+ jobData: {
+ duration: null,
+ finished_at: null,
+ erased_at: null,
+ queued: null,
+ runner: null,
+ coverage: null,
+ tags: [],
+ cancel_path: null,
+ },
+ })
+ .then(() => {
+ vm.$el.querySelectorAll('.blocks-container > *').forEach(block => {
+ expect(block.textContent.trim()).not.toBe('');
+ });
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({ jobData: { archived: true } })
+ .then(done)
+ .catch(done.fail);
});
- it('renders warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
- done();
- }, 0);
+ it('renders warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
});
});
describe('non-archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount()
+ .then(done)
+ .catch(done.fail);
});
- it('does not warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
- done();
- }, 0);
+ it('does not warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
});
});
describe('trace output', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- });
-
describe('with append flag', () => {
it('appends the log content to the existing one', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
-
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>More<span>',
+ status: 'running',
+ state: 'newstate',
+ append: true,
+ complete: true,
+ },
+ })
+ .then(() => {
+ vm.$store.state.trace = 'Update';
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('without append flag', () => {
it('replaces the trace', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
- 'Update',
- );
+ setupAndMount({
+ traceData: {
+ html: '<span>Different<span>',
+ status: 'running',
+ append: false,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
+ 'Update',
+ );
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
- done();
- }, 0);
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain(
+ 'Different',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -589,83 +514,76 @@ describe('Job App ', () => {
complete: true,
});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when size is equal than total', () => {
it('does not show the truncated information', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('trace controls', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('should render scroll buttons', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
- expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
- done();
- }, 0);
+ it('should render scroll buttons', () => {
+ expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
});
- it('should render link to raw ouput', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to raw ouput', () => {
+ expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
});
- it('should render link to erase job', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to erase job', () => {
+ expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
});
});
});
diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js
index dc0f77ceb80..24bb6b9a48b 100644
--- a/spec/javascripts/jobs/components/job_log_spec.js
+++ b/spec/javascripts/jobs/components/job_log_spec.js
@@ -3,6 +3,7 @@ import component from '~/jobs/components/job_log.vue';
import createStore from '~/jobs/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../store/helpers';
+import { logWithCollapsibleSections } from '../mock_data';
describe('Job Log', () => {
const Component = Vue.extend(component);
@@ -10,7 +11,7 @@ describe('Job Log', () => {
let vm;
const trace =
- 'Running with gitlab-runner 11.1.0 (081978aa)<br> on docker-auto-scale-com d5ae8d25<br>Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...<br>';
+ '<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section js-section-header section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
beforeEach(() => {
store = createStore();
@@ -31,7 +32,7 @@ describe('Job Log', () => {
});
expect(vm.$el.querySelector('code').textContent).toContain(
- 'Running with gitlab-runner 11.1.0 (081978aa)',
+ 'Running with gitlab-runner 12.1.0 (de7731dd)',
);
});
@@ -62,4 +63,60 @@ describe('Job Log', () => {
expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
});
});
+
+ describe('Collapsible sections', () => {
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, {
+ props: {
+ trace: logWithCollapsibleSections.html,
+ isComplete: true,
+ },
+ store,
+ });
+ });
+
+ it('renders open arrow', () => {
+ expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull();
+ });
+
+ it('toggles hidden class to the sibilings rows when arrow is clicked', done => {
+ vm.$nextTick()
+ .then(() => {
+ const { section } = vm.$el.querySelector('.js-section-start').dataset;
+ vm.$el.querySelector('.js-section-start').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(true);
+ });
+
+ vm.$el.querySelector('.js-section-start').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(false);
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('toggles hidden class to the sibilings rows when header section is clicked', done => {
+ vm.$nextTick()
+ .then(() => {
+ const { section } = vm.$el.querySelector('.js-section-header').dataset;
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(true);
+ });
+
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(false);
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/jobs/components/manual_variables_form_spec.js b/spec/javascripts/jobs/components/manual_variables_form_spec.js
new file mode 100644
index 00000000000..093aa905185
--- /dev/null
+++ b/spec/javascripts/jobs/components/manual_variables_form_spec.js
@@ -0,0 +1,88 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Form from '~/jobs/components/manual_variables_form.vue';
+
+describe('Manual Variables Form', () => {
+ let wrapper;
+ const requiredProps = {
+ action: {
+ path: '/play',
+ method: 'post',
+ button_title: 'Trigger this manual action',
+ },
+ variablesSettingsUrl: '/settings',
+ };
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(Form, {
+ propsData: props,
+ });
+ };
+
+ beforeEach(() => {
+ factory(requiredProps);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders empty form with correct placeholders', () => {
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('placeholder')).toBe('Input variable key');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('placeholder')).toBe(
+ 'Input variable value',
+ );
+ });
+
+ it('renders help text with provided link', () => {
+ expect(wrapper.find('p').text()).toBe(
+ 'Specify variable values to be used in this run. The values specified in CI/CD settings will be used as default',
+ );
+
+ expect(wrapper.find('a').attributes('href')).toBe(requiredProps.variablesSettingsUrl);
+ });
+
+ describe('when adding a new variable', () => {
+ it('creates a new variable when user types a new key and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputKey' }).setValue('new key'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].key).toBe('new key');
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('creates a new variable when user types a new value and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputSecretValue' }).setValue('new value'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].secret_value).toBe('new value');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when deleting a variable', () => {
+ it('removes the variable row', () => {
+ wrapper.vm.variables = [
+ {
+ key: 'new key',
+ secret_value: 'value',
+ id: '1',
+ },
+ ];
+
+ wrapper.find(GlButton).vm.$emit('click');
+
+ expect(wrapper.vm.variables.length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index e98639bf21e..86b7a8d7848 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -9,7 +9,6 @@ describe('Stages Dropdown', () => {
const mockPipelineData = {
id: 28029444,
- iid: 123,
details: {
status: {
details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
@@ -78,8 +77,8 @@ describe('Stages Dropdown', () => {
expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
});
- it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`;
+ it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
@@ -101,10 +100,8 @@ describe('Stages Dropdown', () => {
});
});
- it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
- pipeline.merge_request.iid
- } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`;
+ it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
@@ -144,10 +141,8 @@ describe('Stages Dropdown', () => {
});
});
- it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
- pipeline.merge_request.iid
- } with ${pipeline.merge_request.source_branch}`;
+ it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 88b0bb206ee..c5022d3e93d 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -960,7 +960,6 @@ export default {
},
pipeline: {
id: 140,
- iid: 13,
user: {
name: 'Root',
username: 'root',
@@ -1190,3 +1189,18 @@ export const jobsInStage = {
path: '/gitlab-org/gitlab-shell/pipelines/27#build',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
};
+
+export const logWithCollapsibleSections = {
+ append: false,
+ complete: true,
+ html:
+ '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571405" data-section="after-script" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-after-script">Running after script...</span><span class="section js-section-header js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><span class="term-fg-l-green term-bold section js-s-after-script">$ date</span><span class="section js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script">Mon Jun 3 14:16:46 UTC 2019<br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><div class="section-end" data-section="after-script"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"data-timestamp="1559571408" data-section="archive-cache" role="button" ></div><span class="term-fg-l-green term-bold section js-section-header js-s-archive-cache">Not uploading cache debian-stretch-ruby-2.6.3-node-10.x-3 due to policy</span><span class="section js-section-header js-s-archive-cache"><br /></span><span class="section s_archive-cache line"></span><span class="section js-s-archive-cache"></span><div class="section-end" data-section="archive-cache"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571409" data-section="upload-artifacts-on-success" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-section-header js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">coverage/: found 5 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">knapsack/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_flaky/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_profiling/: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-yellow section js-s-upload-artifacts-on-success">WARNING: tmp/capybara/: no matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-l-green term-bold section js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">junit_rspec.xml: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><div class="section-end" data-section="upload-artifacts-on-success"></div><span class="term-fg-l-green term-bold">Job succeeded<br /><span class="term-fg-l-green term-bold"></span></span>',
+ id: 1385,
+ offset: 0,
+ size: 78815,
+ state:
+ 'eyJvZmZzZXQiOjc4ODE1LCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowLCJzZWN0aW9ucyI6W10sImxpbmVub19pbl9zZWN0aW9uIjoxMX0=',
+ status: 'success',
+ total: 78815,
+ truncated: false,
+};
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 1295d900de7..3a53ecacb88 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -46,15 +46,30 @@ describe('MergeRequestTabs', function() {
describe('opensInNewTab', function() {
var tabUrl;
var windowTarget = '_blank';
+ let clickTabParams;
beforeEach(function() {
loadFixtures('merge_requests/merge_request_with_task_list.html');
tabUrl = $('.commits-tab a').attr('href');
+
+ clickTabParams = {
+ metaKey: false,
+ ctrlKey: false,
+ which: 1,
+ stopImmediatePropagation: function() {},
+ preventDefault: function() {},
+ currentTarget: {
+ getAttribute: function(attr) {
+ return attr === 'href' ? tabUrl : null;
+ },
+ },
+ };
});
describe('meta click', () => {
let metakeyEvent;
+
beforeEach(function() {
metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
});
@@ -67,6 +82,8 @@ describe('MergeRequestTabs', function() {
this.class.bindEvents();
$('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
+
+ expect(window.open).toHaveBeenCalled();
});
it('opens page when commits badge is clicked', function() {
@@ -77,6 +94,8 @@ describe('MergeRequestTabs', function() {
this.class.bindEvents();
$('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
+
+ expect(window.open).toHaveBeenCalled();
});
});
@@ -86,12 +105,9 @@ describe('MergeRequestTabs', function() {
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({
- metaKey: false,
- ctrlKey: true,
- which: 1,
- stopImmediatePropagation: function() {},
- });
+ this.class.clickTab({ ...clickTabParams, metaKey: true });
+
+ expect(window.open).toHaveBeenCalled();
});
it('opens page tab in a new browser tab with Cmd+Click - Mac', function() {
@@ -100,12 +116,9 @@ describe('MergeRequestTabs', function() {
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({
- metaKey: true,
- ctrlKey: false,
- which: 1,
- stopImmediatePropagation: function() {},
- });
+ this.class.clickTab({ ...clickTabParams, ctrlKey: true });
+
+ expect(window.open).toHaveBeenCalled();
});
it('opens page tab in a new browser tab with Middle-click - Mac/PC', function() {
@@ -114,12 +127,9 @@ describe('MergeRequestTabs', function() {
expect(name).toEqual(windowTarget);
});
- this.class.clickTab({
- metaKey: false,
- ctrlKey: false,
- which: 2,
- stopImmediatePropagation: function() {},
- });
+ this.class.clickTab({ ...clickTabParams, which: 2 });
+
+ expect(window.open).toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index ac7e0bb12a1..57f99a09002 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,21 +1,26 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
+import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
describe('Area component', () => {
+ const mockSha = 'mockSha';
const mockWidgets = 'mockWidgets';
const mockSvgPathContent = 'mockSvgPathContent';
+ const projectPath = `${TEST_HOST}/path/to/project`;
+ const commitUrl = `${projectPath}/commit/${mockSha}`;
let mockGraphData;
let areaChart;
let spriteSpy;
+ let store;
beforeEach(() => {
- const store = createStore();
-
+ store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
@@ -26,10 +31,12 @@ describe('Area component', () => {
graphData: mockGraphData,
containerWidth: 0,
deploymentData: store.state.monitoringDashboard.deploymentData,
+ projectPath,
},
slots: {
default: mockWidgets,
},
+ store,
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
@@ -88,11 +95,14 @@ describe('Area component', () => {
);
});
- it('renders commit sha in tooltip content', () => {
- const mockSha = 'mockSha';
+ it('renders clickable commit sha in tooltip content', () => {
areaChart.vm.tooltip.sha = mockSha;
+ areaChart.vm.tooltip.commitUrl = commitUrl;
+
+ const commitLink = areaChart.find(GlLink);
- expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true);
+ expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+ expect(commitLink.attributes('href')).toEqual(commitUrl);
});
});
});
diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js
new file mode 100644
index 00000000000..d8ac68b9484
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/column_spec.js
@@ -0,0 +1,58 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import ColumnChart from '~/monitoring/components/charts/column.vue';
+
+describe('Column component', () => {
+ let columnChart;
+
+ beforeEach(() => {
+ columnChart = shallowMount(ColumnChart, {
+ propsData: {
+ graphData: {
+ queries: [
+ {
+ x_label: 'Time',
+ y_label: 'Usage',
+ result: [
+ {
+ metric: {},
+ values: [
+ [1495700554.925, '8.0390625'],
+ [1495700614.925, '8.0390625'],
+ [1495700674.925, '8.0390625'],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ containerWidth: 100,
+ },
+ });
+ });
+
+ afterEach(() => {
+ columnChart.destroy();
+ });
+
+ describe('wrapped components', () => {
+ describe('GitLab UI column chart', () => {
+ let glColumnChart;
+
+ beforeEach(() => {
+ glColumnChart = columnChart.find(GlColumnChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glColumnChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glColumnChart.props();
+
+ expect(props.data).toBe(columnChart.vm.chartData);
+ expect(props.option).toBe(columnChart.vm.chartOptions);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/empty_chart_spec.js b/spec/javascripts/monitoring/charts/empty_chart_spec.js
new file mode 100644
index 00000000000..bbfca27dc5a
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/empty_chart_spec.js
@@ -0,0 +1,29 @@
+import { shallowMount } from '@vue/test-utils';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+
+describe('Empty Chart component', () => {
+ let emptyChart;
+ const graphTitle = 'Memory Usage';
+
+ beforeEach(() => {
+ emptyChart = shallowMount(EmptyChart, {
+ propsData: {
+ graphTitle,
+ },
+ });
+ });
+
+ afterEach(() => {
+ emptyChart.destroy();
+ });
+
+ it('render the chart title', () => {
+ expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle);
+ });
+
+ describe('Computed props', () => {
+ it('sets the height for the svg container', () => {
+ expect(emptyChart.vm.svgContainerStyle.height).toBe('300px');
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
index 12b73002f97..127a4a7955a 100644
--- a/spec/javascripts/monitoring/charts/single_stat_spec.js
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import { graphDataPrometheusQuery } from '../mock_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -7,9 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- title: 'Time to render',
- value: 1,
- unit: 'sec',
+ graphData: graphDataPrometheusQuery,
},
});
});
@@ -19,9 +18,9 @@ describe('Single Stat Chart component', () => {
});
describe('computed', () => {
- describe('valueWithUnit', () => {
+ describe('engineeringNotation', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ expect(singleStatChart.vm.engineeringNotation).toBe('91MB');
});
});
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index f1d578648b8..02c3f303912 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -1,17 +1,21 @@
import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlToast } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import {
+import MonitoringMock, {
metricsGroupsAPIResponse,
mockApiEndpoint,
environmentData,
singleGroupResponse,
+ dashboardGitResponse,
} from './mock_data';
+const localVue = createLocalVue();
const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
@@ -20,7 +24,7 @@ const propsData = {
tagsPath: '/path/to/tags',
projectPath: '/path/to/project',
metricsEndpoint: mockApiEndpoint,
- deploymentEndpoint: null,
+ deploymentsEndpoint: null,
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg',
@@ -39,6 +43,7 @@ describe('Dashboard', () => {
let mock;
let store;
let component;
+ let mockGraphData;
beforeEach(() => {
setFixtures(`
@@ -57,21 +62,41 @@ describe('Dashboard', () => {
});
afterEach(() => {
- component.$destroy();
+ if (component) {
+ component.$destroy();
+ }
mock.restore();
});
describe('no metrics are available yet', () => {
- it('shows a getting started empty state when no metrics are present', () => {
+ beforeEach(() => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData },
store,
});
+ });
+ it('shows a getting started empty state when no metrics are present', () => {
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
expect(component.emptyState).toEqual('gettingStarted');
});
+
+ it('shows the environment selector', () => {
+ expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
+ });
+
+ describe('no data found', () => {
+ it('shows the environment selector dropdown', () => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, showEmptyState: true },
+ store,
+ });
+
+ expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
});
describe('requests information to the server', () => {
@@ -150,14 +175,24 @@ describe('Dashboard', () => {
singleGroupResponse,
);
- setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item',
- );
+ Vue.nextTick()
+ .then(() => {
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
- expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
- done();
- });
+ expect(component.environments.length).toEqual(environmentData.length);
+ expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
+
+ Array.from(dropdownMenuEnvironments).forEach((value, index) => {
+ if (environmentData[index].metrics_path) {
+ expect(value).toHaveAttr('href', environmentData[index].metrics_path);
+ }
+ });
+
+ done();
+ })
+ .catch(done.fail);
});
it('hides the environments dropdown list when there is no environments', done => {
@@ -212,7 +247,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
const dropdownItems = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item[active="true"]',
+ '.js-environments-dropdown .dropdown-item.active',
);
expect(dropdownItems.length).toEqual(1);
@@ -278,13 +313,9 @@ describe('Dashboard', () => {
});
spyOn(component.$store, 'dispatch').and.stub();
- const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
component.$store.commit(
- `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
- '/environments',
- );
- component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
@@ -294,7 +325,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
expect(component.$store.dispatch).toHaveBeenCalled();
- expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+ expect(getTimeDiffSpy).toHaveBeenCalled();
done();
})
@@ -302,7 +333,17 @@ describe('Dashboard', () => {
});
it('shows a specific time window selected from the url params', done => {
- spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
+ const start = 1564439536;
+ const end = 1564441336;
+ spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
+ start,
+ end,
+ });
+ spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => {
+ if (param === 'start') return [start];
+ if (param === 'end') return [end];
+ return [];
+ });
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
@@ -337,6 +378,71 @@ describe('Dashboard', () => {
});
});
+ describe('link to chart', () => {
+ let wrapper;
+ const currentDashboard = 'TEST_DASHBOARD';
+ localVue.use(GlToast);
+ const link = () => wrapper.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ wrapper = shallowMount(DashboardComponent, {
+ localVue,
+ sync: false,
+ attachToDocument: true,
+ propsData: { ...propsData, hasMetrics: true, currentDashboard },
+ store,
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('adds a copy button to the dropdown', () => {
+ expect(link().text()).toContain('Generate link to chart');
+ });
+
+ it('contains a link to the dashboard', () => {
+ expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
+ expect(clipboardText()).toContain(`group=`);
+ expect(clipboardText()).toContain(`title=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ });
+
+ it('undefined parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: undefined });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('null parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: null });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('creates a toast when clicked', () => {
+ spyOn(wrapper.vm.$toast, 'show').and.stub();
+
+ link().vm.$emit('click');
+
+ expect(wrapper.vm.$toast.show).toHaveBeenCalled();
+ });
+ });
+
describe('when the window resizes', () => {
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
@@ -402,4 +508,81 @@ describe('Dashboard', () => {
});
});
});
+
+ describe('Dashboard dropdown', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
+ prometheusEndpoint: false,
+ multipleDashboardsEnabled: true,
+ });
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ component.$store.commit(
+ `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
+ dashboardGitResponse,
+ );
+ });
+
+ it('shows the dashboard dropdown', done => {
+ setTimeout(() => {
+ const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown');
+
+ expect(dashboardDropdown).not.toEqual(null);
+ done();
+ });
+ });
+ });
+
+ describe('when downloading metrics data as CSV', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ propsData: {
+ ...propsData,
+ },
+ store,
+ });
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ MonitoringMock.data,
+ );
+ [mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
+ });
+
+ describe('csvText', () => {
+ it('converts metrics data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadCsv', () => {
+ it('produces a link with a Blob', () => {
+ expect(component.downloadCsv(mockGraphData)).toContain(`blob:`);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/monitoring/dashboard_state_spec.js b/spec/javascripts/monitoring/dashboard_state_spec.js
deleted file mode 100644
index 6b2be83aa8c..00000000000
--- a/spec/javascripts/monitoring/dashboard_state_spec.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import Vue from 'vue';
-import EmptyState from '~/monitoring/components/empty_state.vue';
-import { statePaths } from './mock_data';
-
-function createComponent(props) {
- const Component = Vue.extend(EmptyState);
-
- return new Component({
- propsData: {
- ...props,
- settingsPath: statePaths.settingsPath,
- clustersPath: statePaths.clustersPath,
- documentationPath: statePaths.documentationPath,
- emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
- emptyLoadingSvgPath: '/path/to/loading.svg',
- emptyNoDataSvgPath: '/path/to/no-data.svg',
- emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
- },
- }).$mount();
-}
-
-function getTextFromNode(component, selector) {
- return component.$el.querySelector(selector).firstChild.nodeValue.trim();
-}
-
-describe('EmptyState', () => {
- describe('Computed props', () => {
- it('currentState', () => {
- const component = createComponent({
- selectedState: 'gettingStarted',
- });
-
- expect(component.currentState).toBe(component.states.gettingStarted);
- });
-
- it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
- const component = createComponent({
- selectedState: 'unableToConnect',
- });
-
- expect(component.showButtonDescription).toEqual(true);
- });
-
- it('showButtonDescription returns the description without a link for any other state', () => {
- const component = createComponent({
- selectedState: 'loading',
- });
-
- expect(component.showButtonDescription).toEqual(false);
- });
- });
-
- it('should show the gettingStarted state', () => {
- const component = createComponent({
- selectedState: 'gettingStarted',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(
- component.states.gettingStarted.title,
- );
-
- expect(getTextFromNode(component, '.state-description')).toEqual(
- component.states.gettingStarted.description,
- );
-
- expect(getTextFromNode(component, '.btn-success')).toEqual(
- component.states.gettingStarted.buttonText,
- );
- });
-
- it('should show the loading state', () => {
- const component = createComponent({
- selectedState: 'loading',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(component.states.loading.title);
- expect(getTextFromNode(component, '.state-description')).toEqual(
- component.states.loading.description,
- );
-
- expect(getTextFromNode(component, '.btn-success')).toEqual(component.states.loading.buttonText);
- });
-
- it('should show the unableToConnect state', () => {
- const component = createComponent({
- selectedState: 'unableToConnect',
- });
-
- expect(component.$el.querySelector('svg')).toBeDefined();
- expect(getTextFromNode(component, '.state-title')).toEqual(
- component.states.unableToConnect.title,
- );
-
- expect(component.$el.querySelector('.state-description a')).toBeDefined();
- expect(getTextFromNode(component, '.btn-success')).toEqual(
- component.states.unableToConnect.buttonText,
- );
- });
-});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 82e42fe9ade..85e660d3925 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -922,3 +922,88 @@ export const metricsDashboardResponse = {
},
status: 'success',
};
+
+export const dashboardGitResponse = [
+ {
+ path: 'config/prometheus/common_metrics.yml',
+ display_name: 'Common Metrics',
+ default: true,
+ },
+ {
+ path: '.gitlab/dashboards/super.yml',
+ display_name: 'Custom Dashboard 1',
+ default: false,
+ },
+];
+
+export const graphDataPrometheusQuery = {
+ title: 'Super Chart A2',
+ type: 'single-stat',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: { job: 'prometheus' },
+ value: ['2019-06-26T21:03:20.881Z', 91],
+ },
+ ],
+ },
+ ],
+};
+
+export const graphDataPrometheusQueryRange = {
+ title: 'Super Chart A1',
+ type: 'area',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: {},
+ values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
new file mode 100644
index 00000000000..086be628093
--- /dev/null
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import PanelType from '~/monitoring/components/panel_type.vue';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import AreaChart from '~/monitoring/components/charts/area.vue';
+import { graphDataPrometheusQueryRange } from './mock_data';
+import { createStore } from '~/monitoring/stores';
+
+describe('Panel Type component', () => {
+ let store;
+ let panelType;
+ const dashboardWidth = 100;
+
+ describe('When no graphData is available', () => {
+ let glEmptyChart;
+ // Deep clone object before modifying
+ const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
+ graphDataNoResult.queries[0].result = [];
+
+ beforeEach(() => {
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: 'dashboard_link',
+ dashboardWidth,
+ graphData: graphDataNoResult,
+ },
+ });
+ });
+
+ afterEach(() => {
+ panelType.destroy();
+ });
+
+ describe('Empty Chart component', () => {
+ beforeEach(() => {
+ glEmptyChart = panelType.find(EmptyChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glEmptyChart.isVueInstance()).toBe(true);
+ });
+
+ it('it receives a graph title', () => {
+ const props = glEmptyChart.props();
+
+ expect(props.graphTitle).toBe(panelType.vm.graphData.title);
+ });
+ });
+ });
+
+ describe('when Graph data is available', () => {
+ const exampleText = 'example_text';
+
+ beforeEach(() => {
+ store = createStore();
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: exampleText,
+ dashboardWidth,
+ graphData: graphDataPrometheusQueryRange,
+ },
+ store,
+ });
+ });
+
+ describe('Area Chart panel type', () => {
+ it('is rendered', () => {
+ expect(panelType.find(AreaChart).exists()).toBe(true);
+ });
+
+ it('sets clipboard text on the dropdown', () => {
+ const link = () => panelType.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ expect(clipboardText()).toBe(exampleText);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
index 8c02e21eda2..955a39e03a5 100644
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -22,6 +22,7 @@ import {
environmentData,
metricsDashboardResponse,
metricsGroupsAPIResponse,
+ dashboardGitResponse,
} from '../mock_data';
describe('Monitoring store actions', () => {
@@ -51,9 +52,9 @@ describe('Monitoring store actions', () => {
it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
- state.deploymentEndpoint = '/success';
+ state.deploymentsEndpoint = '/success';
- mock.onGet(state.deploymentEndpoint).reply(200, {
+ mock.onGet(state.deploymentsEndpoint).reply(200, {
deployments: deploymentData,
});
@@ -68,9 +69,9 @@ describe('Monitoring store actions', () => {
it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
const dispatch = jasmine.createSpy();
const { state } = store;
- state.deploymentEndpoint = '/error';
+ state.deploymentsEndpoint = '/error';
- mock.onGet(state.deploymentEndpoint).reply(500);
+ mock.onGet(state.deploymentsEndpoint).reply(500);
fetchDeploymentsData({ state, dispatch })
.then(() => {
@@ -212,17 +213,19 @@ describe('Monitoring store actions', () => {
describe('receiveMetricsDashboardSuccess', () => {
let commit;
let dispatch;
+ let state;
beforeEach(() => {
commit = jasmine.createSpy();
dispatch = jasmine.createSpy();
+ state = storeState();
});
it('stores groups ', () => {
const params = {};
const response = metricsDashboardResponse;
- receiveMetricsDashboardSuccess({ commit, dispatch }, { response, params });
+ receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
expect(commit).toHaveBeenCalledWith(
types.RECEIVE_METRICS_DATA_SUCCESS,
@@ -231,6 +234,18 @@ describe('Monitoring store actions', () => {
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params);
});
+
+ it('sets the dashboards loaded from the repository', () => {
+ const params = {};
+ const response = metricsDashboardResponse;
+
+ response.all_dashboards = dashboardGitResponse;
+ state.multipleDashboardsEnabled = true;
+
+ receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
+
+ expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
+ });
});
describe('receiveMetricsDashboardFailure', () => {
@@ -298,8 +313,8 @@ describe('Monitoring store actions', () => {
it('commits prometheus query result', done => {
const commit = jasmine.createSpy();
const params = {
- start: '1557216349.469',
- end: '1557218149.469',
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
};
const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
const state = storeState();
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
index 02ff5847b34..43776b1b7f2 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -1,7 +1,12 @@
import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state';
-import { metricsGroupsAPIResponse, deploymentData, metricsDashboardResponse } from '../mock_data';
+import {
+ metricsGroupsAPIResponse,
+ deploymentData,
+ metricsDashboardResponse,
+ dashboardGitResponse,
+} from '../mock_data';
describe('Monitoring mutations', () => {
let stateCopy;
@@ -110,12 +115,14 @@ describe('Monitoring mutations', () => {
environmentsEndpoint: 'environments.json',
deploymentsEndpoint: 'deployments.json',
dashboardEndpoint: 'dashboard.json',
+ projectPath: '/gitlab-org/gitlab-ce',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
+ expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-ce');
});
});
@@ -156,4 +163,12 @@ describe('Monitoring mutations', () => {
expect(stateCopy.metricsWithData).toEqual([]);
});
});
+
+ describe('SET_ALL_DASHBOARDS', () => {
+ it('stores the dashboards loaded from the git repository', () => {
+ mutations[types.SET_ALL_DASHBOARDS](stateCopy, dashboardGitResponse);
+
+ expect(stateCopy.allDashboards).toEqual(dashboardGitResponse);
+ });
+ });
});
diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/javascripts/monitoring/store/utils_spec.js
new file mode 100644
index 00000000000..73dd370ffb3
--- /dev/null
+++ b/spec/javascripts/monitoring/store/utils_spec.js
@@ -0,0 +1,37 @@
+import { groupQueriesByChartInfo } from '~/monitoring/stores/utils';
+
+describe('groupQueriesByChartInfo', () => {
+ let input;
+ let output;
+
+ it('groups metrics with the same chart title and y_axis label', () => {
+ input = [
+ { title: 'title', y_label: 'MB', queries: [{}] },
+ { title: 'title', y_label: 'MB', queries: [{}] },
+ { title: 'new title', y_label: 'MB', queries: [{}] },
+ ];
+
+ output = [
+ { title: 'title', y_label: 'MB', queries: [{ metricId: null }, { metricId: null }] },
+ { title: 'new title', y_label: 'MB', queries: [{ metricId: null }] },
+ ];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+
+ // Functionality associated with the /additional_metrics endpoint
+ it("associates a chart's stringified metric_id with the metric", () => {
+ input = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{}] }];
+ output = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{ metricId: '3' }] }];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+
+ // Functionality associated with the /metrics_dashboard endpoint
+ it('aliases a stringified metrics_id on the metric to the metricId key', () => {
+ input = [{ title: 'new title', y_label: 'MB', queries: [{ metric_id: 3 }] }];
+ output = [{ title: 'new title', y_label: 'MB', queries: [{ metricId: '3', metric_id: 3 }] }];
+
+ expect(groupQueriesByChartInfo(input)).toEqual(output);
+ });
+});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index e3c455d1686..e22e8cdc03d 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,29 +1,64 @@
-import { getTimeDiff } from '~/monitoring/utils';
+import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
+import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
+ function secondsBetween({ start, end }) {
+ return (new Date(end) - new Date(start)) / 1000;
+ }
+
+ function minutesBetween(timeRange) {
+ return secondsBetween(timeRange) / 60;
+ }
+
+ function hoursBetween(timeRange) {
+ return minutesBetween(timeRange) / 60;
+ }
+
it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff();
- expect(params.end - params.start).toEqual(28800);
+ expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
- const params = getTimeDiff(timeWindows.thirtyMinutes);
+ const params = getTimeDiff('thirtyMinutes');
- expect(params.end - params.start).not.toEqual(28800);
+ expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
- nonDefaultWindows.forEach(window => {
- const params = getTimeDiff(timeWindows[window]);
- const diff = params.end - params.start;
+ nonDefaultWindows.forEach(timeWindow => {
+ const params = getTimeDiff(timeWindow);
- // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
- expect(diff).not.toEqual(28800);
- expect(typeof diff).toEqual('number');
+ // Ensure we're not returning the default
+ expect(hoursBetween(params)).not.toEqual(8);
});
});
});
+
+describe('graphDataValidatorForValues', () => {
+ /*
+ * When dealing with a metric using the query format, e.g.
+ * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024'
+ * the validator will look for the `value` key instead of `values`
+ */
+ it('validates data with the query format', () => {
+ const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery);
+
+ expect(validGraphData).toBe(true);
+ });
+
+ /*
+ * When dealing with a metric using the query?range format, e.g.
+ * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ * the validator will look for the `values` key instead of `value`
+ */
+ it('validates data with the query_range format', () => {
+ const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange);
+
+ expect(validGraphData).toBe(true);
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 362963ddaf4..88c86746992 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -251,6 +251,21 @@ describe('issue_comment_form component', () => {
});
});
});
+
+ describe('when toggling state', () => {
+ it('should update MR count', done => {
+ spyOn(vm, 'closeIssue').and.returnValue(Promise.resolve());
+
+ const updateMrCountSpy = spyOnDependency(CommentForm, 'refreshUserMergeRequestCounts');
+ vm.toggleIssueState();
+
+ Vue.nextTick(() => {
+ expect(updateMrCountSpy).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
});
describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/components/diff_with_note_spec.js b/spec/javascripts/notes/components/diff_with_note_spec.js
index 0752bd05904..f849fe9d8bb 100644
--- a/spec/javascripts/notes/components/diff_with_note_spec.js
+++ b/spec/javascripts/notes/components/diff_with_note_spec.js
@@ -47,6 +47,19 @@ describe('diff_with_note', () => {
vm = mountComponentWithStore(Component, { props, store });
});
+ it('removes trailing "+" char', () => {
+ const richText = vm.$el.querySelectorAll('.line_holder')[4].querySelector('.line_content')
+ .textContent[0];
+
+ expect(richText).not.toEqual('+');
+ });
+
+ it('removes trailing "-" char', () => {
+ const richText = vm.$el.querySelector('#LC13').parentNode.textContent[0];
+
+ expect(richText).not.toEqual('-');
+ });
+
it('shows text diff', () => {
expect(selectors.container).toHaveClass('text-file');
expect(selectors.diffTable).toExist();
diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
index 11fb89808d9..003773d07ea 100644
--- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js
+++ b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
@@ -25,8 +25,7 @@ describe('ReplyButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- startReplying: [[]],
- });
+ expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying.length).toBe(1);
});
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index efa864e7d00..74805ca8c00 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -36,14 +36,20 @@ describe('noteable_discussion component', () => {
});
it('should render user avatar', () => {
+ const discussion = { ...discussionMock };
+ discussion.diff_file = mockDiffFile;
+ discussion.diff_discussion = true;
+
+ wrapper.setProps({ discussion, renderDiffFile: true });
+
expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
});
- it('should not render discussion header for non diff discussions', () => {
+ it('should not render thread header for non diff threads', () => {
expect(wrapper.find('.discussion-header').exists()).toBe(false);
});
- it('should render discussion header', done => {
+ it('should render thread header', done => {
const discussion = { ...discussionMock };
discussion.diff_file = mockDiffFile;
discussion.diff_discussion = true;
@@ -90,16 +96,16 @@ describe('noteable_discussion component', () => {
.catch(done.fail);
});
- it('does not render jump to discussion button', () => {
- expect(
- wrapper.find('*[data-original-title="Jump to next unresolved discussion"]').exists(),
- ).toBe(false);
+ it('does not render jump to thread button', () => {
+ expect(wrapper.find('*[data-original-title="Jump to next unresolved thread"]').exists()).toBe(
+ false,
+ );
});
});
describe('methods', () => {
describe('jumpToNextDiscussion', () => {
- it('expands next unresolved discussion', done => {
+ it('expands next unresolved thread', done => {
const discussion2 = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
discussion2.resolved = false;
discussion2.active = true;
@@ -114,9 +120,7 @@ describe('noteable_discussion component', () => {
const nextDiscussionId = discussion2.id;
- setFixtures(`
- <div class="discussion" data-discussion-id="${nextDiscussionId}"></div>
- `);
+ setFixtures(`<div class="discussion" data-discussion-id="${nextDiscussionId}"></div>`);
wrapper.vm.jumpToNextDiscussion();
@@ -162,20 +166,20 @@ describe('noteable_discussion component', () => {
.catch(done.fail);
});
- describe('for commit discussions', () => {
- it('should display a monospace started a discussion on commit', () => {
- expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+ describe('for commit threads', () => {
+ it('should display a monospace started a thread on commit', () => {
+ expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
expect(commitElement.exists()).toBe(true);
expect(commitElement.text()).toContain(truncatedCommitId);
});
});
- describe('for diff discussion with a commit id', () => {
- it('should display started discussion on commit header', done => {
+ describe('for diff thread with a commit id', () => {
+ it('should display started thread on commit header', done => {
wrapper.vm.discussion.for_commit = false;
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain(`started a discussion on commit ${truncatedCommitId}`);
+ expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
expect(commitElement).not.toBe(null);
@@ -189,7 +193,7 @@ describe('noteable_discussion component', () => {
wrapper.vm.$nextTick(() => {
expect(wrapper.text()).toContain(
- `started a discussion on an outdated change in commit ${truncatedCommitId}`,
+ `started a thread on an outdated change in commit ${truncatedCommitId}`,
);
expect(commitElement).not.toBe(null);
@@ -199,21 +203,21 @@ describe('noteable_discussion component', () => {
});
});
- describe('for diff discussions without a commit id', () => {
- it('should show started a discussion on the diff text', done => {
+ describe('for diff threads without a commit id', () => {
+ it('should show started a thread on the diff text', done => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
});
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a discussion on the diff');
+ expect(wrapper.text()).toContain('started a thread on the diff');
done();
});
});
- it('should show discussion on older version text', done => {
+ it('should show thread on older version text', done => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
@@ -221,7 +225,7 @@ describe('noteable_discussion component', () => {
});
wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a discussion on an old version of the diff');
+ expect(wrapper.text()).toContain('started a thread on an old version of the diff');
done();
});
@@ -229,7 +233,7 @@ describe('noteable_discussion component', () => {
});
});
- describe('for resolved discussion', () => {
+ describe('for resolved thread', () => {
beforeEach(() => {
const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
wrapper.setProps({ discussion });
@@ -242,7 +246,7 @@ describe('noteable_discussion component', () => {
});
});
- describe('for unresolved discussion', () => {
+ describe('for unresolved thread', () => {
beforeEach(done => {
const discussion = {
...getJSONFixture(discussionWithTwoUnresolvedNotes)[0],
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 1df5cf9ef68..5f81a168498 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/notes/mock_data.js
+
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: 1501862675,
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 7a9f32ddcff..e55aa0e965a 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import $ from 'jquery';
import _ from 'underscore';
+import Api from '~/api';
import { TEST_HOST } from 'spec/test_constants';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import actionsModule, * as actions from '~/notes/stores/actions';
@@ -8,7 +9,6 @@ import * as mutationTypes from '~/notes/stores/mutation_types';
import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
-import service from '~/notes/services/notes_service';
import testAction from '../../helpers/vuex_action_helper';
import { resetStore } from '../helpers';
import {
@@ -18,6 +18,8 @@ import {
noteableDataMock,
individualNote,
} from '../mock_data';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
const TEST_ERROR_MESSAGE = 'Test error message';
@@ -335,28 +337,24 @@ describe('Actions Notes Store', () => {
});
describe('deleteNote', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify({}), {
- status: 200,
- }),
- );
- };
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
$('body').attr('data-page', '');
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ axiosMock.restore();
$('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
testAction(
actions.deleteNote,
@@ -373,7 +371,7 @@ describe('Actions Notes Store', () => {
type: 'updateMergeRequestWidget',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
],
done,
@@ -381,7 +379,7 @@ describe('Actions Notes Store', () => {
});
it('dispatches removeDiscussionsFromDiff on merge request page', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
$('body').attr('data-page', 'projects:merge_requests:show');
@@ -400,7 +398,7 @@ describe('Actions Notes Store', () => {
type: 'updateMergeRequestWidget',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'diffs/removeDiscussionsFromDiff',
@@ -452,7 +450,7 @@ describe('Actions Notes Store', () => {
type: 'startTaskList',
},
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
],
done,
@@ -527,7 +525,7 @@ describe('Actions Notes Store', () => {
],
[
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'updateMergeRequestWidget',
@@ -552,7 +550,7 @@ describe('Actions Notes Store', () => {
],
[
{
- type: 'updateResolvableDiscussonsCounts',
+ type: 'updateResolvableDiscussionsCounts',
},
{
type: 'updateMergeRequestWidget',
@@ -587,10 +585,10 @@ describe('Actions Notes Store', () => {
});
});
- describe('updateResolvableDiscussonsCounts', () => {
+ describe('updateResolvableDiscussionsCounts', () => {
it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => {
testAction(
- actions.updateResolvableDiscussonsCounts,
+ actions.updateResolvableDiscussionsCounts,
null,
{},
[{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }],
@@ -712,7 +710,7 @@ describe('Actions Notes Store', () => {
[
{ type: 'updateMergeRequestWidget' },
{ type: 'startTaskList' },
- { type: 'updateResolvableDiscussonsCounts' },
+ { type: 'updateResolvableDiscussionsCounts' },
],
done,
);
@@ -846,9 +844,9 @@ describe('Actions Notes Store', () => {
let flashContainer;
beforeEach(() => {
- spyOn(service, 'applySuggestion');
+ spyOn(Api, 'applySuggestion');
dispatch.and.returnValue(Promise.resolve());
- service.applySuggestion.and.returnValue(Promise.resolve());
+ Api.applySuggestion.and.returnValue(Promise.resolve());
flashContainer = {};
});
@@ -877,7 +875,7 @@ describe('Actions Notes Store', () => {
it('when service fails, flashes error message', done => {
const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
- service.applySuggestion.and.returnValue(Promise.reject(response));
+ Api.applySuggestion.and.returnValue(Promise.reject(response));
testSubmitSuggestion(done, () => {
expect(commit).not.toHaveBeenCalled();
@@ -894,4 +892,31 @@ describe('Actions Notes Store', () => {
});
});
});
+
+ describe('filterDiscussion', () => {
+ const path = 'some-discussion-path';
+ const filter = 0;
+
+ beforeEach(() => {
+ dispatch.and.returnValue(new Promise(() => {}));
+ });
+
+ it('fetches discussions with filter and persistFilter false', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: false }],
+ ]);
+ });
+
+ it('fetches discussions with filter and persistFilter true', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: true }],
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 8f3c493dd4c..71dcba114a9 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -32,6 +32,26 @@ describe('Getters Notes Store', () => {
};
});
+ describe('showJumpToNextDiscussion', () => {
+ it('should return true if there are 2 or more unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123', '456'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(true);
+ });
+
+ it('should return false if there are 1 or less unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(false);
+ });
+ });
+
describe('discussions', () => {
it('should return all discussions in the store', () => {
expect(getters.discussions(state)).toEqual([individualNote]);
@@ -236,6 +256,54 @@ describe('Getters Notes Store', () => {
});
});
+ describe('previousUnresolvedDiscussionId', () => {
+ describe('with unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
+ };
+
+ it('with bogus returns falsey', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('456');
+ });
+
+ [
+ { id: '123', expected: '789' },
+ { id: '456', expected: '123' },
+ { id: '789', expected: '456' },
+ ].forEach(({ id, expected }) => {
+ it(`with ${id}, returns previous value`, () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)(id)).toBe(expected);
+ });
+ });
+ });
+
+ describe('with 1 unresolved discussion', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123'],
+ };
+
+ it('with bogus returns id', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('123');
+ });
+
+ it('with match, returns value', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('123')).toEqual('123');
+ });
+ });
+
+ describe('with 0 unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => [],
+ };
+
+ it('returns undefined', () => {
+ expect(
+ getters.previousUnresolvedDiscussionId(state, localGetters)('bogus'),
+ ).toBeUndefined();
+ });
+ });
+ });
+
describe('firstUnresolvedDiscussionId', () => {
const localGetters = {
unresolvedDiscussionsIdsByDate: ['123', '456'],
diff --git a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
index 08a8362797b..75912612255 100644
--- a/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
+++ b/spec/javascripts/pages/labels/components/promote_label_modal_spec.js
@@ -26,9 +26,7 @@ describe('Promote label modal', () => {
it('contains the proper description', () => {
expect(vm.text).toContain(
- `Promoting ${labelMockData.labelTitle} will make it available for all projects inside ${
- labelMockData.groupName
- }`,
+ `Promoting ${labelMockData.labelTitle} will make it available for all projects inside ${labelMockData.groupName}`,
);
});
diff --git a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
index 2ac73ef3024..3d25a278cef 100644
--- a/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
+++ b/spec/javascripts/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -24,9 +24,7 @@ describe('Promote milestone modal', () => {
it('contains the proper description', () => {
expect(vm.text).toContain(
- `Promoting ${
- milestoneMockData.milestoneTitle
- } will make it available for all projects inside ${milestoneMockData.groupName}.`,
+ `Promoting ${milestoneMockData.milestoneTitle} will make it available for all projects inside ${milestoneMockData.groupName}.`,
);
});
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index 6dea570266b..efeb65acf87 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -17,7 +17,7 @@ describe('Page component', () => {
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
- .then(pdf => pdf.getPage(1))
+ .promise.then(pdf => pdf.getPage(1))
.then(page => {
testPage = page;
})
@@ -31,7 +31,8 @@ describe('Page component', () => {
it('renders the page when mounting', done => {
const promise = Promise.resolve();
- spyOn(testPage, 'render').and.callFake(() => promise);
+ spyOn(testPage, 'render').and.returnValue({ promise });
+
vm = mountComponent(Component, {
page: testPage,
number: 1,
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
index 8a7aa057186..0486b5fa3db 100644
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -44,7 +44,6 @@ describe('detailedMetric', () => {
},
metric: 'gitaly',
header: 'Gitaly calls',
- details: 'details',
keys: ['feature', 'request'],
});
});
@@ -79,8 +78,32 @@ describe('detailedMetric', () => {
});
});
- it('displays the metric name', () => {
+ it('displays the metric title', () => {
expect(vm.$el.innerText).toContain('gitaly');
});
+
+ describe('when using a custom metric title', () => {
+ beforeEach(() => {
+ vm = mountComponent(Vue.extend(detailedMetric), {
+ currentRequest: {
+ details: {
+ gitaly: {
+ duration: '123ms',
+ calls: '456',
+ details: requestDetails,
+ },
+ },
+ },
+ metric: 'gitaly',
+ title: 'custom',
+ header: 'Gitaly calls',
+ keys: ['feature', 'request'],
+ });
+ });
+
+ it('displays the custom title', () => {
+ expect(vm.$el.innerText).toContain('custom');
+ });
+ });
});
});
diff --git a/spec/javascripts/performance_bar/components/simple_metric_spec.js b/spec/javascripts/performance_bar/components/simple_metric_spec.js
deleted file mode 100644
index 98b843e9711..00000000000
--- a/spec/javascripts/performance_bar/components/simple_metric_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import simpleMetric from '~/performance_bar/components/simple_metric.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('simpleMetric', () => {
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('when the current request has no details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(simpleMetric), {
- currentRequest: {},
- metric: 'gitaly',
- });
- });
-
- it('does not display details', () => {
- expect(vm.$el.innerText).not.toContain('/');
- });
-
- it('displays the metric name', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
- });
-
- describe('when the current request has details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(simpleMetric), {
- currentRequest: {
- details: { gitaly: { duration: '123ms', calls: '456' } },
- },
- metric: 'gitaly',
- });
- });
-
- it('diplays details', () => {
- expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
- });
-
- it('displays the metric name', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
- });
-});
diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js
index 2fdfff3db03..d15758be5d2 100644
--- a/spec/javascripts/persistent_user_callout_spec.js
+++ b/spec/javascripts/persistent_user_callout_spec.js
@@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ function createDeferredLinkFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <div
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ data-defer-links="true"
+ >
+ <button type="button" class="js-close"></button>
+ <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a>
+ <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a>
+ </div>
+ `;
+
+ return fixture;
+ }
+
describe('dismiss', () => {
let button;
let mockAxios;
@@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('deferred links', () => {
+ let button;
+ let deferredLink;
+ let normalLink;
+ let mockAxios;
+ let persistentUserCallout;
+ let windowSpy;
+
+ beforeEach(() => {
+ const fixture = createDeferredLinkFixture();
+ const container = fixture.querySelector('.container');
+ button = fixture.querySelector('.js-close');
+ deferredLink = fixture.querySelector('.deferred-link');
+ normalLink = fixture.querySelector('.normal-link');
+ mockAxios = new MockAdapter(axios);
+ persistentUserCallout = new PersistentUserCallout(container);
+ spyOn(persistentUserCallout.container, 'remove');
+ windowSpy = spyOn(window, 'open').and.callFake(() => {});
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('defers loading of a link until callout is dismissed', done => {
+ const { href, target } = deferredLink;
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ deferredLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).toHaveBeenCalledWith(href, target);
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName }),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not dismiss callout on non-deferred links', done => {
+ normalLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not follow link when notification is closed', done => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ button.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
index 8eef9166b8d..03ead6cd8ba 100644
--- a/spec/javascripts/pipelines/mock_data.js
+++ b/spec/javascripts/pipelines/mock_data.js
@@ -1,6 +1,5 @@
export const pipelineWithStages = {
id: 20333396,
- iid: 304399,
user: {
id: 128633,
name: 'Rémy Coutable',
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index 88c0137dc58..aa196af2f33 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -13,7 +13,6 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
- iid: 1,
path: 'foo',
flags: {},
},
@@ -29,7 +28,6 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
- iid: 1,
path: 'foo',
flags: {},
},
@@ -49,7 +47,6 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
- iid: 1,
path: 'foo',
flags: {
latest: true,
@@ -81,7 +78,6 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
- iid: 1,
path: 'foo',
flags: {
latest: true,
@@ -104,7 +100,6 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
- iid: 1,
path: 'foo',
flags: {
failure_reason: true,
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index a7dcd532f4f..953a42b9d15 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
-import eventHub from '~/pipelines/event_hub';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
@@ -7,9 +8,15 @@ import { TEST_HOST } from 'spec/test_constants';
describe('Pipelines Actions dropdown', () => {
const Component = Vue.extend(PipelinesActions);
let vm;
+ let mock;
afterEach(() => {
vm.$destroy();
+ mock.restore();
+ });
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
});
describe('manual actions', () => {
@@ -40,6 +47,22 @@ describe('Pipelines Actions dropdown', () => {
expect(dropdownItem).toBeDisabled();
});
+
+ describe('on click', () => {
+ it('makes a request and toggles the loading state', done => {
+ mock.onPost(actions.path).reply(200);
+
+ vm.$el.querySelector('.dropdown-menu li button').click();
+
+ expect(vm.isLoading).toEqual(true);
+
+ setTimeout(() => {
+ expect(vm.isLoading).toEqual(false);
+
+ done();
+ });
+ });
+ });
});
describe('scheduled jobs', () => {
@@ -71,26 +94,27 @@ describe('Pipelines Actions dropdown', () => {
.catch(done.fail);
});
- it('emits postAction event after confirming', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('makes post request after confirming', done => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => true);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path);
+ setTimeout(() => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ });
});
- it('does not emit postAction event if confirmation is cancelled', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('does not make post request if confirmation is cancelled', () => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => false);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).not.toHaveBeenCalled();
+ expect(mock.history.post.length).toBe(0);
});
it('displays the remaining time in the dropdown', () => {
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 78187b69563..daa898ca687 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -736,10 +736,9 @@ describe('Pipelines', () => {
});
describe('when a request is being made', () => {
- it('stops polling, cancels the request, fetches pipelines & restarts polling', done => {
+ it('stops polling, cancels the request, & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
@@ -754,7 +753,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
@@ -765,10 +763,9 @@ describe('Pipelines', () => {
});
describe('when no request is being made', () => {
- it('stops polling, fetches pipelines & restarts polling', done => {
+ it('stops polling & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
@@ -776,7 +773,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 76a17e6fb31..e7675669f7a 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -8,6 +8,13 @@ import { reposServerResponse } from '../mock_data';
describe('Registry List', () => {
const Component = Vue.extend(registry);
+ const props = {
+ endpoint: `${TEST_HOST}/foo`,
+ helpPagePath: 'foo',
+ noContainersImage: 'foo',
+ containersErrorImage: 'foo',
+ repositoryUrl: 'foo',
+ };
let vm;
let mock;
@@ -24,7 +31,7 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render a list of repos', done => {
@@ -72,7 +79,7 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render empty message', done => {
@@ -83,7 +90,7 @@ describe('Registry List', () => {
.textContent.trim()
.replace(/[\r\n]+/g, ' '),
).toEqual(
- 'No container images stored for this project. Add one by following the instructions above.',
+ 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
);
done();
}, 0);
@@ -94,12 +101,30 @@ describe('Registry List', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
+ vm = mountComponent(Component, { ...props });
});
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
+ done();
+ });
+ });
+ });
+
+ describe('invalid characters in path', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
+
+ vm = mountComponent(Component, {
+ ...props,
+ characterError: true,
+ });
+ });
+
+ it('should render invalid characters error message', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.container-message')).not.toBe(null);
done();
});
});
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
index a3f7ff76dc7..2a5d8dd11da 100644
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -12,6 +12,8 @@ describe('collapsible registry container', () => {
let mock;
const Component = Vue.extend(collapsibleComponent);
+ const findDeleteBtn = () => vm.$el.querySelector('.js-remove-repo');
+
beforeEach(() => {
mock = new MockAdapter(axios);
@@ -67,7 +69,19 @@ describe('collapsible registry container', () => {
describe('delete repo', () => {
it('should be possible to delete a repo', () => {
- expect(vm.$el.querySelector('.js-remove-repo')).not.toBeNull();
+ expect(findDeleteBtn()).not.toBeNull();
+ });
+
+ it('should call deleteItem when confirming deletion', done => {
+ findDeleteBtn().click();
+ spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ document.querySelector(`#${vm.modalId} .btn-danger`).click();
+
+ expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index 7f5252a7d6c..9c7439206ef 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -1,44 +1,161 @@
import Vue from 'vue';
import tableRegistry from '~/registry/components/table_registry.vue';
import store from '~/registry/stores';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { repoPropsData } from '../mock_data';
+const [firstImage, secondImage] = repoPropsData.list;
+
describe('table registry', () => {
let vm;
- let Component;
+ const Component = Vue.extend(tableRegistry);
+ const bulkDeletePath = 'path';
- beforeEach(() => {
- Component = Vue.extend(tableRegistry);
- vm = new Component({
+ const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
+ const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
+ const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
+ const findAllRowCheckboxes = () =>
+ Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
+ const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
+
+ const createComponent = () => {
+ vm = mountComponentWithStore(Component, {
store,
- propsData: {
+ props: {
repo: repoPropsData,
},
- }).$mount();
+ });
+ };
+
+ const selectAllCheckboxes = () => vm.selectAll();
+ const deselectAllCheckboxes = () => vm.deselectAll();
+
+ beforeEach(() => {
+ createComponent();
});
afterEach(() => {
vm.$destroy();
});
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ describe('rendering', () => {
+ it('should render a table with the registry list', () => {
+ expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ });
+
+ it('should render registry tag', () => {
+ const textRendered = vm.$el
+ .querySelector('.table tbody tr')
+ .textContent.trim()
+ // replace additional whitespace characters (e.g. new lines) with a single empty space
+ .replace(/\s\s+/g, ' ');
+
+ expect(textRendered).toContain(repoPropsData.list[0].tag);
+ expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
+ expect(textRendered).toContain(repoPropsData.list[0].layers);
+ expect(textRendered).toContain(repoPropsData.list[0].size);
+ });
});
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- .replace(/\s\s+/g, ' ');
+ describe('multi select', () => {
+ it('should support multiselect and selecting a row should enable delete button', done => {
+ findSelectAllCheckbox().click();
+ selectAllCheckboxes();
+
+ expect(findSelectAllCheckbox().checked).toBe(true);
+
+ Vue.nextTick(() => {
+ expect(findDeleteBtn().disabled).toBe(false);
+ done();
+ });
+ });
+
+ it('selecting all checkbox should select all rows and enable delete button', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(repoPropsData.list.length);
+ done();
+ });
+ });
+
+ it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
+ selectAllCheckboxes();
+ deselectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should delete multiple items when multiple items are selected', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0, 1]);
+ expect(findDeleteBtn().disabled).toBe(false);
+
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ const modal = confirmationModal();
+ confirmationModal('.btn-danger').click();
+
+ expect(modal).toExist();
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag, secondImage.tag],
+ });
+ done();
+ });
+ });
+ });
+ });
});
- it('should be possible to delete a registry', () => {
- expect(vm.$el.querySelector('.table tbody tr .js-delete-registry')).toBeDefined();
+ describe('delete registry', () => {
+ beforeEach(() => {
+ vm.itemsToBeDeleted = [0];
+ });
+
+ it('should be possible to delete a registry', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn()).toBeDefined();
+ expect(findDeleteBtn().disabled).toBe(false);
+ expect(findDeleteBtnRow()).toBeDefined();
+ done();
+ });
+ });
+
+ it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn().disabled).toBe(false);
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ confirmationModal('.btn-danger').click();
+
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag],
+ });
+ done();
+ });
+ });
+ });
});
describe('pagination', () => {
@@ -46,4 +163,27 @@ describe('table registry', () => {
expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
});
});
+
+ describe('modal content', () => {
+ it('should show the singular title and image name when deleting a single image', done => {
+ findDeleteBtnRow().click();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove image');
+ expect(vm.modalDescription).toContain(firstImage.tag);
+ done();
+ });
+ });
+
+ it('should show the plural title and image count when deleting more than one image', done => {
+ selectAllCheckboxes();
+ vm.setModalDescription();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove images');
+ expect(vm.modalDescription).toContain('<b>2</b> images');
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
index 22db203e77f..130ab298e89 100644
--- a/spec/javascripts/registry/mock_data.js
+++ b/spec/javascripts/registry/mock_data.js
@@ -108,6 +108,17 @@ export const repoPropsData = {
destroyPath: 'path',
canDelete: true,
},
+ {
+ tag: 'test-image',
+ revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
+ shortRevision: 'b969de599',
+ size: 19,
+ layers: 10,
+ location: 'location-2',
+ createdAt: 1505828744434,
+ destroyPath: 'path-2',
+ canDelete: true,
+ },
],
location: 'location',
name: 'foo',
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
index c9aa82dba90..0613ec8e0f1 100644
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ b/spec/javascripts/registry/stores/actions_spec.js
@@ -105,4 +105,28 @@ describe('Actions Registry Store', () => {
);
});
});
+
+ describe('deleteItem', () => {
+ it('should perform DELETE request on destroyPath', done => {
+ const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`;
+ let deleted = false;
+ mock.onDelete(destroyPath).replyOnce(() => {
+ deleted = true;
+ return [200];
+ });
+ testAction(
+ actions.deleteItem,
+ {
+ destroyPath,
+ },
+ mockedState,
+ )
+ .then(() => {
+ expect(mock.history.delete.length).toBe(1);
+ expect(deleted).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
index 36b181f24ef..f761a18e326 100644
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -14,7 +14,7 @@ describe('Release block', () => {
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
author_name: 'Release bot',
author_email: 'release-bot@example.com',
- created_at: '2012-05-28T05:00:00-07:00',
+ released_at: '2012-05-28T05:00:00-07:00',
author: {
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
id: 482476,
@@ -78,8 +78,10 @@ describe('Release block', () => {
};
let vm;
+ const factory = props => mountComponent(Component, { release: props });
+
beforeEach(() => {
- vm = mountComponent(Component, { release });
+ vm = factory(release);
});
afterEach(() => {
@@ -99,7 +101,7 @@ describe('Release block', () => {
});
it('renders release date', () => {
- expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at));
+ expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
});
it('renders number of assets provided', () => {
@@ -149,4 +151,14 @@ describe('Release block', () => {
);
});
});
+
+ describe('with upcoming_release flag', () => {
+ beforeEach(() => {
+ vm = factory(Object.assign({}, release, { upcoming_release: true }));
+ });
+
+ it('renders upcoming release badge', () => {
+ expect(vm.$el.textContent).toContain('Upcoming Release');
+ });
+ });
});
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index a17494966a3..1f1e626ed33 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -34,7 +34,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained no changed test results out of 11 total tests',
);
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
index 4c3dd713589..2e1863cff86 100644
--- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
@@ -13,6 +13,7 @@ describe('Issuable Time Tracker', () => {
timeSpent,
timeEstimateHumanReadable,
timeSpentHumanReadable,
+ limitToHours,
}) => {
setFixtures(`
<div>
@@ -25,6 +26,7 @@ describe('Issuable Time Tracker', () => {
timeSpent,
humanTimeEstimate: timeEstimateHumanReadable,
humanTimeSpent: timeSpentHumanReadable,
+ limitToHours: Boolean(limitToHours),
rootPath: '/',
};
@@ -128,6 +130,29 @@ describe('Issuable Time Tracker', () => {
});
});
+ describe('Comparison pane when limitToHours is true', () => {
+ beforeEach(() => {
+ initTimeTrackingComponent({
+ timeEstimate: 100000, // 1d 3h
+ timeSpent: 5000, // 1h 23m
+ timeEstimateHumanReadable: '',
+ timeSpentHumanReadable: '',
+ limitToHours: true,
+ });
+ });
+
+ it('should show the correct tooltip text', done => {
+ Vue.nextTick(() => {
+ expect(vm.showComparisonState).toBe(true);
+ const $title = vm.$el.querySelector('.time-tracking-content .compare-meter').dataset
+ .originalTitle;
+
+ expect($title).toBe('Time remaining: 26h 23m');
+ done();
+ });
+ });
+ });
+
describe('Estimate only pane', () => {
beforeEach(() => {
initTimeTrackingComponent({
diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js
index f46ea5a0499..e7abd19c865 100644
--- a/spec/javascripts/sidebar/todo_spec.js
+++ b/spec/javascripts/sidebar/todo_spec.js
@@ -53,14 +53,14 @@ describe('SidebarTodo', () => {
describe('buttonLabel', () => {
it('returns todo button text for marking todo as done when `isTodo` prop is `true`', () => {
- expect(vm.buttonLabel).toBe('Mark todo as done');
+ expect(vm.buttonLabel).toBe('Mark as done');
});
it('returns todo button text for add todo when `isTodo` prop is `false`', done => {
vm.isTodo = false;
Vue.nextTick()
.then(() => {
- expect(vm.buttonLabel).toBe('Add todo');
+ expect(vm.buttonLabel).toBe('Add a To Do');
})
.then(done)
.catch(done.fail);
@@ -131,14 +131,14 @@ describe('SidebarTodo', () => {
});
it('check button label computed property', () => {
- expect(vm.buttonLabel).toEqual('Mark todo as done');
+ expect(vm.buttonLabel).toEqual('Mark as done');
});
it('renders button label element when `collapsed` prop is `false`', () => {
const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner');
expect(buttonLabelEl).not.toBeNull();
- expect(buttonLabelEl.innerText.trim()).toBe('Mark todo as done');
+ expect(buttonLabelEl.innerText.trim()).toBe('Mark as done');
});
it('renders button icon when `collapsed` prop is `true`', done => {
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 8c80a425581..ce453d7c483 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -3,19 +3,23 @@
*/
import $ from 'jquery';
+import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
-import CheckEE from '~/vue_shared/mixins/is_ee';
import jasmineDiff from 'jasmine-diff';
+import { config as testUtilsConfig } from '@vue/test-utils';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
import customMatchers from './matchers';
+// Tech debt issue TBD
+testUtilsConfig.logModifiedComponents = false;
+
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
Vue.config.productionTip = false;
@@ -44,7 +48,6 @@ Vue.config.errorHandler = function(err) {
Vue.use(VueResource);
Vue.use(Translate);
-Vue.use(CheckEE);
// enable test fixtures
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
index 77c206585fe..c97d47a6406 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -1,6 +1,4 @@
-export const FIXTURES_PATH = `/base/${
- process.env.IS_GITLAB_EE ? 'ee/' : ''
-}spec/javascripts/fixtures`;
+export const FIXTURES_PATH = `/fixtures`;
export const TEST_HOST = 'http://test.host';
export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index a2308b0dfdb..fe831094ecf 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
+ `#${mockData.pipeline.id}`,
);
});
@@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
+ `#${mockData.pipeline.id}`,
);
});
@@ -222,9 +222,7 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
- pipeline.details.status.label
- } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`;
+ const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
@@ -247,11 +245,7 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
- pipeline.details.status.label
- } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
- pipeline.merge_request.source_branch
- } into ${pipeline.merge_request.target_branch}`;
+ const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
@@ -274,11 +268,7 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
- pipeline.details.status.label
- } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
- pipeline.merge_request.source_branch
- }`;
+ const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index f622f52a7b9..5aac37d28df 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
});
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 d93badf8cd3..55a11a72551 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
@@ -38,7 +38,9 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
+ 'gl-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 96e512d222a..70c70eca746 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
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
it('renders information about merging', () => {
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 bb76616be56..53e1f077610 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
@@ -58,9 +58,11 @@ const createComponent = (customConfig = {}) => {
describe('ReadyToMerge', () => {
let vm;
+ let updateMrCountSpy;
beforeEach(() => {
vm = createComponent();
+ updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts');
});
afterEach(() => {
@@ -234,24 +236,26 @@ describe('ReadyToMerge', () => {
});
});
- describe('shouldShowMergeOptionsDropdown', () => {
- it('should return false when no auto merge strategies are available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
+ describe('shouldShowMergeImmediatelyDropdown', () => {
+ it('should return false if no pipeline is active', () => {
+ Vue.set(vm.mr, 'isPipelineActive', false);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return true when at least one auto merge strategy is available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
+ it('should return false if "Pipelines must succeed" is enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
- Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
+ it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true);
});
});
@@ -461,6 +465,7 @@ describe('ReadyToMerge', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent');
expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled();
+ expect(updateMrCountSpy).toHaveBeenCalled();
expect(cpc).toBeFalsy();
expect(spc).toBeTruthy();
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index d6d8eecfcb9..cb656525f06 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -21,7 +21,7 @@ describe('Squash before merge component', () => {
});
describe('checkbox', () => {
- const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+ const findCheckbox = () => wrapper.find('.js-squash-checkbox');
it('is unchecked if passed value prop is false', () => {
createComponent({
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index bd64d7b2926..5bd1af56bcc 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -10,7 +10,7 @@ describe('UnresolvedDiscussions', () => {
vm.$destroy();
});
- describe('with discussions path', () => {
+ describe('with threads path', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
@@ -21,7 +21,7 @@ describe('UnresolvedDiscussions', () => {
it('should have correct elements', () => {
expect(vm.$el.innerText).toContain(
- 'There are unresolved discussions. Please resolve these discussions',
+ 'There are unresolved threads. Please resolve these threads',
);
expect(vm.$el.innerText).toContain('Create an issue to resolve them later');
@@ -29,14 +29,14 @@ describe('UnresolvedDiscussions', () => {
});
});
- describe('without discussions path', () => {
+ describe('without threads path', () => {
beforeEach(() => {
vm = mountComponent(Component, { mr: {} });
});
it('should not show create issue link if user cannot create issue', () => {
expect(vm.$el.innerText).toContain(
- 'There are unresolved discussions. Please resolve these discussions',
+ 'There are unresolved threads. Please resolve these threads',
);
expect(vm.$el.querySelector('.js-create-issue')).toEqual(null);
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 3c9a5cece90..a55d5537df7 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -61,7 +61,6 @@ export default {
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
pipeline: {
id: 172,
- iid: 32,
user: {
name: 'Administrator',
username: 'root',
@@ -219,7 +218,8 @@ export default {
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff',
- status_path: '/root/acets-app/merge_requests/22.json',
+ merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic',
+ merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false,
@@ -233,6 +233,8 @@ export default {
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
+ merge_train_when_pipeline_succeeds_docs_path:
+ '/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
@@ -243,8 +245,6 @@ export default {
export const mockStore = {
pipeline: {
id: 0,
- iid: 0,
- path: '/root/acets-app/pipelines/0',
details: {
status: {
details_path: '/root/review-app-tester/pipelines/66',
@@ -262,8 +262,6 @@ export const mockStore = {
},
mergePipeline: {
id: 1,
- iid: 1,
- path: '/root/acets-app/pipelines/0',
details: {
status: {
details_path: '/root/review-app-tester/pipelines/66',
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 08f7a17515e..30e0504e4e1 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -473,7 +473,7 @@ describe('mrWidgetOptions', () => {
vm.mr.relatedLinks = {
assignToMe: null,
closing: `
- <a class="close-related-link" href="#'>
+ <a class="close-related-link" href="#">
Close
</a>
`,
@@ -544,7 +544,6 @@ describe('mrWidgetOptions', () => {
];
const deploymentMockData = {
id: 15,
- iid: 7,
name: 'review/diplo',
url: '/root/acets-review-apps/environments/15',
stop_url: '/root/acets-review-apps/environments/15/stop',
@@ -591,7 +590,6 @@ describe('mrWidgetOptions', () => {
vm.mr.state = 'merged';
vm.mr.mergePipeline = {
id: 127,
- iid: 35,
user: {
id: 1,
name: 'Administrator',
diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
deleted file mode 100644
index 634ba8403d5..00000000000
--- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import changedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
-import createComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Changed file icon', () => {
- let vm;
-
- function factory(props = {}) {
- const component = Vue.extend(changedFileIcon);
-
- vm = createComponent(component, {
- ...props,
- file: {
- tempFile: false,
- changed: true,
- },
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('centers icon', () => {
- factory({
- isCentered: true,
- });
-
- expect(vm.$el.classList).toContain('ml-auto');
- });
-
- describe('changedIcon', () => {
- it('equals file-modified when not a temp file and has changes', () => {
- factory();
-
- expect(vm.changedIcon).toBe('file-modified');
- });
-
- it('equals file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIcon).toBe('file-addition');
- });
- });
-
- describe('changedIconClass', () => {
- it('includes file-modified when not a temp file', () => {
- factory();
-
- expect(vm.changedIconClass).toContain('file-modified');
- });
-
- it('includes file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIconClass).toContain('file-addition');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 5bea8c43da3..1f61e19fa84 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -72,7 +72,7 @@ describe('File Icon component', () => {
const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('spinner')).toEqual(true);
+ expect(classList.contains('gl-spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 7da69e3fa84..6abcac5c0ff 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -90,6 +90,19 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
+ it('is not rendered for `moved` entries in subfolders', () => {
+ createComponent({
+ file: {
+ path: 't5',
+ moved: true,
+ tree: [],
+ },
+ level: 2,
+ });
+
+ expect(vm.$el.nodeType).not.toEqual(1);
+ });
+
describe('new dropdown', () => {
beforeEach(() => {
createComponent({
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 a9c1a67b39b..2b059e5e9f4 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
deleted file mode 100644
index d4be2451f0b..00000000000
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import Vue from 'vue';
-import $ from 'jquery';
-import headerComponent from '~/vue_shared/components/markdown/header.vue';
-
-describe('Markdown field header component', () => {
- let vm;
-
- beforeEach(done => {
- const Component = Vue.extend(headerComponent);
-
- vm = new Component({
- propsData: {
- previewMarkdown: false,
- },
- }).$mount();
-
- Vue.nextTick(done);
- });
-
- it('renders markdown header buttons', () => {
- const buttons = [
- 'Add bold text',
- 'Add italic text',
- 'Insert a quote',
- 'Insert code',
- 'Add a link',
- 'Add a bullet list',
- 'Add a numbered list',
- 'Add a task list',
- 'Add a table',
- 'Insert suggestion',
- 'Go full screen',
- ];
- const elements = vm.$el.querySelectorAll('.toolbar-btn');
-
- elements.forEach((buttonEl, index) => {
- expect(buttonEl.getAttribute('data-original-title')).toBe(buttons[index]);
- });
- });
-
- it('renders `write` link as active when previewMarkdown is false', () => {
- expect(vm.$el.querySelector('li:nth-child(1)').classList.contains('active')).toBeTruthy();
- });
-
- it('renders `preview` link as active when previewMarkdown is true', done => {
- vm.previewMarkdown = true;
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('li:nth-child(2)').classList.contains('active')).toBeTruthy();
-
- done();
- });
- });
-
- it('emits toggle markdown event when clicking preview', () => {
- spyOn(vm, '$emit');
-
- vm.$el.querySelector('.js-preview-link').click();
-
- expect(vm.$emit).toHaveBeenCalledWith('preview-markdown');
-
- vm.$el.querySelector('.js-write-link').click();
-
- expect(vm.$emit).toHaveBeenCalledWith('write-markdown');
- });
-
- it('does not emit toggle markdown event when triggered from another form', () => {
- spyOn(vm, '$emit');
-
- $(document).triggerHandler('markdown-preview:show', [
- $(
- '<form><div class="js-vue-markdown-field"><textarea class="markdown-area"></textarea></div></form>',
- ),
- ]);
-
- expect(vm.$emit).not.toHaveBeenCalled();
- });
-
- it('blurs preview link after click', done => {
- const link = vm.$el.querySelector('li:nth-child(2) button');
- spyOn(HTMLElement.prototype, 'blur');
-
- link.click();
-
- setTimeout(() => {
- expect(link.blur).toHaveBeenCalled();
-
- done();
- });
- });
-
- it('renders markdown table template', () => {
- expect(vm.mdTable).toEqual(
- '| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |',
- );
- });
-
- it('renders suggestion template', () => {
- vm.lineContent = 'Some content';
-
- expect(vm.mdSuggestion).toEqual('```suggestion:-0+0\n{text}\n```');
- });
-
- it('does not render suggestion button if `canSuggest` is set to false', () => {
- vm.canSuggest = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null);
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index ea74cb9eb21..dc929e83eb7 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
describe('init', () => {
it('renders a suggestion header', () => {
- expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull();
});
it('renders a diff table with syntax highlighting', () => {
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 42abb4d83f0..258530f32f7 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -217,7 +217,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›');
component.$el.querySelector('.js-next-button .page-link').click();
@@ -237,7 +237,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›');
component.$el.querySelector('.js-next-button .page-link').click();
diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
index 997d84dcc42..ad8d5a53291 100644
--- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
+++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js
@@ -1,68 +1,72 @@
-import { mountComponentWithRender } from 'spec/helpers/vue_mount_component_helper';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
const TEST_TITLE = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
-const CLASS_SHOW_TOOLTIP = 'js-show-tooltip';
-const STYLE_TRUNCATED = {
- display: 'inline-block',
- 'max-width': '20px',
-};
-const STYLE_NORMAL = {
- display: 'inline-block',
- 'max-width': '1000px',
-};
-
-function mountTooltipOnTruncate(options, createChildren) {
- return mountComponentWithRender(h => h(TooltipOnTruncate, options, createChildren(h)), '#app');
-}
+const STYLE_TRUNCATED = 'display: inline-block; max-width: 20px;';
+const STYLE_NORMAL = 'display: inline-block; max-width: 1000px;';
-describe('TooltipOnTruncate component', () => {
- let vm;
+const localVue = createLocalVue();
- beforeEach(() => {
- const el = document.createElement('div');
- el.id = 'app';
- document.body.appendChild(el);
- });
+const createElementWithStyle = (style, content) => `<a href="#" style="${style}">${content}</a>`;
+
+describe('TooltipOnTruncate component', () => {
+ let wrapper;
+
+ const createComponent = ({ propsData, ...options } = {}) => {
+ wrapper = shallowMount(localVue.extend(TooltipOnTruncate), {
+ localVue,
+ sync: false,
+ attachToDocument: true,
+ propsData: {
+ title: TEST_TITLE,
+ ...propsData,
+ },
+ ...options,
+ });
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
+ const hasTooltip = () => wrapper.classes('js-show-tooltip');
+
describe('with default target', () => {
it('renders tooltip if truncated', done => {
- const options = {
- style: STYLE_TRUNCATED,
- props: {
- title: TEST_TITLE,
+ createComponent({
+ attrs: {
+ style: STYLE_TRUNCATED,
},
- };
-
- vm = mountTooltipOnTruncate(options, () => [TEST_TITLE]);
+ slots: {
+ default: [TEST_TITLE],
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP);
- expect(vm.$el).toHaveData('original-title', TEST_TITLE);
- expect(vm.$el).toHaveData('placement', 'top');
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-original-title')).toEqual(TEST_TITLE);
+ expect(wrapper.attributes('data-placement')).toEqual('top');
})
.then(done)
.catch(done.fail);
});
it('does not render tooltip if normal', done => {
- const options = {
- style: STYLE_NORMAL,
- props: {
- title: TEST_TITLE,
+ createComponent({
+ attrs: {
+ style: STYLE_NORMAL,
},
- };
-
- vm = mountTooltipOnTruncate(options, () => [TEST_TITLE]);
+ slots: {
+ default: [TEST_TITLE],
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).not.toHaveClass(CLASS_SHOW_TOOLTIP);
+ expect(hasTooltip()).toBe(false);
})
.then(done)
.catch(done.fail);
@@ -71,37 +75,41 @@ describe('TooltipOnTruncate component', () => {
describe('with child target', () => {
it('renders tooltip if truncated', done => {
- const options = {
- style: STYLE_NORMAL,
- props: {
- title: TEST_TITLE,
+ createComponent({
+ attrs: {
+ style: STYLE_NORMAL,
+ },
+ propsData: {
truncateTarget: 'child',
},
- };
-
- vm = mountTooltipOnTruncate(options, h => [h('a', { style: STYLE_TRUNCATED }, TEST_TITLE)]);
+ slots: {
+ default: createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE),
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP);
+ expect(hasTooltip()).toBe(true);
})
.then(done)
.catch(done.fail);
});
it('does not render tooltip if normal', done => {
- const options = {
- props: {
- title: TEST_TITLE,
+ createComponent({
+ propsData: {
truncateTarget: 'child',
},
- };
-
- vm = mountTooltipOnTruncate(options, h => [h('a', { style: STYLE_NORMAL }, TEST_TITLE)]);
+ slots: {
+ default: createElementWithStyle(STYLE_NORMAL, TEST_TITLE),
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).not.toHaveClass(CLASS_SHOW_TOOLTIP);
+ expect(hasTooltip()).toBe(false);
})
.then(done)
.catch(done.fail);
@@ -110,22 +118,25 @@ describe('TooltipOnTruncate component', () => {
describe('with fn target', () => {
it('renders tooltip if truncated', done => {
- const options = {
- style: STYLE_NORMAL,
- props: {
- title: TEST_TITLE,
+ createComponent({
+ attrs: {
+ style: STYLE_NORMAL,
+ },
+ propsData: {
truncateTarget: el => el.childNodes[1],
},
- };
-
- vm = mountTooltipOnTruncate(options, h => [
- h('a', { style: STYLE_NORMAL }, TEST_TITLE),
- h('span', { style: STYLE_TRUNCATED }, TEST_TITLE),
- ]);
+ slots: {
+ default: [
+ createElementWithStyle('', TEST_TITLE),
+ createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE),
+ ],
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP);
+ expect(hasTooltip()).toBe(true);
})
.then(done)
.catch(done.fail);
@@ -134,20 +145,25 @@ describe('TooltipOnTruncate component', () => {
describe('placement', () => {
it('sets data-placement when tooltip is rendered', done => {
- const options = {
- props: {
- title: TEST_TITLE,
- truncateTarget: 'child',
- placement: 'bottom',
- },
- };
+ const placement = 'bottom';
- vm = mountTooltipOnTruncate(options, h => [h('a', { style: STYLE_TRUNCATED }, TEST_TITLE)]);
+ createComponent({
+ propsData: {
+ placement,
+ },
+ attrs: {
+ style: STYLE_TRUNCATED,
+ },
+ slots: {
+ default: TEST_TITLE,
+ },
+ });
- vm.$nextTick()
+ wrapper.vm
+ .$nextTick()
.then(() => {
- expect(vm.$el).toHaveClass(CLASS_SHOW_TOOLTIP);
- expect(vm.$el).toHaveData('placement', options.props.placement);
+ expect(hasTooltip()).toBe(true);
+ expect(wrapper.attributes('data-placement')).toEqual(placement);
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
new file mode 100644
index 00000000000..f1ca5f61496
--- /dev/null
+++ b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
@@ -0,0 +1,38 @@
+import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+
+/**
+ * We're testing this directive's hooks as pure functions
+ * since behaviour of this directive is highly-dependent
+ * on underlying DOM methods.
+ */
+describe('AutofocusOnShow directive', () => {
+ describe('with input invisible on component render', () => {
+ let el;
+
+ beforeAll(() => {
+ setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>');
+ el = document.querySelector('#inputel');
+ });
+
+ it('should bind IntersectionObserver on input element', () => {
+ spyOn(el, 'focus');
+
+ autofocusonshow.inserted(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.focus).not.toHaveBeenCalled();
+ });
+
+ it('should stop IntersectionObserver on input element on unbind hook', () => {
+ el.visibilityObserver = {
+ disconnect: () => {},
+ };
+ spyOn(el.visibilityObserver, 'disconnect');
+
+ autofocusonshow.unbind(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.visibilityObserver.disconnect).toHaveBeenCalled();
+ });
+ });
+});