diff options
Diffstat (limited to 'spec/frontend/pages/projects/pipeline_schedules')
2 files changed, 268 insertions, 0 deletions
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(); + }); + }); + }); +}); |