summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-02-28 20:36:55 +0100
committerKamil Trzciński <ayufan@ayufan.eu>2018-02-28 20:36:55 +0100
commit965dc28691e2d70b7040e28d90ccbc3721a9e416 (patch)
tree84258f35b72f2e7ce6a7198db66032df4ad5aadb /spec/javascripts
parente3fafa7632e038927085cf8c8228c93be44b36bd (diff)
parent7fabc892f251740dbd9a4755baede662e6854870 (diff)
downloadgitlab-ce-965dc28691e2d70b7040e28d90ccbc3721a9e416.tar.gz
Merge commit '7fabc892f251740dbd9a4755baede662e6854870' into object-storage-ee-to-ce-backport
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js31
-rw-r--r--spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js19
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js76
-rw-r--r--spec/javascripts/boards/board_card_spec.js29
-rw-r--r--spec/javascripts/boards/issue_spec.js19
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js18
-rw-r--r--spec/javascripts/clusters/clusters_index_spec.js58
-rw-r--r--spec/javascripts/datetime_utility_spec.js22
-rw-r--r--spec/javascripts/deploy_keys/components/action_btn_spec.js2
-rw-r--r--spec/javascripts/deploy_keys/components/app_spec.js14
-rw-r--r--spec/javascripts/droplab/drop_down_spec.js8
-rw-r--r--spec/javascripts/droplab/hook_spec.js2
-rw-r--r--spec/javascripts/emoji_spec.js19
-rw-r--r--spec/javascripts/environments/emtpy_state_spec.js57
-rw-r--r--spec/javascripts/environments/environment_table_spec.js31
-rw-r--r--spec/javascripts/environments/environments_app_spec.js (renamed from spec/javascripts/environments/environment_spec.js)132
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js157
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js19
-rw-r--r--spec/javascripts/fixtures/clusters.rb15
-rw-r--r--spec/javascripts/fixtures/environments/element.html.haml1
-rw-r--r--spec/javascripts/fixtures/environments/environments.html.haml9
-rw-r--r--spec/javascripts/fixtures/environments/environments_folder_view.html.haml7
-rw-r--r--spec/javascripts/fixtures/pipelines.html.haml8
-rw-r--r--spec/javascripts/flash_spec.js2
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js22
-rw-r--r--spec/javascripts/image_diff/helpers/utils_helper_spec.js10
-rw-r--r--spec/javascripts/issuable_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js56
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js99
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js9
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js1
-rw-r--r--spec/javascripts/issue_show/components/title_spec.js8
-rw-r--r--spec/javascripts/job_spec.js2
-rw-r--r--spec/javascripts/jobs/job_details_mediator_spec.js20
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js47
-rw-r--r--spec/javascripts/lib/utils/datefix_spec.js2
-rw-r--r--spec/javascripts/lib/utils/number_utility_spec.js27
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js6
-rw-r--r--spec/javascripts/lib/utils/text_markdown_spec.js62
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js120
-rw-r--r--spec/javascripts/merge_request_spec.js4
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js12
-rw-r--r--spec/javascripts/monitoring/graph_path_spec.js17
-rw-r--r--spec/javascripts/monitoring/mock_data.js15
-rw-r--r--spec/javascripts/new_branch_spec.js3
-rw-r--r--spec/javascripts/notes/components/issue_comment_form_spec.js27
-rw-r--r--spec/javascripts/notes/components/issue_discussion_spec.js4
-rw-r--r--spec/javascripts/notes/components/issue_note_app_spec.js6
-rw-r--r--spec/javascripts/notes/components/issue_note_body_spec.js4
-rw-r--r--spec/javascripts/notes/components/issue_note_form_spec.js4
-rw-r--r--spec/javascripts/notes/components/issue_note_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js (renamed from spec/javascripts/notes/components/issue_note_actions_spec.js)4
-rw-r--r--spec/javascripts/notes/components/note_attachment_spec.js (renamed from spec/javascripts/notes/components/issue_note_attachment_spec.js)4
-rw-r--r--spec/javascripts/notes/components/note_awards_list_spec.js (renamed from spec/javascripts/notes/components/issue_note_awards_list_spec.js)8
-rw-r--r--spec/javascripts/notes/components/note_edited_text_spec.js (renamed from spec/javascripts/notes/components/issue_note_edited_text_spec.js)6
-rw-r--r--spec/javascripts/notes/components/note_header_spec.js (renamed from spec/javascripts/notes/components/issue_note_header_spec.js)6
-rw-r--r--spec/javascripts/notes/components/note_signed_out_widget_spec.js (renamed from spec/javascripts/notes/components/issue_note_signed_out_widget_spec.js)6
-rw-r--r--spec/javascripts/notes/mock_data.js4
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js8
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js10
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js10
-rw-r--r--spec/javascripts/notes_spec.js1
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js65
-rw-r--r--spec/javascripts/pipelines/navigation_tabs_spec.js127
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js137
-rw-r--r--spec/javascripts/projects_dropdown/components/projects_list_item_spec.js12
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js33
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_item_spec.js53
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_spec.js72
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js22
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js10
-rw-r--r--spec/javascripts/repo/components/repo_sidebar_spec.js13
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js12
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js5
-rw-r--r--spec/javascripts/repo/helpers.js5
-rw-r--r--spec/javascripts/repo/lib/common/disposable_spec.js44
-rw-r--r--spec/javascripts/repo/lib/common/model_manager_spec.js81
-rw-r--r--spec/javascripts/repo/lib/common/model_spec.js84
-rw-r--r--spec/javascripts/repo/lib/decorations/controller_spec.js120
-rw-r--r--spec/javascripts/repo/lib/diff/controller_spec.js176
-rw-r--r--spec/javascripts/repo/lib/diff/diff_spec.js80
-rw-r--r--spec/javascripts/repo/lib/editor_options_spec.js7
-rw-r--r--spec/javascripts/repo/lib/editor_spec.js128
-rw-r--r--spec/javascripts/repo/stores/actions/branch_spec.js38
-rw-r--r--spec/javascripts/repo/stores/actions/file_spec.js417
-rw-r--r--spec/javascripts/repo/stores/actions/tree_spec.js469
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js419
-rw-r--r--spec/javascripts/repo/stores/getters_spec.js146
-rw-r--r--spec/javascripts/repo/stores/mutations/branch_spec.js18
-rw-r--r--spec/javascripts/repo/stores/mutations/file_spec.js131
-rw-r--r--spec/javascripts/repo/stores/mutations/tree_spec.js71
-rw-r--r--spec/javascripts/repo/stores/mutations_spec.js117
-rw-r--r--spec/javascripts/repo/stores/utils_spec.js102
-rw-r--r--spec/javascripts/search_autocomplete_spec.js2
-rw-r--r--spec/javascripts/sidebar/mock_data.js82
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js27
-rw-r--r--spec/javascripts/sidebar/sidebar_service_spec.js66
-rw-r--r--spec/javascripts/sidebar/sidebar_store_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js171
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js1
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js19
-rw-r--r--spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js47
-rw-r--r--spec/javascripts/vue_shared/components/icon_spec.js12
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_warning_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/loading_button_spec.js18
-rw-r--r--spec/javascripts/vue_shared/components/markdown/toolbar_spec.js37
-rw-r--r--spec/javascripts/vue_shared/components/navigation_tabs_spec.js61
-rw-r--r--spec/javascripts/vue_shared/components/pikaday_spec.js29
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js35
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js91
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js117
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js32
-rw-r--r--spec/javascripts/vue_shared/components/toggle_button_spec.js91
-rw-r--r--spec/javascripts/zen_mode_spec.js146
114 files changed, 4706 insertions, 1019 deletions
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index 67afba19190..960b731892a 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -1,21 +1,18 @@
-/* eslint-disable space-before-function-paren, no-var, comma-dangle, no-return-assign, max-len */
-
import '~/behaviors/autosize';
-(function() {
- describe('Autosize behavior', function() {
- var load;
- beforeEach(function() {
- return setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
- });
- it('does not overwrite the resize property', function() {
- load();
- return expect($('textarea')).toHaveCss({
- resize: 'vertical'
- });
+function load() {
+ $(document).trigger('load');
+}
+
+describe('Autosize behavior', () => {
+ beforeEach(() => {
+ setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
+ });
+
+ it('does not overwrite the resize property', () => {
+ load();
+ expect($('textarea')).toHaveCss({
+ resize: 'vertical',
});
- return load = function() {
- return $(document).trigger('load');
- };
});
-}).call(window);
+});
diff --git a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
index ec2c549e032..f96f20ed4a5 100644
--- a/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
+++ b/spec/javascripts/behaviors/gl_emoji/unicode_support_map_spec.js
@@ -21,13 +21,18 @@ describe('Unicode Support Map', () => {
});
it('should call .getItem and .setItem', () => {
- const allArgs = window.localStorage.setItem.calls.allArgs();
-
- expect(window.localStorage.getItem).toHaveBeenCalledWith('gl-emoji-user-agent');
- expect(allArgs[0][0]).toBe('gl-emoji-user-agent');
- expect(allArgs[0][1]).toBe(navigator.userAgent);
- expect(allArgs[1][0]).toBe('gl-emoji-unicode-support-map');
- expect(allArgs[1][1]).toBe(stringSupportMap);
+ const getArgs = window.localStorage.getItem.calls.allArgs();
+ const setArgs = window.localStorage.setItem.calls.allArgs();
+
+ expect(getArgs[0][0]).toBe('gl-emoji-version');
+ expect(getArgs[1][0]).toBe('gl-emoji-user-agent');
+
+ expect(setArgs[0][0]).toBe('gl-emoji-version');
+ expect(setArgs[0][1]).toBe('0.2.0');
+ expect(setArgs[1][0]).toBe('gl-emoji-user-agent');
+ expect(setArgs[1][1]).toBe(navigator.userAgent);
+ expect(setArgs[2][0]).toBe('gl-emoji-unicode-support-map');
+ expect(setArgs[2][1]).toBe(stringSupportMap);
});
});
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index f9fa814b801..8287c58ac5a 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -1,39 +1,43 @@
-/* eslint-disable space-before-function-paren, no-var */
-
import '~/behaviors/requires_input';
-(function() {
- describe('requiresInput', function() {
- preloadFixtures('branches/new_branch.html.raw');
- beforeEach(function() {
- loadFixtures('branches/new_branch.html.raw');
- this.submitButton = $('button[type="submit"]');
- });
- it('disables submit when any field is required', function() {
- $('.js-requires-input').requiresInput();
- return expect(this.submitButton).toBeDisabled();
- });
- it('enables submit when no field is required', function() {
- $('*[required=required]').removeAttr('required');
- $('.js-requires-input').requiresInput();
- return expect(this.submitButton).not.toBeDisabled();
- });
- it('enables submit when all required fields are pre-filled', function() {
- $('*[required=required]').remove();
- $('.js-requires-input').requiresInput();
- return expect($('.submit')).not.toBeDisabled();
- });
- it('enables submit when all required fields receive input', function() {
- $('.js-requires-input').requiresInput();
- $('#required1').val('input1').change();
- expect(this.submitButton).toBeDisabled();
- $('#optional1').val('input1').change();
- expect(this.submitButton).toBeDisabled();
- $('#required2').val('input2').change();
- $('#required3').val('input3').change();
- $('#required4').val('input4').change();
- $('#required5').val('1').change();
- return expect($('.submit')).not.toBeDisabled();
- });
+describe('requiresInput', () => {
+ let submitButton;
+ preloadFixtures('branches/new_branch.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('branches/new_branch.html.raw');
+ submitButton = $('button[type="submit"]');
+ });
+
+ it('disables submit when any field is required', () => {
+ $('.js-requires-input').requiresInput();
+ expect(submitButton).toBeDisabled();
+ });
+
+ it('enables submit when no field is required', () => {
+ $('*[required=required]').removeAttr('required');
+ $('.js-requires-input').requiresInput();
+ expect(submitButton).not.toBeDisabled();
+ });
+
+ it('enables submit when all required fields are pre-filled', () => {
+ $('*[required=required]').remove();
+ $('.js-requires-input').requiresInput();
+ expect($('.submit')).not.toBeDisabled();
+ });
+
+ it('enables submit when all required fields receive input', () => {
+ $('.js-requires-input').requiresInput();
+ $('#required1').val('input1').change();
+ expect(submitButton).toBeDisabled();
+
+ $('#optional1').val('input1').change();
+ expect(submitButton).toBeDisabled();
+
+ $('#required2').val('input2').change();
+ $('#required3').val('input3').change();
+ $('#required4').val('input4').change();
+ $('#required5').val('1').change();
+ expect($('.submit')).not.toBeDisabled();
});
-}).call(window);
+});
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 83b13b06dc1..8f607899b20 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -9,10 +9,11 @@
import Vue from 'vue';
import '~/boards/models/assignee';
+import eventHub from '~/boards/eventhub';
import '~/boards/models/list';
import '~/boards/models/label';
import '~/boards/stores/boards_store';
-import boardCard from '~/boards/components/board_card';
+import boardCard from '~/boards/components/board_card.vue';
import './mock_data';
describe('Board card', () => {
@@ -157,33 +158,35 @@ describe('Board card', () => {
});
it('sets detail issue to card issue on mouse up', () => {
+ spyOn(eventHub, '$emit');
+
triggerEvent('mousedown');
triggerEvent('mouseup');
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue);
+ expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue);
expect(gl.issueBoards.BoardsStore.detail.list).toEqual(vm.list);
});
it('adds active class if detail issue is set', (done) => {
- triggerEvent('mousedown');
- triggerEvent('mouseup');
-
- setTimeout(() => {
- expect(vm.$el.classList.contains('is-active')).toBe(true);
- done();
- }, 0);
+ vm.detailIssue.issue = vm.issue;
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vm.$el.classList.contains('is-active')).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('resets detail issue to empty if already set', () => {
- triggerEvent('mousedown');
- triggerEvent('mouseup');
+ spyOn(eventHub, '$emit');
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual(vm.issue);
+ gl.issueBoards.BoardsStore.detail.issue = vm.issue;
triggerEvent('mousedown');
triggerEvent('mouseup');
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue');
});
});
});
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 022d286d5df..10b88878c2a 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -133,6 +133,25 @@ describe('Issue model', () => {
expect(relativePositionIssue.position).toBe(1);
});
+ it('updates data', () => {
+ issue.updateData({ subscribed: true });
+ expect(issue.subscribed).toBe(true);
+ });
+
+ it('sets fetching state', () => {
+ expect(issue.isFetching.subscriptions).toBe(true);
+
+ issue.setFetchingState('subscriptions', false);
+
+ expect(issue.isFetching.subscriptions).toBe(false);
+ });
+
+ it('sets loading state', () => {
+ issue.setLoadingState('foo', true);
+
+ expect(issue.isLoading.foo).toBe(true);
+ });
+
describe('update', () => {
it('passes assignee ids when there are assignees', (done) => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index 027e8001053..f5be9ea0fb2 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -28,7 +28,7 @@ describe('Clusters', () => {
expect(
cluster.toggleButton.classList,
- ).not.toContain('checked');
+ ).not.toContain('is-checked');
expect(
cluster.toggleInput.getAttribute('value'),
@@ -36,6 +36,20 @@ describe('Clusters', () => {
});
});
+ describe('showToken', () => {
+ it('should update tye field type', () => {
+ cluster.showTokenButton.click();
+ expect(
+ cluster.tokenField.getAttribute('type'),
+ ).toEqual('text');
+
+ cluster.showTokenButton.click();
+ expect(
+ cluster.tokenField.getAttribute('type'),
+ ).toEqual('password');
+ });
+ });
+
describe('checkForNewInstalls', () => {
const INITIAL_APP_MAP = {
helm: { status: null, title: 'Helm Tiller' },
@@ -113,7 +127,7 @@ describe('Clusters', () => {
});
describe('when cluster is created', () => {
- it('should show the success container', () => {
+ it('should show the success container and fresh the page', () => {
cluster.updateContainer(null, 'created');
expect(
diff --git a/spec/javascripts/clusters/clusters_index_spec.js b/spec/javascripts/clusters/clusters_index_spec.js
new file mode 100644
index 00000000000..0a8b63ed5b4
--- /dev/null
+++ b/spec/javascripts/clusters/clusters_index_spec.js
@@ -0,0 +1,58 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import setClusterTableToggles from '~/clusters/clusters_index';
+import { setTimeout } from 'core-js/library/web/timers';
+
+describe('Clusters table', () => {
+ preloadFixtures('clusters/index_cluster.html.raw');
+ let mock;
+
+ beforeEach(() => {
+ loadFixtures('clusters/index_cluster.html.raw');
+ mock = new MockAdapter(axios);
+ setClusterTableToggles();
+ });
+
+ describe('update cluster', () => {
+ it('renders loading state while request is made', () => {
+ const button = document.querySelector('.js-toggle-cluster-list');
+
+ button.click();
+
+ expect(button.classList).toContain('is-loading');
+ expect(button.getAttribute('disabled')).toEqual('true');
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('shows updated state after sucessfull request', (done) => {
+ mock.onPut().reply(200, {}, {});
+ const button = document.querySelector('.js-toggle-cluster-list');
+ button.click();
+
+ expect(button.classList).toContain('is-loading');
+
+ setTimeout(() => {
+ expect(button.classList).not.toContain('is-loading');
+ expect(button.classList).not.toContain('is-checked');
+ done();
+ }, 0);
+ });
+
+ it('shows inital state after failed request', (done) => {
+ mock.onPut().reply(500, {}, {});
+ const button = document.querySelector('.js-toggle-cluster-list');
+
+ button.click();
+ expect(button.classList).toContain('is-loading');
+
+ setTimeout(() => {
+ expect(button.classList).not.toContain('is-loading');
+ expect(button.classList).toContain('is-checked');
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index 3391cade541..0f7bf9ec712 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -1,4 +1,4 @@
-import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+import * as datetimeUtility from '~/lib/utils/datetime_utility';
(() => {
describe('Date time utils', () => {
@@ -89,10 +89,22 @@ import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
describe('timeIntervalInWords', () => {
it('should return string with number of minutes and seconds', () => {
- expect(timeIntervalInWords(9.54)).toEqual('9 seconds');
- expect(timeIntervalInWords(1)).toEqual('1 second');
- expect(timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
- expect(timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
+ expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
+ expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
+ expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
+ expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
+ });
+ });
+
+ describe('dateInWords', () => {
+ const date = new Date('07/01/2016');
+
+ it('should return date in words', () => {
+ expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016');
+ });
+
+ it('should return abbreviated month name', () => {
+ expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
});
});
})();
diff --git a/spec/javascripts/deploy_keys/components/action_btn_spec.js b/spec/javascripts/deploy_keys/components/action_btn_spec.js
index 5b93fbc5575..7025c3d836c 100644
--- a/spec/javascripts/deploy_keys/components/action_btn_spec.js
+++ b/spec/javascripts/deploy_keys/components/action_btn_spec.js
@@ -34,7 +34,7 @@ describe('Deploy keys action btn', () => {
setTimeout(() => {
expect(
eventHub.$emit,
- ).toHaveBeenCalledWith('enable.key', deployKey);
+ ).toHaveBeenCalledWith('enable.key', deployKey, jasmine.anything());
done();
});
diff --git a/spec/javascripts/deploy_keys/components/app_spec.js b/spec/javascripts/deploy_keys/components/app_spec.js
index 700897f50b0..0ca9290d3d2 100644
--- a/spec/javascripts/deploy_keys/components/app_spec.js
+++ b/spec/javascripts/deploy_keys/components/app_spec.js
@@ -139,4 +139,18 @@ describe('Deploy keys app component', () => {
it('hasKeys returns true when there are keys', () => {
expect(vm.hasKeys).toEqual(3);
});
+
+ it('resets remove button loading state', (done) => {
+ spyOn(window, 'confirm').and.returnValue(false);
+
+ const btn = vm.$el.querySelector('.btn-warning');
+
+ btn.click();
+
+ Vue.nextTick(() => {
+ expect(btn.querySelector('.fa')).toBeNull();
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/droplab/drop_down_spec.js b/spec/javascripts/droplab/drop_down_spec.js
index 1ef494a00b8..1225fe2cb66 100644
--- a/spec/javascripts/droplab/drop_down_spec.js
+++ b/spec/javascripts/droplab/drop_down_spec.js
@@ -279,7 +279,12 @@ describe('DropDown', function () {
describe('addEvents', function () {
beforeEach(function () {
this.list = { addEventListener: () => {} };
- this.dropdown = { list: this.list, clickEvent: () => {}, eventWrapper: {} };
+ this.dropdown = {
+ list: this.list,
+ clickEvent: () => {},
+ closeDropdown: () => {},
+ eventWrapper: {},
+ };
spyOn(this.list, 'addEventListener');
@@ -288,6 +293,7 @@ describe('DropDown', function () {
it('should call .addEventListener', function () {
expect(this.list.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function));
+ expect(this.list.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function));
});
});
diff --git a/spec/javascripts/droplab/hook_spec.js b/spec/javascripts/droplab/hook_spec.js
index 75bf5f3d611..3d39bd0812b 100644
--- a/spec/javascripts/droplab/hook_spec.js
+++ b/spec/javascripts/droplab/hook_spec.js
@@ -24,7 +24,7 @@ describe('Hook', function () {
});
it('should call DropDown constructor', function () {
- expect(dropdownSrc.default).toHaveBeenCalledWith(this.list);
+ expect(dropdownSrc.default).toHaveBeenCalledWith(this.list, this.config);
});
it('should set .type', function () {
diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js
index fa11c602ec3..124d91f4477 100644
--- a/spec/javascripts/emoji_spec.js
+++ b/spec/javascripts/emoji_spec.js
@@ -1,6 +1,7 @@
import { glEmojiTag } from '~/emoji';
import isEmojiUnicodeSupported, {
isFlagEmoji,
+ isRainbowFlagEmoji,
isKeycapEmoji,
isSkinToneComboEmoji,
isHorceRacingSkinToneComboEmoji,
@@ -217,6 +218,24 @@ describe('gl_emoji', () => {
});
});
+ describe('isRainbowFlagEmoji', () => {
+ it('should gracefully handle empty string', () => {
+ expect(isRainbowFlagEmoji('')).toBeFalsy();
+ });
+ it('should detect rainbow_flag', () => {
+ expect(isRainbowFlagEmoji('🏳🌈')).toBeTruthy();
+ });
+ it('should not detect flag_white on its\' own', () => {
+ expect(isRainbowFlagEmoji('🏳')).toBeFalsy();
+ });
+ it('should not detect rainbow on its\' own', () => {
+ expect(isRainbowFlagEmoji('🌈')).toBeFalsy();
+ });
+ it('should not detect flag_white with something else', () => {
+ expect(isRainbowFlagEmoji('🏳🔵')).toBeFalsy();
+ });
+ });
+
describe('isKeycapEmoji', () => {
it('should gracefully handle empty string', () => {
expect(isKeycapEmoji('')).toBeFalsy();
diff --git a/spec/javascripts/environments/emtpy_state_spec.js b/spec/javascripts/environments/emtpy_state_spec.js
new file mode 100644
index 00000000000..82de35933f5
--- /dev/null
+++ b/spec/javascripts/environments/emtpy_state_spec.js
@@ -0,0 +1,57 @@
+
+import Vue from 'vue';
+import emptyState from '~/environments/components/empty_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('environments empty state', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(emptyState);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('With permissions', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ newPath: 'foo',
+ canCreateEnvironment: true,
+ helpPath: 'bar',
+ });
+ });
+
+ it('renders empty state and new environment button', () => {
+ expect(
+ vm.$el.querySelector('.js-blank-state-title').textContent.trim(),
+ ).toEqual('You don\'t have any environments right now.');
+
+ expect(
+ vm.$el.querySelector('.js-new-environment-button').getAttribute('href'),
+ ).toEqual('foo');
+ });
+ });
+
+ describe('Without permission', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ newPath: 'foo',
+ canCreateEnvironment: false,
+ helpPath: 'bar',
+ });
+ });
+
+ it('renders empty state without new button', () => {
+ expect(
+ vm.$el.querySelector('.js-blank-state-title').textContent.trim(),
+ ).toEqual('You don\'t have any environments right now.');
+
+ expect(
+ vm.$el.querySelector('.js-new-environment-button'),
+ ).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index 2862971bec4..9bd42863759 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -1,10 +1,17 @@
import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Environment table', () => {
+ let Component;
+ let vm;
-describe('Environment item', () => {
- preloadFixtures('static/environments/element.html.raw');
beforeEach(() => {
- loadFixtures('static/environments/element.html.raw');
+ Component = Vue.extend(environmentTableComp);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
});
it('Should render a table', () => {
@@ -17,18 +24,12 @@ describe('Environment item', () => {
},
};
- const EnvironmentTable = Vue.extend(environmentTableComp);
-
- const component = new EnvironmentTable({
- el: document.querySelector('.test-dom-element'),
- propsData: {
- environments: [{ mockItem }],
- canCreateDeployment: false,
- canReadEnvironment: true,
- service: {},
- },
- }).$mount();
+ vm = mountComponent(Component, {
+ environments: [mockItem],
+ canCreateDeployment: false,
+ canReadEnvironment: true,
+ });
- expect(component.$el.getAttribute('class')).toContain('ci-table');
+ expect(vm.$el.getAttribute('class')).toContain('ci-table');
});
});
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 0c8817a8148..d02adb25b4e 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -1,18 +1,24 @@
import Vue from 'vue';
-import '~/flash';
-import environmentsComponent from '~/environments/components/environment.vue';
+import environmentsComponent from '~/environments/components/environments_app.vue';
import { environment, folder } from './mock_data';
import { headersInterceptor } from '../helpers/vue_resource_helper';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Environment', () => {
- preloadFixtures('static/environments/environments.html.raw');
+ const mockData = {
+ endpoint: 'environments.json',
+ canCreateEnvironment: true,
+ canCreateDeployment: true,
+ canReadEnvironment: true,
+ cssContainerClass: 'container',
+ newEnvironmentPath: 'environments/new',
+ helpPagePath: 'help',
+ };
let EnvironmentsComponent;
let component;
beforeEach(() => {
- loadFixtures('static/environments/environments.html.raw');
-
EnvironmentsComponent = Vue.extend(environmentsComponent);
});
@@ -37,9 +43,7 @@ describe('Environment', () => {
});
it('should render the empty state', (done) => {
- component = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
+ component = mountComponent(EnvironmentsComponent, mockData);
setTimeout(() => {
expect(
@@ -81,9 +85,7 @@ describe('Environment', () => {
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
Vue.http.interceptors.push(headersInterceptor);
- component = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
+ component = mountComponent(EnvironmentsComponent, mockData);
});
afterEach(() => {
@@ -95,7 +97,7 @@ describe('Environment', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
- expect(component.$el.querySelectorAll('table')).toBeDefined();
+ expect(component.$el.querySelectorAll('table')).not.toBeNull();
expect(
component.$el.querySelector('.environment-name').textContent.trim(),
).toEqual(environment.name);
@@ -104,10 +106,6 @@ describe('Environment', () => {
});
describe('pagination', () => {
- afterEach(() => {
- window.history.pushState({}, null, '');
- });
-
it('should render pagination', (done) => {
setTimeout(() => {
expect(
@@ -117,46 +115,23 @@ describe('Environment', () => {
}, 0);
});
- it('should update url when no search params are present', (done) => {
- spyOn(gl.utils, 'visitUrl');
+ it('should make an API request when page is clicked', (done) => {
+ spyOn(component, 'updateContent');
setTimeout(() => {
component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
done();
}, 0);
});
- it('should update url when page is already present', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?page=1');
-
- setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
- done();
- }, 0);
- });
-
- it('should update url when page and scope are already present', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?scope=all&page=1');
-
+ it('should make an API request when using tabs', (done) => {
setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
- done();
- }, 0);
- });
+ spyOn(component, 'updateContent');
+ component.$el.querySelector('.js-environments-tab-stopped').click();
- it('should update url when page and scope are already present and page is first param', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?page=1&scope=all');
-
- setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
done();
- }, 0);
+ });
});
});
});
@@ -180,9 +155,7 @@ describe('Environment', () => {
});
it('should render empty state', (done) => {
- component = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
+ component = mountComponent(EnvironmentsComponent, mockData);
setTimeout(() => {
expect(
@@ -214,9 +187,7 @@ describe('Environment', () => {
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
- component = new EnvironmentsComponent({
- el: document.querySelector('#environments-list-view'),
- });
+ component = mountComponent(EnvironmentsComponent, mockData);
});
afterEach(() => {
@@ -289,4 +260,59 @@ describe('Environment', () => {
});
});
});
+
+ describe('methods', () => {
+ const environmentsEmptyResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
+
+ component = mountComponent(EnvironmentsComponent, mockData);
+ spyOn(history, 'pushState').and.stub();
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsEmptyResponseInterceptor,
+ );
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+ });
+
+ describe('updateContent', () => {
+ it('should set given parameters', (done) => {
+ component.updateContent({ scope: 'stopped', page: '3' })
+ .then(() => {
+ expect(component.page).toEqual('3');
+ expect(component.scope).toEqual('stopped');
+ expect(component.requestData.scope).toEqual('stopped');
+ expect(component.requestData.page).toEqual('3');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('onChangeTab', () => {
+ it('should set page to 1', () => {
+ spyOn(component, 'updateContent');
+ component.onChangeTab('stopped');
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
+ });
+ });
+
+ describe('onChangePage', () => {
+ it('should update page and keep scope', () => {
+ spyOn(component, 'updateContent');
+ component.onChangePage(4);
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 7e62d356bd2..4ea4d9d7499 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -1,25 +1,28 @@
import Vue from 'vue';
-import '~/flash';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
import { headersInterceptor } from '../../helpers/vue_resource_helper';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Environments Folder View', () => {
- preloadFixtures('static/environments/environments_folder_view.html.raw');
- let EnvironmentsFolderViewComponent;
+ let Component;
+ let component;
+ const mockData = {
+ endpoint: 'environments.json',
+ folderName: 'review',
+ canCreateDeployment: true,
+ canReadEnvironment: true,
+ cssContainerClass: 'container',
+ };
beforeEach(() => {
- loadFixtures('static/environments/environments_folder_view.html.raw');
- EnvironmentsFolderViewComponent = Vue.extend(environmentsFolderViewComponent);
- window.history.pushState({}, null, 'environments/folders/build');
+ Component = Vue.extend(environmentsFolderViewComponent);
});
afterEach(() => {
- window.history.pushState({}, null, '/');
+ component.$destroy();
});
- let component;
-
describe('successfull request', () => {
const environmentsResponseInterceptor = (request, next) => {
next(request.respondWith(JSON.stringify({
@@ -31,10 +34,10 @@ describe('Environments Folder View', () => {
headers: {
'X-nExt-pAge': '2',
'x-page': '1',
- 'X-Per-Page': '1',
+ 'X-Per-Page': '2',
'X-Prev-Page': '',
- 'X-TOTAL': '37',
- 'X-Total-Pages': '2',
+ 'X-TOTAL': '20',
+ 'X-Total-Pages': '10',
},
}));
};
@@ -43,9 +46,7 @@ describe('Environments Folder View', () => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
Vue.http.interceptors.push(headersInterceptor);
- component = new EnvironmentsFolderViewComponent({
- el: document.querySelector('#environments-folder-list-view'),
- });
+ component = mountComponent(Component, mockData);
});
afterEach(() => {
@@ -57,7 +58,7 @@ describe('Environments Folder View', () => {
it('should render a table with environments', (done) => {
setTimeout(() => {
- expect(component.$el.querySelectorAll('table')).toBeDefined();
+ expect(component.$el.querySelectorAll('table')).not.toBeNull();
expect(
component.$el.querySelector('.environment-name').textContent.trim(),
).toEqual(environmentsList[0].name);
@@ -68,11 +69,11 @@ describe('Environments Folder View', () => {
it('should render available tab with count', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ component.$el.querySelector('.js-environments-tab-available').textContent,
).toContain('Available');
expect(
- component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ component.$el.querySelector('.js-environments-tab-available .badge').textContent,
).toContain('0');
done();
}, 0);
@@ -81,11 +82,11 @@ describe('Environments Folder View', () => {
it('should render stopped tab with count', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ component.$el.querySelector('.js-environments-tab-stopped').textContent,
).toContain('Stopped');
expect(
- component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ component.$el.querySelector('.js-environments-tab-stopped .badge').textContent,
).toContain('1');
done();
}, 0);
@@ -94,8 +95,8 @@ describe('Environments Folder View', () => {
it('should render parent folder name', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-folder-name').textContent,
- ).toContain('Environments / build');
+ component.$el.querySelector('.js-folder-name').textContent.trim(),
+ ).toContain('Environments / review');
done();
}, 0);
});
@@ -104,52 +105,30 @@ describe('Environments Folder View', () => {
it('should render pagination', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelectorAll('.gl-pagination li').length,
- ).toEqual(5);
+ component.$el.querySelectorAll('.gl-pagination'),
+ ).not.toBeNull();
done();
}, 0);
});
- it('should update url when no search params are present', (done) => {
- spyOn(gl.utils, 'visitUrl');
+ it('should make an API request when changing page', (done) => {
+ spyOn(component, 'updateContent');
setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
- done();
- }, 0);
- });
-
- it('should update url when page is already present', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?page=1');
+ component.$el.querySelector('.gl-pagination .js-last-button a').click();
- setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2');
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' });
done();
}, 0);
});
- it('should update url when page and scope are already present', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?scope=all&page=1');
-
+ it('should make an API request when using tabs', (done) => {
setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?scope=all&page=2');
- done();
- }, 0);
- });
-
- it('should update url when page and scope are already present and page is first param', (done) => {
- spyOn(gl.utils, 'visitUrl');
- window.history.pushState({}, null, '?page=1&scope=all');
+ spyOn(component, 'updateContent');
+ component.$el.querySelector('.js-environments-tab-stopped').click();
- setTimeout(() => {
- component.$el.querySelector('.gl-pagination li:nth-child(5) a').click();
- expect(gl.utils.visitUrl).toHaveBeenCalledWith('?page=2&scope=all');
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
done();
- }, 0);
+ });
});
});
});
@@ -172,9 +151,7 @@ describe('Environments Folder View', () => {
});
it('should not render a table', (done) => {
- component = new EnvironmentsFolderViewComponent({
- el: document.querySelector('#environments-folder-list-view'),
- });
+ component = mountComponent(Component, mockData);
setTimeout(() => {
expect(
@@ -187,11 +164,11 @@ describe('Environments Folder View', () => {
it('should render available tab with count 0', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-available-environments-folder-tab').textContent,
+ component.$el.querySelector('.js-environments-tab-available').textContent,
).toContain('Available');
expect(
- component.$el.querySelector('.js-available-environments-folder-tab .js-available-environments-count').textContent,
+ component.$el.querySelector('.js-environments-tab-available .badge').textContent,
).toContain('0');
done();
}, 0);
@@ -200,14 +177,70 @@ describe('Environments Folder View', () => {
it('should render stopped tab with count 0', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-stopped-environments-folder-tab').textContent,
+ component.$el.querySelector('.js-environments-tab-stopped').textContent,
).toContain('Stopped');
expect(
- component.$el.querySelector('.js-stopped-environments-folder-tab .js-stopped-environments-count').textContent,
+ component.$el.querySelector('.js-environments-tab-stopped .badge').textContent,
).toContain('0');
done();
}, 0);
});
});
+
+ describe('methods', () => {
+ const environmentsEmptyResponseInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
+
+ component = mountComponent(Component, mockData);
+ spyOn(history, 'pushState').and.stub();
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, environmentsEmptyResponseInterceptor,
+ );
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+ });
+
+ describe('updateContent', () => {
+ it('should set given parameters', (done) => {
+ component.updateContent({ scope: 'stopped', page: '4' })
+ .then(() => {
+ expect(component.page).toEqual('4');
+ expect(component.scope).toEqual('stopped');
+ expect(component.requestData.scope).toEqual('stopped');
+ expect(component.requestData.page).toEqual('4');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('onChangeTab', () => {
+ it('should set page to 1', () => {
+ spyOn(component, 'updateContent');
+ component.onChangeTab('stopped');
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
+ });
+ });
+
+ describe('onChangePage', () => {
+ it('should update page and keep scope', () => {
+ spyOn(component, 'updateContent');
+
+ component.onChangePage(4);
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index f209328dee1..230c15e5de6 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -396,6 +396,25 @@ describe('Filtered Search Manager', () => {
});
});
+ describe('Clearing search', () => {
+ beforeEach(() => {
+ initializeManager();
+ });
+
+ it('Clicking the "x" clear button, clears the input', () => {
+ const inputValue = 'label:~bug ';
+ manager.filteredSearchInput.value = inputValue;
+ manager.filteredSearchInput.dispatchEvent(new Event('input'));
+
+ expect(gl.DropdownUtils.getSearchQuery()).toEqual(inputValue);
+
+ manager.clearSearchButton.click();
+
+ expect(manager.filteredSearchInput.value).toEqual('');
+ expect(gl.DropdownUtils.getSearchQuery()).toEqual('');
+ });
+ });
+
describe('toggleInputContainerFocus', () => {
beforeEach(() => {
initializeManager();
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index 8e74c4f859c..d26ea3febe8 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -31,4 +31,19 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
+
+ context 'rendering non-empty state' do
+ before do
+ cluster
+ end
+
+ it 'clusters/index_cluster.html.raw' do |example|
+ get :index,
+ namespace_id: namespace,
+ project_id: project
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
end
diff --git a/spec/javascripts/fixtures/environments/element.html.haml b/spec/javascripts/fixtures/environments/element.html.haml
deleted file mode 100644
index 8d7aeb23356..00000000000
--- a/spec/javascripts/fixtures/environments/element.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.test-dom-element
diff --git a/spec/javascripts/fixtures/environments/environments.html.haml b/spec/javascripts/fixtures/environments/environments.html.haml
deleted file mode 100644
index e6000fbb553..00000000000
--- a/spec/javascripts/fixtures/environments/environments.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%div
- #environments-list-view{ data: { environments_data: "foo/environments",
- "can-create-deployment" => "true",
- "can-read-environment" => "true",
- "can-create-environment" => "true",
- "project-environments-path" => "https://gitlab.com/foo/environments",
- "project-stopped-environments-path" => "https://gitlab.com/foo/environments?scope=stopped",
- "new-environment-path" => "https://gitlab.com/foo/environments/new",
- "help-page-path" => "https://gitlab.com/help_page"}}
diff --git a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml b/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
deleted file mode 100644
index aceec139730..00000000000
--- a/spec/javascripts/fixtures/environments/environments_folder_view.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%div
- #environments-folder-list-view{ data: { "can-create-deployment" => "true",
- "can-read-environment" => "true",
- "css-class" => "",
- "commit-icon-svg" => custom_icon("icon_commit"),
- "terminal-icon-svg" => custom_icon("icon_terminal"),
- "play-icon-svg" => custom_icon("icon_play") } }
diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml
index 97b0c25c923..85ee61f0b54 100644
--- a/spec/javascripts/fixtures/pipelines.html.haml
+++ b/spec/javascripts/fixtures/pipelines.html.haml
@@ -1,16 +1,10 @@
%div
#pipelines-list-vue{ data: { endpoint: 'foo',
- "css-class" => 'foo',
"help-page-path" => 'foo',
+ "help-auto-devops-path" => 'foo',
"empty-state-svg-path" => 'foo',
"error-state-svg-path" => 'foo',
"new-pipeline-path" => 'foo',
"can-create-pipeline" => 'true',
- "all-path" => 'foo',
- "pending-path" => 'foo',
- "running-path" => 'foo',
- "finished-path" => 'foo',
- "branches-path" => 'foo',
- "tags-path" => 'foo',
"has-ci" => 'foo',
"ci-lint-path" => 'foo' } }
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index b669aabcee4..97e3ab682c5 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -278,7 +278,7 @@ describe('Flash', () => {
removeFlashClickListener(flashEl, false);
- flashEl.parentNode.click();
+ flashEl.click();
setTimeout(() => {
expect(document.querySelector('.flash')).toBeNull();
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index ad0c7264616..6f357306ec7 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -67,6 +67,28 @@ describe('GfmAutoComplete', function () {
});
});
+ describe('DefaultOptions.beforeInsert', () => {
+ const beforeInsert = (context, value) => (
+ gfmAutoCompleteCallbacks.beforeInsert.call(context, value)
+ );
+
+ const atwhoInstance = { setting: { skipSpecialCharacterTest: false } };
+
+ it('should not quote if value only contains alphanumeric charecters', () => {
+ expect(beforeInsert(atwhoInstance, '@user1')).toBe('@user1');
+ expect(beforeInsert(atwhoInstance, '~label1')).toBe('~label1');
+ });
+
+ it('should quote if value contains any non-alphanumeric characters', () => {
+ expect(beforeInsert(atwhoInstance, '~label-20')).toBe('~"label-20"');
+ expect(beforeInsert(atwhoInstance, '~label 20')).toBe('~"label 20"');
+ });
+
+ it('should quote integer labels', () => {
+ expect(beforeInsert(atwhoInstance, '~1234')).toBe('~"1234"');
+ });
+ });
+
describe('DefaultOptions.matcher', function () {
const defaultMatcher = (context, flag, subtext) => (
gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext)
diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/javascripts/image_diff/helpers/utils_helper_spec.js
index 56d77a05c4c..31949c39d9c 100644
--- a/spec/javascripts/image_diff/helpers/utils_helper_spec.js
+++ b/spec/javascripts/image_diff/helpers/utils_helper_spec.js
@@ -157,27 +157,19 @@ describe('utilsHelper', () => {
beforeEach(() => {
window.gl = window.gl || (window.gl = {});
glCache = window.gl;
- window.gl.ImageFile = () => {};
fileEl = document.createElement('div');
fileEl.innerHTML = `
<div class="diff-file"></div>
`;
- spyOn(ImageDiff.prototype, 'init').and.callFake(() => {});
spyOn(ReplacedImageDiff.prototype, 'init').and.callFake(() => {});
+ spyOn(ImageDiff.prototype, 'init').and.callFake(() => {});
});
afterEach(() => {
window.gl = glCache;
});
- it('should initialize gl.ImageFile', () => {
- spyOn(window.gl, 'ImageFile');
-
- utilsHelper.initImageDiff(fileEl, false, false);
- expect(gl.ImageFile).toHaveBeenCalled();
- });
-
it('should initialize ImageDiff if js-single-image', () => {
const diffFileEl = fileEl.querySelector('.diff-file');
diffFileEl.innerHTML = `
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index ceee08d47c5..5a9112716f4 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -26,7 +26,7 @@ describe('Issuable', () => {
document.body.appendChild(element);
const input = document.createElement('input');
- input.setAttribute('id', 'issue_email');
+ input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
Issuable = new IssuableIndex('issue_');
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 2ea290108a4..b47a8bf705f 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -35,11 +35,12 @@ describe('Issuable output', () => {
canUpdate: true,
canDestroy: true,
endpoint: '/gitlab-org/gitlab-shell/issues/9/realtime_changes',
+ updateEndpoint: gl.TEST_HOST,
issuableRef: '#1',
initialTitleHtml: '',
initialTitleText: '',
- initialDescriptionHtml: '',
- initialDescriptionText: '',
+ initialDescriptionHtml: 'test',
+ initialDescriptionText: 'test',
markdownPreviewPath: '/',
markdownDocsPath: '/',
projectNamespace: '/',
@@ -223,23 +224,46 @@ describe('Issuable output', () => {
});
});
- it('closes form on error', (done) => {
- spyOn(window, 'Flash').and.callThrough();
- spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => {
- reject();
- }));
+ describe('error when updating', () => {
+ beforeEach(() => {
+ spyOn(window, 'Flash').and.callThrough();
+ spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => {
+ reject();
+ }));
+ });
- vm.updateIssuable();
+ it('closes form on error', (done) => {
+ vm.updateIssuable();
- setTimeout(() => {
- expect(
- eventHub.$emit,
- ).toHaveBeenCalledWith('close.form');
- expect(
- window.Flash,
- ).toHaveBeenCalledWith('Error updating issue');
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error updating issue');
- done();
+ done();
+ });
+ });
+
+ it('returns the correct error message for issuableType', (done) => {
+ vm.issuableType = 'merge request';
+
+ Vue.nextTick(() => {
+ vm.updateIssuable();
+
+ setTimeout(() => {
+ expect(
+ eventHub.$emit,
+ ).toHaveBeenCalledWith('close.form');
+ expect(
+ window.Flash,
+ ).toHaveBeenCalledWith('Error updating merge request');
+
+ done();
+ });
+ });
});
});
});
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 360691a3546..163e5cdd062 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -1,11 +1,22 @@
import Vue from 'vue';
import descriptionComponent from '~/issue_show/components/description.vue';
+import * as taskList from '~/task_list';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Description component', () => {
let vm;
+ let DescriptionComponent;
+ const props = {
+ canUpdate: true,
+ descriptionHtml: 'test',
+ descriptionText: 'test',
+ updatedAt: new Date().toString(),
+ taskStatus: '',
+ updateUrl: gl.TEST_HOST,
+ };
beforeEach(() => {
- const Component = Vue.extend(descriptionComponent);
+ DescriptionComponent = Vue.extend(descriptionComponent);
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
@@ -15,15 +26,11 @@ describe('Description component', () => {
document.body.appendChild(metaData);
}
- vm = new Component({
- propsData: {
- canUpdate: true,
- descriptionHtml: 'test',
- descriptionText: 'test',
- updatedAt: new Date().toString(),
- taskStatus: '',
- },
- }).$mount();
+ vm = mountComponent(DescriptionComponent, props);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
});
it('animates description changes', (done) => {
@@ -44,34 +51,46 @@ describe('Description component', () => {
});
});
- // TODO: gl.TaskList no longer exists. rewrite these tests once we have a way to rewire ES modules
-
- // it('re-inits the TaskList when description changed', (done) => {
- // spyOn(gl, 'TaskList');
- // vm.descriptionHtml = 'changed';
- //
- // setTimeout(() => {
- // expect(
- // gl.TaskList,
- // ).toHaveBeenCalled();
- //
- // done();
- // });
- // });
-
- // it('does not re-init the TaskList when canUpdate is false', (done) => {
- // spyOn(gl, 'TaskList');
- // vm.canUpdate = false;
- // vm.descriptionHtml = 'changed';
- //
- // setTimeout(() => {
- // expect(
- // gl.TaskList,
- // ).not.toHaveBeenCalled();
- //
- // done();
- // });
- // });
+ describe('TaskList', () => {
+ beforeEach(() => {
+ vm = mountComponent(DescriptionComponent, Object.assign({}, props, {
+ issuableType: 'issuableType',
+ }));
+ spyOn(taskList, 'default');
+ });
+
+ it('re-inits the TaskList when description changed', (done) => {
+ vm.descriptionHtml = 'changed';
+
+ setTimeout(() => {
+ expect(taskList.default).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('does not re-init the TaskList when canUpdate is false', (done) => {
+ vm.canUpdate = false;
+ vm.descriptionHtml = 'changed';
+
+ setTimeout(() => {
+ expect(taskList.default).not.toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('calls with issuableType dataType', (done) => {
+ vm.descriptionHtml = 'changed';
+
+ setTimeout(() => {
+ expect(taskList.default).toHaveBeenCalledWith({
+ dataType: 'issuableType',
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ });
+ done();
+ });
+ });
+ });
describe('taskStatus', () => {
it('adds full taskStatus', (done) => {
@@ -126,4 +145,8 @@ describe('Description component', () => {
});
});
});
+
+ it('sets data-update-url', () => {
+ expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(gl.TEST_HOST);
+ });
});
diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js
index f6625b748b6..d779ab7bb31 100644
--- a/spec/javascripts/issue_show/components/edit_actions_spec.js
+++ b/spec/javascripts/issue_show/components/edit_actions_spec.js
@@ -61,6 +61,15 @@ describe('Edit Actions components', () => {
});
});
+ it('should not show delete button if showDeleteButton is false', (done) => {
+ vm.showDeleteButton = false;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.btn-danger')).toBeNull();
+ done();
+ });
+ });
+
describe('updateIssuable', () => {
it('sends update.issauble event when clicking save button', () => {
vm.$el.querySelector('.btn-save').click();
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index 6e89528a3ea..000b53af016 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -34,7 +34,6 @@ describe('Inline edit form component', () => {
});
it('renders template selector when templates exists', (done) => {
- spyOn(gl, 'IssuableTemplateSelectors');
vm.issuableTemplates = ['test'];
Vue.nextTick(() => {
diff --git a/spec/javascripts/issue_show/components/title_spec.js b/spec/javascripts/issue_show/components/title_spec.js
index c1edc785d0f..5370f4e1fea 100644
--- a/spec/javascripts/issue_show/components/title_spec.js
+++ b/spec/javascripts/issue_show/components/title_spec.js
@@ -80,19 +80,19 @@ describe('Title component', () => {
});
it('should not show by default', () => {
- expect(vm.$el.querySelector('.note-action-button')).toBeNull();
+ expect(vm.$el.querySelector('.btn-edit')).toBeNull();
});
it('should not show if canUpdate is false', () => {
vm.showInlineEditButton = true;
vm.canUpdate = false;
- expect(vm.$el.querySelector('.note-action-button')).toBeNull();
+ expect(vm.$el.querySelector('.btn-edit')).toBeNull();
});
it('should show if showInlineEditButton and canUpdate', () => {
vm.showInlineEditButton = true;
vm.canUpdate = true;
- expect(vm.$el.querySelector('.note-action-button')).toBeDefined();
+ expect(vm.$el.querySelector('.btn-edit')).toBeDefined();
});
it('should trigger open.form event when clicked', () => {
@@ -100,7 +100,7 @@ describe('Title component', () => {
vm.canUpdate = true;
Vue.nextTick(() => {
- vm.$el.querySelector('.note-action-button').click();
+ vm.$el.querySelector('.btn-edit').click();
expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
});
diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js
index 5e67911d338..20c4caa865d 100644
--- a/spec/javascripts/job_spec.js
+++ b/spec/javascripts/job_spec.js
@@ -28,7 +28,7 @@ describe('Job', () => {
});
it('copies build options', function () {
- expect(this.job.pageUrl).toBe(JOB_URL);
+ expect(this.job.pagePath).toBe(JOB_URL);
expect(this.job.buildStatus).toBe('success');
expect(this.job.buildStage).toBe('test');
expect(this.job.state).toBe('');
diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js
index 1d7fa7e12fc..3069a0cd60e 100644
--- a/spec/javascripts/jobs/job_details_mediator_spec.js
+++ b/spec/javascripts/jobs/job_details_mediator_spec.js
@@ -1,39 +1,35 @@
-import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import JobMediator from '~/jobs/job_details_mediator';
import job from './mock_data';
describe('JobMediator', () => {
let mediator;
+ let mock;
beforeEach(() => {
- mediator = new JobMediator({ endpoint: 'foo' });
+ mediator = new JobMediator({ endpoint: 'jobs/40291672.json' });
+ mock = new MockAdapter(axios);
});
it('should set defaults', () => {
expect(mediator.store).toBeDefined();
expect(mediator.service).toBeDefined();
- expect(mediator.options).toEqual({ endpoint: 'foo' });
+ expect(mediator.options).toEqual({ endpoint: 'jobs/40291672.json' });
expect(mediator.state.isLoading).toEqual(false);
});
describe('request and store data', () => {
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(job), {
- status: 200,
- }));
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
+ mock.onGet().reply(200, job, {});
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
+ mock.restore();
});
it('should store received data', (done) => {
mediator.fetchJob();
-
setTimeout(() => {
expect(mediator.store.state.job).toEqual(job);
done();
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index a5298be5669..0a9d815f469 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -142,44 +142,33 @@ describe('common_utils', () => {
});
});
- describe('setParamInURL', () => {
+ describe('historyPushState', () => {
afterEach(() => {
- window.history.pushState({}, null, '');
+ window.history.replaceState({}, null, null);
});
- it('should return the parameter', () => {
- window.history.replaceState({}, null, '');
-
- expect(commonUtils.setParamInURL('page', 156)).toBe('?page=156');
- expect(commonUtils.setParamInURL('page', '156')).toBe('?page=156');
- });
+ it('should call pushState with the correct path', () => {
+ spyOn(window.history, 'pushState');
- it('should update the existing parameter when its a number', () => {
- window.history.pushState({}, null, '?page=15');
+ commonUtils.historyPushState('newpath?page=2');
- expect(commonUtils.setParamInURL('page', 16)).toBe('?page=16');
- expect(commonUtils.setParamInURL('page', '16')).toBe('?page=16');
- expect(commonUtils.setParamInURL('page', true)).toBe('?page=true');
+ expect(window.history.pushState).toHaveBeenCalled();
+ expect(window.history.pushState.calls.allArgs()[0][2]).toContain('newpath?page=2');
});
+ });
- it('should update the existing parameter when its a string', () => {
- window.history.pushState({}, null, '?scope=all');
-
- expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
- });
-
- it('should update the existing parameter when more than one parameter exists', () => {
- window.history.pushState({}, null, '?scope=all&page=15');
-
- expect(commonUtils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
+ describe('parseQueryStringIntoObject', () => {
+ it('should return object with query parameters', () => {
+ expect(commonUtils.parseQueryStringIntoObject('scope=all&page=2')).toEqual({ scope: 'all', page: '2' });
+ expect(commonUtils.parseQueryStringIntoObject('scope=all')).toEqual({ scope: 'all' });
+ expect(commonUtils.parseQueryStringIntoObject()).toEqual({});
});
+ });
- it('should add a new parameter to the end of the existing ones', () => {
- window.history.pushState({}, null, '?scope=all');
-
- expect(commonUtils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
- expect(commonUtils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
- expect(commonUtils.setParamInURL('page', true)).toBe('?scope=all&page=true');
+ describe('buildUrlWithCurrentLocation', () => {
+ it('should build an url with current location and given parameters', () => {
+ expect(commonUtils.buildUrlWithCurrentLocation()).toEqual(window.location.pathname);
+ expect(commonUtils.buildUrlWithCurrentLocation('?page=2')).toEqual(`${window.location.pathname}?page=2`);
});
});
diff --git a/spec/javascripts/lib/utils/datefix_spec.js b/spec/javascripts/lib/utils/datefix_spec.js
index e58ac4300ba..a9f3abcf2a4 100644
--- a/spec/javascripts/lib/utils/datefix_spec.js
+++ b/spec/javascripts/lib/utils/datefix_spec.js
@@ -21,7 +21,7 @@ describe('datefix', () => {
describe('pikadayToString', () => {
it('should format a UTC date into yyyy-mm-dd format', () => {
- expect(pikadayToString(new Date('2020-01-29'))).toEqual('2020-01-29');
+ expect(pikadayToString(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');
});
});
});
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/javascripts/lib/utils/number_utility_spec.js
index 83c92deccdc..fcf27f6805f 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/javascripts/lib/utils/number_utility_spec.js
@@ -1,4 +1,4 @@
-import { formatRelevantDigits, bytesToKiB, bytesToMiB } from '~/lib/utils/number_utils';
+import { formatRelevantDigits, bytesToKiB, bytesToMiB, bytesToGiB, numberToHumanSize } from '~/lib/utils/number_utils';
describe('Number Utils', () => {
describe('formatRelevantDigits', () => {
@@ -52,4 +52,29 @@ describe('Number Utils', () => {
expect(bytesToMiB(1000000)).toEqual(0.95367431640625);
});
});
+
+ describe('bytesToGiB', () => {
+ it('calculates GiB for the given bytes', () => {
+ expect(bytesToGiB(1073741824)).toEqual(1);
+ expect(bytesToGiB(10737418240)).toEqual(10);
+ });
+ });
+
+ describe('numberToHumanSize', () => {
+ it('should return bytes', () => {
+ expect(numberToHumanSize(654)).toEqual('654 bytes');
+ });
+
+ it('should return KiB', () => {
+ expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
+ });
+
+ it('should return MiB', () => {
+ expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
+ });
+
+ it('should return GiB', () => {
+ expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index 2aa7011ca51..9b8f68f1676 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -155,7 +155,7 @@ describe('Poll', () => {
successCallback: () => {
Polling.stop();
setTimeout(() => {
- Polling.restart();
+ Polling.restart({ data: { page: 4 } });
}, 0);
},
errorCallback: callbacks.error,
@@ -170,10 +170,10 @@ describe('Poll', () => {
Polling.stop();
expect(service.fetch.calls.count()).toEqual(2);
- expect(service.fetch).toHaveBeenCalledWith({ page: 1 });
+ expect(service.fetch).toHaveBeenCalledWith({ page: 4 });
expect(Polling.stop).toHaveBeenCalled();
expect(Polling.restart).toHaveBeenCalled();
-
+ expect(Polling.options.data).toEqual({ page: 4 });
done();
});
});
diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js
new file mode 100644
index 00000000000..a95a7e2a5be
--- /dev/null
+++ b/spec/javascripts/lib/utils/text_markdown_spec.js
@@ -0,0 +1,62 @@
+import textUtils from '~/lib/utils/text_markdown';
+
+describe('init markdown', () => {
+ let textArea;
+
+ beforeAll(() => {
+ textArea = document.createElement('textarea');
+ document.querySelector('body').appendChild(textArea);
+ textArea.focus();
+ });
+
+ afterAll(() => {
+ textArea.parentNode.removeChild(textArea);
+ });
+
+ describe('without selection', () => {
+ it('inserts the tag on an empty line', () => {
+ const initialValue = '';
+
+ textArea.value = initialValue;
+ textArea.selectionStart = 0;
+ textArea.selectionEnd = 0;
+
+ textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
+
+ it('inserts the tag on a new line if the current one is not empty', () => {
+ const initialValue = 'some text';
+
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
+
+ textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+
+ expect(textArea.value).toEqual(`${initialValue}\n* `);
+ });
+
+ it('inserts the tag on the same line if the current line only contains spaces', () => {
+ const initialValue = ' ';
+
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
+
+ textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
+
+ it('inserts the tag on the same line if the current line only contains tabs', () => {
+ const initialValue = '\t\t\t';
+
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
+
+ textUtils.insertText(textArea, textArea.value, '*', null, '', false);
+
+ expect(textArea.value).toEqual(`${initialValue}* `);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 829b3ef5735..1f46c225071 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -1,109 +1,65 @@
-import { highCountTrim } from '~/lib/utils/text_utility';
+import * as textUtils from '~/lib/utils/text_utility';
describe('text_utility', () => {
- describe('gl.text.getTextWidth', () => {
- it('returns zero width when no text is passed', () => {
- expect(gl.text.getTextWidth('')).toBe(0);
+ describe('addDelimiter', () => {
+ it('should add a delimiter to the given string', () => {
+ expect(textUtils.addDelimiter('1234')).toEqual('1,234');
+ expect(textUtils.addDelimiter('222222')).toEqual('222,222');
});
- it('returns zero width when no text is passed and font is passed', () => {
- expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
+ it('should not add a delimiter if string contains no numbers', () => {
+ expect(textUtils.addDelimiter('aaaa')).toEqual('aaaa');
});
+ });
- it('returns width when text is passed', () => {
- expect(gl.text.getTextWidth('foo') > 0).toBe(true);
+ describe('highCountTrim', () => {
+ it('returns 99+ for count >= 100', () => {
+ expect(textUtils.highCountTrim(105)).toBe('99+');
+ expect(textUtils.highCountTrim(100)).toBe('99+');
});
- it('returns bigger width when font is larger', () => {
- const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
- const regular = gl.text.getTextWidth('foo', '10px sans-serif');
- expect(largeFont > regular).toBe(true);
+ it('returns exact number for count < 100', () => {
+ expect(textUtils.highCountTrim(45)).toBe(45);
});
});
- describe('gl.text.pluralize', () => {
- it('returns pluralized', () => {
- expect(gl.text.pluralize('test', 2)).toBe('tests');
- });
-
- it('returns pluralized when count is 0', () => {
- expect(gl.text.pluralize('test', 0)).toBe('tests');
+ describe('capitalizeFirstCharacter', () => {
+ it('returns string with first letter capitalized', () => {
+ expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
+ expect(textUtils.highCountTrim(105)).toBe('99+');
+ expect(textUtils.highCountTrim(100)).toBe('99+');
});
+ });
- it('does not return pluralized', () => {
- expect(gl.text.pluralize('test', 1)).toBe('test');
+ describe('humanize', () => {
+ it('should remove underscores and uppercase the first letter', () => {
+ expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
});
});
- describe('highCountTrim', () => {
- it('returns 99+ for count >= 100', () => {
- expect(highCountTrim(105)).toBe('99+');
- expect(highCountTrim(100)).toBe('99+');
+ describe('pluralize', () => {
+ it('should pluralize given string', () => {
+ expect(textUtils.pluralize('test', 2)).toBe('tests');
});
- it('returns exact number for count < 100', () => {
- expect(highCountTrim(45)).toBe(45);
+ it('should pluralize when count is 0', () => {
+ expect(textUtils.pluralize('test', 0)).toBe('tests');
});
- });
-
- describe('gl.text.insertText', () => {
- let textArea;
- beforeAll(() => {
- textArea = document.createElement('textarea');
- document.querySelector('body').appendChild(textArea);
- textArea.focus();
+ it('should not pluralize when count is 1', () => {
+ expect(textUtils.pluralize('test', 1)).toBe('test');
});
+ });
- afterAll(() => {
- textArea.parentNode.removeChild(textArea);
+ describe('dasherize', () => {
+ it('should replace underscores with dashes', () => {
+ expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
});
+ });
- describe('without selection', () => {
- it('inserts the tag on an empty line', () => {
- const initialValue = '';
-
- textArea.value = initialValue;
- textArea.selectionStart = 0;
- textArea.selectionEnd = 0;
-
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
-
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
-
- it('inserts the tag on a new line if the current one is not empty', () => {
- const initialValue = 'some text';
-
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
-
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
-
- expect(textArea.value).toEqual(`${initialValue}\n* `);
- });
-
- it('inserts the tag on the same line if the current line only contains spaces', () => {
- const initialValue = ' ';
-
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
-
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
-
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
-
- it('inserts the tag on the same line if the current line only contains tabs', () => {
- const initialValue = '\t\t\t';
-
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
-
- gl.text.insertText(textArea, textArea.value, '*', null, '', false);
-
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ describe('slugify', () => {
+ it('should remove accents and convert to lower case', () => {
+ expect(textUtils.slugify('João')).toEqual('joão');
});
});
});
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 3ab901da6b6..70ae63ba036 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -63,7 +63,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
describe('merge request of another user', () => {
beforeEach(() => {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
- this.el = document.querySelector('.merge-request .issuable-actions');
+ this.el = document.querySelector('.js-issuable-actions');
const merge = new MergeRequest();
merge.hideCloseButton();
});
@@ -83,7 +83,7 @@ import IssuablesHelper from '~/helpers/issuables_helper';
describe('merge request of current_user', () => {
beforeEach(() => {
loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
- this.el = document.querySelector('.merge-request .issuable-actions');
+ this.el = document.querySelector('.js-issuable-actions');
const merge = new MergeRequest();
merge.hideCloseButton();
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 752fdfb4614..9885b8a790f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -1,6 +1,8 @@
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
-import { MonitorMockInterceptor } from './mock_data';
+import axios from '~/lib/utils/axios_utils';
+import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data';
describe('Dashboard', () => {
const fixtureName = 'environments/metrics/metrics.html.raw';
@@ -26,13 +28,17 @@ describe('Dashboard', () => {
});
describe('requests information to the server', () => {
+ let mock;
beforeEach(() => {
document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true');
- Vue.http.interceptors.push(MonitorMockInterceptor);
+ mock = new MockAdapter(axios);
+ mock.onGet(mockApiEndpoint).reply(200, {
+ metricsGroupsAPIResponse,
+ });
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, MonitorMockInterceptor);
+ mock.reset();
});
it('shows up a loading state', (done) => {
diff --git a/spec/javascripts/monitoring/graph_path_spec.js b/spec/javascripts/monitoring/graph_path_spec.js
index 8ece913ada8..c83bd19345f 100644
--- a/spec/javascripts/monitoring/graph_path_spec.js
+++ b/spec/javascripts/monitoring/graph_path_spec.js
@@ -32,4 +32,21 @@ describe('Monitoring Paths', () => {
expect(metricLine.getAttribute('stroke')).toBe('#1f78d1');
expect(metricLine.getAttribute('d')).toBe(firstTimeSeries.linePath);
});
+
+ describe('Computed properties', () => {
+ it('strokeDashArray', () => {
+ const component = createComponent({
+ generatedLinePath: firstTimeSeries.linePath,
+ generatedAreaPath: firstTimeSeries.areaPath,
+ lineColor: firstTimeSeries.lineColor,
+ areaColor: firstTimeSeries.areaColor,
+ });
+
+ component.lineStyle = 'dashed';
+ expect(component.strokeDashArray).toBe('3, 1');
+
+ component.lineStyle = 'dotted';
+ expect(component.strokeDashArray).toBe('1, 1');
+ });
+ });
});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 7ceab657464..6b34855b8b2 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -2425,13 +2425,6 @@ const metricsGroupsAPIResponse = {
export default metricsGroupsAPIResponse;
-const responseMockData = {
- 'GET': {
- '/root/hello-prometheus/environments/30/additional_metrics.json': metricsGroupsAPIResponse,
- 'http://test.host/frontend-fixtures/environments-project/environments/1/additional_metrics.json': metricsGroupsAPIResponse, // TODO: MAke sure this works in the monitoring_bundle_spec
- },
-};
-
export const deploymentData = [
{
id: 111,
@@ -8320,11 +8313,3 @@ export function convertDatesMultipleSeries(multipleSeries) {
});
return convertedMultiple;
}
-
-export function MonitorMockInterceptor(request, next) {
- const body = responseMockData[request.method.toUpperCase()][request.url];
-
- next(request.respondWith(JSON.stringify(body), {
- status: 200,
- }));
-}
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index c57f44dae17..50a5e4ff056 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -1,7 +1,6 @@
/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */
-/* global NewBranchForm */
-import '~/new_branch_form';
+import NewBranchForm from '~/new_branch_form';
(function() {
describe('Branch', function() {
diff --git a/spec/javascripts/notes/components/issue_comment_form_spec.js b/spec/javascripts/notes/components/issue_comment_form_spec.js
index a26fc8f63cc..04a7f8e32f1 100644
--- a/spec/javascripts/notes/components/issue_comment_form_spec.js
+++ b/spec/javascripts/notes/components/issue_comment_form_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import Autosize from 'autosize';
import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue';
-import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data';
+import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_comment_form component', () => {
@@ -23,7 +23,7 @@ describe('issue_comment_form component', () => {
describe('user is logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = mountComponent();
@@ -55,6 +55,25 @@ describe('issue_comment_form component', () => {
expect(vm.toggleIssueState).toHaveBeenCalled();
});
+
+ it('should disable action button whilst submitting', (done) => {
+ const saveNotePromise = Promise.resolve();
+ vm.note = 'hello world';
+ spyOn(vm, 'saveNote').and.returnValue(saveNotePromise);
+ spyOn(vm, 'stopPolling');
+
+ const actionButton = vm.$el.querySelector('.js-action-button');
+
+ vm.handleSave();
+
+ Vue.nextTick()
+ .then(() => expect(actionButton.disabled).toBeTruthy())
+ .then(saveNotePromise)
+ .then(Vue.nextTick)
+ .then(() => expect(actionButton.disabled).toBeFalsy())
+ .then(done)
+ .catch(done.fail);
+ });
});
describe('textarea', () => {
@@ -159,7 +178,7 @@ describe('issue_comment_form component', () => {
describe('issue is confidential', () => {
it('shows information warning', (done) => {
- store.dispatch('setIssueData', Object.assign(issueDataMock, { confidential: true }));
+ store.dispatch('setNoteableData', Object.assign(noteableDataMock, { confidential: true }));
Vue.nextTick(() => {
expect(vm.$el.querySelector('.confidential-issue-warning')).toBeDefined();
done();
@@ -171,7 +190,7 @@ describe('issue_comment_form component', () => {
describe('user is not logged in', () => {
beforeEach(() => {
store.dispatch('setUserData', null);
- store.dispatch('setIssueData', loggedOutIssueData);
+ store.dispatch('setNoteableData', loggedOutnoteableData);
store.dispatch('setNotesData', notesDataMock);
vm = mountComponent();
diff --git a/spec/javascripts/notes/components/issue_discussion_spec.js b/spec/javascripts/notes/components/issue_discussion_spec.js
index 05c6b57f93e..b6ae55d44f5 100644
--- a/spec/javascripts/notes/components/issue_discussion_spec.js
+++ b/spec/javascripts/notes/components/issue_discussion_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import issueDiscussion from '~/notes/components/issue_discussion.vue';
-import { issueDataMock, discussionMock, notesDataMock } from '../mock_data';
+import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
describe('issue_discussion component', () => {
let vm;
@@ -9,7 +9,7 @@ describe('issue_discussion component', () => {
beforeEach(() => {
const Component = Vue.extend(issueDiscussion);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
diff --git a/spec/javascripts/notes/components/issue_note_app_spec.js b/spec/javascripts/notes/components/issue_note_app_spec.js
index 22e91c4c40f..8e43037f356 100644
--- a/spec/javascripts/notes/components/issue_note_app_spec.js
+++ b/spec/javascripts/notes/components/issue_note_app_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import issueNotesApp from '~/notes/components/issue_notes_app.vue';
-import service from '~/notes/services/issue_notes_service';
+import service from '~/notes/services/notes_service';
import * as mockData from '../mock_data';
describe('issue_note_app', () => {
@@ -24,7 +24,7 @@ describe('issue_note_app', () => {
mountComponent = (data) => {
const props = data || {
- issueData: mockData.issueDataMock,
+ noteableData: mockData.noteableDataMock,
notesData: mockData.notesDataMock,
userData: mockData.userDataMock,
};
@@ -60,7 +60,7 @@ describe('issue_note_app', () => {
});
it('should set issue data', () => {
- expect(vm.$store.state.issueData).toEqual(mockData.issueDataMock);
+ expect(vm.$store.state.noteableData).toEqual(mockData.noteableDataMock);
});
it('should set user data', () => {
diff --git a/spec/javascripts/notes/components/issue_note_body_spec.js b/spec/javascripts/notes/components/issue_note_body_spec.js
index 81f07ed47cc..37aad50737b 100644
--- a/spec/javascripts/notes/components/issue_note_body_spec.js
+++ b/spec/javascripts/notes/components/issue_note_body_spec.js
@@ -2,7 +2,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import noteBody from '~/notes/components/issue_note_body.vue';
-import { issueDataMock, notesDataMock, note } from '../mock_data';
+import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note_body component', () => {
let vm;
@@ -10,7 +10,7 @@ describe('issue_note_body component', () => {
beforeEach(() => {
const Component = Vue.extend(noteBody);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
diff --git a/spec/javascripts/notes/components/issue_note_form_spec.js b/spec/javascripts/notes/components/issue_note_form_spec.js
index a90dbcb72b5..d42ef239711 100644
--- a/spec/javascripts/notes/components/issue_note_form_spec.js
+++ b/spec/javascripts/notes/components/issue_note_form_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import issueNoteForm from '~/notes/components/issue_note_form.vue';
-import { issueDataMock, notesDataMock } from '../mock_data';
+import { noteableDataMock, notesDataMock } from '../mock_data';
import { keyboardDownEvent } from '../../issue_show/helpers';
describe('issue_note_form component', () => {
@@ -11,7 +11,7 @@ describe('issue_note_form component', () => {
beforeEach(() => {
const Component = Vue.extend(issueNoteForm);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
props = {
diff --git a/spec/javascripts/notes/components/issue_note_spec.js b/spec/javascripts/notes/components/issue_note_spec.js
index 7ef85d5b4f0..73fd188dbe5 100644
--- a/spec/javascripts/notes/components/issue_note_spec.js
+++ b/spec/javascripts/notes/components/issue_note_spec.js
@@ -2,7 +2,7 @@
import Vue from 'vue';
import store from '~/notes/stores';
import issueNote from '~/notes/components/issue_note.vue';
-import { issueDataMock, notesDataMock, note } from '../mock_data';
+import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note', () => {
let vm;
@@ -10,7 +10,7 @@ describe('issue_note', () => {
beforeEach(() => {
const Component = Vue.extend(issueNote);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
diff --git a/spec/javascripts/notes/components/issue_note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 7bcc061f167..ab81aabb992 100644
--- a/spec/javascripts/notes/components/issue_note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import store from '~/notes/stores';
-import issueActions from '~/notes/components/issue_note_actions.vue';
+import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
describe('issse_note_actions component', () => {
@@ -8,7 +8,7 @@ describe('issse_note_actions component', () => {
let Component;
beforeEach(() => {
- Component = Vue.extend(issueActions);
+ Component = Vue.extend(noteActions);
});
afterEach(() => {
diff --git a/spec/javascripts/notes/components/issue_note_attachment_spec.js b/spec/javascripts/notes/components/note_attachment_spec.js
index 8f33b874ad6..b14a518b622 100644
--- a/spec/javascripts/notes/components/issue_note_attachment_spec.js
+++ b/spec/javascripts/notes/components/note_attachment_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import issueNoteAttachment from '~/notes/components/issue_note_attachment.vue';
+import noteAttachment from '~/notes/components/note_attachment.vue';
describe('issue note attachment', () => {
it('should render properly', () => {
@@ -11,7 +11,7 @@ describe('issue note attachment', () => {
},
};
- const Component = Vue.extend(issueNoteAttachment);
+ const Component = Vue.extend(noteAttachment);
const vm = new Component({
propsData: props,
}).$mount();
diff --git a/spec/javascripts/notes/components/issue_note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js
index 3b6c34f1494..15995ec5a05 100644
--- a/spec/javascripts/notes/components/issue_note_awards_list_spec.js
+++ b/spec/javascripts/notes/components/note_awards_list_spec.js
@@ -1,16 +1,16 @@
import Vue from 'vue';
import store from '~/notes/stores';
-import awardsNote from '~/notes/components/issue_note_awards_list.vue';
-import { issueDataMock, notesDataMock } from '../mock_data';
+import awardsNote from '~/notes/components/note_awards_list.vue';
+import { noteableDataMock, notesDataMock } from '../mock_data';
-describe('issue_note_awards_list component', () => {
+describe('note_awards_list component', () => {
let vm;
let awardsMock;
beforeEach(() => {
const Component = Vue.extend(awardsNote);
- store.dispatch('setIssueData', issueDataMock);
+ store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
awardsMock = [
{
diff --git a/spec/javascripts/notes/components/issue_note_edited_text_spec.js b/spec/javascripts/notes/components/note_edited_text_spec.js
index 6603241eb64..e0b991c32ec 100644
--- a/spec/javascripts/notes/components/issue_note_edited_text_spec.js
+++ b/spec/javascripts/notes/components/note_edited_text_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import issueNoteEditedText from '~/notes/components/issue_note_edited_text.vue';
+import noteEditedText from '~/notes/components/note_edited_text.vue';
-describe('issue_note_edited_text', () => {
+describe('note_edited_text', () => {
let vm;
let props;
beforeEach(() => {
- const Component = Vue.extend(issueNoteEditedText);
+ const Component = Vue.extend(noteEditedText);
props = {
actionText: 'Edited',
className: 'foo-bar',
diff --git a/spec/javascripts/notes/components/issue_note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js
index 83ea18508ae..16a76b11321 100644
--- a/spec/javascripts/notes/components/issue_note_header_spec.js
+++ b/spec/javascripts/notes/components/note_header_spec.js
@@ -1,13 +1,13 @@
import Vue from 'vue';
-import issueNoteHeader from '~/notes/components/issue_note_header.vue';
+import noteHeader from '~/notes/components/note_header.vue';
import store from '~/notes/stores';
-describe('issue_note_header component', () => {
+describe('note_header component', () => {
let vm;
let Component;
beforeEach(() => {
- Component = Vue.extend(issueNoteHeader);
+ Component = Vue.extend(noteHeader);
});
afterEach(() => {
diff --git a/spec/javascripts/notes/components/issue_note_signed_out_widget_spec.js b/spec/javascripts/notes/components/note_signed_out_widget_spec.js
index f20d9ce9268..6cba8053888 100644
--- a/spec/javascripts/notes/components/issue_note_signed_out_widget_spec.js
+++ b/spec/javascripts/notes/components/note_signed_out_widget_spec.js
@@ -1,13 +1,13 @@
import Vue from 'vue';
-import issueNoteSignedOut from '~/notes/components/issue_note_signed_out_widget.vue';
+import noteSignedOut from '~/notes/components/note_signed_out_widget.vue';
import store from '~/notes/stores';
import { notesDataMock } from '../mock_data';
-describe('issue_note_signed_out_widget component', () => {
+describe('note_signed_out_widget component', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(issueNoteSignedOut);
+ const Component = Vue.extend(noteSignedOut);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 89ba3a002b7..42497de3c55 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -18,7 +18,7 @@ export const userDataMock = {
username: 'root',
};
-export const issueDataMock = {
+export const noteableDataMock = {
assignees: [],
author_id: 1,
branch_name: null,
@@ -271,7 +271,7 @@ export const discussionMock = {
individual_note: false,
};
-export const loggedOutIssueData = {
+export const loggedOutnoteableData = {
"id": 98,
"iid": 26,
"author_id": 1,
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 3d1ca870ca4..e092320f9a3 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,6 +1,6 @@
import * as actions from '~/notes/stores/actions';
import testAction from '../../helpers/vuex_action_helper';
-import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
+import { discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Actions Notes Store', () => {
describe('setNotesData', () => {
@@ -11,10 +11,10 @@ describe('Actions Notes Store', () => {
});
});
- describe('setIssueData', () => {
+ describe('setNoteableData', () => {
it('should set received issue data', (done) => {
- testAction(actions.setIssueData, null, { issueData: {} }, [
- { type: 'SET_ISSUE_DATA', payload: issueDataMock },
+ testAction(actions.setNoteableData, null, { noteableData: {} }, [
+ { type: 'SET_NOTEABLE_DATA', payload: noteableDataMock },
], done);
});
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 48ee1bf9a52..c5a84b71788 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -1,5 +1,5 @@
import * as getters from '~/notes/stores/getters';
-import { notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
+import { notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Getters Notes Store', () => {
let state;
@@ -11,7 +11,7 @@ describe('Getters Notes Store', () => {
notesData: notesDataMock,
userData: userDataMock,
- issueData: issueDataMock,
+ noteableData: noteableDataMock,
};
});
describe('notes', () => {
@@ -32,9 +32,9 @@ describe('Getters Notes Store', () => {
});
});
- describe('getIssueData', () => {
- it('should return all data in `issueData`', () => {
- expect(getters.getIssueData(state)).toEqual(issueDataMock);
+ describe('getNoteableData', () => {
+ it('should return all data in `noteableData`', () => {
+ expect(getters.getNoteableData(state)).toEqual(noteableDataMock);
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 1e22e03e178..22d99998a7d 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -1,5 +1,5 @@
import mutations from '~/notes/stores/mutations';
-import { note, discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
+import { note, discussionMock, notesDataMock, userDataMock, noteableDataMock, individualNote } from '../mock_data';
describe('Mutation Notes Store', () => {
describe('ADD_NEW_NOTE', () => {
@@ -74,14 +74,14 @@ describe('Mutation Notes Store', () => {
});
});
- describe('SET_ISSUE_DATA', () => {
+ describe('SET_NOTEABLE_DATA', () => {
it('should set the issue data', () => {
const state = {
- issueData: {},
+ noteableData: {},
};
- mutations.SET_ISSUE_DATA(state, issueDataMock);
- expect(state.issueData).toEqual(issueDataMock);
+ mutations.SET_NOTEABLE_DATA(state, noteableDataMock);
+ expect(state.noteableData).toEqual(noteableDataMock);
});
});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 928a4b461cc..677a389b88f 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -5,7 +5,6 @@ import 'autosize';
import '~/gl_form';
import '~/lib/utils/text_utility';
import '~/render_gfm';
-import '~/render_math';
import '~/notes';
(function() {
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index 342ee6c1242..23c87610d83 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -1,8 +1,10 @@
import Vue from 'vue';
import jobComponent from '~/pipelines/components/graph/job_component.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph job component', () => {
let JobComponent;
+ let component;
const mockJob = {
id: 4256,
@@ -13,6 +15,7 @@ describe('pipeline graph job component', () => {
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4256',
+ has_details: true,
action: {
icon: 'retry',
title: 'Retry',
@@ -26,13 +29,13 @@ describe('pipeline graph job component', () => {
JobComponent = Vue.extend(jobComponent);
});
+ afterEach(() => {
+ component.$destroy();
+ });
+
describe('name with link', () => {
it('should render the job name and status with a link', (done) => {
- const component = new JobComponent({
- propsData: {
- job: mockJob,
- },
- }).$mount();
+ component = mountComponent(JobComponent, { job: mockJob });
Vue.nextTick(() => {
const link = component.$el.querySelector('a');
@@ -56,23 +59,23 @@ describe('pipeline graph job component', () => {
describe('name without link', () => {
it('it should render status and name', () => {
- const component = new JobComponent({
- propsData: {
- job: {
- id: 4256,
- name: 'test',
- status: {
- icon: 'icon_status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- details_path: '/root/ci-mock/builds/4256',
- },
+ component = mountComponent(JobComponent, {
+ job: {
+ id: 4256,
+ name: 'test',
+ status: {
+ icon: 'icon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ details_path: '/root/ci-mock/builds/4256',
+ has_details: false,
},
},
- }).$mount();
+ });
expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
+ expect(component.$el.querySelector('a')).toBeNull();
expect(
component.$el.querySelector('.ci-status-text').textContent.trim(),
@@ -82,11 +85,7 @@ describe('pipeline graph job component', () => {
describe('action icon', () => {
it('it should render the action icon', () => {
- const component = new JobComponent({
- propsData: {
- job: mockJob,
- },
- }).$mount();
+ component = mountComponent(JobComponent, { job: mockJob });
expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined();
expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined();
@@ -95,24 +94,20 @@ describe('pipeline graph job component', () => {
describe('dropdown', () => {
it('should render the dropdown action icon', () => {
- const component = new JobComponent({
- propsData: {
- job: mockJob,
- isDropdown: true,
- },
- }).$mount();
+ component = mountComponent(JobComponent, {
+ job: mockJob,
+ isDropdown: true,
+ });
expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined();
});
});
it('should render provided class name', () => {
- const component = new JobComponent({
- propsData: {
- job: mockJob,
- cssClassJobName: 'css-class-job-name',
- },
- }).$mount();
+ component = mountComponent(JobComponent, {
+ job: mockJob,
+ cssClassJobName: 'css-class-job-name',
+ });
expect(
component.$el.querySelector('a').classList.contains('css-class-job-name'),
diff --git a/spec/javascripts/pipelines/navigation_tabs_spec.js b/spec/javascripts/pipelines/navigation_tabs_spec.js
deleted file mode 100644
index 53a88e6322f..00000000000
--- a/spec/javascripts/pipelines/navigation_tabs_spec.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import Vue from 'vue';
-import navigationTabs from '~/pipelines/components/navigation_tabs.vue';
-import mountComponent from '../helpers/vue_mount_component_helper';
-
-describe('navigation tabs pipeline component', () => {
- let vm;
- let Component;
- let data;
-
- beforeEach(() => {
- data = {
- scope: 'all',
- count: {
- all: 16,
- running: 1,
- pending: 10,
- finished: 0,
- },
- paths: {
- allPath: '/gitlab-org/gitlab-ce/pipelines',
- pendingPath: '/gitlab-org/gitlab-ce/pipelines?scope=pending',
- finishedPath: '/gitlab-org/gitlab-ce/pipelines?scope=finished',
- runningPath: '/gitlab-org/gitlab-ce/pipelines?scope=running',
- branchesPath: '/gitlab-org/gitlab-ce/pipelines?scope=branches',
- tagsPath: '/gitlab-org/gitlab-ce/pipelines?scope=tags',
- },
- };
-
- Component = Vue.extend(navigationTabs);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should render tabs with correct paths', () => {
- vm = mountComponent(Component, data);
-
- // All
- const allTab = vm.$el.querySelector('.js-pipelines-tab-all a');
- expect(allTab.textContent.trim()).toContain('All');
- expect(allTab.getAttribute('href')).toEqual(data.paths.allPath);
-
- // Pending
- const pendingTab = vm.$el.querySelector('.js-pipelines-tab-pending a');
- expect(pendingTab.textContent.trim()).toContain('Pending');
- expect(pendingTab.getAttribute('href')).toEqual(data.paths.pendingPath);
-
- // Running
- const runningTab = vm.$el.querySelector('.js-pipelines-tab-running a');
- expect(runningTab.textContent.trim()).toContain('Running');
- expect(runningTab.getAttribute('href')).toEqual(data.paths.runningPath);
-
- // Finished
- const finishedTab = vm.$el.querySelector('.js-pipelines-tab-finished a');
- expect(finishedTab.textContent.trim()).toContain('Finished');
- expect(finishedTab.getAttribute('href')).toEqual(data.paths.finishedPath);
-
- // Branches
- const branchesTab = vm.$el.querySelector('.js-pipelines-tab-branches a');
- expect(branchesTab.textContent.trim()).toContain('Branches');
-
- // Tags
- const tagsTab = vm.$el.querySelector('.js-pipelines-tab-tags a');
- expect(tagsTab.textContent.trim()).toContain('Tags');
- });
-
- describe('scope', () => {
- it('should render scope provided as active tab', () => {
- vm = mountComponent(Component, data);
- expect(vm.$el.querySelector('.js-pipelines-tab-all').className).toContain('active');
- });
- });
-
- describe('badges', () => {
- it('should render provided number', () => {
- vm = mountComponent(Component, data);
- // All
- expect(
- vm.$el.querySelector('.js-totalbuilds-count').textContent.trim(),
- ).toContain(data.count.all);
-
- // Pending
- expect(
- vm.$el.querySelector('.js-pipelines-tab-pending .badge').textContent.trim(),
- ).toContain(data.count.pending);
-
- // Running
- expect(
- vm.$el.querySelector('.js-pipelines-tab-running .badge').textContent.trim(),
- ).toContain(data.count.running);
-
- // Finished
- expect(
- vm.$el.querySelector('.js-pipelines-tab-finished .badge').textContent.trim(),
- ).toContain(data.count.finished);
- });
-
- it('should not render badge when number is undefined', () => {
- vm = mountComponent(Component, {
- scope: 'all',
- paths: {},
- count: {},
- });
-
- // All
- expect(
- vm.$el.querySelector('.js-totalbuilds-count'),
- ).toEqual(null);
-
- // Pending
- expect(
- vm.$el.querySelector('.js-pipelines-tab-pending .badge'),
- ).toEqual(null);
-
- // Running
- expect(
- vm.$el.querySelector('.js-pipelines-tab-running .badge'),
- ).toEqual(null);
-
- // Finished
- expect(
- vm.$el.querySelector('.js-pipelines-tab-finished .badge'),
- ).toEqual(null);
- });
- });
-});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index c30abb2edb0..367b42cefb0 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
@@ -9,26 +10,33 @@ describe('Pipelines', () => {
preloadFixtures(jsonFixtureName);
let PipelinesComponent;
- let pipeline;
+ let pipelines;
+ let component;
beforeEach(() => {
loadFixtures('static/pipelines.html.raw');
- const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+ pipelines = getJSONFixture(jsonFixtureName);
PipelinesComponent = Vue.extend(pipelinesComp);
});
+ afterEach(() => {
+ component.$destroy();
+ });
+
describe('successfull request', () => {
describe('with pipelines', () => {
const pipelinesInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(pipeline), {
+ next(request.respondWith(JSON.stringify(pipelines), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(pipelinesInterceptor);
+ component = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ });
});
afterEach(() => {
@@ -38,18 +46,71 @@ describe('Pipelines', () => {
});
it('should render table', (done) => {
- const component = new PipelinesComponent({
- propsData: {
- store: new Store(),
- },
- }).$mount();
-
setTimeout(() => {
expect(component.$el.querySelector('.table-holder')).toBeDefined();
- expect(component.$el.querySelector('.realtime-loading')).toBe(null);
+ expect(
+ component.$el.querySelectorAll('.gl-responsive-table-row').length,
+ ).toEqual(pipelines.pipelines.length + 1);
+ done();
+ });
+ });
+
+ it('should render navigation tabs', (done) => {
+ setTimeout(() => {
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-pending').textContent.trim(),
+ ).toContain('Pending');
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-all').textContent.trim(),
+ ).toContain('All');
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-running').textContent.trim(),
+ ).toContain('Running');
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-finished').textContent.trim(),
+ ).toContain('Finished');
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-branches').textContent.trim(),
+ ).toContain('Branches');
+ expect(
+ component.$el.querySelector('.js-pipelines-tab-tags').textContent.trim(),
+ ).toContain('Tags');
+ done();
+ });
+ });
+
+ it('should make an API request when using tabs', (done) => {
+ setTimeout(() => {
+ spyOn(component, 'updateContent');
+ component.$el.querySelector('.js-pipelines-tab-finished').click();
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
done();
});
});
+
+ describe('with pagination', () => {
+ it('should make an API request when using pagination', (done) => {
+ setTimeout(() => {
+ spyOn(component, 'updateContent');
+ // Mock pagination
+ component.store.state.pageInfo = {
+ page: 1,
+ total: 10,
+ perPage: 2,
+ nextPage: 2,
+ totalPages: 5,
+ };
+
+ Vue.nextTick(() => {
+ component.$el.querySelector('.js-next-button a').click();
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
+
+ done();
+ });
+ });
+ });
+ });
});
describe('without pipelines', () => {
@@ -70,15 +131,14 @@ describe('Pipelines', () => {
});
it('should render empty state', (done) => {
- const component = new PipelinesComponent({
+ component = new PipelinesComponent({
propsData: {
store: new Store(),
},
}).$mount();
setTimeout(() => {
- expect(component.$el.querySelector('.empty-state')).toBeDefined();
- expect(component.$el.querySelector('.realtime-loading')).toBe(null);
+ expect(component.$el.querySelector('.empty-state')).not.toBe(null);
done();
});
});
@@ -103,7 +163,7 @@ describe('Pipelines', () => {
});
it('should render error state', (done) => {
- const component = new PipelinesComponent({
+ component = new PipelinesComponent({
propsData: {
store: new Store(),
},
@@ -111,9 +171,54 @@ describe('Pipelines', () => {
setTimeout(() => {
expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
- expect(component.$el.querySelector('.realtime-loading')).toBe(null);
done();
});
});
});
+
+ describe('methods', () => {
+ beforeEach(() => {
+ spyOn(history, 'pushState').and.stub();
+ });
+
+ describe('updateContent', () => {
+ it('should set given parameters', () => {
+ component = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ });
+ component.updateContent({ scope: 'finished', page: '4' });
+
+ expect(component.page).toEqual('4');
+ expect(component.scope).toEqual('finished');
+ expect(component.requestData.scope).toEqual('finished');
+ expect(component.requestData.page).toEqual('4');
+ });
+ });
+
+ describe('onChangeTab', () => {
+ it('should set page to 1', () => {
+ component = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ });
+ spyOn(component, 'updateContent');
+
+ component.onChangeTab('running');
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+ });
+ });
+
+ describe('onChangePage', () => {
+ it('should update page and keep scope', () => {
+ component = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ });
+ spyOn(component, 'updateContent');
+
+ component.onChangePage(4);
+
+ expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
index 171629fcd6b..edef150dd1e 100644
--- a/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
+++ b/spec/javascripts/projects_dropdown/components/projects_list_item_spec.js
@@ -50,6 +50,18 @@ describe('ProjectsListItemComponent', () => {
expect(vm.highlightedProjectName).toBe(mockProject.name);
});
});
+
+ describe('truncatedNamespace', () => {
+ it('should truncate project name from namespace string', () => {
+ vm.namespace = 'platform / nokia-3310';
+ expect(vm.truncatedNamespace).toBe('platform');
+ });
+
+ it('should truncate namespace string from the middle if it includes more than two groups in path', () => {
+ vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310';
+ expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset');
+ });
+ });
});
describe('template', () => {
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
new file mode 100644
index 00000000000..f750061a6a1
--- /dev/null
+++ b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import store from '~/repo/stores';
+import listCollapsed from '~/repo/components/commit_sidebar/list_collapsed.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { file } from '../../helpers';
+
+describe('Multi-file editor commit sidebar list collapsed', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(listCollapsed);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.openFiles.push(file(), file());
+ vm.$store.state.openFiles[0].tempFile = true;
+ vm.$store.state.openFiles.forEach((f) => {
+ Object.assign(f, {
+ changed: true,
+ });
+ });
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders added & modified files count', () => {
+ expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
+ });
+});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
new file mode 100644
index 00000000000..18c9b46fcd9
--- /dev/null
+++ b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
@@ -0,0 +1,53 @@
+import Vue from 'vue';
+import listItem from '~/repo/components/commit_sidebar/list_item.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+import { file } from '../../helpers';
+
+describe('Multi-file editor commit sidebar list item', () => {
+ let vm;
+ let f;
+
+ beforeEach(() => {
+ const Component = Vue.extend(listItem);
+
+ f = file();
+
+ vm = mountComponent(Component, {
+ file: f,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders file path', () => {
+ expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
+ });
+
+ describe('computed', () => {
+ describe('iconName', () => {
+ it('returns modified when not a tempFile', () => {
+ expect(vm.iconName).toBe('file-modified');
+ });
+
+ it('returns addition when not a tempFile', () => {
+ f.tempFile = true;
+
+ expect(vm.iconName).toBe('file-addition');
+ });
+ });
+
+ describe('iconClass', () => {
+ it('returns modified when not a tempFile', () => {
+ expect(vm.iconClass).toContain('multi-file-modified');
+ });
+
+ it('returns addition when not a tempFile', () => {
+ f.tempFile = true;
+
+ expect(vm.iconClass).toContain('multi-file-addition');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
new file mode 100644
index 00000000000..df7e3c5de21
--- /dev/null
+++ b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
@@ -0,0 +1,72 @@
+import Vue from 'vue';
+import store from '~/repo/stores';
+import commitSidebarList from '~/repo/components/commit_sidebar/list.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { file } from '../../helpers';
+
+describe('Multi-file editor commit sidebar list', () => {
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(commitSidebarList);
+
+ vm = createComponentWithStore(Component, store, {
+ title: 'Staged',
+ fileList: [],
+ collapsed: false,
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('empty file list', () => {
+ it('renders no changes text', () => {
+ expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes');
+ });
+ });
+
+ describe('with a list of files', () => {
+ beforeEach((done) => {
+ const f = file('file name');
+ f.changed = true;
+ vm.fileList.push(f);
+
+ Vue.nextTick(done);
+ });
+
+ it('renders list', () => {
+ expect(vm.$el.querySelectorAll('li').length).toBe(1);
+ });
+ });
+
+ describe('collapsed', () => {
+ beforeEach((done) => {
+ vm.collapsed = true;
+
+ Vue.nextTick(done);
+ });
+
+ it('adds collapsed class', () => {
+ expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
+ });
+
+ it('hides list', () => {
+ expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
+ expect(vm.$el.querySelector('.help-block')).toBeNull();
+ });
+
+ it('hides collapse button', () => {
+ expect(vm.$el.querySelector('.multi-file-commit-panel-collapse-btn')).toBeNull();
+ });
+ });
+
+ it('clicking toggle collapse button emits toggle event', () => {
+ spyOn(vm, '$emit');
+
+ vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed');
+ });
+});
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index 0f991e1b727..1c794123095 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -25,8 +25,12 @@ describe('RepoCommitSection', () => {
return comp.$mount();
}
- beforeEach(() => {
+ beforeEach((done) => {
vm = createComponent();
+
+ vm.collapsed = false;
+
+ Vue.nextTick(done);
});
afterEach(() => {
@@ -36,12 +40,11 @@ describe('RepoCommitSection', () => {
});
it('renders a commit section', () => {
- const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')];
- const submitCommit = vm.$el.querySelector('.btn');
- const targetBranch = vm.$el.querySelector('.target-branch');
+ const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
+ const submitCommit = vm.$el.querySelector('form .btn');
- expect(vm.$el.querySelector(':scope > form')).toBeTruthy();
- expect(vm.$el.querySelector('.staged-files').textContent.trim()).toEqual('Staged files (2)');
+ expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
+ expect(vm.$el.querySelector('.multi-file-commit-panel-section header').textContent.trim()).toEqual('Staged');
expect(changedFileElements.length).toEqual(2);
changedFileElements.forEach((changedFile, i) => {
@@ -49,10 +52,7 @@ describe('RepoCommitSection', () => {
});
expect(submitCommit.disabled).toBeTruthy();
- expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy();
- expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files');
- expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch');
- expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual('master');
+ expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
});
describe('when submitting', () => {
@@ -69,7 +69,7 @@ describe('RepoCommitSection', () => {
});
it('allows you to submit', () => {
- expect(vm.$el.querySelector('.btn').disabled).toBeTruthy();
+ expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy();
});
it('submits commit', (done) => {
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 979d2185076..81158cad639 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -1,12 +1,13 @@
import Vue from 'vue';
import store from '~/repo/stores';
import repoEditor from '~/repo/components/repo_editor.vue';
+import monacoLoader from '~/repo/monaco_loader';
import { file, resetStore } from '../helpers';
describe('RepoEditor', () => {
let vm;
- beforeEach(() => {
+ beforeEach((done) => {
const f = file();
const RepoEditor = Vue.extend(repoEditor);
@@ -21,6 +22,10 @@ describe('RepoEditor', () => {
vm.monaco = true;
vm.$mount();
+
+ monacoLoader(['vs/editor/editor.main'], () => {
+ setTimeout(done, 0);
+ });
});
afterEach(() => {
@@ -32,7 +37,6 @@ describe('RepoEditor', () => {
it('renders an ide container', (done) => {
Vue.nextTick(() => {
expect(vm.shouldHideEditor).toBeFalsy();
- expect(vm.$el.textContent.trim()).toBe('');
done();
});
@@ -50,7 +54,7 @@ describe('RepoEditor', () => {
});
it('shows activeFile html', () => {
- expect(vm.$el.textContent.trim()).toBe('testing');
+ expect(vm.$el.textContent).toContain('testing');
});
});
});
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js
index 7cb4dace491..df7cf8aabbb 100644
--- a/spec/javascripts/repo/components/repo_sidebar_spec.js
+++ b/spec/javascripts/repo/components/repo_sidebar_spec.js
@@ -29,7 +29,6 @@ describe('RepoSidebar', () => {
const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody');
- expect(vm.$el.id).toEqual('sidebar');
expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
expect(thead.querySelector('.name').textContent.trim()).toEqual('Name');
expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit');
@@ -40,18 +39,6 @@ describe('RepoSidebar', () => {
expect(tbody.querySelector('.file')).toBeTruthy();
});
- it('does not render a thead, renders repo-file-options and sets sidebar-mini class if isMini', (done) => {
- vm.$store.state.openFiles.push(vm.$store.state.tree[0]);
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
- expect(vm.$el.querySelector('thead')).toBeTruthy();
- expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy();
-
- done();
- });
- });
-
it('renders 5 loading files if tree is loading', (done) => {
vm.$store.state.tree = [];
vm.$store.state.loading = true;
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index df0ca55aafc..7d2174196c9 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -24,8 +24,8 @@ describe('RepoTab', () => {
tab: file(),
});
vm.$store.state.openFiles.push(vm.tab);
- const close = vm.$el.querySelector('.close-btn');
- const name = vm.$el.querySelector(`a[title="${vm.tab.url}"]`);
+ const close = vm.$el.querySelector('.multi-file-tab-close');
+ const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
expect(close.querySelector('.fa-times')).toBeTruthy();
expect(name.textContent.trim()).toEqual(vm.tab.name);
@@ -50,7 +50,7 @@ describe('RepoTab', () => {
spyOn(vm, 'closeFile');
- vm.$el.querySelector('.close-btn').click();
+ vm.$el.querySelector('.multi-file-tab-close').click();
expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
});
@@ -62,7 +62,7 @@ describe('RepoTab', () => {
tab,
});
- expect(vm.$el.querySelector('.close-btn .fa-circle')).toBeTruthy();
+ expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull();
});
describe('methods', () => {
@@ -77,7 +77,7 @@ describe('RepoTab', () => {
vm.$store.state.openFiles.push(tab);
vm.$store.dispatch('setFileActive', tab);
- vm.$el.querySelector('.close-btn').click();
+ vm.$el.querySelector('.multi-file-tab-close').click();
vm.$nextTick(() => {
expect(tab.opened).toBeTruthy();
@@ -95,7 +95,7 @@ describe('RepoTab', () => {
vm.$store.state.openFiles.push(tab);
vm.$store.dispatch('setFileActive', tab);
- vm.$el.querySelector('.close-btn').click();
+ vm.$el.querySelector('.multi-file-tab-close').click();
vm.$nextTick(() => {
expect(tab.opened).toBeFalsy();
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index d0246cc72e6..1fb2242c051 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -25,12 +25,11 @@ describe('RepoTabs', () => {
vm.$store.state.openFiles = openedFiles;
vm.$nextTick(() => {
- const tabs = [...vm.$el.querySelectorAll(':scope > li')];
+ const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
- expect(tabs.length).toEqual(3);
+ expect(tabs.length).toEqual(2);
expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy();
- expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
done();
});
diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js
index 376c291c64b..820a44992b4 100644
--- a/spec/javascripts/repo/helpers.js
+++ b/spec/javascripts/repo/helpers.js
@@ -12,9 +12,4 @@ export const file = (name = 'name', id = name, type = '') => decorateData({
url: 'url',
name,
path: name,
- last_commit: {
- id: '123',
- message: 'test',
- committed_date: new Date().toISOString(),
- },
});
diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js
new file mode 100644
index 00000000000..62c3913bf4d
--- /dev/null
+++ b/spec/javascripts/repo/lib/common/disposable_spec.js
@@ -0,0 +1,44 @@
+import Disposable from '~/repo/lib/common/disposable';
+
+describe('Multi-file editor library disposable class', () => {
+ let instance;
+ let disposableClass;
+
+ beforeEach(() => {
+ instance = new Disposable();
+
+ disposableClass = {
+ dispose: jasmine.createSpy('dispose'),
+ };
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ });
+
+ describe('add', () => {
+ it('adds disposable classes', () => {
+ instance.add(disposableClass);
+
+ expect(instance.disposers.size).toBe(1);
+ });
+ });
+
+ describe('dispose', () => {
+ beforeEach(() => {
+ instance.add(disposableClass);
+ });
+
+ it('calls dispose on all cached disposers', () => {
+ instance.dispose();
+
+ expect(disposableClass.dispose).toHaveBeenCalled();
+ });
+
+ it('clears cached disposers', () => {
+ instance.dispose();
+
+ expect(instance.disposers.size).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js
new file mode 100644
index 00000000000..8c134f178c0
--- /dev/null
+++ b/spec/javascripts/repo/lib/common/model_manager_spec.js
@@ -0,0 +1,81 @@
+/* global monaco */
+import monacoLoader from '~/repo/monaco_loader';
+import ModelManager from '~/repo/lib/common/model_manager';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library model manager', () => {
+ let instance;
+
+ beforeEach((done) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ instance = new ModelManager(monaco);
+
+ done();
+ });
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ });
+
+ describe('addModel', () => {
+ it('caches model', () => {
+ instance.addModel(file());
+
+ expect(instance.models.size).toBe(1);
+ });
+
+ it('caches model by file path', () => {
+ instance.addModel(file('path-name'));
+
+ expect(instance.models.keys().next().value).toBe('path-name');
+ });
+
+ it('adds model into disposable', () => {
+ spyOn(instance.disposable, 'add').and.callThrough();
+
+ instance.addModel(file());
+
+ expect(instance.disposable.add).toHaveBeenCalled();
+ });
+
+ it('returns cached model', () => {
+ spyOn(instance.models, 'get').and.callThrough();
+
+ instance.addModel(file());
+ instance.addModel(file());
+
+ expect(instance.models.get).toHaveBeenCalled();
+ });
+ });
+
+ describe('hasCachedModel', () => {
+ it('returns false when no models exist', () => {
+ expect(instance.hasCachedModel('path')).toBeFalsy();
+ });
+
+ it('returns true when model exists', () => {
+ instance.addModel(file('path-name'));
+
+ expect(instance.hasCachedModel('path-name')).toBeTruthy();
+ });
+ });
+
+ describe('dispose', () => {
+ it('clears cached models', () => {
+ instance.addModel(file());
+
+ instance.dispose();
+
+ expect(instance.models.size).toBe(0);
+ });
+
+ it('calls disposable dispose', () => {
+ spyOn(instance.disposable, 'dispose').and.callThrough();
+
+ instance.dispose();
+
+ expect(instance.disposable.dispose).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js
new file mode 100644
index 00000000000..d41ade237ca
--- /dev/null
+++ b/spec/javascripts/repo/lib/common/model_spec.js
@@ -0,0 +1,84 @@
+/* global monaco */
+import monacoLoader from '~/repo/monaco_loader';
+import Model from '~/repo/lib/common/model';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library model', () => {
+ let model;
+
+ beforeEach((done) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ model = new Model(monaco, file('path'));
+
+ done();
+ });
+ });
+
+ afterEach(() => {
+ model.dispose();
+ });
+
+ it('creates original model & new model', () => {
+ expect(model.originalModel).not.toBeNull();
+ expect(model.model).not.toBeNull();
+ });
+
+ describe('path', () => {
+ it('returns file path', () => {
+ expect(model.path).toBe('path');
+ });
+ });
+
+ describe('getModel', () => {
+ it('returns model', () => {
+ expect(model.getModel()).toBe(model.model);
+ });
+ });
+
+ describe('getOriginalModel', () => {
+ it('returns original model', () => {
+ expect(model.getOriginalModel()).toBe(model.originalModel);
+ });
+ });
+
+ describe('onChange', () => {
+ it('caches event by path', () => {
+ model.onChange(() => {});
+
+ expect(model.events.size).toBe(1);
+ expect(model.events.keys().next().value).toBe('path');
+ });
+
+ it('calls callback on change', (done) => {
+ const spy = jasmine.createSpy();
+ model.onChange(spy);
+
+ model.getModel().setValue('123');
+
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything());
+ done();
+ });
+ });
+ });
+
+ describe('dispose', () => {
+ it('calls disposable dispose', () => {
+ spyOn(model.disposable, 'dispose').and.callThrough();
+
+ model.dispose();
+
+ expect(model.disposable.dispose).toHaveBeenCalled();
+ });
+
+ it('clears events', () => {
+ model.onChange(() => {});
+
+ expect(model.events.size).toBe(1);
+
+ model.dispose();
+
+ expect(model.events.size).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js
new file mode 100644
index 00000000000..2e32e8fa0bd
--- /dev/null
+++ b/spec/javascripts/repo/lib/decorations/controller_spec.js
@@ -0,0 +1,120 @@
+/* global monaco */
+import monacoLoader from '~/repo/monaco_loader';
+import editor from '~/repo/lib/editor';
+import DecorationsController from '~/repo/lib/decorations/controller';
+import Model from '~/repo/lib/common/model';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library decorations controller', () => {
+ let editorInstance;
+ let controller;
+ let model;
+
+ beforeEach((done) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ editorInstance = editor.create(monaco);
+ editorInstance.createInstance(document.createElement('div'));
+
+ controller = new DecorationsController(editorInstance);
+ model = new Model(monaco, file('path'));
+
+ done();
+ });
+ });
+
+ afterEach(() => {
+ model.dispose();
+ editorInstance.dispose();
+ controller.dispose();
+ });
+
+ describe('getAllDecorationsForModel', () => {
+ it('returns empty array when no decorations exist for model', () => {
+ const decorations = controller.getAllDecorationsForModel(model);
+
+ expect(decorations).toEqual([]);
+ });
+
+ it('returns decorations by model URL', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ const decorations = controller.getAllDecorationsForModel(model);
+
+ expect(decorations[0]).toEqual({ decoration: 'decorationValue' });
+ });
+ });
+
+ describe('addDecorations', () => {
+ it('caches decorations in a new map', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ expect(controller.decorations.size).toBe(1);
+ });
+
+ it('does not create new cache model', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]);
+
+ expect(controller.decorations.size).toBe(1);
+ });
+
+ it('caches decorations by model URL', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ expect(controller.decorations.size).toBe(1);
+ expect(controller.decorations.keys().next().value).toBe('path');
+ });
+
+ it('calls decorate method', () => {
+ spyOn(controller, 'decorate');
+
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ expect(controller.decorate).toHaveBeenCalled();
+ });
+ });
+
+ describe('decorate', () => {
+ it('sets decorations on editor instance', () => {
+ spyOn(controller.editor.instance, 'deltaDecorations');
+
+ controller.decorate(model);
+
+ expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
+ });
+
+ it('caches decorations', () => {
+ spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+
+ controller.decorate(model);
+
+ expect(controller.editorDecorations.size).toBe(1);
+ });
+
+ it('caches decorations by model URL', () => {
+ spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
+
+ controller.decorate(model);
+
+ expect(controller.editorDecorations.keys().next().value).toBe('path');
+ });
+ });
+
+ describe('dispose', () => {
+ it('clears cached decorations', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ controller.dispose();
+
+ expect(controller.decorations.size).toBe(0);
+ });
+
+ it('clears cached editorDecorations', () => {
+ controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
+
+ controller.dispose();
+
+ expect(controller.editorDecorations.size).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js
new file mode 100644
index 00000000000..ed62e28d3a3
--- /dev/null
+++ b/spec/javascripts/repo/lib/diff/controller_spec.js
@@ -0,0 +1,176 @@
+/* global monaco */
+import monacoLoader from '~/repo/monaco_loader';
+import editor from '~/repo/lib/editor';
+import ModelManager from '~/repo/lib/common/model_manager';
+import DecorationsController from '~/repo/lib/decorations/controller';
+import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/repo/lib/diff/controller';
+import { computeDiff } from '~/repo/lib/diff/diff';
+import { file } from '../../helpers';
+
+describe('Multi-file editor library dirty diff controller', () => {
+ let editorInstance;
+ let controller;
+ let modelManager;
+ let decorationsController;
+ let model;
+
+ beforeEach((done) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ editorInstance = editor.create(monaco);
+ editorInstance.createInstance(document.createElement('div'));
+
+ modelManager = new ModelManager(monaco);
+ decorationsController = new DecorationsController(editorInstance);
+
+ model = modelManager.addModel(file());
+
+ controller = new DirtyDiffController(modelManager, decorationsController);
+
+ done();
+ });
+ });
+
+ afterEach(() => {
+ controller.dispose();
+ model.dispose();
+ decorationsController.dispose();
+ editorInstance.dispose();
+ });
+
+ describe('getDiffChangeType', () => {
+ ['added', 'removed', 'modified'].forEach((type) => {
+ it(`returns ${type}`, () => {
+ const change = {
+ [type]: true,
+ };
+
+ expect(getDiffChangeType(change)).toBe(type);
+ });
+ });
+ });
+
+ describe('getDecorator', () => {
+ ['added', 'removed', 'modified'].forEach((type) => {
+ it(`returns with linesDecorationsClassName for ${type}`, () => {
+ const change = {
+ [type]: true,
+ };
+
+ expect(
+ getDecorator(change).options.linesDecorationsClassName,
+ ).toBe(`dirty-diff dirty-diff-${type}`);
+ });
+
+ it('returns with line numbers', () => {
+ const change = {
+ lineNumber: 1,
+ endLineNumber: 2,
+ [type]: true,
+ };
+
+ const range = getDecorator(change).range;
+
+ expect(range.startLineNumber).toBe(1);
+ expect(range.endLineNumber).toBe(2);
+ expect(range.startColumn).toBe(1);
+ expect(range.endColumn).toBe(1);
+ });
+ });
+ });
+
+ describe('attachModel', () => {
+ it('adds change event callback', () => {
+ spyOn(model, 'onChange');
+
+ controller.attachModel(model);
+
+ expect(model.onChange).toHaveBeenCalled();
+ });
+
+ it('calls throttledComputeDiff on change', () => {
+ spyOn(controller, 'throttledComputeDiff');
+
+ controller.attachModel(model);
+
+ model.getModel().setValue('123');
+
+ expect(controller.throttledComputeDiff).toHaveBeenCalled();
+ });
+ });
+
+ describe('computeDiff', () => {
+ it('posts to worker', () => {
+ spyOn(controller.dirtyDiffWorker, 'postMessage');
+
+ controller.computeDiff(model);
+
+ expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({
+ path: model.path,
+ originalContent: '',
+ newContent: '',
+ });
+ });
+ });
+
+ describe('reDecorate', () => {
+ it('calls decorations controller decorate', () => {
+ spyOn(controller.decorationsController, 'decorate');
+
+ controller.reDecorate(model);
+
+ expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
+ });
+ });
+
+ describe('decorate', () => {
+ it('adds decorations into decorations controller', () => {
+ spyOn(controller.decorationsController, 'addDecorations');
+
+ controller.decorate({ data: { changes: [], path: 'path' } });
+
+ expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything());
+ });
+
+ it('adds decorations into editor', () => {
+ const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
+
+ controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } });
+
+ expect(spy).toHaveBeenCalledWith([], [{
+ range: new monaco.Range(
+ 1, 1, 1, 1,
+ ),
+ options: {
+ isWholeLine: true,
+ linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
+ },
+ }]);
+ });
+ });
+
+ describe('dispose', () => {
+ it('calls disposable dispose', () => {
+ spyOn(controller.disposable, 'dispose').and.callThrough();
+
+ controller.dispose();
+
+ expect(controller.disposable.dispose).toHaveBeenCalled();
+ });
+
+ it('terminates worker', () => {
+ spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
+
+ controller.dispose();
+
+ expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled();
+ });
+
+ it('removes worker event listener', () => {
+ spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
+
+ controller.dispose();
+
+ expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything());
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js
new file mode 100644
index 00000000000..3269ec5d2c9
--- /dev/null
+++ b/spec/javascripts/repo/lib/diff/diff_spec.js
@@ -0,0 +1,80 @@
+import { computeDiff } from '~/repo/lib/diff/diff';
+
+describe('Multi-file editor library diff calculator', () => {
+ describe('computeDiff', () => {
+ it('returns empty array if no changes', () => {
+ const diff = computeDiff('123', '123');
+
+ expect(diff).toEqual([]);
+ });
+
+ describe('modified', () => {
+ it('', () => {
+ const diff = computeDiff('123', '1234')[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeTruthy();
+ expect(diff.removed).toBeUndefined();
+ });
+
+ it('', () => {
+ const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeTruthy();
+ expect(diff.removed).toBeUndefined();
+ expect(diff.lineNumber).toBe(2);
+ });
+ });
+
+ describe('added', () => {
+ it('', () => {
+ const diff = computeDiff('123', '123\n123')[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeUndefined();
+ expect(diff.removed).toBeUndefined();
+ });
+
+ it('', () => {
+ const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0];
+
+ expect(diff.added).toBeTruthy();
+ expect(diff.modified).toBeUndefined();
+ expect(diff.removed).toBeUndefined();
+ expect(diff.lineNumber).toBe(3);
+ });
+ });
+
+ describe('removed', () => {
+ it('', () => {
+ const diff = computeDiff('123', '')[0];
+
+ expect(diff.added).toBeUndefined();
+ expect(diff.modified).toBeUndefined();
+ expect(diff.removed).toBeTruthy();
+ });
+
+ it('', () => {
+ const diff = computeDiff('123\n123\n123', '123\n123')[0];
+
+ expect(diff.added).toBeUndefined();
+ expect(diff.modified).toBeTruthy();
+ expect(diff.removed).toBeTruthy();
+ expect(diff.lineNumber).toBe(2);
+ });
+ });
+
+ it('includes line number of change', () => {
+ const diff = computeDiff('123', '')[0];
+
+ expect(diff.lineNumber).toBe(1);
+ });
+
+ it('includes end line number of change', () => {
+ const diff = computeDiff('123', '')[0];
+
+ expect(diff.endLineNumber).toBe(1);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js
new file mode 100644
index 00000000000..b4887d063ed
--- /dev/null
+++ b/spec/javascripts/repo/lib/editor_options_spec.js
@@ -0,0 +1,7 @@
+import editorOptions from '~/repo/lib/editor_options';
+
+describe('Multi-file editor library editor options', () => {
+ it('returns an array', () => {
+ expect(editorOptions).toEqual(jasmine.any(Array));
+ });
+});
diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js
new file mode 100644
index 00000000000..cd32832a232
--- /dev/null
+++ b/spec/javascripts/repo/lib/editor_spec.js
@@ -0,0 +1,128 @@
+/* global monaco */
+import monacoLoader from '~/repo/monaco_loader';
+import editor from '~/repo/lib/editor';
+import { file } from '../helpers';
+
+describe('Multi-file editor library', () => {
+ let instance;
+
+ beforeEach((done) => {
+ monacoLoader(['vs/editor/editor.main'], () => {
+ instance = editor.create(monaco);
+
+ done();
+ });
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ });
+
+ it('creates instance of editor', () => {
+ expect(editor.editorInstance).not.toBeNull();
+ });
+
+ describe('createInstance', () => {
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ });
+
+ it('creates editor instance', () => {
+ spyOn(instance.monaco.editor, 'create').and.callThrough();
+
+ instance.createInstance(el);
+
+ expect(instance.monaco.editor.create).toHaveBeenCalled();
+ });
+
+ it('creates dirty diff controller', () => {
+ instance.createInstance(el);
+
+ expect(instance.dirtyDiffController).not.toBeNull();
+ });
+ });
+
+ describe('createModel', () => {
+ it('calls model manager addModel', () => {
+ spyOn(instance.modelManager, 'addModel');
+
+ instance.createModel('FILE');
+
+ expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE');
+ });
+ });
+
+ describe('attachModel', () => {
+ let model;
+
+ beforeEach(() => {
+ instance.createInstance(document.createElement('div'));
+
+ model = instance.createModel(file());
+ });
+
+ it('sets the current model on the instance', () => {
+ instance.attachModel(model);
+
+ expect(instance.currentModel).toBe(model);
+ });
+
+ it('attaches the model to the current instance', () => {
+ spyOn(instance.instance, 'setModel');
+
+ instance.attachModel(model);
+
+ expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel());
+ });
+
+ it('attaches the model to the dirty diff controller', () => {
+ spyOn(instance.dirtyDiffController, 'attachModel');
+
+ instance.attachModel(model);
+
+ expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model);
+ });
+
+ it('re-decorates with the dirty diff controller', () => {
+ spyOn(instance.dirtyDiffController, 'reDecorate');
+
+ instance.attachModel(model);
+
+ expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model);
+ });
+ });
+
+ describe('clearEditor', () => {
+ it('resets the editor model', () => {
+ instance.createInstance(document.createElement('div'));
+
+ spyOn(instance.instance, 'setModel');
+
+ instance.clearEditor();
+
+ expect(instance.instance.setModel).toHaveBeenCalledWith(null);
+ });
+ });
+
+ describe('dispose', () => {
+ it('calls disposble dispose method', () => {
+ spyOn(instance.disposable, 'dispose').and.callThrough();
+
+ instance.dispose();
+
+ expect(instance.disposable.dispose).toHaveBeenCalled();
+ });
+
+ it('resets instance', () => {
+ instance.createInstance(document.createElement('div'));
+
+ expect(instance.instance).not.toBeNull();
+
+ instance.dispose();
+
+ expect(instance.instance).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js
new file mode 100644
index 00000000000..af9d6835a67
--- /dev/null
+++ b/spec/javascripts/repo/stores/actions/branch_spec.js
@@ -0,0 +1,38 @@
+import store from '~/repo/stores';
+import service from '~/repo/services';
+import { resetStore } from '../../helpers';
+
+describe('Multi-file store branch actions', () => {
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ describe('createNewBranch', () => {
+ beforeEach(() => {
+ spyOn(service, 'createBranch').and.returnValue(Promise.resolve({
+ json: () => ({
+ name: 'testing',
+ }),
+ }));
+ spyOn(history, 'pushState');
+
+ store.state.project.id = 2;
+ store.state.currentBranch = 'testing';
+ });
+
+ it('creates new branch', (done) => {
+ store.dispatch('createNewBranch', 'master')
+ .then(() => {
+ expect(store.state.currentBranch).toBe('testing');
+ expect(service.createBranch).toHaveBeenCalledWith(2, {
+ branch: 'master',
+ ref: 'testing',
+ });
+ expect(history.pushState).toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
new file mode 100644
index 00000000000..099c0556e71
--- /dev/null
+++ b/spec/javascripts/repo/stores/actions/file_spec.js
@@ -0,0 +1,417 @@
+import Vue from 'vue';
+import store from '~/repo/stores';
+import service from '~/repo/services';
+import { file, resetStore } from '../../helpers';
+
+describe('Multi-file store file actions', () => {
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ describe('closeFile', () => {
+ let localFile;
+ let getLastCommitDataSpy;
+ let oldGetLastCommitData;
+
+ beforeEach(() => {
+ getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
+ oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
+ store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
+
+ localFile = file();
+ localFile.active = true;
+ localFile.opened = true;
+ localFile.parentTreeUrl = 'parentTreeUrl';
+
+ store.state.openFiles.push(localFile);
+
+ spyOn(history, 'pushState');
+ });
+
+ afterEach(() => {
+ store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
+ });
+
+ it('closes open files', (done) => {
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(localFile.opened).toBeFalsy();
+ expect(localFile.active).toBeFalsy();
+ expect(store.state.openFiles.length).toBe(0);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('does not close file if has changed', (done) => {
+ localFile.changed = true;
+
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(localFile.opened).toBeTruthy();
+ expect(localFile.active).toBeTruthy();
+ expect(store.state.openFiles.length).toBe(1);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('does not close file if temp file', (done) => {
+ localFile.tempFile = true;
+
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(localFile.opened).toBeTruthy();
+ expect(localFile.active).toBeTruthy();
+ expect(store.state.openFiles.length).toBe(1);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('force closes a changed file', (done) => {
+ localFile.changed = true;
+
+ store.dispatch('closeFile', { file: localFile, force: true })
+ .then(() => {
+ expect(localFile.opened).toBeFalsy();
+ expect(localFile.active).toBeFalsy();
+ expect(store.state.openFiles.length).toBe(0);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls pushState when no open files are left', (done) => {
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'parentTreeUrl');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets next file as active', (done) => {
+ const f = file();
+ store.state.openFiles.push(f);
+
+ expect(f.active).toBeFalsy();
+
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(f.active).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls getLastCommitData', (done) => {
+ store.dispatch('closeFile', { file: localFile })
+ .then(() => {
+ expect(getLastCommitDataSpy).toHaveBeenCalled();
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('setFileActive', () => {
+ let scrollToTabSpy;
+ let oldScrollToTab;
+
+ beforeEach(() => {
+ scrollToTabSpy = jasmine.createSpy('scrollToTab');
+ oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
+ store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
+ });
+
+ afterEach(() => {
+ store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
+ });
+
+ it('calls scrollToTab', (done) => {
+ store.dispatch('setFileActive', file())
+ .then(() => {
+ expect(scrollToTabSpy).toHaveBeenCalled();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets the file active', (done) => {
+ const localFile = file();
+
+ store.dispatch('setFileActive', localFile)
+ .then(() => {
+ expect(localFile.active).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('returns early if file is already active', (done) => {
+ const localFile = file();
+ localFile.active = true;
+
+ store.dispatch('setFileActive', localFile)
+ .then(() => {
+ expect(scrollToTabSpy).not.toHaveBeenCalled();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets current active file to not active', (done) => {
+ const localFile = file();
+ localFile.active = true;
+ store.state.openFiles.push(localFile);
+
+ store.dispatch('setFileActive', file())
+ .then(() => {
+ expect(localFile.active).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('resets location.hash for line highlighting', (done) => {
+ location.hash = 'test';
+
+ store.dispatch('setFileActive', file())
+ .then(() => {
+ expect(location.hash).not.toBe('test');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('getFileData', () => {
+ let localFile = file();
+
+ beforeEach(() => {
+ spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
+ headers: {
+ 'page-title': 'testing getFileData',
+ },
+ json: () => Promise.resolve({
+ blame_path: 'blame_path',
+ commits_path: 'commits_path',
+ permalink: 'permalink',
+ raw_path: 'raw_path',
+ binary: false,
+ html: '123',
+ render_error: '',
+ }),
+ }));
+
+ localFile = file();
+ localFile.url = 'getFileDataURL';
+ });
+
+ it('calls the service', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(() => {
+ expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets the file data', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(localFile.blamePath).toBe('blame_path');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets document title', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(() => {
+ expect(document.title).toBe('testing getFileData');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets the file as active', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(localFile.active).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('adds the file to open files', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.openFiles.length).toBe(1);
+ expect(store.state.openFiles[0].name).toBe(localFile.name);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('toggles the file loading', (done) => {
+ store.dispatch('getFileData', localFile)
+ .then(() => {
+ expect(localFile.loading).toBeTruthy();
+
+ return Vue.nextTick();
+ })
+ .then(() => {
+ expect(localFile.loading).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('getRawFileData', () => {
+ let tmpFile;
+
+ beforeEach(() => {
+ spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
+
+ tmpFile = file();
+ });
+
+ it('calls getRawFileData service method', (done) => {
+ store.dispatch('getRawFileData', tmpFile)
+ .then(() => {
+ expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('updates file raw data', (done) => {
+ store.dispatch('getRawFileData', tmpFile)
+ .then(() => {
+ expect(tmpFile.raw).toBe('raw');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('changeFileContent', () => {
+ let tmpFile;
+
+ beforeEach(() => {
+ tmpFile = file();
+ });
+
+ it('updates file content', (done) => {
+ store.dispatch('changeFileContent', {
+ file: tmpFile,
+ content: 'content',
+ })
+ .then(() => {
+ expect(tmpFile.content).toBe('content');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('createTempFile', () => {
+ beforeEach(() => {
+ document.body.innerHTML += '<div class="flash-container"></div>';
+ });
+
+ afterEach(() => {
+ document.querySelector('.flash-container').remove();
+ });
+
+ it('creates temp file', (done) => {
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then((f) => {
+ expect(f.tempFile).toBeTruthy();
+ expect(store.state.tree.length).toBe(1);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('adds tmp file to open files', (done) => {
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then((f) => {
+ expect(store.state.openFiles.length).toBe(1);
+ expect(store.state.openFiles[0].name).toBe(f.name);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets tmp file as active', (done) => {
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then((f) => {
+ expect(f.active).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('enters edit mode if file is not base64', (done) => {
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then(() => {
+ expect(store.state.editMode).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('does not enter edit mode if file is base64', (done) => {
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ base64: true,
+ }).then(() => {
+ expect(store.state.editMode).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('creates flash message is file already exists', (done) => {
+ store.state.tree.push(file('test', '1', 'blob'));
+
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then(() => {
+ expect(document.querySelector('.flash-alert')).not.toBeNull();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('increases level of file', (done) => {
+ store.state.level = 1;
+
+ store.dispatch('createTempFile', {
+ tree: store.state,
+ name: 'test',
+ }).then((f) => {
+ expect(f.level).toBe(2);
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js
new file mode 100644
index 00000000000..393a797c6a3
--- /dev/null
+++ b/spec/javascripts/repo/stores/actions/tree_spec.js
@@ -0,0 +1,469 @@
+import Vue from 'vue';
+import store from '~/repo/stores';
+import service from '~/repo/services';
+import { file, resetStore } from '../../helpers';
+
+describe('Multi-file store tree actions', () => {
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ describe('getTreeData', () => {
+ beforeEach(() => {
+ spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
+ headers: {
+ 'page-title': 'test',
+ },
+ json: () => Promise.resolve({
+ last_commit_path: 'last_commit_path',
+ parent_tree_url: 'parent_tree_url',
+ path: '/',
+ trees: [{ name: 'tree' }],
+ blobs: [{ name: 'blob' }],
+ submodules: [{ name: 'submodule' }],
+ }),
+ }));
+ spyOn(history, 'pushState');
+
+ Object.assign(store.state.endpoints, {
+ rootEndpoint: 'rootEndpoint',
+ });
+ });
+
+ it('calls service getTreeData', (done) => {
+ store.dispatch('getTreeData')
+ .then(() => {
+ expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('adds data into tree', (done) => {
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.tree.length).toBe(3);
+ expect(store.state.tree[0].type).toBe('tree');
+ expect(store.state.tree[1].type).toBe('submodule');
+ expect(store.state.tree[2].type).toBe('blob');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets parent tree URL', (done) => {
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.parentTreeUrl).toBe('parent_tree_url');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets last commit path', (done) => {
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.lastCommitPath).toBe('last_commit_path');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets root if not currently at root', (done) => {
+ store.state.isInitialRoot = false;
+
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.isInitialRoot).toBeTruthy();
+ expect(store.state.isRoot).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets page title', (done) => {
+ store.dispatch('getTreeData')
+ .then(() => {
+ expect(document.title).toBe('test');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('toggles loading', (done) => {
+ store.dispatch('getTreeData')
+ .then(() => {
+ expect(store.state.loading).toBeTruthy();
+
+ return Vue.nextTick();
+ })
+ .then(() => {
+ expect(store.state.loading).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls pushState with endpoint', (done) => {
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'rootEndpoint');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls getLastCommitData if prevLastCommitPath is not null', (done) => {
+ const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
+ const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
+ store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
+ store.state.prevLastCommitPath = 'test';
+
+ store.dispatch('getTreeData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(getLastCommitDataSpy).toHaveBeenCalledWith(store.state);
+
+ store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('toggleTreeOpen', () => {
+ let oldGetTreeData;
+ let getTreeDataSpy;
+ let tree;
+
+ beforeEach(() => {
+ getTreeDataSpy = jasmine.createSpy('getTreeData');
+
+ oldGetTreeData = store._actions.getTreeData; // eslint-disable-line
+ store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line
+
+ tree = {
+ opened: false,
+ tree: [],
+ };
+ });
+
+ afterEach(() => {
+ store._actions.getTreeData = oldGetTreeData; // eslint-disable-line
+ });
+
+ it('toggles the tree open', (done) => {
+ store.dispatch('toggleTreeOpen', {
+ endpoint: 'test',
+ tree,
+ }).then(() => {
+ expect(tree.opened).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls getTreeData if tree is closed', (done) => {
+ store.dispatch('toggleTreeOpen', {
+ endpoint: 'test',
+ tree,
+ }).then(() => {
+ expect(getTreeDataSpy).toHaveBeenCalledWith({
+ endpoint: 'test',
+ tree,
+ });
+ expect(store.state.previousUrl).toBe('test');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('resets entries tree', (done) => {
+ Object.assign(tree, {
+ opened: true,
+ tree: ['a'],
+ });
+
+ store.dispatch('toggleTreeOpen', {
+ endpoint: 'test',
+ tree,
+ }).then(() => {
+ expect(tree.tree.length).toBe(0);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('pushes new state', (done) => {
+ spyOn(history, 'pushState');
+ Object.assign(tree, {
+ opened: true,
+ parentTreeUrl: 'testing',
+ });
+
+ store.dispatch('toggleTreeOpen', {
+ endpoint: 'test',
+ tree,
+ }).then(() => {
+ expect(history.pushState).toHaveBeenCalledWith(jasmine.anything(), '', 'testing');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('clickedTreeRow', () => {
+ describe('tree', () => {
+ let toggleTreeOpenSpy;
+ let oldToggleTreeOpen;
+
+ beforeEach(() => {
+ toggleTreeOpenSpy = jasmine.createSpy('toggleTreeOpen');
+
+ oldToggleTreeOpen = store._actions.toggleTreeOpen; // eslint-disable-line
+ store._actions.toggleTreeOpen = [toggleTreeOpenSpy]; // eslint-disable-line
+ });
+
+ afterEach(() => {
+ store._actions.toggleTreeOpen = oldToggleTreeOpen; // eslint-disable-line
+ });
+
+ it('opens tree', (done) => {
+ const tree = {
+ url: 'a',
+ type: 'tree',
+ };
+
+ store.dispatch('clickedTreeRow', tree)
+ .then(() => {
+ expect(toggleTreeOpenSpy).toHaveBeenCalledWith({
+ endpoint: tree.url,
+ tree,
+ });
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('submodule', () => {
+ let row;
+
+ beforeEach(() => {
+ spyOn(gl.utils, 'visitUrl');
+
+ row = {
+ url: 'submoduleurl',
+ type: 'submodule',
+ loading: false,
+ };
+ });
+
+ it('toggles loading for row', (done) => {
+ store.dispatch('clickedTreeRow', row)
+ .then(() => {
+ expect(row.loading).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('opens submodule URL', (done) => {
+ store.dispatch('clickedTreeRow', row)
+ .then(() => {
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('submoduleurl');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('blob', () => {
+ let row;
+
+ beforeEach(() => {
+ row = {
+ type: 'blob',
+ opened: false,
+ };
+ });
+
+ it('calls getFileData', (done) => {
+ const getFileDataSpy = jasmine.createSpy('getFileData');
+ const oldGetFileData = store._actions.getFileData; // eslint-disable-line
+ store._actions.getFileData = [getFileDataSpy]; // eslint-disable-line
+
+ store.dispatch('clickedTreeRow', row)
+ .then(() => {
+ expect(getFileDataSpy).toHaveBeenCalledWith(row);
+
+ store._actions.getFileData = oldGetFileData; // eslint-disable-line
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('calls setFileActive when file is opened', (done) => {
+ const setFileActiveSpy = jasmine.createSpy('setFileActive');
+ const oldSetFileActive = store._actions.setFileActive; // eslint-disable-line
+ store._actions.setFileActive = [setFileActiveSpy]; // eslint-disable-line
+
+ row.opened = true;
+
+ store.dispatch('clickedTreeRow', row)
+ .then(() => {
+ expect(setFileActiveSpy).toHaveBeenCalledWith(row);
+
+ store._actions.setFileActive = oldSetFileActive; // eslint-disable-line
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+ });
+
+ describe('createTempTree', () => {
+ it('creates temp tree', (done) => {
+ store.dispatch('createTempTree', 'test')
+ .then(() => {
+ expect(store.state.tree[0].tempFile).toBeTruthy();
+ expect(store.state.tree[0].name).toBe('test');
+ expect(store.state.tree[0].type).toBe('tree');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('creates .gitkeep file in temp tree', (done) => {
+ store.dispatch('createTempTree', 'test')
+ .then(() => {
+ expect(store.state.tree[0].tree[0].tempFile).toBeTruthy();
+ expect(store.state.tree[0].tree[0].name).toBe('.gitkeep');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('creates new folder inside another tree', (done) => {
+ const tree = {
+ type: 'tree',
+ name: 'testing',
+ tree: [],
+ };
+
+ store.state.tree.push(tree);
+
+ store.dispatch('createTempTree', 'testing/test')
+ .then(() => {
+ expect(store.state.tree[0].name).toBe('testing');
+ expect(store.state.tree[0].tree[0].tempFile).toBeTruthy();
+ expect(store.state.tree[0].tree[0].name).toBe('test');
+ expect(store.state.tree[0].tree[0].type).toBe('tree');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('does not create new tree if already exists', (done) => {
+ const tree = {
+ type: 'tree',
+ name: 'testing',
+ tree: [],
+ };
+
+ store.state.tree.push(tree);
+
+ store.dispatch('createTempTree', 'testing/test')
+ .then(() => {
+ expect(store.state.tree[0].name).toBe('testing');
+ expect(store.state.tree[0].tempFile).toBeUndefined();
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('getLastCommitData', () => {
+ beforeEach(() => {
+ spyOn(service, 'getTreeLastCommit').and.returnValue(Promise.resolve({
+ headers: {
+ 'more-logs-url': null,
+ },
+ json: () => Promise.resolve([{
+ type: 'tree',
+ file_name: 'testing',
+ commit: {
+ message: 'commit message',
+ authored_date: '123',
+ },
+ }]),
+ }));
+
+ store.state.tree.push(file('testing', '1', 'tree'));
+ store.state.lastCommitPath = 'lastcommitpath';
+ });
+
+ it('calls service with lastCommitPath', (done) => {
+ store.dispatch('getLastCommitData')
+ .then(() => {
+ expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('updates trees last commit data', (done) => {
+ store.dispatch('getLastCommitData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.tree[0].lastCommit.message).toBe('commit message');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('does not update entry if not found', (done) => {
+ store.state.tree[0].name = 'a';
+
+ store.dispatch('getLastCommitData')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.tree[0].lastCommit.message).not.toBe('commit message');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('updateDirectoryData', () => {
+ it('adds data into tree', (done) => {
+ const tree = {
+ tree: [],
+ };
+ const data = {
+ trees: [{ name: 'tree' }],
+ submodules: [{ name: 'submodule' }],
+ blobs: [{ name: 'blob' }],
+ };
+
+ store.dispatch('updateDirectoryData', {
+ data,
+ tree,
+ }).then(() => {
+ expect(tree.tree[0].name).toBe('tree');
+ expect(tree.tree[0].type).toBe('tree');
+ expect(tree.tree[1].name).toBe('submodule');
+ expect(tree.tree[1].type).toBe('submodule');
+ expect(tree.tree[2].name).toBe('blob');
+ expect(tree.tree[2].type).toBe('blob');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
new file mode 100644
index 00000000000..f2a7a698912
--- /dev/null
+++ b/spec/javascripts/repo/stores/actions_spec.js
@@ -0,0 +1,419 @@
+import Vue from 'vue';
+import store from '~/repo/stores';
+import service from '~/repo/services';
+import { resetStore, file } from '../helpers';
+
+describe('Multi-file store actions', () => {
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ describe('redirectToUrl', () => {
+ it('calls visitUrl', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+
+ store.dispatch('redirectToUrl', 'test')
+ .then(() => {
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('test');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('setInitialData', () => {
+ it('commits initial data', (done) => {
+ store.dispatch('setInitialData', { canCommit: true })
+ .then(() => {
+ expect(store.state.canCommit).toBeTruthy();
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('closeDiscardPopup', () => {
+ it('closes the discard popup', (done) => {
+ store.dispatch('closeDiscardPopup', false)
+ .then(() => {
+ expect(store.state.discardPopupOpen).toBeFalsy();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('discardAllChanges', () => {
+ beforeEach(() => {
+ store.state.openFiles.push(file());
+ store.state.openFiles[0].changed = true;
+ });
+ });
+
+ describe('closeAllFiles', () => {
+ beforeEach(() => {
+ store.state.openFiles.push(file());
+ store.state.openFiles[0].opened = true;
+ });
+
+ it('closes all open files', (done) => {
+ store.dispatch('closeAllFiles')
+ .then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('toggleEditMode', () => {
+ it('toggles edit mode', (done) => {
+ store.state.editMode = true;
+
+ store.dispatch('toggleEditMode')
+ .then(() => {
+ expect(store.state.editMode).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('sets preview mode', (done) => {
+ store.state.currentBlobView = 'repo-editor';
+ store.state.editMode = true;
+
+ store.dispatch('toggleEditMode')
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.currentBlobView).toBe('repo-preview');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('opens discard popup if there are changed files', (done) => {
+ store.state.editMode = true;
+ store.state.openFiles.push(file());
+ store.state.openFiles[0].changed = true;
+
+ store.dispatch('toggleEditMode')
+ .then(() => {
+ expect(store.state.discardPopupOpen).toBeTruthy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('can force closed if there are changed files', (done) => {
+ store.state.editMode = true;
+ store.state.openFiles.push(file());
+ store.state.openFiles[0].changed = true;
+
+ store.dispatch('toggleEditMode', true)
+ .then(() => {
+ expect(store.state.discardPopupOpen).toBeFalsy();
+ expect(store.state.editMode).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('discards file changes', (done) => {
+ const f = file();
+ store.state.editMode = true;
+ store.state.tree.push(f);
+ store.state.openFiles.push(f);
+ f.changed = true;
+
+ store.dispatch('toggleEditMode', true)
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(f.changed).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('toggleBlobView', () => {
+ it('sets edit mode view if in edit mode', (done) => {
+ store.state.editMode = true;
+
+ store.dispatch('toggleBlobView')
+ .then(() => {
+ expect(store.state.currentBlobView).toBe('repo-editor');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('sets preview mode view if not in edit mode', (done) => {
+ store.dispatch('toggleBlobView')
+ .then(() => {
+ expect(store.state.currentBlobView).toBe('repo-preview');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('checkCommitStatus', () => {
+ beforeEach(() => {
+ store.state.project.id = 2;
+ store.state.currentBranch = 'master';
+ store.state.currentRef = '1';
+ });
+
+ it('calls service', (done) => {
+ spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
+ commit: { id: '123' },
+ }));
+
+ store.dispatch('checkCommitStatus')
+ .then(() => {
+ expect(service.getBranchData).toHaveBeenCalledWith(2, 'master');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('returns true if current ref does not equal returned ID', (done) => {
+ spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
+ commit: { id: '123' },
+ }));
+
+ store.dispatch('checkCommitStatus')
+ .then((val) => {
+ expect(val).toBeTruthy();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('returns false if current ref equals returned ID', (done) => {
+ spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
+ commit: { id: '1' },
+ }));
+
+ store.dispatch('checkCommitStatus')
+ .then((val) => {
+ expect(val).toBeFalsy();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('commitChanges', () => {
+ let payload;
+
+ beforeEach(() => {
+ spyOn(window, 'scrollTo');
+
+ document.body.innerHTML += '<div class="flash-container"></div>';
+
+ store.state.project.id = 123;
+ payload = {
+ branch: 'master',
+ };
+ });
+
+ afterEach(() => {
+ document.querySelector('.flash-container').remove();
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ spyOn(service, 'commit').and.returnValue(Promise.resolve({
+ id: '123456',
+ short_id: '123',
+ message: 'test message',
+ committed_date: 'date',
+ stats: {
+ additions: '1',
+ deletions: '2',
+ },
+ }));
+ });
+
+ it('calls service', (done) => {
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ expect(service.commit).toHaveBeenCalledWith(123, payload);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('shows flash notice', (done) => {
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ const alert = document.querySelector('.flash-container');
+
+ expect(alert.querySelector('.flash-notice')).not.toBeNull();
+ expect(alert.textContent.trim()).toBe(
+ 'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.',
+ );
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('adds commit data to changed files', (done) => {
+ const changedFile = file();
+ const f = file();
+ changedFile.changed = true;
+
+ store.state.openFiles.push(changedFile, f);
+
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ expect(changedFile.lastCommit.message).toBe('test message');
+ expect(f.lastCommit.message).not.toBe('test message');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('toggles edit mode', (done) => {
+ store.state.editMode = true;
+
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ expect(store.state.editMode).toBeFalsy();
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('closes all files', (done) => {
+ store.state.openFiles.push(file());
+ store.state.openFiles[0].opened = true;
+
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(Vue.nextTick)
+ .then(() => {
+ expect(store.state.openFiles.length).toBe(0);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('scrolls to top of page', (done) => {
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('updates commit ref', (done) => {
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ expect(store.state.currentRef).toBe('123456');
+
+ done();
+ }).catch(done.fail);
+ });
+
+ it('redirects to new merge request page', (done) => {
+ spyOn(gl.utils, 'visitUrl');
+
+ store.state.endpoints.newMergeRequestUrl = 'newMergeRequestUrl?branch=';
+
+ store.dispatch('commitChanges', { payload, newMr: true })
+ .then(() => {
+ expect(gl.utils.visitUrl).toHaveBeenCalledWith('newMergeRequestUrl?branch=master');
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+
+ describe('failed', () => {
+ beforeEach(() => {
+ spyOn(service, 'commit').and.returnValue(Promise.resolve({
+ message: 'failed message',
+ }));
+ });
+
+ it('shows failed message', (done) => {
+ store.dispatch('commitChanges', { payload, newMr: false })
+ .then(() => {
+ const alert = document.querySelector('.flash-container');
+
+ expect(alert.textContent.trim()).toBe(
+ 'failed message',
+ );
+
+ done();
+ }).catch(done.fail);
+ });
+ });
+ });
+
+ describe('createTempEntry', () => {
+ it('creates a temp tree', (done) => {
+ store.dispatch('createTempEntry', {
+ name: 'test',
+ type: 'tree',
+ })
+ .then(() => {
+ expect(store.state.tree.length).toBe(1);
+ expect(store.state.tree[0].tempFile).toBeTruthy();
+ expect(store.state.tree[0].type).toBe('tree');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('creates temp file', (done) => {
+ store.dispatch('createTempEntry', {
+ name: 'test',
+ type: 'blob',
+ })
+ .then(() => {
+ expect(store.state.tree.length).toBe(1);
+ expect(store.state.tree[0].tempFile).toBeTruthy();
+ expect(store.state.tree[0].type).toBe('blob');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('popHistoryState', () => {
+
+ });
+
+ describe('scrollToTab', () => {
+ it('focuses the current active element', (done) => {
+ document.body.innerHTML += '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
+ const el = document.querySelector('.repo-tab');
+ spyOn(el, 'focus');
+
+ store.dispatch('scrollToTab')
+ .then(() => {
+ setTimeout(() => {
+ expect(el.focus).toHaveBeenCalled();
+
+ document.getElementById('tabs').remove();
+
+ done();
+ });
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js
new file mode 100644
index 00000000000..952b8ec3a59
--- /dev/null
+++ b/spec/javascripts/repo/stores/getters_spec.js
@@ -0,0 +1,146 @@
+import * as getters from '~/repo/stores/getters';
+import state from '~/repo/stores/state';
+import { file } from '../helpers';
+
+describe('Multi-file store getters', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('treeList', () => {
+ it('returns flat tree list', () => {
+ localState.tree.push(file('1'));
+ localState.tree[0].tree.push(file('2'));
+ localState.tree[0].tree[0].tree.push(file('3'));
+
+ const treeList = getters.treeList(localState);
+
+ expect(treeList.length).toBe(3);
+ expect(treeList[1].name).toBe(localState.tree[0].tree[0].name);
+ expect(treeList[2].name).toBe(localState.tree[0].tree[0].tree[0].name);
+ });
+ });
+
+ describe('changedFiles', () => {
+ it('returns a list of changed opened files', () => {
+ localState.openFiles.push(file());
+ localState.openFiles.push(file('changed'));
+ localState.openFiles[1].changed = true;
+
+ const changedFiles = getters.changedFiles(localState);
+
+ expect(changedFiles.length).toBe(1);
+ expect(changedFiles[0].name).toBe('changed');
+ });
+ });
+
+ describe('activeFile', () => {
+ it('returns the current active file', () => {
+ localState.openFiles.push(file());
+ localState.openFiles.push(file('active'));
+ localState.openFiles[1].active = true;
+
+ expect(getters.activeFile(localState).name).toBe('active');
+ });
+
+ it('returns undefined if no active files are found', () => {
+ localState.openFiles.push(file());
+ localState.openFiles.push(file('active'));
+
+ expect(getters.activeFile(localState)).toBeUndefined();
+ });
+ });
+
+ describe('activeFileExtension', () => {
+ it('returns the file extension for the current active file', () => {
+ localState.openFiles.push(file('active'));
+ localState.openFiles[0].active = true;
+ localState.openFiles[0].path = 'test.js';
+
+ expect(getters.activeFileExtension(localState)).toBe('.js');
+
+ localState.openFiles[0].path = 'test.es6.js';
+
+ expect(getters.activeFileExtension(localState)).toBe('.js');
+ });
+ });
+
+ describe('isCollapsed', () => {
+ it('returns true if state has open files', () => {
+ localState.openFiles.push(file());
+
+ expect(getters.isCollapsed(localState)).toBeTruthy();
+ });
+
+ it('returns false if state has no open files', () => {
+ expect(getters.isCollapsed(localState)).toBeFalsy();
+ });
+ });
+
+ describe('canEditFile', () => {
+ beforeEach(() => {
+ localState.onTopOfBranch = true;
+ localState.canCommit = true;
+
+ localState.openFiles.push(file());
+ localState.openFiles[0].active = true;
+ });
+
+ it('returns true if user can commit and has open files', () => {
+ expect(getters.canEditFile(localState)).toBeTruthy();
+ });
+
+ it('returns false if user can commit and has no open files', () => {
+ localState.openFiles = [];
+
+ expect(getters.canEditFile(localState)).toBeFalsy();
+ });
+
+ it('returns false if user can commit and active file is binary', () => {
+ localState.openFiles[0].binary = true;
+
+ expect(getters.canEditFile(localState)).toBeFalsy();
+ });
+
+ it('returns false if user cant commit', () => {
+ localState.canCommit = false;
+
+ expect(getters.canEditFile(localState)).toBeFalsy();
+ });
+
+ it('returns false if user can commit but on a branch', () => {
+ localState.onTopOfBranch = false;
+
+ expect(getters.canEditFile(localState)).toBeFalsy();
+ });
+ });
+
+ describe('modifiedFiles', () => {
+ it('returns a list of modified files', () => {
+ localState.openFiles.push(file());
+ localState.openFiles.push(file('changed'));
+ localState.openFiles[1].changed = true;
+
+ const modifiedFiles = getters.modifiedFiles(localState);
+
+ expect(modifiedFiles.length).toBe(1);
+ expect(modifiedFiles[0].name).toBe('changed');
+ });
+ });
+
+ describe('addedFiles', () => {
+ it('returns a list of added files', () => {
+ localState.openFiles.push(file());
+ localState.openFiles.push(file('added'));
+ localState.openFiles[1].changed = true;
+ localState.openFiles[1].tempFile = true;
+
+ const modifiedFiles = getters.addedFiles(localState);
+
+ expect(modifiedFiles.length).toBe(1);
+ expect(modifiedFiles[0].name).toBe('added');
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/repo/stores/mutations/branch_spec.js
new file mode 100644
index 00000000000..3c06794d5e3
--- /dev/null
+++ b/spec/javascripts/repo/stores/mutations/branch_spec.js
@@ -0,0 +1,18 @@
+import mutations from '~/repo/stores/mutations/branch';
+import state from '~/repo/stores/state';
+
+describe('Multi-file store branch mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('SET_CURRENT_BRANCH', () => {
+ it('sets currentBranch', () => {
+ mutations.SET_CURRENT_BRANCH(localState, 'master');
+
+ expect(localState.currentBranch).toBe('master');
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
new file mode 100644
index 00000000000..2f2835dde1f
--- /dev/null
+++ b/spec/javascripts/repo/stores/mutations/file_spec.js
@@ -0,0 +1,131 @@
+import mutations from '~/repo/stores/mutations/file';
+import state from '~/repo/stores/state';
+import { file } from '../../helpers';
+
+describe('Multi-file store file mutations', () => {
+ let localState;
+ let localFile;
+
+ beforeEach(() => {
+ localState = state();
+ localFile = file();
+ });
+
+ describe('SET_FILE_ACTIVE', () => {
+ it('sets the file active', () => {
+ mutations.SET_FILE_ACTIVE(localState, {
+ file: localFile,
+ active: true,
+ });
+
+ expect(localFile.active).toBeTruthy();
+ });
+ });
+
+ describe('TOGGLE_FILE_OPEN', () => {
+ beforeEach(() => {
+ mutations.TOGGLE_FILE_OPEN(localState, localFile);
+ });
+
+ it('adds into opened files', () => {
+ expect(localFile.opened).toBeTruthy();
+ expect(localState.openFiles.length).toBe(1);
+ });
+
+ it('removes from opened files', () => {
+ mutations.TOGGLE_FILE_OPEN(localState, localFile);
+
+ expect(localFile.opened).toBeFalsy();
+ expect(localState.openFiles.length).toBe(0);
+ });
+ });
+
+ describe('SET_FILE_DATA', () => {
+ it('sets extra file data', () => {
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ blame_path: 'blame',
+ commits_path: 'commits',
+ permalink: 'permalink',
+ raw_path: 'raw',
+ binary: true,
+ html: 'html',
+ render_error: 'render_error',
+ },
+ file: localFile,
+ });
+
+ expect(localFile.blamePath).toBe('blame');
+ expect(localFile.commitsPath).toBe('commits');
+ expect(localFile.permalink).toBe('permalink');
+ expect(localFile.rawPath).toBe('raw');
+ expect(localFile.binary).toBeTruthy();
+ expect(localFile.html).toBe('html');
+ expect(localFile.renderError).toBe('render_error');
+ });
+ });
+
+ describe('SET_FILE_RAW_DATA', () => {
+ it('sets raw data', () => {
+ mutations.SET_FILE_RAW_DATA(localState, {
+ file: localFile,
+ raw: 'testing',
+ });
+
+ expect(localFile.raw).toBe('testing');
+ });
+ });
+
+ describe('UPDATE_FILE_CONTENT', () => {
+ beforeEach(() => {
+ localFile.raw = 'test';
+ });
+
+ it('sets content', () => {
+ mutations.UPDATE_FILE_CONTENT(localState, {
+ file: localFile,
+ content: 'test',
+ });
+
+ expect(localFile.content).toBe('test');
+ });
+
+ it('sets changed if content does not match raw', () => {
+ mutations.UPDATE_FILE_CONTENT(localState, {
+ file: localFile,
+ content: 'testing',
+ });
+
+ expect(localFile.content).toBe('testing');
+ expect(localFile.changed).toBeTruthy();
+ });
+ });
+
+ describe('DISCARD_FILE_CHANGES', () => {
+ beforeEach(() => {
+ localFile.content = 'test';
+ localFile.changed = true;
+ });
+
+ it('resets content and changed', () => {
+ mutations.DISCARD_FILE_CHANGES(localState, localFile);
+
+ expect(localFile.content).toBe('');
+ expect(localFile.changed).toBeFalsy();
+ });
+ });
+
+ describe('CREATE_TMP_FILE', () => {
+ it('adds file into parent tree', () => {
+ const f = file();
+
+ mutations.CREATE_TMP_FILE(localState, {
+ file: f,
+ parent: localFile,
+ });
+
+ expect(localFile.tree.length).toBe(1);
+ expect(localFile.tree[0].name).toBe(f.name);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
new file mode 100644
index 00000000000..1c76cfed9c8
--- /dev/null
+++ b/spec/javascripts/repo/stores/mutations/tree_spec.js
@@ -0,0 +1,71 @@
+import mutations from '~/repo/stores/mutations/tree';
+import state from '~/repo/stores/state';
+import { file } from '../../helpers';
+
+describe('Multi-file store tree mutations', () => {
+ let localState;
+ let localTree;
+
+ beforeEach(() => {
+ localState = state();
+ localTree = file();
+ });
+
+ describe('TOGGLE_TREE_OPEN', () => {
+ it('toggles tree open', () => {
+ mutations.TOGGLE_TREE_OPEN(localState, localTree);
+
+ expect(localTree.opened).toBeTruthy();
+
+ mutations.TOGGLE_TREE_OPEN(localState, localTree);
+
+ expect(localTree.opened).toBeFalsy();
+ });
+ });
+
+ describe('SET_DIRECTORY_DATA', () => {
+ const data = [{
+ name: 'tree',
+ },
+ {
+ name: 'submodule',
+ },
+ {
+ name: 'blob',
+ }];
+
+ it('adds directory data', () => {
+ mutations.SET_DIRECTORY_DATA(localState, {
+ data,
+ tree: localState,
+ });
+
+ expect(localState.tree.length).toBe(3);
+ expect(localState.tree[0].name).toBe('tree');
+ expect(localState.tree[1].name).toBe('submodule');
+ expect(localState.tree[2].name).toBe('blob');
+ });
+ });
+
+ describe('SET_PARENT_TREE_URL', () => {
+ it('sets the parent tree url', () => {
+ mutations.SET_PARENT_TREE_URL(localState, 'test');
+
+ expect(localState.parentTreeUrl).toBe('test');
+ });
+ });
+
+ describe('CREATE_TMP_TREE', () => {
+ it('adds tree into parent tree', () => {
+ const tmpEntry = file();
+
+ mutations.CREATE_TMP_TREE(localState, {
+ tmpEntry,
+ parent: localTree,
+ });
+
+ expect(localTree.tree.length).toBe(1);
+ expect(localTree.tree[0].name).toBe(tmpEntry.name);
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js
new file mode 100644
index 00000000000..d1c9885e01d
--- /dev/null
+++ b/spec/javascripts/repo/stores/mutations_spec.js
@@ -0,0 +1,117 @@
+import mutations from '~/repo/stores/mutations';
+import state from '~/repo/stores/state';
+import { file } from '../helpers';
+
+describe('Multi-file store mutations', () => {
+ let localState;
+ let entry;
+
+ beforeEach(() => {
+ localState = state();
+ entry = file();
+ });
+
+ describe('SET_INITIAL_DATA', () => {
+ it('sets all initial data', () => {
+ mutations.SET_INITIAL_DATA(localState, {
+ test: 'test',
+ });
+
+ expect(localState.test).toBe('test');
+ });
+ });
+
+ describe('SET_PREVIEW_MODE', () => {
+ it('sets currentBlobView to repo-preview', () => {
+ mutations.SET_PREVIEW_MODE(localState);
+
+ expect(localState.currentBlobView).toBe('repo-preview');
+
+ localState.currentBlobView = 'testing';
+
+ mutations.SET_PREVIEW_MODE(localState);
+
+ expect(localState.currentBlobView).toBe('repo-preview');
+ });
+ });
+
+ describe('SET_EDIT_MODE', () => {
+ it('sets currentBlobView to repo-editor', () => {
+ mutations.SET_EDIT_MODE(localState);
+
+ expect(localState.currentBlobView).toBe('repo-editor');
+
+ localState.currentBlobView = 'testing';
+
+ mutations.SET_EDIT_MODE(localState);
+
+ expect(localState.currentBlobView).toBe('repo-editor');
+ });
+ });
+
+ describe('TOGGLE_LOADING', () => {
+ it('toggles loading of entry', () => {
+ mutations.TOGGLE_LOADING(localState, entry);
+
+ expect(entry.loading).toBeTruthy();
+
+ mutations.TOGGLE_LOADING(localState, entry);
+
+ expect(entry.loading).toBeFalsy();
+ });
+ });
+
+ describe('TOGGLE_EDIT_MODE', () => {
+ it('toggles editMode', () => {
+ mutations.TOGGLE_EDIT_MODE(localState);
+
+ expect(localState.editMode).toBeTruthy();
+
+ mutations.TOGGLE_EDIT_MODE(localState);
+
+ expect(localState.editMode).toBeFalsy();
+ });
+ });
+
+ describe('TOGGLE_DISCARD_POPUP', () => {
+ it('sets discardPopupOpen', () => {
+ mutations.TOGGLE_DISCARD_POPUP(localState, true);
+
+ expect(localState.discardPopupOpen).toBeTruthy();
+
+ mutations.TOGGLE_DISCARD_POPUP(localState, false);
+
+ expect(localState.discardPopupOpen).toBeFalsy();
+ });
+ });
+
+ describe('SET_COMMIT_REF', () => {
+ it('sets currentRef', () => {
+ mutations.SET_COMMIT_REF(localState, '123');
+
+ expect(localState.currentRef).toBe('123');
+ });
+ });
+
+ describe('SET_ROOT', () => {
+ it('sets isRoot & initialRoot', () => {
+ mutations.SET_ROOT(localState, true);
+
+ expect(localState.isRoot).toBeTruthy();
+ expect(localState.isInitialRoot).toBeTruthy();
+
+ mutations.SET_ROOT(localState, false);
+
+ expect(localState.isRoot).toBeFalsy();
+ expect(localState.isInitialRoot).toBeFalsy();
+ });
+ });
+
+ describe('SET_PREVIOUS_URL', () => {
+ it('sets previousUrl', () => {
+ mutations.SET_PREVIOUS_URL(localState, 'testing');
+
+ expect(localState.previousUrl).toBe('testing');
+ });
+ });
+});
diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js
new file mode 100644
index 00000000000..37287c587d7
--- /dev/null
+++ b/spec/javascripts/repo/stores/utils_spec.js
@@ -0,0 +1,102 @@
+import * as utils from '~/repo/stores/utils';
+
+describe('Multi-file store utils', () => {
+ describe('setPageTitle', () => {
+ it('sets the document page title', () => {
+ utils.setPageTitle('test');
+
+ expect(document.title).toBe('test');
+ });
+ });
+
+ describe('pushState', () => {
+ it('calls history.pushState', () => {
+ spyOn(history, 'pushState');
+
+ utils.pushState('test');
+
+ expect(history.pushState).toHaveBeenCalledWith({ url: 'test' }, '', 'test');
+ });
+ });
+
+ describe('createTemp', () => {
+ it('creates temp tree', () => {
+ const tmp = utils.createTemp({
+ name: 'test',
+ path: 'test',
+ type: 'tree',
+ level: 0,
+ changed: false,
+ content: '',
+ base64: '',
+ });
+
+ expect(tmp.tempFile).toBeTruthy();
+ expect(tmp.icon).toBe('fa-folder');
+ });
+
+ it('creates temp file', () => {
+ const tmp = utils.createTemp({
+ name: 'test',
+ path: 'test',
+ type: 'blob',
+ level: 0,
+ changed: false,
+ content: '',
+ base64: '',
+ });
+
+ expect(tmp.tempFile).toBeTruthy();
+ expect(tmp.icon).toBe('fa-file-text-o');
+ });
+ });
+
+ describe('findIndexOfFile', () => {
+ let state;
+
+ beforeEach(() => {
+ state = [{
+ path: '1',
+ }, {
+ path: '2',
+ }];
+ });
+
+ it('finds in the index of an entry by path', () => {
+ const index = utils.findIndexOfFile(state, {
+ path: '2',
+ });
+
+ expect(index).toBe(1);
+ });
+ });
+
+ describe('findEntry', () => {
+ let state;
+
+ beforeEach(() => {
+ state = {
+ tree: [{
+ type: 'tree',
+ name: 'test',
+ }, {
+ type: 'blob',
+ name: 'file',
+ }],
+ };
+ });
+
+ it('returns an entry found by name', () => {
+ const foundEntry = utils.findEntry(state, 'tree', 'test');
+
+ expect(foundEntry.type).toBe('tree');
+ expect(foundEntry.name).toBe('test');
+ });
+
+ it('returns undefined when no entry found', () => {
+ const foundEntry = utils.findEntry(state, 'blob', 'test');
+
+ expect(foundEntry).toBeUndefined();
+ });
+ });
+});
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index a2394857b82..fdfc59a6f12 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -191,8 +191,6 @@ import '~/lib/utils/common_utils';
// browsers will not trigger default behavior (form submit, in this
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenTriggered();
- // Does a worse job at capturing the intent of the test, but works.
- expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
});
});
}).call(window);
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index 0682b463043..3b094d20838 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -1,6 +1,6 @@
/* eslint-disable quote-props*/
-const sidebarMockData = {
+const RESPONSE_MAP = {
'GET': {
'/gitlab-org/gitlab-shell/issues/5.json': {
id: 45,
@@ -66,6 +66,65 @@ const sidebarMockData = {
},
labels: [],
},
+ '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': {
+ assignees: [
+ {
+ name: 'User 0',
+ username: 'user0',
+ id: 22,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/user0',
+ },
+ {
+ name: 'Marguerite Bartell',
+ username: 'tajuana',
+ id: 18,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/tajuana',
+ },
+ {
+ name: 'Laureen Ritchie',
+ username: 'michaele.will',
+ id: 16,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/michaele.will',
+ },
+ ],
+ human_time_estimate: null,
+ human_total_time_spent: null,
+ participants: [
+ {
+ name: 'User 0',
+ username: 'user0',
+ id: 22,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/user0',
+ },
+ {
+ name: 'Marguerite Bartell',
+ username: 'tajuana',
+ id: 18,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/tajuana',
+ },
+ {
+ name: 'Laureen Ritchie',
+ username: 'michaele.will',
+ id: 16,
+ state: 'active',
+ avatar_url: 'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
+ web_url: 'http: //localhost:3001/michaele.will',
+ },
+ ],
+ subscribed: true,
+ time_estimate: 0,
+ total_time_spent: 0,
+ },
'/autocomplete/projects?project_id=15': [
{
'id': 0,
@@ -113,9 +172,10 @@ const sidebarMockData = {
},
};
-export default {
+const mockData = {
+ responseMap: RESPONSE_MAP,
mediator: {
- endpoint: '/gitlab-org/gitlab-shell/issues/5.json',
+ endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar',
toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
@@ -141,12 +201,14 @@ export default {
name: 'Administrator',
username: 'root',
},
+};
- sidebarMockInterceptor(request, next) {
- const body = sidebarMockData[request.method.toUpperCase()][request.url];
+mockData.sidebarMockInterceptor = function (request, next) {
+ const body = this.responseMap[request.method.toUpperCase()][request.url];
- next(request.respondWith(JSON.stringify(body), {
- status: 200,
- }));
- },
-};
+ next(request.respondWith(JSON.stringify(body), {
+ status: 200,
+ }));
+}.bind(mockData);
+
+export default mockData;
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 7deb1fd2118..14c34d5a78c 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -33,10 +33,29 @@ describe('Sidebar mediator', () => {
.catch(done.fail);
});
- it('fetches the data', () => {
- spyOn(this.mediator.service, 'get').and.callThrough();
- this.mediator.fetch();
- expect(this.mediator.service.get).toHaveBeenCalled();
+ it('fetches the data', (done) => {
+ const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
+ spyOn(this.mediator, 'processFetchedData').and.callThrough();
+
+ this.mediator.fetch()
+ .then(() => {
+ expect(this.mediator.processFetchedData).toHaveBeenCalledWith(mockData);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('processes fetched data', () => {
+ const mockData = Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
+ this.mediator.processFetchedData(mockData);
+
+ expect(this.mediator.store.assignees).toEqual(mockData.assignees);
+ expect(this.mediator.store.humanTimeEstimate).toEqual(mockData.human_time_estimate);
+ expect(this.mediator.store.humanTotalTimeSpent).toEqual(mockData.human_total_time_spent);
+ expect(this.mediator.store.participants).toEqual(mockData.participants);
+ expect(this.mediator.store.subscribed).toEqual(mockData.subscribed);
+ expect(this.mediator.store.timeEstimate).toEqual(mockData.time_estimate);
+ expect(this.mediator.store.totalTimeSpent).toEqual(mockData.total_time_spent);
});
it('sets moveToProjectId', () => {
diff --git a/spec/javascripts/sidebar/sidebar_service_spec.js b/spec/javascripts/sidebar/sidebar_service_spec.js
deleted file mode 100644
index 7324d34d84a..00000000000
--- a/spec/javascripts/sidebar/sidebar_service_spec.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import Vue from 'vue';
-import SidebarService from '~/sidebar/services/sidebar_service';
-import Mock from './mock_data';
-
-describe('Sidebar service', () => {
- beforeEach(() => {
- Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
- this.service = new SidebarService({
- endpoint: '/gitlab-org/gitlab-shell/issues/5.json',
- toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
- moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
- projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
- });
- });
-
- afterEach(() => {
- SidebarService.singleton = null;
- Vue.http.interceptors = _.without(Vue.http.interceptors, Mock.sidebarMockInterceptor);
- });
-
- it('gets the data', (done) => {
- this.service.get()
- .then((resp) => {
- expect(resp).toBeDefined();
- done();
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('updates the data', (done) => {
- this.service.update('issue[assignee_ids]', [1])
- .then((resp) => {
- expect(resp).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('gets projects for autocomplete', (done) => {
- this.service.getProjectsAutocomplete()
- .then((resp) => {
- expect(resp).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('moves the issue to another project', (done) => {
- this.service.moveIssue(123)
- .then((resp) => {
- expect(resp).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('toggles the subscription', (done) => {
- this.service.toggleSubscription()
- .then((resp) => {
- expect(resp).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
-});
diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js
index 51dee64fb93..ea4eae1e23f 100644
--- a/spec/javascripts/sidebar/sidebar_store_spec.js
+++ b/spec/javascripts/sidebar/sidebar_store_spec.js
@@ -120,6 +120,12 @@ describe('Sidebar store', () => {
expect(this.store.isFetching.participants).toEqual(false);
});
+ it('sets loading state', () => {
+ this.store.setLoadingState('assignees', true);
+
+ expect(this.store.isLoading.assignees).toEqual(true);
+ });
+
it('set time tracking data', () => {
this.store.setTimeTrackingData(Mock.time);
expect(this.store.timeEstimate).toEqual(Mock.time.time_estimate);
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 33ed0cb4342..d7af956c9c1 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
@@ -1,140 +1,147 @@
import Vue from 'vue';
-import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline';
+import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
import mockData from '../mock_data';
-const createComponent = (mr) => {
- const Component = Vue.extend(pipelineComponent);
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
-
describe('MRWidgetPipeline', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = pipelineComponent.props;
+ let vm;
+ let Component;
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(pipelineComponent);
});
- describe('components', () => {
- it('should have components added', () => {
- expect(pipelineComponent.components['pipeline-stage']).toBeDefined();
- expect(pipelineComponent.components.ciIcon).toBeDefined();
- });
+ afterEach(() => {
+ vm.$destroy();
});
describe('computed', () => {
describe('hasPipeline', () => {
it('should return true when there is a pipeline', () => {
- expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0);
-
- const vm = createComponent({
+ vm = mountComponent(Component, {
pipeline: mockData.pipeline,
+ ciStatus: 'success',
+ hasCi: true,
});
- expect(vm.hasPipeline).toBeTruthy();
+ expect(vm.hasPipeline).toEqual(true);
});
it('should return false when there is no pipeline', () => {
- const vm = createComponent({
- pipeline: null,
+ vm = mountComponent(Component, {
+ pipeline: {},
});
- expect(vm.hasPipeline).toBeFalsy();
+ expect(vm.hasPipeline).toEqual(false);
});
});
describe('hasCIError', () => {
it('should return false when there is no CI error', () => {
- const vm = createComponent({
+ vm = mountComponent(Component, {
pipeline: mockData.pipeline,
- hasCI: true,
+ hasCi: true,
ciStatus: 'success',
});
- expect(vm.hasCIError).toBeFalsy();
+ expect(vm.hasCIError).toEqual(false);
});
it('should return true when there is a CI error', () => {
- const vm = createComponent({
+ vm = mountComponent(Component, {
pipeline: mockData.pipeline,
- hasCI: true,
+ hasCi: true,
ciStatus: null,
});
- expect(vm.hasCIError).toBeTruthy();
+ expect(vm.hasCIError).toEqual(true);
});
});
});
- describe('template', () => {
- let vm;
- let el;
- const { pipeline } = mockData;
- const mr = {
- hasCI: true,
- ciStatus: 'success',
- pipelineDetailedStatus: pipeline.details.status,
- pipeline,
- };
-
- beforeEach(() => {
- vm = createComponent(mr);
- el = vm.$el;
- });
+ describe('rendered output', () => {
+ it('should render CI error', () => {
+ vm = mountComponent(Component, {
+ pipeline: mockData.pipeline,
+ hasCi: true,
+ ciStatus: null,
+ });
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
- expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
- expect(el.querySelector('.pipeline-id').textContent).toContain(`#${pipeline.id}`);
- expect(el.innerText).toContain('passed');
- expect(el.querySelector('.pipeline-id').getAttribute('href')).toEqual(pipeline.path);
- expect(el.querySelectorAll('.stage-container').length).toEqual(2);
- expect(el.querySelector('.js-ci-error')).toEqual(null);
- expect(el.querySelector('.js-commit-link').getAttribute('href')).toEqual(pipeline.commit.commit_path);
- expect(el.querySelector('.js-commit-link').textContent).toContain(pipeline.commit.short_id);
- expect(el.querySelector('.js-mr-coverage').textContent).toContain(`Coverage ${pipeline.coverage}%`);
+ expect(
+ vm.$el.querySelector('.media-body').textContent.trim(),
+ ).toEqual('Could not connect to the CI server. Please check your settings and try again');
});
- it('should list single stage', (done) => {
- pipeline.details.stages.splice(0, 1);
+ describe('with a pipeline', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ pipeline: mockData.pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ });
+ });
+
+ it('should render pipeline ID', () => {
+ expect(
+ vm.$el.querySelector('.pipeline-id').textContent.trim(),
+ ).toEqual(`#${mockData.pipeline.id}`);
+ });
+
+ it('should render pipeline status and commit id', () => {
+ expect(
+ vm.$el.querySelector('.media-body').textContent.trim(),
+ ).toContain(mockData.pipeline.details.status.label);
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
- done();
+ expect(
+ vm.$el.querySelector('.js-commit-link').textContent.trim(),
+ ).toEqual(mockData.pipeline.commit.short_id);
+
+ expect(
+ vm.$el.querySelector('.js-commit-link').getAttribute('href'),
+ ).toEqual(mockData.pipeline.commit.commit_path);
});
- });
- it('should not have stages when there is no stage', (done) => {
- vm.mr.pipeline.details.stages = [];
+ it('should render pipeline graph', () => {
+ expect(vm.$el.querySelector('.mr-widget-pipeline-graph')).toBeDefined();
+ expect(vm.$el.querySelectorAll('.stage-container').length).toEqual(mockData.pipeline.details.stages.length);
+ });
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
- done();
+ it('should render coverage information', () => {
+ expect(
+ vm.$el.querySelector('.media-body').textContent,
+ ).toContain(`Coverage ${mockData.pipeline.coverage}`);
});
});
- it('should not have coverage text when pipeline has no coverage info', (done) => {
- vm.mr.pipeline.coverage = null;
+ describe('without coverage', () => {
+ it('should not render a coverage', () => {
+ const mockCopy = Object.assign({}, mockData);
+ delete mockCopy.pipeline.coverage;
- Vue.nextTick(() => {
- expect(el.querySelector('.js-mr-coverage')).toEqual(null);
- done();
+ vm = mountComponent(Component, {
+ pipeline: mockCopy.pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ });
+
+ expect(
+ vm.$el.querySelector('.media-body').textContent,
+ ).not.toContain('Coverage');
});
});
- it('should show CI error when there is a CI error', (done) => {
- vm.mr.ciStatus = null;
+ describe('without a pipeline graph', () => {
+ it('should not render a pipeline graph', () => {
+ const mockCopy = Object.assign({}, mockData);
+ delete mockCopy.pipeline.details.stages;
+
+ vm = mountComponent(Component, {
+ pipeline: mockCopy.pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ });
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
- expect(el.innerText).toContain('Could not connect to the CI server');
- expect(el.querySelector('.ci-status-icon svg use').getAttribute('xlink:href')).toContain('status_failed');
- done();
+ expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 0795d0aaa82..1ad7c2d8efa 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -202,7 +202,6 @@ export default {
"revert_in_fork_path": "/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",
- "ci_status_path": "/root/acets-app/merge_requests/22/ci_status",
"status_path": "/root/acets-app/merge_requests/22.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",
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 8832dd161c7..9e6d0aa472c 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -3,13 +3,7 @@ import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import mockData from './mock_data';
-
-const createComponent = () => {
- delete mrWidgetOptions.el; // Prevent component mounting
- gl.mrWidgetData = mockData;
- const Component = Vue.extend(mrWidgetOptions);
- return new Component();
-};
+import mountComponent from '../helpers/vue_mount_component_helper';
const returnPromise = data => new Promise((resolve) => {
resolve({
@@ -22,9 +16,16 @@ const returnPromise = data => new Promise((resolve) => {
describe('mrWidgetOptions', () => {
let vm;
+ let MrWidgetOptions;
beforeEach(() => {
- vm = createComponent();
+ // Prevent component mounting
+ delete mrWidgetOptions.el;
+
+ MrWidgetOptions = Vue.extend(mrWidgetOptions);
+ vm = mountComponent(MrWidgetOptions, {
+ mrData: { ...mockData },
+ });
});
describe('data', () => {
@@ -77,7 +78,7 @@ describe('mrWidgetOptions', () => {
});
it('should return true if there is relatedLinks in MR', () => {
- vm.mr.relatedLinks = {};
+ Vue.set(vm.mr, 'relatedLinks', {});
expect(vm.shouldRenderRelatedLinks).toBeTruthy();
});
});
diff --git a/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js b/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js
deleted file mode 100644
index e667b4b3677..00000000000
--- a/spec/javascripts/vue_mr_widget/services/mr_widget_service_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-
-Vue.use(VueResource);
-
-describe('MRWidgetService', () => {
- const mr = {
- mergePath: './',
- mergeCheckPath: './',
- cancelAutoMergePath: './',
- removeWIPPath: './',
- sourceBranchPath: './',
- ciEnvironmentsStatusPath: './',
- statusPath: './',
- mergeActionsContentPath: './',
- isServiceStore: true,
- };
-
- it('should have store and resources created in constructor', () => {
- const service = new MRWidgetService(mr);
-
- expect(service.mergeResource).toBeDefined();
- expect(service.mergeCheckResource).toBeDefined();
- expect(service.cancelAutoMergeResource).toBeDefined();
- expect(service.removeWIPResource).toBeDefined();
- expect(service.removeSourceBranchResource).toBeDefined();
- expect(service.deploymentsResource).toBeDefined();
- expect(service.pollResource).toBeDefined();
- expect(service.mergeActionsContentResource).toBeDefined();
- });
-
- it('should have methods defined', () => {
- window.history.pushState({}, null, '/');
- const service = new MRWidgetService(mr);
-
- expect(service.merge()).toBeDefined();
- expect(service.cancelAutomaticMerge()).toBeDefined();
- expect(service.removeWIP()).toBeDefined();
- expect(service.removeSourceBranch()).toBeDefined();
- expect(service.fetchDeployments()).toBeDefined();
- expect(service.poll()).toBeDefined();
- expect(service.checkStatus()).toBeDefined();
- expect(service.fetchMergeActionsContent()).toBeDefined();
- expect(MRWidgetService.stopEnvironment()).toBeDefined();
- });
-});
diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js
index 104da4473ce..a22b6bd3a67 100644
--- a/spec/javascripts/vue_shared/components/icon_spec.js
+++ b/spec/javascripts/vue_shared/components/icon_spec.js
@@ -11,7 +11,7 @@ describe('Sprite Icon Component', function () {
icon = mountComponent(IconComponent, {
name: 'test',
- size: 99,
+ size: 32,
cssClasses: 'extraclasses',
});
});
@@ -34,12 +34,18 @@ describe('Sprite Icon Component', function () {
});
it('should properly compute iconSizeClass', function () {
- expect(icon.iconSizeClass).toBe('s99');
+ expect(icon.iconSizeClass).toBe('s32');
+ });
+
+ it('forbids invalid size prop', () => {
+ expect(icon.$options.props.size.validator(NaN)).toBeFalsy();
+ expect(icon.$options.props.size.validator(0)).toBeFalsy();
+ expect(icon.$options.props.size.validator(9001)).toBeFalsy();
});
it('should properly render img css', function () {
const classList = icon.$el.classList;
- const containsSizeClass = classList.contains('s99');
+ const containsSizeClass = classList.contains('s32');
const containsCustomClass = classList.contains('extraclasses');
expect(containsSizeClass).toBe(true);
expect(containsCustomClass).toBe(true);
diff --git a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js
index 2cf4d8e00ed..24484796bf1 100644
--- a/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js
+++ b/spec/javascripts/vue_shared/components/issue/issue_warning_spec.js
@@ -16,7 +16,7 @@ describe('Issue Warning Component', () => {
isLocked: true,
});
- expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-lock');
+ expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/lock$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is locked. Only project members can comment.');
});
});
@@ -27,7 +27,7 @@ describe('Issue Warning Component', () => {
isConfidential: true,
});
- expect(vm.$el.querySelector('i').className).toEqual('fa icon fa-eye-slash');
+ expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/eye-slash$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This is a confidential issue. Your comment will not be visible to the public.');
});
});
@@ -39,7 +39,7 @@ describe('Issue Warning Component', () => {
isConfidential: true,
});
- expect(vm.$el.querySelector('i')).toBeFalsy();
+ expect(vm.$el.querySelector('.icon')).toBeFalsy();
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is confidential and locked. People without permission will never get a notification and won\'t be able to comment.');
});
});
diff --git a/spec/javascripts/vue_shared/components/loading_button_spec.js b/spec/javascripts/vue_shared/components/loading_button_spec.js
index 97c8a08fcdd..49bf8ee6f7c 100644
--- a/spec/javascripts/vue_shared/components/loading_button_spec.js
+++ b/spec/javascripts/vue_shared/components/loading_button_spec.js
@@ -66,6 +66,23 @@ describe('LoadingButton', function () {
});
});
+ describe('container class', () => {
+ it('should default to btn btn-align-content', () => {
+ vm = mountComponent(LoadingButton, {});
+ expect(vm.$el.classList.contains('btn')).toEqual(true);
+ expect(vm.$el.classList.contains('btn-align-content')).toEqual(true);
+ });
+
+ it('should be configurable through props', () => {
+ vm = mountComponent(LoadingButton, {
+ containerClass: 'test-class',
+ });
+ expect(vm.$el.classList.contains('btn')).toEqual(false);
+ expect(vm.$el.classList.contains('btn-align-content')).toEqual(false);
+ expect(vm.$el.classList.contains('test-class')).toEqual(true);
+ });
+ });
+
describe('click callback prop', () => {
it('calls given callback when normal', () => {
vm = mountComponent(LoadingButton, {
@@ -81,7 +98,6 @@ describe('LoadingButton', function () {
it('does not call given callback when disabled because of loading', () => {
vm = mountComponent(LoadingButton, {
loading: true,
- indeterminate: true,
});
spyOn(vm, '$emit');
diff --git a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
new file mode 100644
index 00000000000..818ef0af3c2
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import toolbar from '~/vue_shared/components/markdown/toolbar.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('toolbar', () => {
+ let vm;
+ const Toolbar = Vue.extend(toolbar);
+ const props = {
+ markdownDocsPath: '',
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('user can attach file', () => {
+ beforeEach(() => {
+ vm = mountComponent(Toolbar, props);
+ });
+
+ it('should render uploading-container', () => {
+ expect(vm.$el.querySelector('.uploading-container')).not.toBeNull();
+ });
+ });
+
+ describe('user cannot attach file', () => {
+ beforeEach(() => {
+ vm = mountComponent(Toolbar, Object.assign({}, props, {
+ canAttachFile: false,
+ }));
+ });
+
+ it('should not render uploading-container', () => {
+ expect(vm.$el.querySelector('.uploading-container')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/navigation_tabs_spec.js b/spec/javascripts/vue_shared/components/navigation_tabs_spec.js
new file mode 100644
index 00000000000..78e7d747b92
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/navigation_tabs_spec.js
@@ -0,0 +1,61 @@
+import Vue from 'vue';
+import navigationTabs from '~/vue_shared/components/navigation_tabs.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('navigation tabs component', () => {
+ let vm;
+ let Component;
+ let data;
+
+ beforeEach(() => {
+ data = [
+ {
+ name: 'All',
+ scope: 'all',
+ count: 1,
+ isActive: true,
+ },
+ {
+ name: 'Pending',
+ scope: 'pending',
+ count: 0,
+ isActive: false,
+ },
+ {
+ name: 'Running',
+ scope: 'running',
+ isActive: false,
+ },
+ ];
+
+ Component = Vue.extend(navigationTabs);
+ vm = mountComponent(Component, { tabs: data, scope: 'pipelines' });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render tabs', () => {
+ expect(vm.$el.querySelectorAll('li').length).toEqual(data.length);
+ });
+
+ it('should render active tab', () => {
+ expect(vm.$el.querySelector('.active .js-pipelines-tab-all')).toBeDefined();
+ });
+
+ it('should render badge', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all .badge').textContent.trim()).toEqual('1');
+ expect(vm.$el.querySelector('.js-pipelines-tab-pending .badge').textContent.trim()).toEqual('0');
+ });
+
+ it('should not render badge', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-running .badge')).toEqual(null);
+ });
+
+ it('should trigger onTabClick', () => {
+ spyOn(vm, '$emit');
+ vm.$el.querySelector('.js-pipelines-tab-pending').click();
+ expect(vm.$emit).toHaveBeenCalledWith('onChangeTab', 'pending');
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/pikaday_spec.js b/spec/javascripts/vue_shared/components/pikaday_spec.js
new file mode 100644
index 00000000000..47af9534737
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/pikaday_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import datePicker from '~/vue_shared/components/pikaday.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('datePicker', () => {
+ let vm;
+ beforeEach(() => {
+ const DatePicker = Vue.extend(datePicker);
+ vm = mountComponent(DatePicker, {
+ label: 'label',
+ });
+ });
+
+ it('should render label text', () => {
+ expect(vm.$el.querySelector('.dropdown-toggle-text').innerText.trim()).toEqual('label');
+ });
+
+ it('should show calendar', () => {
+ expect(vm.$el.querySelector('.pika-single')).toBeDefined();
+ });
+
+ it('should toggle when dropdown is clicked', () => {
+ const hidePicker = jasmine.createSpy();
+ vm.$on('hidePicker', hidePicker);
+
+ vm.$el.querySelector('.dropdown-menu-toggle').click();
+ expect(hidePicker).toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
new file mode 100644
index 00000000000..cce53193870
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('collapsedCalendarIcon', () => {
+ let vm;
+ beforeEach(() => {
+ const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon);
+ vm = mountComponent(CollapsedCalendarIcon, {
+ containerClass: 'test-class',
+ text: 'text',
+ showIcon: false,
+ });
+ });
+
+ it('should add class to container', () => {
+ expect(vm.$el.classList.contains('test-class')).toEqual(true);
+ });
+
+ it('should hide calendar icon if showIcon', () => {
+ expect(vm.$el.querySelector('.fa-calendar')).toBeNull();
+ });
+
+ it('should render text', () => {
+ expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text');
+ });
+
+ it('should emit click event when container is clicked', () => {
+ const click = jasmine.createSpy();
+ vm.$on('click', click);
+
+ vm.$el.click();
+ expect(click).toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
new file mode 100644
index 00000000000..20363e78094
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('collapsedGroupedDatePicker', () => {
+ let vm;
+ beforeEach(() => {
+ const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker);
+ vm = mountComponent(CollapsedGroupedDatePicker, {
+ showToggleSidebar: true,
+ });
+ });
+
+ it('should render toggle sidebar if showToggleSidebar', (done) => {
+ expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeDefined();
+
+ vm.showToggleSidebar = false;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.issuable-sidebar-header')).toBeNull();
+ done();
+ });
+ });
+
+ it('toggleCollapse events', () => {
+ const toggleCollapse = jasmine.createSpy();
+
+ beforeEach((done) => {
+ vm.minDate = new Date('07/17/2016');
+ Vue.nextTick(done);
+ });
+
+ it('should emit when sidebar is toggled', () => {
+ vm.$el.querySelector('.gutter-toggle').click();
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+
+ it('should emit when collapsed-calendar-icon is clicked', () => {
+ vm.$el.querySelector('.sidebar-collapsed-icon').click();
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+ });
+
+ describe('minDate and maxDate', () => {
+ beforeEach((done) => {
+ vm.minDate = new Date('07/17/2016');
+ vm.maxDate = new Date('07/17/2017');
+ Vue.nextTick(done);
+ });
+
+ it('should render both collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+ expect(icons.length).toEqual(2);
+ expect(icons[0].innerText.trim()).toEqual('Jul 17 2016');
+ expect(icons[1].innerText.trim()).toEqual('Jul 17 2017');
+ });
+ });
+
+ describe('minDate', () => {
+ beforeEach((done) => {
+ vm.minDate = new Date('07/17/2016');
+ Vue.nextTick(done);
+ });
+
+ it('should render minDate in collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016');
+ });
+ });
+
+ describe('maxDate', () => {
+ beforeEach((done) => {
+ vm.maxDate = new Date('07/17/2017');
+ Vue.nextTick(done);
+ });
+
+ it('should render maxDate in collapsed-calendar-icon', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017');
+ });
+ });
+
+ describe('no dates', () => {
+ it('should render None', () => {
+ const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
+ expect(icons.length).toEqual(1);
+ expect(icons[0].innerText.trim()).toEqual('None');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
new file mode 100644
index 00000000000..926e11b4d30
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
@@ -0,0 +1,117 @@
+import Vue from 'vue';
+import sidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('sidebarDatePicker', () => {
+ let vm;
+ beforeEach(() => {
+ const SidebarDatePicker = Vue.extend(sidebarDatePicker);
+ vm = mountComponent(SidebarDatePicker, {
+ label: 'label',
+ isLoading: true,
+ });
+ });
+
+ it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
+ const toggleCollapse = jasmine.createSpy();
+ vm.$on('toggleCollapse', toggleCollapse);
+
+ vm.$el.querySelector('.issuable-sidebar-header .gutter-toggle').click();
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+
+ it('should render collapsed-calendar-icon', () => {
+ expect(vm.$el.querySelector('.sidebar-collapsed-icon')).toBeDefined();
+ });
+
+ it('should render label', () => {
+ expect(vm.$el.querySelector('.title').innerText.trim()).toEqual('label');
+ });
+
+ it('should render loading-icon when isLoading', () => {
+ expect(vm.$el.querySelector('.fa-spin')).toBeDefined();
+ });
+
+ it('should render value when not editing', () => {
+ expect(vm.$el.querySelector('.value-content')).toBeDefined();
+ });
+
+ it('should render None if there is no selectedDate', () => {
+ expect(vm.$el.querySelector('.value-content span').innerText.trim()).toEqual('None');
+ });
+
+ it('should render date-picker when editing', (done) => {
+ vm.editing = true;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.pika-label')).toBeDefined();
+ done();
+ });
+ });
+
+ describe('editable', () => {
+ beforeEach((done) => {
+ vm.editable = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render edit button', () => {
+ expect(vm.$el.querySelector('.title .btn-blank').innerText.trim()).toEqual('Edit');
+ });
+
+ it('should enable editing when edit button is clicked', (done) => {
+ vm.isLoading = false;
+ Vue.nextTick(() => {
+ vm.$el.querySelector('.title .btn-blank').click();
+ expect(vm.editing).toEqual(true);
+ done();
+ });
+ });
+ });
+
+ it('should render date if selectedDate', (done) => {
+ vm.selectedDate = new Date('07/07/2017');
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.value-content strong').innerText.trim()).toEqual('Jul 7, 2017');
+ done();
+ });
+ });
+
+ describe('selectedDate and editable', () => {
+ beforeEach((done) => {
+ vm.selectedDate = new Date('07/07/2017');
+ vm.editable = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render remove button if selectedDate and editable', () => {
+ expect(vm.$el.querySelector('.value-content .btn-blank').innerText.trim()).toEqual('remove');
+ });
+
+ it('should emit saveDate when remove button is clicked', () => {
+ const saveDate = jasmine.createSpy();
+ vm.$on('saveDate', saveDate);
+
+ vm.$el.querySelector('.value-content .btn-blank').click();
+ expect(saveDate).toHaveBeenCalled();
+ });
+ });
+
+ describe('showToggleSidebar', () => {
+ beforeEach((done) => {
+ vm.showToggleSidebar = true;
+ Vue.nextTick(done);
+ });
+
+ it('should render toggle-sidebar when showToggleSidebar', () => {
+ expect(vm.$el.querySelector('.title .gutter-toggle')).toBeDefined();
+ });
+
+ it('should emit toggleCollapse when toggle sidebar is clicked', () => {
+ const toggleCollapse = jasmine.createSpy();
+ vm.$on('toggleCollapse', toggleCollapse);
+
+ vm.$el.querySelector('.title .gutter-toggle').click();
+ expect(toggleCollapse).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
new file mode 100644
index 00000000000..752a9e89d50
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
@@ -0,0 +1,32 @@
+import Vue from 'vue';
+import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
+
+describe('toggleSidebar', () => {
+ let vm;
+ beforeEach(() => {
+ const ToggleSidebar = Vue.extend(toggleSidebar);
+ vm = mountComponent(ToggleSidebar, {
+ collapsed: true,
+ });
+ });
+
+ it('should render << when collapsed', () => {
+ expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-left')).toEqual(true);
+ });
+
+ it('should render >> when collapsed', () => {
+ vm.collapsed = false;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.fa').classList.contains('fa-angle-double-right')).toEqual(true);
+ });
+ });
+
+ it('should emit toggle event when button clicked', () => {
+ const toggle = jasmine.createSpy();
+ vm.$on('toggle', toggle);
+ vm.$el.click();
+
+ expect(toggle).toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/toggle_button_spec.js b/spec/javascripts/vue_shared/components/toggle_button_spec.js
new file mode 100644
index 00000000000..447d74d4e08
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/toggle_button_spec.js
@@ -0,0 +1,91 @@
+import Vue from 'vue';
+import toggleButton from '~/vue_shared/components/toggle_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Toggle Button', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(toggleButton);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('render output', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ value: true,
+ name: 'foo',
+ });
+ });
+
+ it('renders input with provided name', () => {
+ expect(vm.$el.querySelector('input').getAttribute('name')).toEqual('foo');
+ });
+
+ it('renders input with provided value', () => {
+ expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true');
+ });
+
+ it('renders Enabled and Disabled text data attributes', () => {
+ expect(vm.$el.querySelector('button').getAttribute('data-enabled-text')).toEqual('Enabled');
+ expect(vm.$el.querySelector('button').getAttribute('data-disabled-text')).toEqual('Disabled');
+ });
+ });
+
+ describe('is-checked', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ value: true,
+ });
+
+ spyOn(vm, '$emit');
+ });
+
+ it('renders is checked class', () => {
+ expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true);
+ });
+
+ it('emits change event when clicked', () => {
+ vm.$el.querySelector('button').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('change', false);
+ });
+ });
+
+ describe('is-disabled', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ value: true,
+ disabledInput: true,
+ });
+ spyOn(vm, '$emit');
+ });
+
+ it('renders disabled button', () => {
+ expect(vm.$el.querySelector('button').classList.contains('is-disabled')).toEqual(true);
+ });
+
+ it('does not emit change event when clicked', () => {
+ vm.$el.querySelector('button').click();
+
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('is-loading', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ value: true,
+ isLoading: true,
+ });
+ });
+
+ it('renders loading class', () => {
+ expect(vm.$el.querySelector('button').classList.contains('is-loading')).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 7047053d131..45a0bb0650f 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,77 +1,93 @@
-/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */
/* global Mousetrap */
import Dropzone from 'dropzone';
import ZenMode from '~/zen_mode';
-(function() {
- var enterZen, escapeKeydown, exitZen;
-
- describe('ZenMode', function() {
- var fixtureName = 'merge_requests/merge_request_with_comment.html.raw';
- preloadFixtures(fixtureName);
- beforeEach(function() {
- loadFixtures(fixtureName);
- spyOn(Dropzone, 'forElement').and.callFake(function() {
- return {
- enable: function() {
- return true;
- }
- };
- // Stub Dropzone.forElement(...).enable()
- });
- this.zen = new ZenMode();
- // Set this manually because we can't actually scroll the window
- return this.zen.scroll_position = 456;
+describe('ZenMode', () => {
+ let zen;
+ const fixtureName = 'merge_requests/merge_request_with_comment.html.raw';
+
+ preloadFixtures(fixtureName);
+
+ function enterZen() {
+ $('.notes-form .js-zen-enter').click();
+ }
+
+ function exitZen() {
+ $('.notes-form .js-zen-leave').click();
+ }
+
+ function escapeKeydown() {
+ $('.notes-form textarea').trigger($.Event('keydown', {
+ keyCode: 27,
+ }));
+ }
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+
+ spyOn(Dropzone, 'forElement').and.callFake(() => ({
+ enable: () => true,
+ }));
+ zen = new ZenMode();
+
+ // Set this manually because we can't actually scroll the window
+ zen.scroll_position = 456;
+ });
+
+ describe('on enter', () => {
+ it('pauses Mousetrap', () => {
+ spyOn(Mousetrap, 'pause');
+ enterZen();
+ expect(Mousetrap.pause).toHaveBeenCalled();
});
- describe('on enter', function() {
- it('pauses Mousetrap', function() {
- spyOn(Mousetrap, 'pause');
- enterZen();
- return expect(Mousetrap.pause).toHaveBeenCalled();
- });
- return it('removes textarea styling', function() {
- $('.notes-form textarea').attr('style', 'height: 400px');
- enterZen();
- return expect($('.notes-form textarea')).not.toHaveAttr('style');
- });
+
+ it('removes textarea styling', () => {
+ $('.notes-form textarea').attr('style', 'height: 400px');
+ enterZen();
+ expect($('.notes-form textarea')).not.toHaveAttr('style');
});
- describe('in use', function() {
- beforeEach(function() {
- return enterZen();
- });
- return it('exits on Escape', function() {
- escapeKeydown();
- return expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen');
- });
+ });
+
+ describe('in use', () => {
+ beforeEach(enterZen);
+
+ it('exits on Escape', () => {
+ escapeKeydown();
+ expect($('.notes-form .zen-backdrop')).not.toHaveClass('fullscreen');
+ });
+ });
+
+ describe('on exit', () => {
+ beforeEach(enterZen);
+
+ it('unpauses Mousetrap', () => {
+ spyOn(Mousetrap, 'unpause');
+ exitZen();
+ expect(Mousetrap.unpause).toHaveBeenCalled();
});
- return describe('on exit', function() {
- beforeEach(function() {
- return enterZen();
- });
- it('unpauses Mousetrap', function() {
- spyOn(Mousetrap, 'unpause');
- exitZen();
- return expect(Mousetrap.unpause).toHaveBeenCalled();
- });
- return it('restores the scroll position', function() {
- spyOn(this.zen, 'scrollTo');
- exitZen();
- return expect(this.zen.scrollTo).toHaveBeenCalled();
- });
+
+ it('restores the scroll position', () => {
+ spyOn(zen, 'scrollTo');
+ exitZen();
+ expect(zen.scrollTo).toHaveBeenCalled();
});
});
- enterZen = function() {
- return $('.notes-form .js-zen-enter').click();
- };
+ describe('enabling dropzone', () => {
+ beforeEach(() => {
+ enterZen();
+ });
- exitZen = function() {
- return $('.notes-form .js-zen-leave').click();
- };
+ it('should not call dropzone if element is not dropzone valid', () => {
+ $('.div-dropzone').addClass('js-invalid-dropzone');
+ exitZen();
+ expect(Dropzone.forElement).not.toHaveBeenCalled();
+ });
- escapeKeydown = function() {
- return $('.notes-form textarea').trigger($.Event('keydown', {
- keyCode: 27
- }));
- };
-}).call(window);
+ it('should call dropzone if element is dropzone valid', () => {
+ $('.div-dropzone').removeClass('js-invalid-dropzone');
+ exitZen();
+ expect(Dropzone.forElement).toHaveBeenCalled();
+ });
+ });
+});