From a09983ae35713f5a2bbb100981116d31ce99826e Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Jul 2020 12:26:25 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-2-stable-ee --- .../new/components/fork_groups_list_item_spec.js | 78 ++++++++++++ .../forks/new/components/fork_groups_list_spec.js | 133 +++++++++++++++++++++ .../pages/projects/graphs/code_coverage_spec.js | 6 +- spec/frontend/pages/projects/graphs/mock_data.js | 91 +++++++------- .../components/interval_pattern_input_spec.js | 121 +++++++++++++------ 5 files changed, 352 insertions(+), 77 deletions(-) create mode 100644 spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js create mode 100644 spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js (limited to 'spec/frontend/pages/projects') diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js new file mode 100644 index 00000000000..73e3c385d33 --- /dev/null +++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js @@ -0,0 +1,78 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlBadge, GlButton, GlLink } from '@gitlab/ui'; +import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue'; + +describe('Fork groups list item component', () => { + let wrapper; + + const DEFAULT_PROPS = { + hasReachedProjectLimit: false, + }; + + const DEFAULT_GROUP_DATA = { + id: 22, + name: 'Gitlab Org', + description: 'Ad et ipsam earum id aut nobis.', + visibility: 'public', + full_name: 'Gitlab Org', + created_at: '2020-06-22T03:32:05.664Z', + updated_at: '2020-06-22T03:32:05.664Z', + avatar_url: null, + fork_path: '/twitter/typeahead-js/-/forks?namespace_key=22', + forked_project_path: null, + permission: 'Owner', + relative_path: '/gitlab-org', + markdown_description: + '

Ad et ipsam earum id aut nobis.

', + can_create_project: true, + marked_for_deletion: false, + }; + + const DUMMY_PATH = '/dummy/path'; + + const createWrapper = propsData => { + wrapper = shallowMount(ForkGroupsListItem, { + propsData: { + ...DEFAULT_PROPS, + ...propsData, + }, + }); + }; + + it('renders pending removal badge if applicable', () => { + createWrapper({ group: { ...DEFAULT_GROUP_DATA, marked_for_deletion: true } }); + + expect(wrapper.find(GlBadge).text()).toBe('pending removal'); + }); + + it('renders go to fork button if has forked project', () => { + createWrapper({ group: { ...DEFAULT_GROUP_DATA, forked_project_path: DUMMY_PATH } }); + + expect(wrapper.find(GlButton).text()).toBe('Go to fork'); + expect(wrapper.find(GlButton).attributes().href).toBe(DUMMY_PATH); + }); + + it('renders select button if has no forked project', () => { + createWrapper({ + group: { ...DEFAULT_GROUP_DATA, forked_project_path: null, fork_path: DUMMY_PATH }, + }); + + expect(wrapper.find(GlButton).text()).toBe('Select'); + expect(wrapper.find('form').attributes().action).toBe(DUMMY_PATH); + }); + + it('renders link to current group', () => { + const DUMMY_FULL_NAME = 'dummy'; + createWrapper({ + group: { ...DEFAULT_GROUP_DATA, relative_path: DUMMY_PATH, full_name: DUMMY_FULL_NAME }, + }); + + expect( + wrapper + .findAll(GlLink) + .filter(w => w.text() === DUMMY_FULL_NAME) + .at(0) + .attributes().href, + ).toBe(DUMMY_PATH); + }); +}); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js new file mode 100644 index 00000000000..979dff78eba --- /dev/null +++ b/spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js @@ -0,0 +1,133 @@ +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import createFlash from '~/flash'; +import ForkGroupsList from '~/pages/projects/forks/new/components/fork_groups_list.vue'; +import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_groups_list_item.vue'; +import waitForPromises from 'helpers/wait_for_promises'; + +jest.mock('~/flash', () => jest.fn()); + +describe('Fork groups list component', () => { + let wrapper; + let axiosMock; + + const DEFAULT_PROPS = { + endpoint: '/dummy', + hasReachedProjectLimit: false, + }; + + const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args); + + const createWrapper = propsData => { + wrapper = shallowMount(ForkGroupsList, { + propsData: { + ...DEFAULT_PROPS, + ...propsData, + }, + stubs: { + GlTabs: { + template: '
', + }, + }, + }); + }; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + }); + + afterEach(() => { + axiosMock.reset(); + + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + it('fires load groups request on mount', async () => { + replyWith(200, { namespaces: [] }); + createWrapper(); + + await waitForPromises(); + + expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROPS.endpoint); + }); + + it('displays flash if loading groups fails', async () => { + replyWith(500); + createWrapper(); + + await waitForPromises(); + + expect(createFlash).toHaveBeenCalled(); + }); + + it('displays loading indicator while loading groups', () => { + replyWith(() => new Promise(() => {})); + createWrapper(); + + expect(wrapper.contains(GlLoadingIcon)).toBe(true); + }); + + it('displays empty text if no groups are available', async () => { + const EMPTY_TEXT = 'No available groups to fork the project.'; + replyWith(200, { namespaces: [] }); + createWrapper(); + + await waitForPromises(); + + expect(wrapper.text()).toContain(EMPTY_TEXT); + }); + + it('displays filter field when groups are available', async () => { + replyWith(200, { namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }] }); + createWrapper(); + + await waitForPromises(); + + expect(wrapper.contains(GlSearchBoxByType)).toBe(true); + }); + + it('renders list items for each available group', async () => { + const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }]; + const hasReachedProjectLimit = true; + + replyWith(200, { namespaces }); + createWrapper({ hasReachedProjectLimit }); + + await waitForPromises(); + + expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(namespaces.length); + + namespaces.forEach((namespace, idx) => { + expect( + wrapper + .findAll(ForkGroupsListItem) + .at(idx) + .props(), + ).toStrictEqual({ group: namespace, hasReachedProjectLimit }); + }); + }); + + it('filters repositories on the fly', async () => { + replyWith(200, { + namespaces: [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }], + }); + createWrapper(); + await waitForPromises(); + wrapper.find(GlSearchBoxByType).vm.$emit('input', 'other'); + await nextTick(); + + expect(wrapper.findAll(ForkGroupsListItem)).toHaveLength(1); + expect( + wrapper + .findAll(ForkGroupsListItem) + .at(0) + .props().group.name, + ).toBe('otherdummy'); + }); +}); diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js index 4990985b076..30c7ff78c6e 100644 --- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js +++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js @@ -5,7 +5,7 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts'; import axios from '~/lib/utils/axios_utils'; import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue'; -import codeCoverageMockData from './mock_data'; +import { codeCoverageMockData, sortedDataByDates } from './mock_data'; import waitForPromises from 'helpers/wait_for_promises'; import httpStatusCodes from '~/lib/utils/http_status'; @@ -52,6 +52,10 @@ describe('Code Coverage', () => { expect(findAreaChart().exists()).toBe(true); }); + it('sorts the dates in ascending order', () => { + expect(wrapper.vm.sortedData).toEqual(sortedDataByDates); + }); + it('matches the snapshot', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/pages/projects/graphs/mock_data.js b/spec/frontend/pages/projects/graphs/mock_data.js index a15f861ee7a..28d97b9d3f0 100644 --- a/spec/frontend/pages/projects/graphs/mock_data.js +++ b/spec/frontend/pages/projects/graphs/mock_data.js @@ -1,60 +1,69 @@ -export default [ +export const codeCoverageMockData = [ { group_name: 'rspec', data: [ - { date: '2020-04-30', coverage: 40.0 }, - { date: '2020-05-01', coverage: 80.0 }, - { date: '2020-05-02', coverage: 99.0 }, - { date: '2020-05-10', coverage: 80.0 }, - { date: '2020-05-15', coverage: 70.0 }, { date: '2020-05-20', coverage: 69.0 }, + { date: '2020-05-15', coverage: 70.0 }, + { date: '2020-05-10', coverage: 80.0 }, + { date: '2020-05-02', coverage: 99.0 }, + { date: '2020-05-01', coverage: 80.0 }, + { date: '2020-04-30', coverage: 40.0 }, ], }, { group_name: 'cypress', data: [ - { date: '2022-07-30', coverage: 1.0 }, - { date: '2022-08-01', coverage: 2.4 }, - { date: '2022-08-02', coverage: 5.0 }, - { date: '2022-08-10', coverage: 15.0 }, - { date: '2022-08-15', coverage: 30.0 }, { date: '2022-08-20', coverage: 40.0 }, + { date: '2022-08-15', coverage: 30.0 }, + { date: '2022-08-10', coverage: 15.0 }, + { date: '2022-08-02', coverage: 5.0 }, + { date: '2022-08-01', coverage: 2.4 }, + { date: '2022-07-30', coverage: 1.0 }, ], }, { group_name: 'karma', data: [ - { date: '2020-05-01', coverage: 94.0 }, - { date: '2020-05-02', coverage: 94.0 }, - { date: '2020-05-03', coverage: 94.0 }, - { date: '2020-05-04', coverage: 94.0 }, - { date: '2020-05-05', coverage: 92.0 }, - { date: '2020-05-06', coverage: 91.0 }, - { date: '2020-05-07', coverage: 78.0 }, - { date: '2020-05-08', coverage: 94.0 }, - { date: '2020-05-09', coverage: 94.0 }, - { date: '2020-05-10', coverage: 94.0 }, - { date: '2020-05-11', coverage: 94.0 }, - { date: '2020-05-12', coverage: 94.0 }, - { date: '2020-05-13', coverage: 92.0 }, - { date: '2020-05-14', coverage: 91.0 }, - { date: '2020-05-15', coverage: 78.0 }, - { date: '2020-05-16', coverage: 94.0 }, - { date: '2020-05-17', coverage: 94.0 }, - { date: '2020-05-18', coverage: 93.0 }, - { date: '2020-05-19', coverage: 92.0 }, - { date: '2020-05-20', coverage: 91.0 }, - { date: '2020-05-21', coverage: 90.0 }, - { date: '2020-05-22', coverage: 91.0 }, - { date: '2020-05-23', coverage: 92.0 }, - { date: '2020-05-24', coverage: 75.0 }, - { date: '2020-05-25', coverage: 74.0 }, - { date: '2020-05-26', coverage: 74.0 }, - { date: '2020-05-27', coverage: 74.0 }, - { date: '2020-05-28', coverage: 80.0 }, - { date: '2020-05-29', coverage: 85.0 }, - { date: '2020-05-30', coverage: 92.0 }, { date: '2020-05-31', coverage: 91.0 }, + { date: '2020-05-30', coverage: 94.0 }, + { date: '2020-05-29', coverage: 94.0 }, + { date: '2020-05-28', coverage: 92.0 }, + { date: '2020-05-27', coverage: 91.0 }, + { date: '2020-05-26', coverage: 78.0 }, + { date: '2020-05-25', coverage: 94.0 }, + { date: '2020-05-24', coverage: 94.0 }, + { date: '2020-05-23', coverage: 94.0 }, + { date: '2020-05-22', coverage: 94.0 }, + { date: '2020-05-21', coverage: 94.0 }, + { date: '2020-05-20', coverage: 92.0 }, + { date: '2020-05-19', coverage: 91.0 }, + { date: '2020-05-18', coverage: 78.0 }, + { date: '2020-05-17', coverage: 94.0 }, + { date: '2020-05-16', coverage: 94.0 }, + { date: '2020-05-15', coverage: 93.0 }, + { date: '2020-05-14', coverage: 92.0 }, + { date: '2020-05-13', coverage: 91.0 }, + { date: '2020-05-12', coverage: 90.0 }, + { date: '2020-05-11', coverage: 91.0 }, + { date: '2020-05-10', coverage: 92.0 }, + { date: '2020-05-09', coverage: 75.0 }, + { date: '2020-05-08', coverage: 74.0 }, + { date: '2020-05-07', coverage: 74.0 }, + { date: '2020-05-06', coverage: 74.0 }, + { date: '2020-05-05', coverage: 80.0 }, + { date: '2020-05-04', coverage: 85.0 }, + { date: '2020-05-03', coverage: 92.0 }, + { date: '2020-05-02', coverage: 94.0 }, + { date: '2020-05-01', coverage: 94.0 }, ], }, ]; + +export const sortedDataByDates = [ + { date: '2020-04-30', coverage: 40.0 }, + { date: '2020-05-01', coverage: 80.0 }, + { date: '2020-05-02', coverage: 99.0 }, + { date: '2020-05-10', coverage: 80.0 }, + { date: '2020-05-15', coverage: 70.0 }, + { date: '2020-05-20', coverage: 69.0 }, +]; 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 index 9cc1d6eeb5a..9a119377542 100644 --- 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 @@ -1,4 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import { trimText } from 'helpers/text_helper'; import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue'; describe('Interval Pattern Input Component', () => { @@ -14,15 +15,22 @@ describe('Interval Pattern Input Component', () => { 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 customKey = 'custom'; + const everyDayKey = 'everyDay'; + const cronIntervalNotInPreset = `0 12 * * *`; + + const findEveryDayRadio = () => wrapper.find(`[data-testid=${everyDayKey}]`); + const findEveryWeekRadio = () => wrapper.find('[data-testid="everyWeek"]'); + const findEveryMonthRadio = () => wrapper.find('[data-testid="everyMonth"]'); + const findCustomRadio = () => wrapper.find(`[data-testid="${customKey}"]`); const findCustomInput = () => wrapper.find('#schedule_cron'); - const selectEveryDayRadio = () => findEveryDayRadio().setChecked(); - const selectEveryWeekRadio = () => findEveryWeekRadio().setChecked(); - const selectEveryMonthRadio = () => findEveryMonthRadio().setChecked(); + const findAllLabels = () => wrapper.findAll('label'); + const findSelectedRadio = () => + wrapper.findAll('input[type="radio"]').wrappers.find(x => x.element.checked); + const findSelectedRadioKey = () => findSelectedRadio()?.attributes('data-testid'); + const selectEveryDayRadio = () => findEveryDayRadio().trigger('click'); + const selectEveryWeekRadio = () => findEveryWeekRadio().trigger('click'); + const selectEveryMonthRadio = () => findEveryMonthRadio().trigger('click'); const selectCustomRadio = () => findCustomRadio().trigger('click'); const createWrapper = (props = {}, data = {}) => { @@ -30,7 +38,7 @@ describe('Interval Pattern Input Component', () => { throw new Error('A wrapper already exists'); } - wrapper = shallowMount(IntervalPatternInput, { + wrapper = mount(IntervalPatternInput, { propsData: { ...props }, data() { return { @@ -63,8 +71,8 @@ describe('Interval Pattern Input Component', () => { createWrapper(); }); - it('to a non empty string when no initial value is not passed', () => { - expect(findCustomInput()).not.toBe(''); + it('defaults to every day value when no `initialCronInterval` is passed', () => { + expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay); }); }); @@ -85,20 +93,20 @@ describe('Interval Pattern Input Component', () => { createWrapper(); }); - it('when a default option is selected', () => { + it('when a default option is selected', async () => { selectEveryDayRadio(); - return wrapper.vm.$nextTick().then(() => { - expect(findCustomInput().attributes('disabled')).toBeUndefined(); - }); + await wrapper.vm.$nextTick(); + + expect(findCustomInput().attributes('disabled')).toBeUndefined(); }); - it('when the custom option is selected', () => { + it('when the custom option is selected', async () => { selectCustomRadio(); - return wrapper.vm.$nextTick().then(() => { - expect(findCustomInput().attributes('disabled')).toBeUndefined(); - }); + await wrapper.vm.$nextTick(); + + expect(findCustomInput().attributes('disabled')).toBeUndefined(); }); }); @@ -115,40 +123,83 @@ describe('Interval Pattern Input Component', () => { }); }); + describe('Time strings', () => { + beforeEach(() => { + createWrapper(); + }); + + it('renders each label for radio options properly', () => { + const labels = findAllLabels().wrappers.map(el => trimText(el.text())); + + expect(labels).toEqual([ + 'Every day (at 4:00am)', + 'Every week (Monday at 4:00am)', + 'Every month (Day 1 at 4:00am)', + 'Custom ( Cron syntax )', + ]); + }); + }); + 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 }); + describe('Default option', () => { + beforeEach(() => { + createWrapper(); + }); + + it('when everyday is selected, update value', async () => { + selectEveryWeekRadio(); + await wrapper.vm.$nextTick(); + expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyWeek); + + selectEveryDayRadio(); + await wrapper.vm.$nextTick(); + expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay); + }); + }); + + describe('Other options', () => { + it.each` + desc | initialCronInterval | act | expectedValue + ${'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, value remains the same'} | ${cronIntervalPresets.everyMonth} | ${selectCustomRadio} | ${cronIntervalPresets.everyMonth} + `('$desc', async ({ initialCronInterval, act, expectedValue }) => { + createWrapper({ initialCronInterval }); + + act(); - act(); + await wrapper.vm.$nextTick(); - 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', () => { + it('when editing the cron input it selects the custom radio button', async () => { const newValue = '0 * * * *'; + expect(findSelectedRadioKey()).toBe(everyDayKey); + findCustomInput().setValue(newValue); - expect(wrapper.vm.cronInterval).toBe(newValue); + await wrapper.vm.$nextTick; + + expect(findSelectedRadioKey()).toBe(customKey); }); + }); - it('when value of input is one of the defaults, it selects the corresponding radio button', () => { - findCustomInput().setValue(cronIntervalPresets.everyWeek); + describe('Edit form field', () => { + beforeEach(() => { + createWrapper({ initialCronInterval: cronIntervalNotInPreset }); + }); - expect(wrapper.vm.cronInterval).toBe(cronIntervalPresets.everyWeek); + it('loads with the custom option being selected', () => { + expect(findSelectedRadioKey()).toBe(customKey); }); }); }); -- cgit v1.2.1