From 9f46488805e86b1bc341ea1620b866016c2ce5ed Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 May 2020 14:34:42 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-0-stable-ee --- .../account_and_limits_spec.js | 36 +++++ .../jobs/index/components/stop_jobs_modal_spec.js | 64 +++++++++ .../__snapshots__/delete_user_modal_spec.js.snap | 1 - spec/frontend/pages/admin/users/new/index_spec.js | 43 ++++++ .../labels/components/promote_label_modal_spec.js | 103 ++++++++++++++ .../components/delete_milestone_modal_spec.js | 109 +++++++++++++++ .../components/promote_milestone_modal_spec.js | 98 +++++++++++++ .../components/interval_pattern_input_spec.js | 154 +++++++++++++++++++++ .../components/pipeline_schedule_callout_spec.js | 114 +++++++++++++++ .../permissions/components/settings_panel_spec.js | 29 +++- .../sessions/new/preserve_url_fragment_spec.js | 61 ++++++++ 11 files changed, 805 insertions(+), 7 deletions(-) create mode 100644 spec/frontend/pages/admin/application_settings/account_and_limits_spec.js create mode 100644 spec/frontend/pages/admin/jobs/index/components/stop_jobs_modal_spec.js create mode 100644 spec/frontend/pages/admin/users/new/index_spec.js create mode 100644 spec/frontend/pages/labels/components/promote_label_modal_spec.js create mode 100644 spec/frontend/pages/milestones/shared/components/delete_milestone_modal_spec.js create mode 100644 spec/frontend/pages/milestones/shared/components/promote_milestone_modal_spec.js create mode 100644 spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js create mode 100644 spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js create mode 100644 spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js (limited to 'spec/frontend/pages') 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="" /> - { + 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', + () => '', +); + +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(` +
+ `); + }); + + 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(' { + 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(' { + 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', + ); + }); + }); +}); -- cgit v1.2.3