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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 17:34:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 17:34:42 +0300
commit9f46488805e86b1bc341ea1620b866016c2ce5ed (patch)
treef9748c7e287041e37d6da49e0a29c9511dc34768 /spec/frontend/pages
parentdfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff)
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'spec/frontend/pages')
-rw-r--r--spec/frontend/pages/admin/application_settings/account_and_limits_spec.js36
-rw-r--r--spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js64
-rw-r--r--spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap1
-rw-r--r--spec/frontend/pages/admin/users/new/index_spec.js43
-rw-r--r--spec/frontend/pages/labels/components/promote_label_modal_spec.js103
-rw-r--r--spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js109
-rw-r--r--spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js98
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js154
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js114
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js29
-rw-r--r--spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js61
11 files changed, 805 insertions, 7 deletions
diff --git a/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
new file mode 100644
index 00000000000..6a239e307e9
--- /dev/null
+++ b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
@@ -0,0 +1,36 @@
+import $ from 'jquery';
+import initUserInternalRegexPlaceholder, {
+ PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
+ PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE,
+} from '~/pages/admin/application_settings/account_and_limits';
+
+describe('AccountAndLimits', () => {
+ const FIXTURE = 'application_settings/accounts_and_limit.html';
+ let $userDefaultExternal;
+ let $userInternalRegex;
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ initUserInternalRegexPlaceholder();
+ $userDefaultExternal = $('#application_setting_user_default_external');
+ $userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
+ });
+
+ describe('Changing of userInternalRegex when userDefaultExternal', () => {
+ it('is unchecked', () => {
+ expect($userDefaultExternal.prop('checked')).toBeFalsy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
+ expect($userInternalRegex.readOnly).toBeTruthy();
+ });
+
+ it('is checked', done => {
+ if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
+
+ expect($userDefaultExternal.prop('checked')).toBeTruthy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
+ expect($userInternalRegex.readOnly).toBeFalsy();
+ done();
+ });
+ });
+});
diff --git a/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
new file mode 100644
index 00000000000..fe17c03389c
--- /dev/null
+++ b/spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import axios from '~/lib/utils/axios_utils';
+import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ redirectTo: jest.fn(),
+}));
+
+describe('stop_jobs_modal.vue', () => {
+ const props = {
+ url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ const Component = Vue.extend(stopJobsModal);
+ vm = mountComponent(Component, props);
+ });
+
+ describe('onSubmit', () => {
+ it('stops jobs and redirects to overview page', done => {
+ const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(props.url);
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectTo).toHaveBeenCalledWith(responseURL);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if stopping jobs failed', done => {
+ const dummyError = new Error('stopping jobs failed');
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(props.url);
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .then(done.fail)
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ expect(redirectTo).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
index ea3bedf59e0..82589e5147c 100644
--- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
+++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
@@ -37,7 +37,6 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
value=""
/>
</form>
-
<gl-deprecated-button-stub
size="md"
variant="secondary"
diff --git a/spec/frontend/pages/admin/users/new/index_spec.js b/spec/frontend/pages/admin/users/new/index_spec.js
new file mode 100644
index 00000000000..3896323eef7
--- /dev/null
+++ b/spec/frontend/pages/admin/users/new/index_spec.js
@@ -0,0 +1,43 @@
+import $ from 'jquery';
+import UserInternalRegexHandler from '~/pages/admin/users/new/index';
+
+describe('UserInternalRegexHandler', () => {
+ const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
+ let $userExternal;
+ let $userEmail;
+ let $warningMessage;
+
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ // eslint-disable-next-line no-new
+ new UserInternalRegexHandler();
+ $userExternal = $('#user_external');
+ $userEmail = $('#user_email');
+ $warningMessage = $('#warning_external_automatically_set');
+ if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
+ });
+
+ describe('Behaviour of userExternal checkbox when', () => {
+ it('matches email as internal', done => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeFalsy();
+ expect($warningMessage.hasClass('hidden')).toBeFalsy();
+ done();
+ });
+
+ it('matches email as external', done => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test.ext@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeTruthy();
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+ done();
+ });
+ });
+});
diff --git a/spec/frontend/pages/labels/components/promote_label_modal_spec.js b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
new file mode 100644
index 00000000000..9d5beca70b5
--- /dev/null
+++ b/spec/frontend/pages/labels/components/promote_label_modal_spec.js
@@ -0,0 +1,103 @@
+import Vue from 'vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import promoteLabelModal from '~/pages/projects/labels/components/promote_label_modal.vue';
+import eventHub from '~/pages/projects/labels/event_hub';
+import axios from '~/lib/utils/axios_utils';
+
+describe('Promote label modal', () => {
+ let vm;
+ const Component = Vue.extend(promoteLabelModal);
+ const labelMockData = {
+ labelTitle: 'Documentation',
+ labelColor: '#5cb85c',
+ labelTextColor: '#ffffff',
+ url: `${gl.TEST_HOST}/dummy/promote/labels`,
+ groupName: 'group',
+ };
+
+ describe('Modal title and description', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, labelMockData);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('contains the proper description', () => {
+ expect(vm.text).toContain(
+ `Promoting ${labelMockData.labelTitle} will make it available for all projects inside ${labelMockData.groupName}`,
+ );
+ });
+
+ it('contains a label span with the color', () => {
+ const labelFromTitle = vm.$el.querySelector('.modal-header .label.color-label');
+
+ expect(labelFromTitle.style.backgroundColor).not.toBe(null);
+ expect(labelFromTitle.textContent).toContain(vm.labelTitle);
+ });
+ });
+
+ describe('When requesting a label promotion', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...labelMockData,
+ });
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('redirects when a label is promoted', done => {
+ const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(labelMockData.url);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'promoteLabelModal.requestStarted',
+ labelMockData.url,
+ );
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', {
+ labelUrl: labelMockData.url,
+ successful: true,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays an error if promoting a label failed', done => {
+ const dummyError = new Error('promoting label failed');
+ dummyError.response = { status: 500 };
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(labelMockData.url);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'promoteLabelModal.requestStarted',
+ labelMockData.url,
+ );
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ expect(eventHub.$emit).toHaveBeenCalledWith('promoteLabelModal.requestFinished', {
+ labelUrl: labelMockData.url,
+ successful: false,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
new file mode 100644
index 00000000000..ff5dc6d8988
--- /dev/null
+++ b/spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js
@@ -0,0 +1,109 @@
+import Vue from 'vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import axios from '~/lib/utils/axios_utils';
+import deleteMilestoneModal from '~/pages/milestones/shared/components/delete_milestone_modal.vue';
+import eventHub from '~/pages/milestones/shared/event_hub';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ redirectTo: jest.fn(),
+}));
+
+describe('delete_milestone_modal.vue', () => {
+ const Component = Vue.extend(deleteMilestoneModal);
+ const props = {
+ issueCount: 1,
+ mergeRequestCount: 2,
+ milestoneId: 3,
+ milestoneTitle: 'my milestone title',
+ milestoneUrl: `${gl.TEST_HOST}/delete_milestone_modal.vue/milestone`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('onSubmit', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ it('deletes milestone and redirects to overview page', done => {
+ const responseURL = `${gl.TEST_HOST}/delete_milestone_modal.vue/milestoneOverview`;
+ jest.spyOn(axios, 'delete').mockImplementation(url => {
+ expect(url).toBe(props.milestoneUrl);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'deleteMilestoneModal.requestStarted',
+ props.milestoneUrl,
+ );
+ eventHub.$emit.mockReset();
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectTo).toHaveBeenCalledWith(responseURL);
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: props.milestoneUrl,
+ successful: true,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if deleting milestone failed', done => {
+ const dummyError = new Error('deleting milestone failed');
+ dummyError.response = { status: 418 };
+ jest.spyOn(axios, 'delete').mockImplementation(url => {
+ expect(url).toBe(props.milestoneUrl);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'deleteMilestoneModal.requestStarted',
+ props.milestoneUrl,
+ );
+ eventHub.$emit.mockReset();
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ expect(redirectTo).not.toHaveBeenCalled();
+ expect(eventHub.$emit).toHaveBeenCalledWith('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: props.milestoneUrl,
+ successful: false,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('text', () => {
+ it('contains the issue and milestone count', () => {
+ vm = mountComponent(Component, props);
+ const value = vm.text;
+
+ expect(value).toContain('remove it from 1 issue and 2 merge requests');
+ });
+
+ it('contains neither issue nor milestone count', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ issueCount: 0,
+ mergeRequestCount: 0,
+ });
+
+ const value = vm.text;
+
+ expect(value).toContain('is not currently used');
+ });
+ });
+});
diff --git a/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
new file mode 100644
index 00000000000..ff896354d96
--- /dev/null
+++ b/spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js
@@ -0,0 +1,98 @@
+import Vue from 'vue';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import promoteMilestoneModal from '~/pages/milestones/shared/components/promote_milestone_modal.vue';
+import eventHub from '~/pages/milestones/shared/event_hub';
+import axios from '~/lib/utils/axios_utils';
+
+describe('Promote milestone modal', () => {
+ let vm;
+ const Component = Vue.extend(promoteMilestoneModal);
+ const milestoneMockData = {
+ milestoneTitle: 'v1.0',
+ url: `${gl.TEST_HOST}/dummy/promote/milestones`,
+ groupName: 'group',
+ };
+
+ describe('Modal title and description', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, milestoneMockData);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('contains the proper description', () => {
+ expect(vm.text).toContain(
+ `Promoting ${milestoneMockData.milestoneTitle} will make it available for all projects inside ${milestoneMockData.groupName}.`,
+ );
+ });
+
+ it('contains the correct title', () => {
+ expect(vm.title).toEqual('Promote v1.0 to group milestone?');
+ });
+ });
+
+ describe('When requesting a milestone promotion', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...milestoneMockData,
+ });
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('redirects when a milestone is promoted', done => {
+ const responseURL = `${gl.TEST_HOST}/dummy/endpoint`;
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(milestoneMockData.url);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'promoteMilestoneModal.requestStarted',
+ milestoneMockData.url,
+ );
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', {
+ milestoneUrl: milestoneMockData.url,
+ successful: true,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays an error if promoting a milestone failed', done => {
+ const dummyError = new Error('promoting milestone failed');
+ dummyError.response = { status: 500 };
+ jest.spyOn(axios, 'post').mockImplementation(url => {
+ expect(url).toBe(milestoneMockData.url);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'promoteMilestoneModal.requestStarted',
+ milestoneMockData.url,
+ );
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ expect(eventHub.$emit).toHaveBeenCalledWith('promoteMilestoneModal.requestFinished', {
+ milestoneUrl: milestoneMockData.url,
+ successful: false,
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
new file mode 100644
index 00000000000..9cc1d6eeb5a
--- /dev/null
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
@@ -0,0 +1,154 @@
+import { shallowMount } from '@vue/test-utils';
+import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
+
+describe('Interval Pattern Input Component', () => {
+ let oldWindowGl;
+ let wrapper;
+
+ const mockHour = 4;
+ const mockWeekDayIndex = 1;
+ const mockDay = 1;
+
+ const cronIntervalPresets = {
+ everyDay: `0 ${mockHour} * * *`,
+ everyWeek: `0 ${mockHour} * * ${mockWeekDayIndex}`,
+ everyMonth: `0 ${mockHour} ${mockDay} * *`,
+ };
+
+ const findEveryDayRadio = () => wrapper.find('#every-day');
+ const findEveryWeekRadio = () => wrapper.find('#every-week');
+ const findEveryMonthRadio = () => wrapper.find('#every-month');
+ const findCustomRadio = () => wrapper.find('#custom');
+ const findCustomInput = () => wrapper.find('#schedule_cron');
+ const selectEveryDayRadio = () => findEveryDayRadio().setChecked();
+ const selectEveryWeekRadio = () => findEveryWeekRadio().setChecked();
+ const selectEveryMonthRadio = () => findEveryMonthRadio().setChecked();
+ const selectCustomRadio = () => findCustomRadio().trigger('click');
+
+ const createWrapper = (props = {}, data = {}) => {
+ if (wrapper) {
+ throw new Error('A wrapper already exists');
+ }
+
+ wrapper = shallowMount(IntervalPatternInput, {
+ propsData: { ...props },
+ data() {
+ return {
+ randomHour: data?.hour || mockHour,
+ randomWeekDayIndex: mockWeekDayIndex,
+ randomDay: mockDay,
+ };
+ },
+ });
+ };
+
+ beforeEach(() => {
+ oldWindowGl = window.gl;
+ window.gl = {
+ ...(window.gl || {}),
+ pipelineScheduleFieldErrors: {
+ updateFormValidityState: jest.fn(),
+ },
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ window.gl = oldWindowGl;
+ });
+
+ describe('the input field defaults', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('to a non empty string when no initial value is not passed', () => {
+ expect(findCustomInput()).not.toBe('');
+ });
+ });
+
+ describe('the input field', () => {
+ const initialCron = '0 * * * *';
+
+ beforeEach(() => {
+ createWrapper({ initialCronInterval: initialCron });
+ });
+
+ it('is equal to the prop `initialCronInterval` when passed', () => {
+ expect(findCustomInput().element.value).toBe(initialCron);
+ });
+ });
+
+ describe('The input field is enabled', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('when a default option is selected', () => {
+ selectEveryDayRadio();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
+ });
+ });
+
+ it('when the custom option is selected', () => {
+ selectCustomRadio();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCustomInput().attributes('disabled')).toBeUndefined();
+ });
+ });
+ });
+
+ describe('formattedTime computed property', () => {
+ it.each`
+ desc | hour | expectedValue
+ ${'returns a time in the afternoon if the value of `random time` is higher than 12'} | ${13} | ${'1:00pm'}
+ ${'returns a time in the morning if the value of `random time` is lower than 12'} | ${11} | ${'11:00am'}
+ ${'returns "12:00pm" if the value of `random time` is exactly 12'} | ${12} | ${'12:00pm'}
+ `('$desc', ({ hour, expectedValue }) => {
+ createWrapper({}, { hour });
+
+ expect(wrapper.vm.formattedTime).toBe(expectedValue);
+ });
+ });
+
+ describe('User Actions with radio buttons', () => {
+ it.each`
+ desc | initialCronInterval | act | expectedValue
+ ${'when everyday is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryDayRadio} | ${cronIntervalPresets.everyDay}
+ ${'when everyweek is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryWeekRadio} | ${cronIntervalPresets.everyWeek}
+ ${'when everymonth is selected, update value'} | ${'1 2 3 4 5'} | ${selectEveryMonthRadio} | ${cronIntervalPresets.everyMonth}
+ ${'when custom is selected, add space to value'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${`${cronIntervalPresets.everyMonth} `}
+ `('$desc', ({ initialCronInterval, act, expectedValue }) => {
+ createWrapper({ initialCronInterval });
+
+ act();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCustomInput().element.value).toBe(expectedValue);
+ });
+ });
+ });
+ describe('User actions with input field for Cron syntax', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('when editing the cron input it selects the custom radio button', () => {
+ const newValue = '0 * * * *';
+
+ findCustomInput().setValue(newValue);
+
+ expect(wrapper.vm.cronInterval).toBe(newValue);
+ });
+
+ it('when value of input is one of the defaults, it selects the corresponding radio button', () => {
+ findCustomInput().setValue(cronIntervalPresets.everyWeek);
+
+ expect(wrapper.vm.cronInterval).toBe(cronIntervalPresets.everyWeek);
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
new file mode 100644
index 00000000000..5a61f9fca69
--- /dev/null
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
@@ -0,0 +1,114 @@
+import Vue from 'vue';
+import Cookies from 'js-cookie';
+import PipelineSchedulesCallout from '~/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue';
+import '~/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg';
+
+jest.mock(
+ '~/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg',
+ () => '<svg></svg>',
+);
+
+const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout);
+const cookieKey = 'pipeline_schedules_callout_dismissed';
+const docsUrl = 'help/ci/scheduled_pipelines';
+
+describe('Pipeline Schedule Callout', () => {
+ let calloutComponent;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id='pipeline-schedules-callout' data-docs-url=${docsUrl}></div>
+ `);
+ });
+
+ describe('independent of cookies', () => {
+ beforeEach(() => {
+ calloutComponent = new PipelineSchedulesCalloutComponent().$mount();
+ });
+
+ it('the component can be initialized', () => {
+ expect(calloutComponent).toBeDefined();
+ });
+
+ it('correctly sets illustrationSvg', () => {
+ expect(calloutComponent.illustrationSvg).toContain('<svg');
+ });
+
+ it('correctly sets docsUrl', () => {
+ expect(calloutComponent.docsUrl).toContain(docsUrl);
+ });
+ });
+
+ describe(`when ${cookieKey} cookie is set`, () => {
+ beforeEach(() => {
+ Cookies.set(cookieKey, true);
+ calloutComponent = new PipelineSchedulesCalloutComponent().$mount();
+ });
+
+ it('correctly sets calloutDismissed to true', () => {
+ expect(calloutComponent.calloutDismissed).toBe(true);
+ });
+
+ it('does not render the callout', () => {
+ expect(calloutComponent.$el.childNodes.length).toBe(0);
+ });
+ });
+
+ describe('when cookie is not set', () => {
+ beforeEach(() => {
+ Cookies.remove(cookieKey);
+ calloutComponent = new PipelineSchedulesCalloutComponent().$mount();
+ });
+
+ it('correctly sets calloutDismissed to false', () => {
+ expect(calloutComponent.calloutDismissed).toBe(false);
+ });
+
+ it('renders the callout container', () => {
+ expect(calloutComponent.$el.querySelector('.bordered-box')).not.toBeNull();
+ });
+
+ it('renders the callout svg', () => {
+ expect(calloutComponent.$el.outerHTML).toContain('<svg');
+ });
+
+ it('renders the callout title', () => {
+ expect(calloutComponent.$el.outerHTML).toContain('Scheduling Pipelines');
+ });
+
+ it('renders the callout text', () => {
+ expect(calloutComponent.$el.outerHTML).toContain('runs pipelines in the future');
+ });
+
+ it('renders the documentation url', () => {
+ expect(calloutComponent.$el.outerHTML).toContain(docsUrl);
+ });
+
+ it('updates calloutDismissed when close button is clicked', done => {
+ calloutComponent.$el.querySelector('#dismiss-callout-btn').click();
+
+ Vue.nextTick(() => {
+ expect(calloutComponent.calloutDismissed).toBe(true);
+ done();
+ });
+ });
+
+ it('#dismissCallout updates calloutDismissed', done => {
+ calloutComponent.dismissCallout();
+
+ Vue.nextTick(() => {
+ expect(calloutComponent.calloutDismissed).toBe(true);
+ done();
+ });
+ });
+
+ it('is hidden when close button is clicked', done => {
+ calloutComponent.$el.querySelector('#dismiss-callout-btn').click();
+
+ Vue.nextTick(() => {
+ expect(calloutComponent.$el.childNodes.length).toBe(0);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 9c292fa0f2b..1f7eec567b8 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -23,6 +23,7 @@ const defaultProps = {
lfsEnabled: true,
emailsDisabled: false,
packagesEnabled: true,
+ showDefaultAwardEmojis: true,
},
canDisableEmails: true,
canChangeVisibilityLevel: true,
@@ -57,9 +58,6 @@ describe('Settings Panel', () => {
return mountFn(settingsPanel, {
propsData,
- provide: {
- glFeatures: { metricsDashboardVisibilitySwitchingAvailable: true },
- },
});
};
@@ -477,6 +475,18 @@ describe('Settings Panel', () => {
});
});
+ describe('Default award emojis', () => {
+ it('should show the "Show default award emojis" input', () => {
+ return wrapper.vm.$nextTick(() => {
+ expect(
+ wrapper
+ .find('input[name="project[project_setting_attributes][show_default_award_emojis]"]')
+ .exists(),
+ ).toBe(true);
+ });
+ });
+ });
+
describe('Metrics dashboard', () => {
it('should show the metrics dashboard access toggle', () => {
return wrapper.vm.$nextTick(() => {
@@ -489,15 +499,22 @@ describe('Settings Panel', () => {
.find('[name="project[project_feature_attributes][metrics_dashboard_access_level]"]')
.setValue(visibilityOptions.PUBLIC);
- expect(wrapper.vm.metricsAccessLevel).toBe(visibilityOptions.PUBLIC);
+ expect(wrapper.vm.metricsDashboardAccessLevel).toBe(visibilityOptions.PUBLIC);
});
it('should contain help text', () => {
- wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
-
expect(wrapper.find({ ref: 'metrics-visibility-settings' }).props().helpText).toEqual(
'With Metrics Dashboard you can visualize this project performance metrics',
);
});
+
+ it('should disable the metrics visibility dropdown when the project visibility level changes to private', () => {
+ wrapper = overrideCurrentSettings({ visibilityLevel: visibilityOptions.PRIVATE });
+
+ const metricsSettingsRow = wrapper.find({ ref: 'metrics-visibility-settings' });
+
+ expect(wrapper.vm.metricsOptionsDropdownEnabled).toBe(true);
+ expect(metricsSettingsRow.find('select').attributes('disabled')).toEqual('disabled');
+ });
});
});
diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
new file mode 100644
index 00000000000..1809e92e1d9
--- /dev/null
+++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
@@ -0,0 +1,61 @@
+import $ from 'jquery';
+import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
+
+describe('preserve_url_fragment', () => {
+ preloadFixtures('sessions/new.html');
+
+ beforeEach(() => {
+ loadFixtures('sessions/new.html');
+ });
+
+ it('adds the url fragment to all login and sign up form actions', () => {
+ preserveUrlFragment('#L65');
+
+ expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in#L65');
+ expect($('#new_new_user').attr('action')).toBe('http://test.host/users#L65');
+ });
+
+ it('does not add an empty url fragment to login and sign up form actions', () => {
+ preserveUrlFragment();
+
+ expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in');
+ expect($('#new_new_user').attr('action')).toBe('http://test.host/users');
+ });
+
+ it('does not add an empty query parameter to OmniAuth login buttons', () => {
+ preserveUrlFragment();
+
+ expect($('#oauth-login-cas3').attr('href')).toBe('http://test.host/users/auth/cas3');
+
+ expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0',
+ );
+ });
+
+ describe('adds "redirect_fragment" query parameter to OmniAuth login buttons', () => {
+ it('when "remember_me" is not present', () => {
+ preserveUrlFragment('#L65');
+
+ expect($('#oauth-login-cas3').attr('href')).toBe(
+ 'http://test.host/users/auth/cas3?redirect_fragment=L65',
+ );
+
+ expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0?redirect_fragment=L65',
+ );
+ });
+
+ it('when "remember-me" is present', () => {
+ $('a.omniauth-btn').attr('href', (i, href) => `${href}?remember_me=1`);
+ preserveUrlFragment('#L65');
+
+ expect($('#oauth-login-cas3').attr('href')).toBe(
+ 'http://test.host/users/auth/cas3?remember_me=1&redirect_fragment=L65',
+ );
+
+ expect($('#oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
+ );
+ });
+ });
+});