Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Jarvis <jarv@gitlab.com>2019-04-08 11:50:19 +0300
committerJohn Jarvis <jarv@gitlab.com>2019-04-08 11:50:19 +0300
commitb8ed65619075d3b5892a3ec16e562518d5285a75 (patch)
treef6a31b4819339aeb170f9df5886afab23638f172 /spec/javascripts
parent9ed8b61b8692dbafe189fce68237efcc9345ee9f (diff)
parent435c492bac54f588b92e733b632edd4149a6db37 (diff)
Merge branch 'master' into 11-10-stable
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/boards/issue_spec.js2
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js346
-rw-r--r--spec/javascripts/clusters/components/application_row_spec.js406
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js350
-rw-r--r--spec/javascripts/clusters/services/mock_data.js130
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js142
-rw-r--r--spec/javascripts/diffs/components/app_spec.js18
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js20
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js4
-rw-r--r--spec/javascripts/fixtures/issues.rb58
-rw-r--r--spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js62
-rw-r--r--spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js25
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js84
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js2
-rw-r--r--spec/javascripts/ide/components/new_dropdown/index_spec.js4
-rw-r--r--spec/javascripts/ide/stores/actions/merge_request_spec.js75
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js17
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js33
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js20
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js62
-rw-r--r--spec/javascripts/ide/stores/mutations/tree_spec.js61
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js125
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js36
-rw-r--r--spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js37
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js6
-rw-r--r--spec/javascripts/lib/utils/higlight_spec.js43
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js23
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js93
-rw-r--r--spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js89
-rw-r--r--spec/javascripts/related_merge_requests/store/actions_spec.js110
-rw-r--r--spec/javascripts/related_merge_requests/store/mutations_spec.js49
-rw-r--r--spec/javascripts/serverless/components/environment_row_spec.js81
-rw-r--r--spec/javascripts/serverless/components/function_row_spec.js33
-rw-r--r--spec/javascripts/serverless/components/functions_spec.js68
-rw-r--r--spec/javascripts/serverless/components/url_spec.js28
-rw-r--r--spec/javascripts/serverless/mock_data.js79
-rw-r--r--spec/javascripts/serverless/stores/serverless_store_spec.js36
-rw-r--r--spec/javascripts/test_bundle.js6
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js60
-rw-r--r--spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js110
-rw-r--r--spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js132
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js15
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js20
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js40
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js7
45 files changed, 1395 insertions, 1852 deletions
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 54fb0e8228b..e4ff3eb381f 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -178,6 +178,7 @@ describe('Issue model', () => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([1]);
done();
+ return Promise.resolve();
});
issue.update('url');
@@ -187,6 +188,7 @@ describe('Issue model', () => {
spyOn(Vue.http, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([0]);
done();
+ return Promise.resolve();
});
issue.removeAllAssignees();
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
deleted file mode 100644
index 0d3dcc29f22..00000000000
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ /dev/null
@@ -1,346 +0,0 @@
-import Clusters from '~/clusters/clusters_bundle';
-import {
- REQUEST_SUBMITTED,
- REQUEST_FAILURE,
- APPLICATION_STATUS,
- INGRESS_DOMAIN_SUFFIX,
-} from '~/clusters/constants';
-import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-
-describe('Clusters', () => {
- let cluster;
- preloadFixtures('clusters/show_cluster.html');
-
- beforeEach(() => {
- loadFixtures('clusters/show_cluster.html');
- cluster = new Clusters();
- });
-
- afterEach(() => {
- cluster.destroy();
- });
-
- describe('toggle', () => {
- it('should update the button and the input field on click', done => {
- const toggleButton = document.querySelector(
- '.js-cluster-enable-toggle-area .js-project-feature-toggle',
- );
- const toggleInput = document.querySelector(
- '.js-cluster-enable-toggle-area .js-project-feature-toggle-input',
- );
-
- toggleButton.click();
-
- getSetTimeoutPromise()
- .then(() => {
- expect(toggleButton.classList).not.toContain('is-checked');
-
- expect(toggleInput.getAttribute('value')).toEqual('false');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('showToken', () => {
- it('should update token field type', () => {
- cluster.showTokenButton.click();
-
- expect(cluster.tokenField.getAttribute('type')).toEqual('text');
-
- cluster.showTokenButton.click();
-
- expect(cluster.tokenField.getAttribute('type')).toEqual('password');
- });
-
- it('should update show token button text', () => {
- cluster.showTokenButton.click();
-
- expect(cluster.showTokenButton.textContent).toEqual('Hide');
-
- cluster.showTokenButton.click();
-
- expect(cluster.showTokenButton.textContent).toEqual('Show');
- });
- });
-
- describe('checkForNewInstalls', () => {
- const INITIAL_APP_MAP = {
- helm: { status: null, title: 'Helm Tiller' },
- ingress: { status: null, title: 'Ingress' },
- runner: { status: null, title: 'GitLab Runner' },
- };
-
- it('does not show alert when things transition from initial null state to something', () => {
- cluster.checkForNewInstalls(INITIAL_APP_MAP, {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' },
- });
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).toBeNull();
- });
-
- it('shows an alert when something gets newly installed', () => {
- cluster.checkForNewInstalls(
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
- },
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
- },
- );
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).not.toBeNull();
- expect(flashMessage.textContent.trim()).toEqual(
- 'Helm Tiller was successfully installed on your Kubernetes cluster',
- );
- });
-
- it('shows an alert when multiple things gets newly installed', () => {
- cluster.checkForNewInstalls(
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' },
- },
- {
- ...INITIAL_APP_MAP,
- helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' },
- },
- );
-
- const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
-
- expect(flashMessage).not.toBeNull();
- expect(flashMessage.textContent.trim()).toEqual(
- 'Helm Tiller, Ingress was successfully installed on your Kubernetes cluster',
- );
- });
- });
-
- describe('updateContainer', () => {
- describe('when creating cluster', () => {
- it('should show the creating container', () => {
- cluster.updateContainer(null, 'creating');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
-
- it('should continue to show `creating` banner with subsequent updates of the same status', () => {
- cluster.updateContainer('creating', 'creating');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
- });
-
- describe('when cluster is created', () => {
- it('should show the success container and fresh the page', () => {
- cluster.updateContainer(null, 'created');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
-
- it('should not show a banner when status is already `created`', () => {
- cluster.updateContainer('created', 'created');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
- });
- });
-
- describe('when cluster has error', () => {
- it('should show the error container', () => {
- cluster.updateContainer(null, 'errored', 'this is an error');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
-
- expect(cluster.errorReasonContainer.textContent).toContain('this is an error');
- });
-
- it('should show `error` banner when previously `creating`', () => {
- cluster.updateContainer('creating', 'errored');
-
- expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
- expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
- });
- });
- });
-
- describe('installApplication', () => {
- it('tries to install helm', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'helm' });
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('helm', undefined);
- });
-
- it('tries to install ingress', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'ingress' });
-
- expect(cluster.store.state.applications.ingress.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.ingress.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('ingress', undefined);
- });
-
- it('tries to install runner', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.runner.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'runner' });
-
- expect(cluster.store.state.applications.runner.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.runner.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('runner', undefined);
- });
-
- it('tries to install jupyter', () => {
- spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve());
-
- expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null);
- cluster.installApplication({
- id: 'jupyter',
- params: { hostname: cluster.store.state.applications.jupyter.hostname },
- });
-
- expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.jupyter.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalledWith('jupyter', {
- hostname: cluster.store.state.applications.jupyter.hostname,
- });
- });
-
- it('sets error request status when the request fails', done => {
- spyOn(cluster.service, 'installApplication').and.returnValue(
- Promise.reject(new Error('STUBBED ERROR')),
- );
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(null);
-
- cluster.installApplication({ id: 'helm' });
-
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED);
- expect(cluster.store.state.applications.helm.requestReason).toEqual(null);
- expect(cluster.service.installApplication).toHaveBeenCalled();
-
- getSetTimeoutPromise()
- .then(() => {
- expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE);
- expect(cluster.store.state.applications.helm.requestReason).toBeDefined();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('handleSuccess', () => {
- beforeEach(() => {
- spyOn(cluster.store, 'updateStateFromServer');
- spyOn(cluster, 'toggleIngressDomainHelpText');
- spyOn(cluster, 'checkForNewInstalls');
- spyOn(cluster, 'updateContainer');
-
- cluster.handleSuccess({ data: {} });
- });
-
- it('updates clusters store', () => {
- expect(cluster.store.updateStateFromServer).toHaveBeenCalled();
- });
-
- it('checks for new installable apps', () => {
- expect(cluster.checkForNewInstalls).toHaveBeenCalled();
- });
-
- it('toggles ingress domain help text', () => {
- expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled();
- });
-
- it('updates message containers', () => {
- expect(cluster.updateContainer).toHaveBeenCalled();
- });
- });
-
- describe('toggleIngressDomainHelpText', () => {
- const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS;
-
- const ingressPreviousState = { status: INSTALLABLE };
- const ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' };
-
- describe(`when ingress application new status is ${INSTALLED}`, () => {
- beforeEach(() => {
- ingressNewState.status = INSTALLED;
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
- });
-
- it('displays custom domain help text', () => {
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false);
- });
-
- it('updates ingress external ip address', () => {
- expect(cluster.ingressDomainSnippet.textContent).toEqual(
- `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`,
- );
- });
- });
-
- describe(`when ingress application new status is different from ${INSTALLED}`, () => {
- it('hides custom domain help text', () => {
- ingressNewState.status = NOT_INSTALLABLE;
- cluster.ingressDomainHelpText.classList.remove('hide');
-
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
-
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
- });
- });
-
- describe('when ingress application new status and old status are the same', () => {
- it('does not modify custom domain help text', () => {
- ingressPreviousState.status = INSTALLED;
- ingressNewState.status = ingressPreviousState.status;
-
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
-
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
- });
- });
- });
-});
diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js
deleted file mode 100644
index a2dd4e93daf..00000000000
--- a/spec/javascripts/clusters/components/application_row_spec.js
+++ /dev/null
@@ -1,406 +0,0 @@
-import Vue from 'vue';
-import eventHub from '~/clusters/event_hub';
-import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants';
-import applicationRow from '~/clusters/components/application_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { DEFAULT_APPLICATION_STATE } from '../services/mock_data';
-
-describe('Application Row', () => {
- let vm;
- let ApplicationRow;
-
- beforeEach(() => {
- ApplicationRow = Vue.extend(applicationRow);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('Title', () => {
- it('shows title', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- titleLink: null,
- });
- const title = vm.$el.querySelector('.js-cluster-application-title');
-
- expect(title.tagName).toEqual('SPAN');
- expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
- });
-
- it('shows title link', () => {
- expect(DEFAULT_APPLICATION_STATE.titleLink).toBeDefined();
-
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- });
- const title = vm.$el.querySelector('.js-cluster-application-title');
-
- expect(title.tagName).toEqual('A');
- expect(title.textContent.trim()).toEqual(DEFAULT_APPLICATION_STATE.title);
- });
- });
-
- describe('Install button', () => {
- it('has indeterminate state on page load', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- });
-
- expect(vm.installButtonLabel).toBeUndefined();
- });
-
- it('has install button', () => {
- const installationBtn = vm.$el.querySelector('.js-cluster-application-install-button');
-
- expect(installationBtn).not.toBe(null);
- });
-
- it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.NOT_INSTALLABLE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.SCHEDULED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLING,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has loading "Installing" when REQUEST_SUBMITTED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_SUBMITTED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installing');
- expect(vm.installButtonLoading).toEqual(true);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLED,
- });
-
- expect(vm.installButtonLabel).toEqual('Installed');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has disabled "Installed" when APPLICATION_STATUS.UPDATING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
-
- expect(vm.installButtonLabel).toEqual('Installed');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(true);
- });
-
- it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.ERROR,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_FAILURE,
- });
-
- expect(vm.installButtonLabel).toEqual('Install');
- expect(vm.installButtonLoading).toEqual(false);
- expect(vm.installButtonDisabled).toEqual(false);
- });
-
- it('clicking install button emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- installButton.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: {},
- });
- });
-
- it('clicking install button when installApplicationRequestParams are provided emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- installApplicationRequestParams: { hostname: 'jupyter' },
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- installButton.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('installApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: { hostname: 'jupyter' },
- });
- });
-
- it('clicking disabled install button emits nothing', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLING,
- });
- const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
-
- expect(vm.installButtonDisabled).toEqual(true);
-
- installButton.click();
-
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
- });
-
- describe('Upgrade button', () => {
- it('has indeterminate state on page load', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).toBe(null);
- });
-
- it('has enabled "Upgrade" when "upgradeAvailable" is true', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- upgradeAvailable: true,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Upgrade');
- });
-
- it('has enabled "Retry update" when APPLICATION_STATUS.UPDATE_ERRORED', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(vm.upgradeFailed).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Retry update');
- });
-
- it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- expect(upgradeBtn).not.toBe(null);
- expect(vm.isUpgrading).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Updating');
- });
-
- it('clicking upgrade button emits event', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- upgradeBtn.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', {
- id: DEFAULT_APPLICATION_STATE.id,
- params: {},
- });
- });
-
- it('clicking disabled upgrade button emits nothing', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATING,
- });
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
-
- upgradeBtn.click();
-
- expect(eventHub.$emit).not.toHaveBeenCalled();
- });
-
- it('displays an error message if application upgrade failed', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- title: 'GitLab Runner',
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- });
- const failureMessage = vm.$el.querySelector(
- '.js-cluster-application-upgrade-failure-message',
- );
-
- expect(failureMessage).not.toBe(null);
- expect(failureMessage.innerHTML).toContain(
- 'Update failed. Please check the logs and try again.',
- );
- });
- });
-
- describe('Version', () => {
- it('displays a version number if application has been upgraded', () => {
- const version = '0.1.45';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATED,
- version,
- });
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(upgradeDetails.innerHTML).toContain('Upgraded');
- expect(versionEl).not.toBe(null);
- expect(versionEl.innerHTML).toContain(version);
- });
-
- it('contains a link to the chart repo if application has been upgraded', () => {
- const version = '0.1.45';
- const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATED,
- chartRepo,
- version,
- });
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(versionEl.href).toEqual(chartRepo);
- expect(versionEl.target).toEqual('_blank');
- });
-
- it('does not display a version number if application upgrade failed', () => {
- const version = '0.1.45';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.UPDATE_ERRORED,
- version,
- });
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
-
- expect(upgradeDetails.innerHTML).toContain('failed');
- expect(versionEl).toBe(null);
- });
- });
-
- describe('Error block', () => {
- it('does not show error block when there is no error', () => {
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: null,
- requestStatus: null,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
-
- expect(generalErrorMessage).toBeNull();
- });
-
- it('shows status reason when APPLICATION_STATUS.ERROR', () => {
- const statusReason = 'We broke it 0.0';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.ERROR,
- statusReason,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
- const statusErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-status-error-message',
- );
-
- expect(generalErrorMessage.textContent.trim()).toEqual(
- `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
- );
-
- expect(statusErrorMessage.textContent.trim()).toEqual(statusReason);
- });
-
- it('shows request reason when REQUEST_FAILURE', () => {
- const requestReason = 'We broke thre request 0.0';
- vm = mountComponent(ApplicationRow, {
- ...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_STATUS.INSTALLABLE,
- requestStatus: REQUEST_FAILURE,
- requestReason,
- });
- const generalErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-general-error-message',
- );
- const requestErrorMessage = vm.$el.querySelector(
- '.js-cluster-application-request-error-message',
- );
-
- expect(generalErrorMessage.textContent.trim()).toEqual(
- `Something went wrong while installing ${DEFAULT_APPLICATION_STATE.title}`,
- );
-
- expect(requestErrorMessage.textContent.trim()).toEqual(requestReason);
- });
- });
-});
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
deleted file mode 100644
index 0f8153ad493..00000000000
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ /dev/null
@@ -1,350 +0,0 @@
-import Vue from 'vue';
-import applications from '~/clusters/components/applications.vue';
-import { CLUSTER_TYPE } from '~/clusters/constants';
-import eventHub from '~/clusters/event_hub';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
-
-describe('Applications', () => {
- let vm;
- let Applications;
-
- beforeEach(() => {
- Applications = Vue.extend(applications);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('Project cluster applications', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- applications: APPLICATIONS_MOCK_STATE,
- type: CLUSTER_TYPE.PROJECT,
- });
- });
-
- it('renders a row for Helm Tiller', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
- });
-
- it('renders a row for Ingress', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
- });
-
- it('renders a row for Cert-Manager', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
- });
-
- it('renders a row for Prometheus', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
- });
-
- it('renders a row for GitLab Runner', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
- });
-
- it('renders a row for Jupyter', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull();
- });
-
- it('renders a row for Knative', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
- });
- });
-
- describe('Group cluster applications', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- type: CLUSTER_TYPE.GROUP,
- applications: APPLICATIONS_MOCK_STATE,
- });
- });
-
- it('renders a row for Helm Tiller', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
- });
-
- it('renders a row for Ingress', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
- });
-
- it('renders a row for Cert-Manager', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
- });
-
- it('renders a row for Prometheus', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeNull();
- });
-
- it('renders a row for GitLab Runner', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
- });
-
- it('renders a row for Jupyter', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull();
- });
-
- it('renders a row for Knative', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull();
- });
- });
-
- describe('Ingress application', () => {
- describe('when installed', () => {
- describe('with ip address', () => {
- it('renders ip address with a clipboard button', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- ingress: {
- title: 'Ingress',
- status: 'installed',
- externalIp: '0.0.0.0',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-endpoint').value).toEqual('0.0.0.0');
-
- expect(
- vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
- ).toEqual('0.0.0.0');
- });
- });
-
- describe('with hostname', () => {
- it('renders hostname with a clipboard button', () => {
- vm = mountComponent(Applications, {
- applications: {
- ingress: {
- title: 'Ingress',
- status: 'installed',
- externalHostname: 'localhost.localdomain',
- },
- helm: { title: 'Helm Tiller' },
- cert_manager: { title: 'Cert-Manager' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', hostname: '' },
- knative: { title: 'Knative', hostname: '' },
- },
- });
-
- expect(vm.$el.querySelector('.js-endpoint').value).toEqual('localhost.localdomain');
-
- expect(
- vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text'),
- ).toEqual('localhost.localdomain');
- });
- });
-
- describe('without ip address', () => {
- it('renders an input text with a loading icon and an alert text', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- ingress: {
- title: 'Ingress',
- status: 'installed',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null);
- expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null);
- });
- });
- });
-
- describe('before installing', () => {
- it('does not render the IP address', () => {
- vm = mountComponent(Applications, {
- applications: APPLICATIONS_MOCK_STATE,
- });
-
- expect(vm.$el.textContent).not.toContain('Ingress IP Address');
- expect(vm.$el.querySelector('.js-endpoint')).toBe(null);
- });
- });
-
- describe('Cert-Manager application', () => {
- describe('when not installed', () => {
- it('renders email & allows editing', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- cert_manager: {
- title: 'Cert-Manager',
- email: 'before@example.com',
- status: 'installable',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com');
- expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null);
- });
- });
-
- describe('when installed', () => {
- it('renders email in readonly', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- cert_manager: {
- title: 'Cert-Manager',
- email: 'after@example.com',
- status: 'installed',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com');
- expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly');
- });
- });
- });
-
- describe('Jupyter application', () => {
- describe('with ingress installed with ip & jupyter installable', () => {
- it('renders hostname active input', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- ingress: {
- title: 'Ingress',
- status: 'installed',
- externalIp: '1.1.1.1',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual(null);
- });
- });
-
- describe('with ingress installed without external ip', () => {
- it('does not render hostname input', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- ingress: { title: 'Ingress', status: 'installed' },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname')).toBe(null);
- });
- });
-
- describe('with ingress & jupyter installed', () => {
- it('renders readonly input', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
- jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' },
- },
- });
-
- expect(vm.$el.querySelector('.js-hostname').getAttribute('readonly')).toEqual('readonly');
- });
- });
-
- describe('without ingress installed', () => {
- beforeEach(() => {
- vm = mountComponent(Applications, {
- applications: APPLICATIONS_MOCK_STATE,
- });
- });
-
- it('does not render input', () => {
- expect(vm.$el.querySelector('.js-hostname')).toBe(null);
- });
-
- it('renders disabled install button', () => {
- expect(
- vm.$el
- .querySelector(
- '.js-cluster-application-row-jupyter .js-cluster-application-install-button',
- )
- .getAttribute('disabled'),
- ).toEqual('disabled');
- });
- });
- });
- });
-
- describe('Knative application', () => {
- describe('when installed', () => {
- describe('with ip address', () => {
- const props = {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- knative: {
- title: 'Knative',
- hostname: 'example.com',
- status: 'installed',
- externalIp: '1.1.1.1',
- },
- },
- };
- it('renders ip address with a clipboard button', () => {
- vm = mountComponent(Applications, props);
-
- expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1');
-
- expect(
- vm.$el
- .querySelector('.js-knative-endpoint-clipboard-btn')
- .getAttribute('data-clipboard-text'),
- ).toEqual('1.1.1.1');
- });
-
- it('renders domain & allows editing', () => {
- expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com');
- expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe(
- null,
- );
- });
-
- it('renders an update/save Knative domain button', () => {
- expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null);
- });
-
- it('emits event when clicking Save changes button', () => {
- spyOn(eventHub, '$emit');
- vm = mountComponent(Applications, props);
-
- const saveButton = vm.$el.querySelector('.js-knative-save-domain-button');
-
- saveButton.click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
- id: 'knative',
- params: { hostname: 'example.com' },
- });
- });
- });
-
- describe('without ip address', () => {
- it('renders an input text with a loading icon and an alert text', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- knative: {
- title: 'Knative',
- hostname: 'example.com',
- status: 'installed',
- },
- },
- });
-
- expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null);
- expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null);
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
deleted file mode 100644
index b4d1bb710e0..00000000000
--- a/spec/javascripts/clusters/services/mock_data.js
+++ /dev/null
@@ -1,130 +0,0 @@
-import { APPLICATION_STATUS } from '~/clusters/constants';
-
-const CLUSTERS_MOCK_DATA = {
- GET: {
- '/gitlab-org/gitlab-shell/clusters/1/status.json': {
- data: {
- status: 'errored',
- status_reason: 'Failed to request to CloudPlatform.',
- applications: [
- {
- name: 'helm',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: null,
- },
- {
- name: 'ingress',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- external_ip: null,
- external_hostname: null,
- },
- {
- name: 'runner',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: null,
- },
- {
- name: 'prometheus',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- },
- {
- name: 'jupyter',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: 'Cannot connect',
- },
- {
- name: 'knative',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: 'Cannot connect',
- },
- {
- name: 'cert_manager',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- email: 'test@example.com',
- },
- ],
- },
- },
- '/gitlab-org/gitlab-shell/clusters/2/status.json': {
- data: {
- status: 'errored',
- status_reason: 'Failed to request to CloudPlatform.',
- applications: [
- {
- name: 'helm',
- status: APPLICATION_STATUS.INSTALLED,
- status_reason: null,
- },
- {
- name: 'ingress',
- status: APPLICATION_STATUS.INSTALLED,
- status_reason: 'Cannot connect',
- external_ip: '1.1.1.1',
- external_hostname: null,
- },
- {
- name: 'runner',
- status: APPLICATION_STATUS.INSTALLING,
- status_reason: null,
- },
- {
- name: 'prometheus',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- },
- {
- name: 'jupyter',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: 'Cannot connect',
- },
- {
- name: 'knative',
- status: APPLICATION_STATUS.INSTALLABLE,
- status_reason: 'Cannot connect',
- },
- {
- name: 'cert_manager',
- status: APPLICATION_STATUS.ERROR,
- status_reason: 'Cannot connect',
- email: 'test@example.com',
- },
- ],
- },
- },
- },
- POST: {
- '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {},
- '/gitlab-org/gitlab-shell/clusters/1/applications/knative': {},
- },
-};
-
-const DEFAULT_APPLICATION_STATE = {
- id: 'some-app',
- title: 'My App',
- titleLink: 'https://about.gitlab.com/',
- description: 'Some description about this interesting application!',
- status: null,
- statusReason: null,
- requestStatus: null,
- requestReason: null,
-};
-
-const APPLICATIONS_MOCK_STATE = {
- helm: { title: 'Helm Tiller', status: 'installable' },
- ingress: { title: 'Ingress', status: 'installable' },
- cert_manager: { title: 'Cert-Manager', status: 'installable' },
- runner: { title: 'GitLab Runner' },
- prometheus: { title: 'Prometheus' },
- jupyter: { title: 'JupyterHub', status: 'installable', hostname: '' },
- knative: { title: 'Knative ', status: 'installable', hostname: '' },
-};
-
-export { CLUSTERS_MOCK_DATA, DEFAULT_APPLICATION_STATE, APPLICATIONS_MOCK_STATE };
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
deleted file mode 100644
index 161722ec571..00000000000
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import ClustersStore from '~/clusters/stores/clusters_store';
-import { APPLICATION_STATUS } from '~/clusters/constants';
-import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
-
-describe('Clusters Store', () => {
- let store;
-
- beforeEach(() => {
- store = new ClustersStore();
- });
-
- describe('updateStatus', () => {
- it('should store new status', () => {
- expect(store.state.status).toEqual(null);
-
- const newStatus = 'errored';
- store.updateStatus(newStatus);
-
- expect(store.state.status).toEqual(newStatus);
- });
- });
-
- describe('updateStatusReason', () => {
- it('should store new reason', () => {
- expect(store.state.statusReason).toEqual(null);
-
- const newReason = 'Something went wrong!';
- store.updateStatusReason(newReason);
-
- expect(store.state.statusReason).toEqual(newReason);
- });
- });
-
- describe('updateAppProperty', () => {
- it('should store new request status', () => {
- expect(store.state.applications.helm.requestStatus).toEqual(null);
-
- const newStatus = APPLICATION_STATUS.INSTALLING;
- store.updateAppProperty('helm', 'requestStatus', newStatus);
-
- expect(store.state.applications.helm.requestStatus).toEqual(newStatus);
- });
-
- it('should store new request reason', () => {
- expect(store.state.applications.helm.requestReason).toEqual(null);
-
- const newReason = 'We broke it.';
- store.updateAppProperty('helm', 'requestReason', newReason);
-
- expect(store.state.applications.helm.requestReason).toEqual(newReason);
- });
- });
-
- describe('updateStateFromServer', () => {
- it('should store new polling data from server', () => {
- const mockResponseData =
- CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/1/status.json'].data;
- store.updateStateFromServer(mockResponseData);
-
- expect(store.state).toEqual({
- helpPath: null,
- ingressHelpPath: null,
- status: mockResponseData.status,
- statusReason: mockResponseData.status_reason,
- rbac: false,
- applications: {
- helm: {
- title: 'Helm Tiller',
- status: mockResponseData.applications[0].status,
- statusReason: mockResponseData.applications[0].status_reason,
- requestStatus: null,
- requestReason: null,
- },
- ingress: {
- title: 'Ingress',
- status: mockResponseData.applications[1].status,
- statusReason: mockResponseData.applications[1].status_reason,
- requestStatus: null,
- requestReason: null,
- externalIp: null,
- externalHostname: null,
- },
- runner: {
- title: 'GitLab Runner',
- status: mockResponseData.applications[2].status,
- statusReason: mockResponseData.applications[2].status_reason,
- requestStatus: null,
- requestReason: null,
- version: mockResponseData.applications[2].version,
- upgradeAvailable: mockResponseData.applications[2].update_available,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
- },
- prometheus: {
- title: 'Prometheus',
- status: mockResponseData.applications[3].status,
- statusReason: mockResponseData.applications[3].status_reason,
- requestStatus: null,
- requestReason: null,
- },
- jupyter: {
- title: 'JupyterHub',
- status: mockResponseData.applications[4].status,
- statusReason: mockResponseData.applications[4].status_reason,
- requestStatus: null,
- requestReason: null,
- hostname: '',
- },
- knative: {
- title: 'Knative',
- status: mockResponseData.applications[5].status,
- statusReason: mockResponseData.applications[5].status_reason,
- requestStatus: null,
- requestReason: null,
- hostname: null,
- isEditingHostName: false,
- externalIp: null,
- externalHostname: null,
- },
- cert_manager: {
- title: 'Cert-Manager',
- status: mockResponseData.applications[6].status,
- statusReason: mockResponseData.applications[6].status_reason,
- requestStatus: null,
- requestReason: null,
- email: mockResponseData.applications[6].email,
- },
- },
- });
- });
-
- it('sets default hostname for jupyter when ingress has a ip address', () => {
- const mockResponseData =
- CLUSTERS_MOCK_DATA.GET['/gitlab-org/gitlab-shell/clusters/2/status.json'].data;
-
- store.updateStateFromServer(mockResponseData);
-
- expect(store.state.applications.jupyter.hostname).toEqual(
- `jupyter.${store.state.applications.ingress.externalIp}.nip.io`,
- );
- });
- });
-});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 8d7c52a2876..3ce69bc3c20 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -57,6 +57,24 @@ describe('diffs/components/app', () => {
wrapper.destroy();
});
+ it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.showTreeList = false;
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(true);
+ });
+
+ it('does not add container-limiting classes when showFileTree is false with inline diffs', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.showTreeList = true;
+ state.diffs.isParallelView = false;
+ });
+
+ expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false);
+ });
+
it('displays loading icon on loading', () => {
createComponent({}, ({ state }) => {
state.diffs.isLoading = true;
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index e886f962d2f..77f8352047c 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -66,6 +66,26 @@ describe('CompareVersions', () => {
expect(inlineBtn.innerHTML).toContain('Inline');
expect(parallelBtn.innerHTML).toContain('Side-by-side');
});
+
+ it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
+ vm.isLimitedContainer = true;
+
+ vm.$nextTick(() => {
+ const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width');
+
+ expect(limitedContainer).not.toBeNull();
+ });
+ });
+
+ it('does not add container-limiting classes when showFileTree is false with inline diffs', () => {
+ vm.isLimitedContainer = false;
+
+ vm.$nextTick(() => {
+ const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width');
+
+ expect(limitedContainer).toBeNull();
+ });
+ });
});
describe('setInlineDiffViewType', () => {
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 6614069f44d..e1170c9762e 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -672,7 +672,7 @@ describe('diff_file_header', () => {
vm = mountComponentWithStore(Component, { props, store });
- expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show changes only');
+ expect(vm.$el.querySelector('.ic-doc-changes')).not.toBeNull();
});
it('shows expand text', () => {
@@ -680,7 +680,7 @@ describe('diff_file_header', () => {
vm = mountComponentWithStore(Component, { props, store });
- expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show full file');
+ expect(vm.$el.querySelector('.ic-doc-expand')).not.toBeNull();
});
it('renders loading icon', () => {
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 645b3aa788a..0f3f9a10f94 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -65,3 +65,61 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
store_frontend_fixture(response, fixture_file_name)
end
end
+
+describe API::Issues, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+ include JavaScriptFixturesHelpers
+
+ def get_related_merge_requests(project_id, issue_iid, user = nil)
+ get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
+ end
+
+ def create_referencing_mr(user, project, issue)
+ attributes = {
+ author: user,
+ source_project: project,
+ target_project: project,
+ source_branch: "master",
+ target_branch: "test",
+ assignee: user,
+ description: "See #{issue.to_reference}"
+ }
+ create(:merge_request, attributes).tap do |merge_request|
+ create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
+ end
+ end
+
+ it 'issues/related_merge_requests.json' do |example|
+ user = create(:user)
+ project = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ issue_title = 'foo'
+ issue_description = 'closed'
+ milestone = create(:milestone, title: '1.0.0', project: project)
+ issue = create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+
+ project.add_reporter(user)
+ create_referencing_mr(user, project, issue)
+
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "Some description")
+ project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline))
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
index 7deed985219..f00bc2eeb6d 100644
--- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
+++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js
@@ -1,25 +1,31 @@
import Vue from 'vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/vue_component_helper';
import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here
const createComponent = () => {
const Component = Vue.extend(frequentItemsListItemComponent);
- return mountComponent(Component, {
- itemId: mockProject.id,
- itemName: mockProject.name,
- namespace: mockProject.namespace,
- webUrl: mockProject.webUrl,
- avatarUrl: mockProject.avatarUrl,
+ return shallowMount(Component, {
+ propsData: {
+ itemId: mockProject.id,
+ itemName: mockProject.name,
+ namespace: mockProject.namespace,
+ webUrl: mockProject.webUrl,
+ avatarUrl: mockProject.avatarUrl,
+ },
});
};
describe('FrequentItemsListItemComponent', () => {
+ let wrapper;
let vm;
beforeEach(() => {
- vm = createComponent();
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
});
afterEach(() => {
@@ -29,11 +35,11 @@ describe('FrequentItemsListItemComponent', () => {
describe('computed', () => {
describe('hasAvatar', () => {
it('should return `true` or `false` if whether avatar is present or not', () => {
- vm.avatarUrl = 'path/to/avatar.png';
+ wrapper.setProps({ avatarUrl: 'path/to/avatar.png' });
expect(vm.hasAvatar).toBe(true);
- vm.avatarUrl = null;
+ wrapper.setProps({ avatarUrl: null });
expect(vm.hasAvatar).toBe(false);
});
@@ -41,41 +47,49 @@ describe('FrequentItemsListItemComponent', () => {
describe('highlightedItemName', () => {
it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => {
- vm.matcher = 'lab';
+ wrapper.setProps({ matcher: 'lab' });
- expect(vm.highlightedItemName).toContain('<b>Lab</b>');
+ expect(wrapper.find('.js-frequent-items-item-title').html()).toContain(
+ '<b>L</b><b>a</b><b>b</b>',
+ );
});
it('should return project name as it is if `matcher` is not available', () => {
- vm.matcher = null;
+ wrapper.setProps({ matcher: null });
- expect(vm.highlightedItemName).toBe(mockProject.name);
+ expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe(
+ mockProject.name,
+ );
});
});
describe('truncatedNamespace', () => {
it('should truncate project name from namespace string', () => {
- vm.namespace = 'platform / nokia-3310';
+ wrapper.setProps({ namespace: 'platform / nokia-3310' });
- expect(vm.truncatedNamespace).toBe('platform');
+ expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).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';
+ wrapper.setProps({
+ namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310',
+ });
- expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset');
+ expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe(
+ 'platform / ... / Mobile Chipset',
+ );
});
});
});
describe('template', () => {
it('should render component element', () => {
- expect(vm.$el.classList.contains('frequent-items-list-item-container')).toBeTruthy();
- expect(vm.$el.querySelectorAll('a').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-avatar-container').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-metadata-container').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-title').length).toBe(1);
- expect(vm.$el.querySelectorAll('.frequent-items-item-namespace').length).toBe(1);
+ expect(wrapper.classes()).toContain('frequent-items-list-item-container');
+ expect(wrapper.findAll('a').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-metadata-container').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-title').length).toBe(1);
+ expect(wrapper.findAll('.frequent-items-item-namespace').length).toBe(1);
});
});
});
diff --git a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
index d564292f1ba..ddbbc5c2d29 100644
--- a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
+++ b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js
@@ -1,19 +1,22 @@
import Vue from 'vue';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
import eventHub from '~/frequent_items/event_hub';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
const createComponent = (namespace = 'projects') => {
const Component = Vue.extend(searchComponent);
- return mountComponent(Component, { namespace });
+ return shallowMount(Component, { propsData: { namespace } });
};
describe('FrequentItemsSearchInputComponent', () => {
+ let wrapper;
let vm;
beforeEach(() => {
- vm = createComponent();
+ wrapper = createComponent();
+
+ ({ vm } = wrapper);
});
afterEach(() => {
@@ -35,7 +38,7 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('mounted', () => {
it('should listen `dropdownOpen` event', done => {
spyOn(eventHub, '$on');
- const vmX = createComponent();
+ const vmX = createComponent().vm;
Vue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith(
@@ -49,7 +52,7 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('beforeDestroy', () => {
it('should unbind event listeners on eventHub', done => {
- const vmX = createComponent();
+ const vmX = createComponent().vm;
spyOn(eventHub, '$off');
vmX.$mount();
@@ -67,12 +70,12 @@ describe('FrequentItemsSearchInputComponent', () => {
describe('template', () => {
it('should render component element', () => {
- const inputEl = vm.$el.querySelector('input.form-control');
-
- expect(vm.$el.classList.contains('search-input-container')).toBeTruthy();
- expect(inputEl).not.toBe(null);
- expect(inputEl.getAttribute('placeholder')).toBe('Search your projects');
- expect(vm.$el.querySelector('.search-icon')).toBeDefined();
+ expect(wrapper.classes()).toContain('search-input-container');
+ expect(wrapper.contains('input.form-control')).toBe(true);
+ expect(wrapper.contains('.search-icon')).toBe(true);
+ expect(wrapper.find('input.form-control').attributes('placeholder')).toBe(
+ 'Search your projects',
+ );
});
});
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index 3a5d6c8a90b..23e6a055518 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import store from '~/ide/stores';
+import consts from '~/ide/stores/modules/commit/constants';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from 'spec/ide/helpers';
@@ -7,20 +8,33 @@ import { projectData } from 'spec/ide/mock_data';
describe('IDE commit sidebar actions', () => {
let vm;
-
- beforeEach(done => {
+ const createComponent = ({
+ hasMR = false,
+ commitAction = consts.COMMIT_TO_NEW_BRANCH,
+ mergeRequestsEnabled = true,
+ currentBranchId = 'master',
+ shouldCreateMR = false,
+ } = {}) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
- vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
+ vm.$store.state.commit.commitAction = commitAction;
Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
+ vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled;
+ vm.$store.state.commit.shouldCreateMR = shouldCreateMR;
- vm.$mount();
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
- Vue.nextTick(done);
- });
+ return vm.$mount();
+ };
afterEach(() => {
vm.$destroy();
@@ -28,16 +42,20 @@ describe('IDE commit sidebar actions', () => {
resetStore(vm.$store);
});
- it('renders 3 groups', () => {
- expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3);
+ it('renders 2 groups', () => {
+ createComponent();
+
+ expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
});
it('renders current branch text', () => {
+ createComponent();
+
expect(vm.$el.textContent).toContain('Commit to master branch');
});
it('hides merge request option when project merge requests are disabled', done => {
- vm.$store.state.projects.abcproject.merge_requests_enabled = false;
+ createComponent({ mergeRequestsEnabled: false });
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2);
@@ -49,9 +67,53 @@ describe('IDE commit sidebar actions', () => {
describe('commitToCurrentBranchText', () => {
it('escapes current branch', () => {
- vm.$store.state.currentBranchId = '<img src="x" />';
+ const injectedSrc = '<img src="x" />';
+ createComponent({ currentBranchId: injectedSrc });
+
+ expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
+ });
+ });
+
+ describe('create new MR checkbox', () => {
+ it('disables `createMR` button when an MR already exists and committing to current branch', () => {
+ createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true);
+ });
+
+ it('does not disable checkbox if MR does not exist', () => {
+ createComponent({ hasMR: false });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
+ });
+
+ it('does not disable checkbox when creating a new branch', () => {
+ createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
+ });
+
+ it('toggles off new MR when switching back to commit to current branch and MR exists', () => {
+ createComponent({
+ commitAction: consts.COMMIT_TO_NEW_BRANCH,
+ shouldCreateMR: true,
+ });
+ const currentBranchRadio = vm.$el.querySelector(
+ `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`,
+ );
+ currentBranchRadio.click();
+
+ vm.$nextTick(() => {
+ expect(vm.$store.state.commit.shouldCreateMR).toBe(false);
+ });
+ });
+
+ it('toggles `shouldCreateMR` when clicking checkbox', () => {
+ createComponent();
+ const el = vm.$el.querySelector('input[type="checkbox"]');
+ el.dispatchEvent(new Event('change'));
- expect(vm.commitToCurrentBranchText).not.toContain('<img src="x" />');
+ expect(vm.$store.state.commit.shouldCreateMR).toBe(true);
});
});
});
diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js
index c93a939ad71..d7fed3f0681 100644
--- a/spec/javascripts/ide/components/file_row_extra_spec.js
+++ b/spec/javascripts/ide/components/file_row_extra_spec.js
@@ -20,7 +20,7 @@ describe('IDE extra file row component', () => {
file: {
...file('test'),
},
- mouseOver: false,
+ dropdownOpen: false,
});
spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount);
diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js
index 83e530f0a6a..aaebe88f314 100644
--- a/spec/javascripts/ide/components/new_dropdown/index_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js
@@ -56,11 +56,11 @@ describe('new dropdown component', () => {
});
});
- describe('dropdownOpen', () => {
+ describe('isOpen', () => {
it('scrolls dropdown into view', done => {
spyOn(vm.$refs.dropdownMenu, 'scrollIntoView');
- vm.dropdownOpen = true;
+ vm.isOpen = true;
setTimeout(() => {
expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({
diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js
index a5839630657..4dd0c1150eb 100644
--- a/spec/javascripts/ide/stores/actions/merge_request_spec.js
+++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js
@@ -2,7 +2,6 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import actions, {
- getMergeRequestsForBranch,
getMergeRequestData,
getMergeRequestChanges,
getMergeRequestVersions,
@@ -12,13 +11,17 @@ import service from '~/ide/services';
import { activityBarViews } from '~/ide/constants';
import { resetStore } from '../../helpers';
+const TEST_PROJECT = 'abcproject';
+const TEST_PROJECT_ID = 17;
+
describe('IDE store merge request actions', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- store.state.projects.abcproject = {
+ store.state.projects[TEST_PROJECT] = {
+ id: TEST_PROJECT_ID,
mergeRequests: {},
};
});
@@ -41,10 +44,11 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequests service method', done => {
store
- .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' })
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
.then(() => {
- expect(service.getProjectMergeRequests).toHaveBeenCalledWith('abcproject', {
+ expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, {
source_branch: 'bar',
+ source_project_id: TEST_PROJECT_ID,
order_by: 'created_at',
per_page: 1,
});
@@ -56,13 +60,11 @@ describe('IDE store merge request actions', () => {
it('sets the "Merge Request" Object', done => {
store
- .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' })
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
.then(() => {
- expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(1);
- expect(Object.keys(store.state.projects.abcproject.mergeRequests)[0]).toEqual('2');
- expect(store.state.projects.abcproject.mergeRequests[2]).toEqual(
- jasmine.objectContaining(mrData),
- );
+ expect(store.state.projects.abcproject.mergeRequests).toEqual({
+ '2': jasmine.objectContaining(mrData),
+ });
done();
})
.catch(done.fail);
@@ -70,7 +72,7 @@ describe('IDE store merge request actions', () => {
it('sets "Current Merge Request" object to the most recent MR', done => {
store
- .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' })
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
.then(() => {
expect(store.state.currentMergeRequestId).toEqual('2');
done();
@@ -87,9 +89,9 @@ describe('IDE store merge request actions', () => {
it('does not fail if there are no merge requests for current branch', done => {
store
- .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'foo' })
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'foo' })
.then(() => {
- expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(0);
+ expect(store.state.projects[TEST_PROJECT].mergeRequests).toEqual({});
expect(store.state.currentMergeRequestId).toEqual('');
done();
})
@@ -106,7 +108,8 @@ describe('IDE store merge request actions', () => {
it('flashes message, if error', done => {
const flashSpy = spyOnDependency(actions, 'flash');
- getMergeRequestsForBranch({ commit() {} }, { projectId: 'abcproject', branchId: 'bar' })
+ store
+ .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' })
.then(() => {
fail('Expected getMergeRequestsForBranch to throw an error');
})
@@ -132,9 +135,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestData service method', done => {
store
- .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1, {
+ expect(service.getProjectMergeRequestData).toHaveBeenCalledWith(TEST_PROJECT, 1, {
render_html: true,
});
@@ -145,10 +148,12 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Object', done => {
store
- .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest');
expect(store.state.currentMergeRequestId).toBe(1);
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].title).toBe(
+ 'mergerequest',
+ );
done();
})
@@ -170,7 +175,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -179,7 +184,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -193,7 +198,7 @@ describe('IDE store merge request actions', () => {
describe('getMergeRequestChanges', () => {
beforeEach(() => {
- store.state.projects.abcproject.mergeRequests['1'] = { changes: [] };
+ store.state.projects[TEST_PROJECT].mergeRequests['1'] = { changes: [] };
});
describe('success', () => {
@@ -207,9 +212,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestChanges service method', done => {
store
- .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1);
+ expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith(TEST_PROJECT, 1);
done();
})
@@ -218,9 +223,9 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Changes Object', done => {
store
- .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe(
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].changes.title).toBe(
'mergerequest',
);
done();
@@ -243,7 +248,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -252,7 +257,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -266,7 +271,7 @@ describe('IDE store merge request actions', () => {
describe('getMergeRequestVersions', () => {
beforeEach(() => {
- store.state.projects.abcproject.mergeRequests['1'] = { versions: [] };
+ store.state.projects[TEST_PROJECT].mergeRequests['1'] = { versions: [] };
});
describe('success', () => {
@@ -279,9 +284,9 @@ describe('IDE store merge request actions', () => {
it('calls getProjectMergeRequestVersions service method', done => {
store
- .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1);
+ expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith(TEST_PROJECT, 1);
done();
})
@@ -290,9 +295,9 @@ describe('IDE store merge request actions', () => {
it('sets the Merge Request Versions Object', done => {
store
- .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 })
+ .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 })
.then(() => {
- expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1);
+ expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].versions.length).toBe(1);
done();
})
.catch(done.fail);
@@ -313,7 +318,7 @@ describe('IDE store merge request actions', () => {
dispatch,
state: store.state,
},
- { projectId: 'abcproject', mergeRequestId: 1 },
+ { projectId: TEST_PROJECT, mergeRequestId: 1 },
)
.then(done.fail)
.catch(() => {
@@ -322,7 +327,7 @@ describe('IDE store merge request actions', () => {
action: jasmine.any(Function),
actionText: 'Please try again',
actionPayload: {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
mergeRequestId: 1,
force: false,
},
@@ -336,7 +341,7 @@ describe('IDE store merge request actions', () => {
describe('openMergeRequest', () => {
const mr = {
- projectId: 'abcproject',
+ projectId: TEST_PROJECT,
targetProjectId: 'defproject',
mergeRequestId: 2,
};
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 7b0963713fb..cd519eaed7c 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -279,5 +279,22 @@ describe('IDE store project actions', () => {
.then(done)
.catch(done.fail);
});
+
+ it('creates a new file supplied via URL if the file does not exist yet', done => {
+ openBranch(store, { ...branch, basePath: 'not-existent.md' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ jasmine.anything(),
+ );
+
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: 'not-existent.md',
+ type: 'blob',
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index fbb676aab33..5ed9b9003a7 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'spec/helpers/vuex_action_helper';
-import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree';
+import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree';
import * as types from '~/ide/stores/mutation_types';
import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
@@ -206,4 +206,35 @@ describe('Multi-file store tree actions', () => {
);
});
});
+
+ describe('setDirectoryData', () => {
+ it('sets tree correctly if there are no opened files yet', done => {
+ const treeFile = file({ name: 'README.md' });
+ store.state.trees['abcproject/master'] = {};
+
+ testAction(
+ setDirectoryData,
+ { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] },
+ store.state,
+ [
+ {
+ type: types.SET_DIRECTORY_DATA,
+ payload: {
+ treePath: 'abcproject/master',
+ data: [treeFile],
+ },
+ },
+ {
+ type: types.TOGGLE_LOADING,
+ payload: {
+ entry: {},
+ forceValue: false,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 34d97347438..abc97f3072c 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -3,7 +3,7 @@ import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
-import * as consts from '~/ide/stores/modules/commit/constants';
+import consts from '~/ide/stores/modules/commit/constants';
import { resetStore, file } from 'spec/ide/helpers';
describe('IDE commit module actions', () => {
@@ -389,7 +389,8 @@ describe('IDE commit module actions', () => {
it('redirects to new merge request page', done => {
spyOn(eventHub, '$on');
- store.state.commit.commitAction = '3';
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.shouldCreateMR = true;
store
.dispatch('commit/commitChanges')
@@ -405,6 +406,21 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
+ it('does not redirect to new merge request page when shouldCreateMR is not checked', done => {
+ spyOn(eventHub, '$on');
+
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.shouldCreateMR = false;
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(visitUrl).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
it('resets changed files before redirecting', done => {
spyOn(eventHub, '$on');
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 702e78ef773..e00fd7199d7 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -1,5 +1,5 @@
import commitState from '~/ide/stores/modules/commit/state';
-import * as consts from '~/ide/stores/modules/commit/constants';
+import consts from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
describe('IDE commit module getters', () => {
@@ -46,7 +46,7 @@ describe('IDE commit module getters', () => {
currentBranchId: 'master',
};
const localGetters = {
- placeholderBranchName: 'newBranchName',
+ placeholderBranchName: 'placeholder-branch-name',
};
beforeEach(() => {
@@ -59,25 +59,28 @@ describe('IDE commit module getters', () => {
expect(getters.branchName(state, null, rootState)).toBe('master');
});
- ['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach(type => {
- describe(type, () => {
- beforeEach(() => {
- Object.assign(state, {
- commitAction: consts[type],
- });
+ describe('COMMIT_TO_NEW_BRANCH', () => {
+ beforeEach(() => {
+ Object.assign(state, {
+ commitAction: consts.COMMIT_TO_NEW_BRANCH,
});
+ });
- it('uses newBranchName when not empty', () => {
- expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName');
+ it('uses newBranchName when not empty', () => {
+ const newBranchName = 'nonempty-branch-name';
+ Object.assign(state, {
+ newBranchName,
});
- it('uses placeholderBranchName when state newBranchName is empty', () => {
- Object.assign(state, {
- newBranchName: '',
- });
+ expect(getters.branchName(state, localGetters, rootState)).toBe(newBranchName);
+ });
- expect(getters.branchName(state, localGetters, rootState)).toBe('newBranchName');
+ it('uses placeholderBranchName when state newBranchName is empty', () => {
+ Object.assign(state, {
+ newBranchName: '',
});
+
+ expect(getters.branchName(state, localGetters, rootState)).toBe('placeholder-branch-name');
});
});
});
@@ -141,4 +144,33 @@ describe('IDE commit module getters', () => {
});
});
});
+
+ describe('shouldDisableNewMrOption', () => {
+ it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => {
+ state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ const rootState = {
+ currentMergeRequest: { foo: 'bar' },
+ };
+
+ expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
+ });
+
+ it('returns false if there is no current merge request', () => {
+ state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ const rootState = {
+ currentMergeRequest: null,
+ };
+
+ expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
+ });
+
+ it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => {
+ state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ const rootState = {
+ currentMergeRequest: { foo: 'bar' },
+ };
+
+ expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy();
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/mutations/tree_spec.js b/spec/javascripts/ide/stores/mutations/tree_spec.js
index 67e9f7509da..7f9c978aa46 100644
--- a/spec/javascripts/ide/stores/mutations/tree_spec.js
+++ b/spec/javascripts/ide/stores/mutations/tree_spec.js
@@ -26,17 +26,11 @@ describe('Multi-file store tree mutations', () => {
});
describe('SET_DIRECTORY_DATA', () => {
- const data = [
- {
- name: 'tree',
- },
- {
- name: 'submodule',
- },
- {
- name: 'blob',
- },
- ];
+ let data;
+
+ beforeEach(() => {
+ data = [file('tree'), file('foo'), file('blob')];
+ });
it('adds directory data', () => {
localState.trees['project/master'] = {
@@ -52,7 +46,7 @@ describe('Multi-file store tree mutations', () => {
expect(tree.tree.length).toBe(3);
expect(tree.tree[0].name).toBe('tree');
- expect(tree.tree[1].name).toBe('submodule');
+ expect(tree.tree[1].name).toBe('foo');
expect(tree.tree[2].name).toBe('blob');
});
@@ -65,6 +59,49 @@ describe('Multi-file store tree mutations', () => {
expect(localState.trees['project/master'].loading).toBe(true);
});
+
+ it('does not override tree already in state, but merges the two with correct order', () => {
+ const openedFile = file('new');
+
+ localState.trees['project/master'] = {
+ loading: true,
+ tree: [openedFile],
+ };
+
+ mutations.SET_DIRECTORY_DATA(localState, {
+ data,
+ treePath: 'project/master',
+ });
+
+ const { tree } = localState.trees['project/master'];
+
+ expect(tree.length).toBe(4);
+ expect(tree[0].name).toBe('blob');
+ expect(tree[1].name).toBe('foo');
+ expect(tree[2].name).toBe('new');
+ expect(tree[3].name).toBe('tree');
+ });
+
+ it('returns tree unchanged if the opened file is already in the tree', () => {
+ const openedFile = file('foo');
+ localState.trees['project/master'] = {
+ loading: true,
+ tree: [openedFile],
+ };
+
+ mutations.SET_DIRECTORY_DATA(localState, {
+ data,
+ treePath: 'project/master',
+ });
+
+ const { tree } = localState.trees['project/master'];
+
+ expect(tree.length).toBe(3);
+
+ expect(tree[0].name).toBe('tree');
+ expect(tree[1].name).toBe('foo');
+ expect(tree[2].name).toBe('blob');
+ });
});
describe('REMOVE_ALL_CHANGES_FILES', () => {
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index 9f18034f8a3..c4f122efdda 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -235,4 +235,129 @@ describe('Multi-file store utils', () => {
]);
});
});
+
+ describe('mergeTrees', () => {
+ let fromTree;
+ let toTree;
+
+ beforeEach(() => {
+ fromTree = [file('foo')];
+ toTree = [file('bar')];
+ });
+
+ it('merges simple trees with sorting the result', () => {
+ toTree = [file('beta'), file('alpha'), file('gamma')];
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(4);
+ expect(res[0].name).toEqual('alpha');
+ expect(res[1].name).toEqual('beta');
+ expect(res[2].name).toEqual('foo');
+ expect(res[3].name).toEqual('gamma');
+ expect(res[2]).toBe(fromTree[0]);
+ });
+
+ it('handles edge cases', () => {
+ expect(utils.mergeTrees({}, []).length).toEqual(0);
+
+ let res = utils.mergeTrees({}, toTree);
+
+ expect(res.length).toEqual(1);
+ expect(res[0].name).toEqual('bar');
+
+ res = utils.mergeTrees(fromTree, []);
+
+ expect(res.length).toEqual(1);
+ expect(res[0].name).toEqual('foo');
+ expect(res[0]).toBe(fromTree[0]);
+ });
+
+ it('merges simple trees without producing duplicates', () => {
+ toTree.push(file('foo'));
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(2);
+ expect(res[0].name).toEqual('bar');
+ expect(res[1].name).toEqual('foo');
+ expect(res[1]).not.toBe(fromTree[0]);
+ });
+
+ it('merges nested tree into the main one without duplicates', () => {
+ fromTree[0].tree.push({
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('beta.md'),
+ path: 'foo/alpha/beta.md',
+ },
+ ],
+ });
+
+ toTree.push({
+ ...file('foo'),
+ tree: [
+ {
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('gamma.md'),
+ path: 'foo/alpha/gamma.md',
+ },
+ ],
+ },
+ ],
+ });
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res.length).toEqual(2);
+ expect(res[1].name).toEqual('foo');
+
+ const finalBranch = res[1].tree[0].tree;
+
+ expect(finalBranch.length).toEqual(2);
+ expect(finalBranch[0].name).toEqual('beta.md');
+ expect(finalBranch[1].name).toEqual('gamma.md');
+ });
+
+ it('marks correct folders as opened as the parsing goes on', () => {
+ fromTree[0].tree.push({
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('beta.md'),
+ path: 'foo/alpha/beta.md',
+ },
+ ],
+ });
+
+ toTree.push({
+ ...file('foo'),
+ tree: [
+ {
+ ...file('alpha'),
+ path: 'foo/alpha',
+ tree: [
+ {
+ ...file('gamma.md'),
+ path: 'foo/alpha/gamma.md',
+ },
+ ],
+ },
+ ],
+ });
+
+ const res = utils.mergeTrees(fromTree, toTree);
+
+ expect(res[1].name).toEqual('foo');
+ expect(res[1].opened).toEqual(true);
+
+ expect(res[1].tree[0].name).toEqual('alpha');
+ expect(res[1].tree[0].opened).toEqual(true);
+ });
+ });
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index ba5d672f189..cef40117304 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -17,6 +17,7 @@ describe('Job App ', () => {
const props = {
endpoint: `${gl.TEST_HOST}jobs/123.json`,
runnerHelpUrl: 'help/runner',
+ deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
@@ -253,6 +254,41 @@ describe('Job App ', () => {
});
});
+ describe('unmet prerequisites block', () => {
+ it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
+ mock.onGet(props.endpoint).replyOnce(
+ 200,
+ Object.assign({}, job, {
+ status: {
+ group: 'failed',
+ icon: 'status_failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ illustration: {
+ content: 'Retry this job in order to create the necessary resources.',
+ image: 'path',
+ size: 'svg-430',
+ title: 'Failed to create resources',
+ },
+ },
+ failure_reason: 'unmet_prerequisites',
+ has_trace: false,
+ runners: {
+ available: true,
+ },
+ tags: [],
+ }),
+ );
+ vm = mountComponentWithStore(Component, { props, store });
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ done();
+ }, 0);
+ });
+ });
+
describe('environments block', () => {
it('renders environment block when job has environment', done => {
mock.onGet(props.endpoint).replyOnce(
diff --git a/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js
new file mode 100644
index 00000000000..68fcb321214
--- /dev/null
+++ b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import component from '~/jobs/components/unmet_prerequisites_block.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Unmet Prerequisites Block Job component', () => {
+ const Component = Vue.extend(component);
+ let vm;
+ const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs';
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ hasNoRunnersForProject: true,
+ helpPath,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders an alert with the correct message', () => {
+ const container = vm.$el.querySelector('.js-failed-unmet-prerequisites');
+ const alertMessage =
+ 'This job failed because the necessary resources were not successfully created.';
+
+ expect(container).not.toBeNull();
+ expect(container.innerHTML).toContain(alertMessage);
+ });
+
+ it('renders link to help page', () => {
+ const helpLink = vm.$el.querySelector('.js-help-path');
+
+ expect(helpLink).not.toBeNull();
+ expect(helpLink.innerHTML).toContain('More information');
+ expect(helpLink.getAttribute('href')).toEqual(helpPath);
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 2084c36e484..da012e1d5f7 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -2,7 +2,7 @@ import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
-import BreakpointInstance from '~/breakpoints';
+import breakpointInstance from '~/breakpoints';
const PIXEL_TOLERANCE = 0.2;
@@ -383,7 +383,7 @@ describe('common_utils', () => {
describe('contentTop', () => {
it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => {
- spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm');
+ spyOn(breakpointInstance, 'isDesktop').and.returnValue(false);
setFixtures(`
<div class="diff-file file-title-flex-parent">
@@ -398,7 +398,7 @@ describe('common_utils', () => {
});
it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => {
- spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg');
+ spyOn(breakpointInstance, 'isDesktop').and.returnValue(true);
setFixtures(`
<div class="diff-file file-title-flex-parent">
diff --git a/spec/javascripts/lib/utils/higlight_spec.js b/spec/javascripts/lib/utils/higlight_spec.js
new file mode 100644
index 00000000000..638bbf65ae9
--- /dev/null
+++ b/spec/javascripts/lib/utils/higlight_spec.js
@@ -0,0 +1,43 @@
+import highlight from '~/lib/utils/highlight';
+
+describe('highlight', () => {
+ it(`should appropriately surround substring matches`, () => {
+ const expected = 'g<b>i</b><b>t</b>lab';
+
+ expect(highlight('gitlab', 'it')).toBe(expected);
+ });
+
+ it(`should return an empty string in the case of invalid inputs`, () => {
+ [null, undefined].forEach(input => {
+ expect(highlight(input, 'match')).toBe('');
+ });
+ });
+
+ it(`should return the original value if match is null, undefined, or ''`, () => {
+ [null, undefined].forEach(match => {
+ expect(highlight('gitlab', match)).toBe('gitlab');
+ });
+ });
+
+ it(`should highlight matches in non-string inputs`, () => {
+ const expected = '123<b>4</b><b>5</b>6';
+
+ expect(highlight(123456, 45)).toBe(expected);
+ });
+
+ it(`should sanitize the input string before highlighting matches`, () => {
+ const expected = 'hello <b>w</b>orld';
+
+ expect(highlight('hello <b>world</b>', 'w')).toBe(expected);
+ });
+
+ it(`should not highlight anything if no matches are found`, () => {
+ expect(highlight('gitlab', 'hello')).toBe('gitlab');
+ });
+
+ it(`should allow wrapping elements to be customized`, () => {
+ const expected = '1<hello>2</hello>3';
+
+ expect(highlight('123', '2', '<hello>', '</hello>')).toBe(expected);
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index 549a7935c0f..41a6c04efb9 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
import MonitoringStore from '~/monitoring/stores/monitoring_store';
@@ -65,7 +65,7 @@ describe('Area component', () => {
expect(props.data).toBe(areaChart.vm.chartData);
expect(props.option).toBe(areaChart.vm.chartOptions);
expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText);
- expect(props.thresholds).toBe(areaChart.props('alertData'));
+ expect(props.thresholds).toBe(areaChart.vm.thresholds);
});
it('recieves a tooltip title', () => {
@@ -105,12 +105,13 @@ describe('Area component', () => {
seriesName: areaChart.vm.chartData[0].name,
componentSubType: type,
value: [mockDate, 5.55555],
+ seriesIndex: 0,
},
],
value: mockDate,
});
- describe('series is of line type', () => {
+ describe('when series is of line type', () => {
beforeEach(() => {
areaChart.vm.formatTooltipText(generateSeriesData('line'));
});
@@ -120,18 +121,20 @@ describe('Area component', () => {
});
it('formats tooltip content', () => {
- expect(areaChart.vm.tooltip.content).toEqual([{ name: 'Core Usage', value: '5.556' }]);
+ const name = 'Core Usage';
+ const value = '5.556';
+ const seriesLabel = areaChart.find(GlChartSeriesLabel);
+
+ expect(seriesLabel.vm.color).toBe('');
+ expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+ expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
expect(
- shallowWrapperContainsSlotText(
- areaChart.find(GlAreaChart),
- 'tooltipContent',
- 'Core Usage 5.556',
- ),
+ shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value),
).toBe(true);
});
});
- describe('series is of scatter type', () => {
+ describe('when series is of scatter type', () => {
beforeEach(() => {
areaChart.vm.formatTooltipText(generateSeriesData('scatter'));
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 454777fa912..ce2c6c43c0f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
+import { timeWindows } from '~/monitoring/constants';
import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
@@ -50,7 +51,7 @@ describe('Dashboard', () => {
it('shows a getting started empty state when no metrics are present', () => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData,
+ propsData: { ...propsData, showTimeWindowDropdown: false },
});
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
@@ -66,7 +67,7 @@ describe('Dashboard', () => {
it('shows up a loading state', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true },
+ propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false },
});
Vue.nextTick(() => {
@@ -78,7 +79,12 @@ describe('Dashboard', () => {
it('hides the legend when showLegend is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showLegend: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showLegend: false,
+ showTimeWindowDropdown: false,
+ },
});
setTimeout(() => {
@@ -92,7 +98,12 @@ describe('Dashboard', () => {
it('hides the group panels when showPanels is false', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
});
setTimeout(() => {
@@ -106,7 +117,12 @@ describe('Dashboard', () => {
it('renders the environments dropdown with a number of environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
});
component.store.storeEnvironmentsData(environmentData);
@@ -124,7 +140,12 @@ describe('Dashboard', () => {
it('hides the environments dropdown list when there is no environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
});
component.store.storeEnvironmentsData([]);
@@ -142,7 +163,12 @@ describe('Dashboard', () => {
it('renders the environments dropdown with a single is-active element', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
});
component.store.storeEnvironmentsData(environmentData);
@@ -166,6 +192,7 @@ describe('Dashboard', () => {
hasMetrics: true,
showPanels: false,
environmentsEndpoint: '',
+ showTimeWindowDropdown: false,
},
});
@@ -176,6 +203,51 @@ describe('Dashboard', () => {
done();
});
});
+
+ it('does not show the time window dropdown when the feature flag is not set', done => {
+ const component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
+ });
+
+ setTimeout(() => {
+ const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+
+ expect(timeWindowDropdown).toBeNull();
+
+ done();
+ });
+ });
+
+ it('renders the time window dropdown with a set of options', done => {
+ const component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: true,
+ },
+ });
+ const numberOfTimeWindows = Object.keys(timeWindows).length;
+
+ setTimeout(() => {
+ const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+ const timeWindowDropdownEls = component.$el.querySelectorAll(
+ '.js-time-window-dropdown .dropdown-item',
+ );
+
+ expect(timeWindowDropdown).not.toBeNull();
+ expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
+
+ done();
+ });
+ });
});
describe('when the window resizes', () => {
@@ -191,7 +263,12 @@ describe('Dashboard', () => {
it('sets elWidth to page width when the sidebar is resized', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showPanels: false },
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ },
});
expect(component.elWidth).toEqual(0);
diff --git a/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js
new file mode 100644
index 00000000000..29760f79c3c
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js
@@ -0,0 +1,89 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
+import RelatedMergeRequests from '~/related_merge_requests/components/related_merge_requests.vue';
+import createStore from '~/related_merge_requests/store/index';
+
+const FIXTURE_PATH = 'issues/related_merge_requests.json';
+const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests';
+const localVue = createLocalVue();
+
+describe('RelatedMergeRequests', () => {
+ let wrapper;
+ let mock;
+ let mockData;
+
+ beforeEach(done => {
+ loadFixtures(FIXTURE_PATH);
+ mockData = getJSONFixture(FIXTURE_PATH);
+ mock = new MockAdapter(axios);
+ mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });
+
+ wrapper = mount(RelatedMergeRequests, {
+ localVue,
+ sync: false,
+ store: createStore(),
+ propsData: {
+ endpoint: API_ENDPOINT,
+ projectNamespace: 'gitlab-org',
+ projectPath: 'gitlab-ce',
+ },
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('methods', () => {
+ describe('getAssignees', () => {
+ const assignees = [{ name: 'foo' }, { name: 'bar' }];
+
+ describe('when there is assignees array', () => {
+ it('should return assignees array', () => {
+ const mr = { assignees };
+
+ expect(wrapper.vm.getAssignees(mr)).toEqual(assignees);
+ });
+ });
+
+ it('should return an array with single assingee', () => {
+ const mr = { assignee: assignees[0] };
+
+ expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]);
+ });
+
+ it('should return empty array when assignee is not set', () => {
+ expect(wrapper.vm.getAssignees({})).toEqual([]);
+ expect(wrapper.vm.getAssignees({ assignee: null })).toEqual([]);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('should render related merge request items', () => {
+ expect(wrapper.find('.js-items-count').text()).toEqual('2');
+ expect(wrapper.findAll(RelatedIssuableItem).length).toEqual(2);
+
+ const props = wrapper
+ .findAll(RelatedIssuableItem)
+ .at(1)
+ .props();
+ const data = mockData[1];
+
+ expect(props.idKey).toEqual(data.id);
+ expect(props.pathIdSeparator).toEqual('!');
+ expect(props.pipelineStatus).toBe(data.head_pipeline.detailed_status);
+ expect(props.assignees).toEqual([data.assignee]);
+ expect(props.isMergeRequest).toBe(true);
+ expect(props.confidential).toEqual(false);
+ expect(props.title).toEqual(data.title);
+ expect(props.state).toEqual(data.state);
+ expect(props.createdAt).toEqual(data.created_at);
+ });
+ });
+});
diff --git a/spec/javascripts/related_merge_requests/store/actions_spec.js b/spec/javascripts/related_merge_requests/store/actions_spec.js
new file mode 100644
index 00000000000..65e436fbb17
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/store/actions_spec.js
@@ -0,0 +1,110 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import * as types from '~/related_merge_requests/store/mutation_types';
+import actionsModule, * as actions from '~/related_merge_requests/store/actions';
+import testAction from 'spec/helpers/vuex_action_helper';
+
+describe('RelatedMergeRequest store actions', () => {
+ let state;
+ let flashSpy;
+ let mock;
+
+ beforeEach(() => {
+ state = {
+ apiEndpoint: '/api/related_merge_requests',
+ };
+ flashSpy = spyOnDependency(actionsModule, 'createFlash');
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('setInitialState', () => {
+ it('commits types.SET_INITIAL_STATE with given props', done => {
+ const props = { a: 1, b: 2 };
+
+ testAction(
+ actions.setInitialState,
+ props,
+ {},
+ [{ type: types.SET_INITIAL_STATE, payload: props }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestData', () => {
+ it('commits types.REQUEST_DATA', done => {
+ testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], [], done);
+ });
+ });
+
+ describe('receiveDataSuccess', () => {
+ it('commits types.RECEIVE_DATA_SUCCESS with data', done => {
+ const data = { a: 1, b: 2 };
+
+ testAction(
+ actions.receiveDataSuccess,
+ data,
+ {},
+ [{ type: types.RECEIVE_DATA_SUCCESS, payload: data }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveDataError', () => {
+ it('commits types.RECEIVE_DATA_ERROR', done => {
+ testAction(
+ actions.receiveDataError,
+ null,
+ {},
+ [{ type: types.RECEIVE_DATA_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchMergeRequests', () => {
+ describe('for a successful request', () => {
+ it('should dispatch success action', done => {
+ const data = { a: 1 };
+ mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(200, data, { 'x-total': 2 });
+
+ testAction(
+ actions.fetchMergeRequests,
+ null,
+ state,
+ [],
+ [{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }],
+ done,
+ );
+ });
+ });
+
+ describe('for a failing request', () => {
+ it('should dispatch error action', done => {
+ mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(400);
+
+ testAction(
+ actions.fetchMergeRequests,
+ null,
+ state,
+ [],
+ [{ type: 'requestData' }, { type: 'receiveDataError' }],
+ () => {
+ expect(flashSpy).toHaveBeenCalledTimes(1);
+ expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('Something went wrong'));
+
+ done();
+ },
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/related_merge_requests/store/mutations_spec.js b/spec/javascripts/related_merge_requests/store/mutations_spec.js
new file mode 100644
index 00000000000..21b6e26376b
--- /dev/null
+++ b/spec/javascripts/related_merge_requests/store/mutations_spec.js
@@ -0,0 +1,49 @@
+import mutations from '~/related_merge_requests/store/mutations';
+import * as types from '~/related_merge_requests/store/mutation_types';
+
+describe('RelatedMergeRequests Store Mutations', () => {
+ describe('SET_INITIAL_STATE', () => {
+ it('should set initial state according to given data', () => {
+ const apiEndpoint = '/api';
+ const state = {};
+
+ mutations[types.SET_INITIAL_STATE](state, { apiEndpoint });
+
+ expect(state.apiEndpoint).toEqual(apiEndpoint);
+ });
+ });
+
+ describe('REQUEST_DATA', () => {
+ it('should set loading flag', () => {
+ const state = {};
+
+ mutations[types.REQUEST_DATA](state);
+
+ expect(state.isFetchingMergeRequests).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_DATA_SUCCESS', () => {
+ it('should set loading flag and data', () => {
+ const state = {};
+ const mrs = [1, 2, 3];
+
+ mutations[types.RECEIVE_DATA_SUCCESS](state, { data: mrs, total: mrs.length });
+
+ expect(state.isFetchingMergeRequests).toEqual(false);
+ expect(state.mergeRequests).toEqual(mrs);
+ expect(state.totalCount).toEqual(mrs.length);
+ });
+ });
+
+ describe('RECEIVE_DATA_ERROR', () => {
+ it('should set loading and error flags', () => {
+ const state = {};
+
+ mutations[types.RECEIVE_DATA_ERROR](state);
+
+ expect(state.isFetchingMergeRequests).toEqual(false);
+ expect(state.hasErrorFetchingMergeRequests).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js
deleted file mode 100644
index bdf7a714910..00000000000
--- a/spec/javascripts/serverless/components/environment_row_spec.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import Vue from 'vue';
-
-import environmentRowComponent from '~/serverless/components/environment_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import ServerlessStore from '~/serverless/stores/serverless_store';
-
-import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
-
-const createComponent = (env, envName) =>
- mountComponent(Vue.extend(environmentRowComponent), { env, envName });
-
-describe('environment row component', () => {
- describe('default global cluster case', () => {
- let vm;
-
- beforeEach(() => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctions);
- vm = createComponent(store.state.functions['*'], '*');
- });
-
- it('has the correct envId', () => {
- expect(vm.envId).toEqual('env-global');
- vm.$destroy();
- });
-
- it('is open by default', () => {
- expect(vm.isOpenClass).toEqual({ 'is-open': true });
- vm.$destroy();
- });
-
- it('generates correct output', () => {
- expect(vm.$el.querySelectorAll('li').length).toEqual(2);
- expect(vm.$el.id).toEqual('env-global');
- expect(vm.$el.classList.contains('is-open')).toBe(true);
- expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*');
-
- vm.$destroy();
- });
-
- it('opens and closes correctly', () => {
- expect(vm.isOpen).toBe(true);
-
- vm.toggleOpen();
- Vue.nextTick(() => {
- expect(vm.isOpen).toBe(false);
- });
-
- vm.$destroy();
- });
- });
-
- describe('default named cluster case', () => {
- let vm;
-
- beforeEach(() => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctionsDiffEnv);
- vm = createComponent(store.state.functions.test, 'test');
- });
-
- it('has the correct envId', () => {
- expect(vm.envId).toEqual('env-test');
- vm.$destroy();
- });
-
- it('is open by default', () => {
- expect(vm.isOpenClass).toEqual({ 'is-open': true });
- vm.$destroy();
- });
-
- it('generates correct output', () => {
- expect(vm.$el.querySelectorAll('li').length).toEqual(1);
- expect(vm.$el.id).toEqual('env-test');
- expect(vm.$el.classList.contains('is-open')).toBe(true);
- expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test');
-
- vm.$destroy();
- });
- });
-});
diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js
deleted file mode 100644
index 6933a8f6c87..00000000000
--- a/spec/javascripts/serverless/components/function_row_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-
-import functionRowComponent from '~/serverless/components/function_row.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-import { mockServerlessFunction } from '../mock_data';
-
-const createComponent = func => mountComponent(Vue.extend(functionRowComponent), { func });
-
-describe('functionRowComponent', () => {
- it('Parses the function details correctly', () => {
- const vm = createComponent(mockServerlessFunction);
-
- expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name);
- expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image);
- expect(vm.$el.querySelector('time').getAttribute('data-original-title')).not.toBe(null);
- expect(vm.$el.querySelector('div.url-text-field').innerHTML).toEqual(
- mockServerlessFunction.url,
- );
-
- vm.$destroy();
- });
-
- it('handles clicks correctly', () => {
- const vm = createComponent(mockServerlessFunction);
-
- expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row
- expect(vm.checkClass(vm.$el.querySelector('svg'))).toBe(false); // check a button image
- expect(vm.checkClass(vm.$el.querySelector('div.url-text-field'))).toBe(false); // check the url bar
-
- vm.$destroy();
- });
-});
diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js
deleted file mode 100644
index 85cfe71281f..00000000000
--- a/spec/javascripts/serverless/components/functions_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import Vue from 'vue';
-
-import functionsComponent from '~/serverless/components/functions.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import ServerlessStore from '~/serverless/stores/serverless_store';
-
-import { mockServerlessFunctions } from '../mock_data';
-
-const createComponent = (
- functions,
- installed = true,
- loadingData = true,
- hasFunctionData = true,
-) => {
- const component = Vue.extend(functionsComponent);
-
- return mountComponent(component, {
- functions,
- installed,
- clustersPath: '/testClusterPath',
- helpPath: '/helpPath',
- loadingData,
- hasFunctionData,
- });
-};
-
-describe('functionsComponent', () => {
- it('should render empty state when Knative is not installed', () => {
- const vm = createComponent({}, false);
-
- expect(vm.$el.querySelector('div.row').classList.contains('js-empty-state')).toBe(true);
- expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual(
- 'Getting started with serverless',
- );
-
- vm.$destroy();
- });
-
- it('should render a loading component', () => {
- const vm = createComponent({});
-
- expect(vm.$el.querySelector('.gl-responsive-table-row')).not.toBe(null);
- expect(vm.$el.querySelector('div.animation-container')).not.toBe(null);
- });
-
- it('should render empty state when there is no function data', () => {
- const vm = createComponent({}, true, false, false);
-
- expect(
- vm.$el.querySelector('.empty-state, .js-empty-state').classList.contains('js-empty-state'),
- ).toBe(true);
-
- expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual(
- 'No functions available',
- );
-
- vm.$destroy();
- });
-
- it('should render the functions list', () => {
- const store = new ServerlessStore(false, '/cluster_path', 'help_path');
- store.updateFunctionsFromServer(mockServerlessFunctions);
- const vm = createComponent(store.state.functions, true, false);
-
- expect(vm.$el.querySelector('div.groups-list-tree-container')).not.toBe(null);
- expect(vm.$el.querySelector('#env-global').classList.contains('has-children')).toBe(true);
- });
-});
diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js
deleted file mode 100644
index 21a879a49bb..00000000000
--- a/spec/javascripts/serverless/components/url_spec.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue';
-
-import urlComponent from '~/serverless/components/url.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = uri => {
- const component = Vue.extend(urlComponent);
-
- return mountComponent(component, {
- uri,
- });
-};
-
-describe('urlComponent', () => {
- it('should render correctly', () => {
- const uri = 'http://testfunc.apps.example.com';
- const vm = createComponent(uri);
-
- expect(vm.$el.classList.contains('clipboard-group')).toBe(true);
- expect(vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text')).toEqual(
- uri,
- );
-
- expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri);
-
- vm.$destroy();
- });
-});
diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js
deleted file mode 100644
index ecd393b174c..00000000000
--- a/spec/javascripts/serverless/mock_data.js
+++ /dev/null
@@ -1,79 +0,0 @@
-export const mockServerlessFunctions = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
-
-export const mockServerlessFunctionsDiffEnv = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: 'test',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
-
-export const mockServerlessFunction = {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: '3',
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
-};
-
-export const mockMultilineServerlessFunction = {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: '3',
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'testfunc1\nA test service line\\nWith additional services',
- image: 'knative-test-container-buildtemplate',
-};
diff --git a/spec/javascripts/serverless/stores/serverless_store_spec.js b/spec/javascripts/serverless/stores/serverless_store_spec.js
deleted file mode 100644
index 72fd903d7d1..00000000000
--- a/spec/javascripts/serverless/stores/serverless_store_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import ServerlessStore from '~/serverless/stores/serverless_store';
-import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data';
-
-describe('Serverless Functions Store', () => {
- let store;
-
- beforeEach(() => {
- store = new ServerlessStore(false, '/cluster_path', 'help_path');
- });
-
- describe('#updateFunctionsFromServer', () => {
- it('should pass an empty hash object', () => {
- store.updateFunctionsFromServer();
-
- expect(store.state.functions).toEqual({});
- });
-
- it('should group functions to one global environment', () => {
- const mockServerlessData = mockServerlessFunctions;
- store.updateFunctionsFromServer(mockServerlessData);
-
- expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*']));
- expect(store.state.functions['*'].length).toEqual(2);
- });
-
- it('should group functions to multiple environments', () => {
- const mockServerlessData = mockServerlessFunctionsDiffEnv;
- store.updateFunctionsFromServer(mockServerlessData);
-
- expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*']));
- expect(store.state.functions['*'].length).toEqual(1);
- expect(store.state.functions.test.length).toEqual(1);
- expect(store.state.functions.test[0].name).toEqual('testfunc2');
- });
- });
-});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 235a17d13b0..87ef0885d8c 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -69,7 +69,7 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
-window.gon.ee = process.env.EE;
+window.gon.ee = process.env.IS_GITLAB_EE;
gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
@@ -124,7 +124,7 @@ const axiosDefaultAdapter = getDefaultAdapter();
// render all of our tests
const testContexts = [require.context('spec', true, /_spec$/)];
-if (process.env.EE) {
+if (process.env.IS_GITLAB_EE) {
testContexts.push(require.context('ee_spec', true, /_spec$/));
}
@@ -213,7 +213,7 @@ if (process.env.BABEL_ENV === 'coverage') {
describe('Uncovered files', function() {
const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
- if (process.env.EE) {
+ if (process.env.IS_GITLAB_EE) {
sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/));
}
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index d1fd899c1a8..7da69e3fa84 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import FileRow from '~/vue_shared/components/file_row.vue';
+import FileRowExtra from '~/ide/components/file_row_extra.vue';
import { file } from 'spec/ide/helpers';
import mountComponent from '../../helpers/vue_mount_component_helper';
@@ -16,6 +17,10 @@ describe('File row component', () => {
vm.$destroy();
});
+ const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown');
+ const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button');
+ const findFileRow = () => vm.$el.querySelector('.file-row');
+
it('renders name', () => {
createComponent({
file: file('t4'),
@@ -84,4 +89,59 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
+
+ describe('new dropdown', () => {
+ beforeEach(() => {
+ createComponent({
+ file: file('t5'),
+ level: 1,
+ extraComponent: FileRowExtra,
+ });
+ });
+
+ it('renders in extra component', () => {
+ expect(findNewDropdown()).not.toBe(null);
+ });
+
+ it('is hidden at start', () => {
+ expect(findNewDropdown()).not.toHaveClass('show');
+ });
+
+ it('is opened when button is clicked', done => {
+ expect(vm.dropdownOpen).toBe(false);
+ findNewDropdownButton().dispatchEvent(new Event('click'));
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.dropdownOpen).toBe(true);
+ expect(findNewDropdown()).toHaveClass('show');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('when opened', () => {
+ beforeEach(() => {
+ vm.dropdownOpen = true;
+ });
+
+ it('stays open when button triggers mouseout', () => {
+ findNewDropdownButton().dispatchEvent(new Event('mouseout'));
+
+ expect(vm.dropdownOpen).toBe(true);
+ });
+
+ it('stays open when button triggers mouseleave', () => {
+ findNewDropdownButton().dispatchEvent(new Event('mouseleave'));
+
+ expect(vm.dropdownOpen).toBe(true);
+ });
+
+ it('closes when row triggers mouseleave', () => {
+ findFileRow().dispatchEvent(new Event('mouseleave'));
+
+ expect(vm.dropdownOpen).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js
new file mode 100644
index 00000000000..b95183747bb
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js
@@ -0,0 +1,110 @@
+import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/vue_component_helper';
+
+const localVue = createLocalVue();
+
+describe('ProjectListItem component', () => {
+ const Component = localVue.extend(ProjectListItem);
+ let wrapper;
+ let vm;
+ let options;
+ loadJSONFixtures('projects.json');
+ const project = getJSONFixture('projects.json')[0];
+
+ beforeEach(() => {
+ options = {
+ propsData: {
+ project,
+ selected: false,
+ },
+ sync: false,
+ localVue,
+ };
+ });
+
+ afterEach(() => {
+ wrapper.vm.$destroy();
+ });
+
+ it('does not render a check mark icon if selected === false', () => {
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true);
+ });
+
+ it('renders a check mark icon if selected === true', () => {
+ options.propsData.selected = true;
+
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-selected-icon.js-selected')).toBe(true);
+ });
+
+ it(`emits a "clicked" event when clicked`, () => {
+ wrapper = shallowMount(Component, options);
+ ({ vm } = wrapper);
+
+ spyOn(vm, '$emit');
+ wrapper.vm.onClick();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('click');
+ });
+
+ it(`renders the project avatar`, () => {
+ wrapper = shallowMount(Component, options);
+
+ expect(wrapper.contains('.js-project-avatar')).toBe(true);
+ });
+
+ it(`renders a simple namespace name with a trailing slash`, () => {
+ options.propsData.project.name_with_namespace = 'a / b';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('a /');
+ });
+
+ it(`renders a properly truncated namespace with a trailing slash`, () => {
+ options.propsData.project.name_with_namespace = 'a / b / c / d / e / f';
+
+ wrapper = shallowMount(Component, options);
+ const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+
+ expect(renderedNamespace).toBe('a / ... / e /');
+ });
+
+ it(`renders the project name`, () => {
+ options.propsData.project.name = 'my-test-project';
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').text());
+
+ expect(renderedName).toBe('my-test-project');
+ });
+
+ it(`renders the project name with highlighting in the case of a search query match`, () => {
+ options.propsData.project.name = 'my-test-project';
+ options.propsData.matcher = 'pro';
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').html());
+ const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject';
+
+ expect(renderedName).toContain(expected);
+ });
+
+ it('prevents search query and project name XSS', () => {
+ const alertSpy = spyOn(window, 'alert');
+ options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject";
+ options.propsData.matcher = "pro<script>alert('XSS');</script>";
+
+ wrapper = shallowMount(Component, options);
+ const renderedName = trimText(wrapper.find('.js-project-name').html());
+ const expected = 'my-xss-project';
+
+ expect(renderedName).toContain(expected);
+ expect(alertSpy).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
new file mode 100644
index 00000000000..ba9ec8f2f19
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
@@ -0,0 +1,132 @@
+import Vue from 'vue';
+import _ from 'underscore';
+import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue';
+import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
+import { shallowMount } from '@vue/test-utils';
+import { trimText } from 'spec/helpers/vue_component_helper';
+
+describe('ProjectSelector component', () => {
+ let wrapper;
+ let vm;
+ loadJSONFixtures('projects.json');
+ const allProjects = getJSONFixture('projects.json');
+ const searchResults = allProjects.slice(0, 5);
+ let selected = [];
+ selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8));
+
+ beforeEach(() => {
+ jasmine.clock().install();
+
+ wrapper = shallowMount(Vue.extend(ProjectSelector), {
+ propsData: {
+ projectSearchResults: searchResults,
+ selectedProjects: selected,
+ showNoResultsMessage: false,
+ showMinimumSearchQueryMessage: false,
+ showLoadingIndicator: false,
+ showSearchErrorMessage: false,
+ },
+ attachToDocument: true,
+ });
+
+ ({ vm } = wrapper);
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ vm.$destroy();
+ });
+
+ it('renders the search results', () => {
+ expect(wrapper.findAll('.js-project-list-item').length).toBe(5);
+ });
+
+ it(`triggers a (debounced) search when the search input value changes`, () => {
+ spyOn(vm, '$emit');
+ const query = 'my test query!';
+ const searchInput = wrapper.find('.js-project-selector-input');
+ searchInput.setValue(query);
+ searchInput.trigger('input');
+
+ expect(vm.$emit).not.toHaveBeenCalledWith();
+ jasmine.clock().tick(501);
+
+ expect(vm.$emit).toHaveBeenCalledWith('searched', query);
+ });
+
+ it(`debounces the search input`, () => {
+ spyOn(vm, '$emit');
+ const searchInput = wrapper.find('.js-project-selector-input');
+
+ const updateSearchQuery = (count = 0) => {
+ if (count === 10) {
+ jasmine.clock().tick(101);
+
+ expect(vm.$emit).toHaveBeenCalledTimes(1);
+ expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`);
+ } else {
+ searchInput.setValue(`search query #${count}`);
+ searchInput.trigger('input');
+
+ jasmine.clock().tick(400);
+ updateSearchQuery(count + 1);
+ }
+ };
+
+ updateSearchQuery();
+ });
+
+ it(`includes a placeholder in the search box`, () => {
+ expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe(
+ 'Search your projects',
+ );
+ });
+
+ it(`triggers a "projectClicked" event when a project is clicked`, () => {
+ spyOn(vm, '$emit');
+ wrapper.find(ProjectListItem).vm.$emit('click', _.first(searchResults));
+
+ expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults));
+ });
+
+ it(`shows a "no results" message if showNoResultsMessage === true`, () => {
+ wrapper.setProps({ showNoResultsMessage: true });
+
+ expect(wrapper.contains('.js-no-results-message')).toBe(true);
+
+ const noResultsEl = wrapper.find('.js-no-results-message');
+
+ expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
+ });
+
+ it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => {
+ wrapper.setProps({ showMinimumSearchQueryMessage: true });
+
+ expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true);
+
+ const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
+
+ expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
+ });
+
+ it(`shows a error message if showSearchErrorMessage === true`, () => {
+ wrapper.setProps({ showSearchErrorMessage: true });
+
+ expect(wrapper.contains('.js-search-error-message')).toBe(true);
+
+ const errorMessageEl = wrapper.find('.js-search-error-message');
+
+ expect(trimText(errorMessageEl.text())).toEqual(
+ 'Something went wrong, unable to search projects',
+ );
+ });
+
+ it(`focuses the input element when the focusSearchInput() method is called`, () => {
+ const input = wrapper.find('.js-project-selector-input');
+
+ expect(document.activeElement).not.toBe(input.element);
+ vm.focusSearchInput();
+
+ expect(document.activeElement).toBe(input.element);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
index 5cf6afebd7e..0689fc1cf1f 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -45,12 +45,21 @@ describe('DropdownButtonComponent', () => {
});
const vmMoreLabels = createComponent(mockMoreLabels);
- expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more');
+ expect(vmMoreLabels.dropdownToggleText).toBe(
+ `Foo Label +${mockMoreLabels.labels.length - 1} more`,
+ );
vmMoreLabels.$destroy();
});
it('returns first label name when `labels` prop has only one item present', () => {
- expect(vm.dropdownToggleText).toBe('Foo Label');
+ const singleLabel = Object.assign({}, componentConfig, {
+ labels: [mockLabels[0]],
+ });
+ const vmSingleLabel = createComponent(singleLabel);
+
+ expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title);
+
+ vmSingleLabel.$destroy();
});
});
});
@@ -73,7 +82,7 @@ describe('DropdownButtonComponent', () => {
const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
expect(dropdownToggleTextEl).not.toBeNull();
- expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label');
+ expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more');
});
it('renders dropdown button icon', () => {
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
index 804b33422bd..cb49fa31d20 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -35,9 +35,12 @@ describe('DropdownValueCollapsedComponent', () => {
});
it('returns labels names separated by coma when `labels` prop has more than one item', () => {
- const vmMoreLabels = createComponent(mockLabels.concat(mockLabels));
+ const labels = mockLabels.concat(mockLabels);
+ const vmMoreLabels = createComponent(labels);
- expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label');
+ const expectedText = labels.map(label => label.title).join(', ');
+
+ expect(vmMoreLabels.labelsList).toBe(expectedText);
vmMoreLabels.$destroy();
});
@@ -49,14 +52,19 @@ describe('DropdownValueCollapsedComponent', () => {
const vmMoreLabels = createComponent(mockMoreLabels);
- expect(vmMoreLabels.labelsList).toBe(
- 'Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more',
- );
+ const expectedText = `${mockMoreLabels
+ .slice(0, 5)
+ .map(label => label.title)
+ .join(', ')}, and ${mockMoreLabels.length - 5} more`;
+
+ expect(vmMoreLabels.labelsList).toBe(expectedText);
vmMoreLabels.$destroy();
});
it('returns first label name when `labels` prop has only one item present', () => {
- expect(vm.labelsList).toBe('Foo Label');
+ const text = mockLabels.map(label => label.title).join(', ');
+
+ expect(vm.labelsList).toBe(text);
});
});
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
index 3fff781594f..35a9c300953 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import $ from 'jquery';
import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
@@ -15,6 +16,7 @@ const createComponent = (
return mountComponent(Component, {
labels,
labelFilterBasePath,
+ enableScopedLabels: true,
});
};
@@ -67,6 +69,26 @@ describe('DropdownValueComponent', () => {
expect(styleObj.backgroundColor).toBe(label.color);
});
});
+
+ describe('scopedLabelsDescription', () => {
+ it('returns html for tooltip', () => {
+ const html = vm.scopedLabelsDescription(mockLabels[1]);
+ const $el = $.parseHTML(html);
+
+ expect($el[0]).toHaveClass('scoped-label-tooltip-title');
+ expect($el[2].textContent).toEqual(mockLabels[1].description);
+ });
+ });
+
+ describe('showScopedLabels', () => {
+ it('returns true if the label is scoped label', () => {
+ expect(vm.showScopedLabels(mockLabels[1])).toBe(true);
+ });
+
+ it('returns false when label is a regular label', () => {
+ expect(vm.showScopedLabels(mockLabels[0])).toBe(false);
+ });
+ });
});
describe('template', () => {
@@ -91,15 +113,25 @@ describe('DropdownValueComponent', () => {
);
});
- it('renders label element with tooltip and styles based on label details', () => {
+ it('renders label element and styles based on label details', () => {
const labelEl = vm.$el.querySelector('a span.badge.color-label');
expect(labelEl).not.toBeNull();
- expect(labelEl.dataset.placement).toBe('bottom');
- expect(labelEl.dataset.container).toBe('body');
- expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description);
expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);');
expect(labelEl.innerText.trim()).toBe(mockLabels[0].title);
});
+
+ describe('label is of scoped-label type', () => {
+ it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => {
+ expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull();
+ });
+
+ it('renders anchor tag containing question icon', () => {
+ const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label');
+
+ expect(anchor).not.toBeNull();
+ expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull();
+ });
+ });
});
});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
index 3fcb91b6f5e..70025f041a7 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -6,6 +6,13 @@ export const mockLabels = [
color: '#BADA55',
text_color: '#FFFFFF',
},
+ {
+ id: 27,
+ title: 'Foo::Bar',
+ description: 'Foobar',
+ color: '#0033CC',
+ text_color: '#FFFFFF',
+ },
];
export const mockSuggestedColors = [