Commit 90775637 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'refactor-sidebar-date-picker-spec' into 'master'

Migrate sidebar_date_picker_spec to Jest

Closes #32462

See merge request gitlab-org/gitlab!17399
parents 84ea3e4a fac3af76
......@@ -99,10 +99,14 @@ export default {
required: false,
default: true,
},
fieldName: {
type: String,
required: false,
default: () => _.uniqueId('dateType_'),
},
},
data() {
return {
fieldName: _.uniqueId('dateType_'),
editing: false,
};
},
......@@ -171,8 +175,8 @@ export default {
this.editing = false;
this.$emit('toggleDateType', true, true);
},
toggleDatePicker(e) {
this.editing = !this.editing;
startEditing(e) {
this.editing = true;
e.stopPropagation();
},
newDateSelected(date = null) {
......@@ -204,9 +208,10 @@ export default {
/>
<gl-button
v-show="canUpdate && !editing"
ref="editButton"
variant="link"
class="btn-sidebar-action"
@click="toggleDatePicker"
@click="startEditing"
>
{{ __('Edit') }}
</gl-button>
......@@ -250,6 +255,7 @@ export default {
<span v-if="selectedAndEditable" class="no-value d-flex">
&nbsp;&ndash;&nbsp;
<gl-button
ref="removeButton"
variant="link"
class="btn-sidebar-date-remove"
@click="newDateSelected(null)"
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SidebarDatePicker renders expected template 1`] = `
<div
class="block date epic-date"
>
<collapsedcalendaricon-stub
class="sidebar-collapsed-icon"
containerclass=""
showicon="true"
text="None"
tooltiptext=""
/>
<div
class="title"
>
Date
<!---->
<div
class="float-right d-flex"
>
<icon-stub
cssclasses="help-icon append-right-5"
name="question-o"
size="16"
tabindex="0"
/>
<glbutton-stub
class="btn-sidebar-action"
variant="link"
>
Edit
</glbutton-stub>
<!---->
</div>
</div>
<div
class="value"
>
<div
class="value-type-fixed text-secondary is-option-selected d-flex"
>
<input
name="datetype_test"
type="radio"
/>
<span
class="prepend-left-5"
>
Fixed:
</span>
<span
class="d-flex value-content prepend-left-2"
>
<span
class="no-value"
>
None
</span>
</span>
</div>
<abbr
class="value-type-dynamic text-secondary d-flex prepend-top-10"
data-html="true"
data-original-title="Select an issue with milestone to set date"
data-placement="bottom"
title=""
>
<input
name="datetype_test"
type="radio"
/>
<span
class="prepend-left-5"
>
From milestones:
</span>
<span
class="value-content prepend-left-2"
>
None
</span>
<!---->
</abbr>
</div>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import SidebarDatepicker from 'ee/epic/components/sidebar_items/sidebar_date_picker.vue';
import { mockDatePickerProps } from '../../mock_data';
import Icon from '~/vue_shared/components/icon.vue';
import DatePicker from '~/vue_shared/components/pikaday.vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
const mockPopoverBind = jest.fn();
jest.mock('~/vue_shared/directives/popover', () => ({
bind: (...args) => mockPopoverBind(...args),
}));
describe('SidebarDatePicker', () => {
let originalGon;
beforeAll(() => {
originalGon = global.gon;
global.gon = { gitlab_url: TEST_HOST };
});
afterAll(() => {
global.gon = originalGon;
});
let wrapper;
const findIconByName = name =>
wrapper
.findAll(Icon)
.filter(w => w.props().name === name)
.at(0);
const findEditButton = () => wrapper.find({ ref: 'editButton' });
const findDirectiveCallByTitle = title =>
mockPopoverBind.mock.calls.find(([, binding]) => binding.value.title === title);
const findRemoveButton = () => wrapper.find({ ref: 'removeButton' });
const createFakeEvent = () => ({ stopPropagation: jest.fn() });
const startEditing = () => {
const e = createFakeEvent();
findEditButton().vm.$emit('click', e);
};
const createComponent = props => {
wrapper = shallowMount(SidebarDatepicker, {
propsData: {
...mockDatePickerProps,
...props,
},
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('generates unique names for input if `fieldName` prop is not provided', () => {
createComponent();
const anotherWrapper = shallowMount(SidebarDatepicker, {
propsData: mockDatePickerProps,
sync: false,
});
const firstInputName = wrapper.find('input').attributes('name');
const otherInputName = anotherWrapper.find('input').attributes('name');
expect(firstInputName).toContain('dateType_');
expect(otherInputName).toContain('dateType_');
expect(firstInputName).not.toEqual(otherInputName);
anotherWrapper.destroy();
});
it('renders remove button when both `selectedDate` is defined and `canUpdate` is true', () => {
createComponent({
selectedDate: new Date(),
dateFixed: new Date(),
canUpdate: true,
});
expect(findRemoveButton().exists()).toBe(true);
});
describe('collapsed calendar icon', () => {
it('receives full date string in words based on `selectedDate` prop value', () => {
createComponent({
selectedDate: new Date(2018, 0, 1),
});
expect(wrapper.find(CollapsedCalendarIcon).props('text')).toBe('Jan 1, 2018');
});
it('receives `None` when `selectedDateWords` is not defined', () => {
createComponent();
expect(wrapper.find(CollapsedCalendarIcon).props('text')).toBe('None');
});
});
it('returns full date string in words based on `dateFixed` prop value', () => {
createComponent({
dateFixed: new Date(2018, 0, 1),
});
expect(wrapper.text()).toContain('Jan 1, 2018');
});
it('returns full date string in words when `dateFromMilestones` is defined', () => {
createComponent({ dateFromMilestones: new Date(2018, 0, 1) });
expect(wrapper.text()).toContain('From milestones: Jan 1, 2018');
});
it('returns `None` when `dateFromMilestones` is not defined', () => {
createComponent();
expect(wrapper.text()).toContain('From milestones: None');
});
it('passes correct popover options to directive', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
const expectedTitle =
'These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely.';
const [, binding] = findDirectiveCallByTitle(expectedTitle);
const { content, ...popoverConfig } = binding.value;
delete popoverConfig.template;
const expectedContent = '/help/user/group/epics/index.md#start-date-and-due-date';
const expectedPopoverConfig = {
html: true,
trigger: 'focus',
title: expectedTitle,
container: 'body',
boundary: 'viewport',
};
expect(mockPopoverBind).toHaveBeenCalled();
expect(popoverConfig).toStrictEqual(expectedPopoverConfig);
expect(content).toContain(expectedContent);
});
});
it('returns popover config object containing title with appropriate string', () => {
createComponent({ isDateInvalid: true, selectedDateIsFixed: false });
return wrapper.vm.$nextTick().then(() => {
const expectedTitle = 'Selected date is invalid';
const [, targetBinding] = findDirectiveCallByTitle(expectedTitle);
const { content } = targetBinding.value;
expect(content).toContain('/help/user/group/epics/index.md#start-date-and-due-date');
expect(content).toContain('How can I solve this?');
});
});
it('stops editing and emits `toggleDateType` event on component on `hidePicker` from date picker', () => {
createComponent({ canUpdate: true });
startEditing();
return wrapper.vm
.$nextTick()
.then(() => {
wrapper.find(DatePicker).vm.$emit('hidePicker');
expect(wrapper.emitted().toggleDateType[0]).toStrictEqual([true, true]);
})
.then(() => wrapper.vm.$nextTick())
.then(() => {
expect(wrapper.find(DatePicker).exists()).toBe(false);
});
});
it('starts editing when clicked on edit button', () => {
createComponent();
expect(wrapper.find(DatePicker).exists()).toBe(false);
const e = createFakeEvent();
findEditButton().vm.$emit('click', e);
return wrapper.vm.$nextTick().then(() => {
expect(e.stopPropagation).toHaveBeenCalled();
expect(wrapper.find(DatePicker).exists()).toBe(true);
});
});
it('stops editing and emits `saveDate` when `newDateSelected` emitted by date picker', () => {
const date = new Date();
createComponent();
startEditing();
return wrapper.vm.$nextTick().then(() => {
wrapper.find(DatePicker).vm.$emit('newDateSelected', date);
expect(wrapper.emitted().saveDate).toStrictEqual([[date]]);
});
});
it('emits `toggleDateType` event on component when input is clicked', () => {
createComponent({ canUpdate: true });
wrapper.find('input').trigger('click');
expect(wrapper.emitted().toggleDateType).toStrictEqual([[true]]);
});
it('emits `toggleCollapse` event when toggle-sidebar emits `toggle` event', () => {
createComponent({ showToggleSidebar: true });
wrapper.find(ToggleSidebar).vm.$emit('toggle');
expect(wrapper.emitted().toggleCollapse).toBeDefined();
});
it('renders expected template', () => {
createComponent({
fieldName: 'datetype_test',
});
expect(wrapper.element).toMatchSnapshot();
});
it('renders collapse button when `showToggleSidebar` prop is `true`', () => {
createComponent({ showToggleSidebar: true });
expect(wrapper.find(ToggleSidebar).exists()).toBe(true);
});
it('renders loading icon when `dateSaveInProgress` prop is true', () => {
createComponent({ dateSaveInProgress: true });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('renders date warning icon when `isDateInvalid` prop is `true`', () => {
createComponent({ isDateInvalid: true, selectedDateIsFixed: false });
const warningIcon = findIconByName('warning');
expect(warningIcon.exists()).toBe(true);
expect(warningIcon.attributes('tabindex')).toBe('0');
});
});
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { TEST_HOST } from 'spec/test_constants';
const metaFixture = getJSONFixture('epic/mock_meta.json');
const meta = JSON.parse(metaFixture.meta);
const initial = JSON.parse(metaFixture.initial);
export const mockEpicMeta = convertObjectPropsToCamelCase(meta, {
deep: true,
});
export const mockEpicData = convertObjectPropsToCamelCase(
Object.assign({}, getJSONFixture('epic/mock_data.json'), initial, {
endpoint: TEST_HOST,
sidebarCollapsed: false,
}),
{ deep: true },
);
export const mockDatePickerProps = {
blockClass: 'epic-date',
sidebarCollapsed: false,
showToggleSidebar: false,
dateSaveInProgress: false,
canUpdate: true,
label: 'Date',
datePickerLabel: 'Fixed date',
selectedDate: null,
selectedDateIsFixed: true,
dateFromMilestones: null,
dateFixed: null,
dateFromMilestonesTooltip: 'Select an issue with milestone to set date',
isDateInvalid: false,
dateInvalidTooltip: 'Selected date is invalid',
};
export const mockLabels = [
{
id: 26,
title: 'Foo Label',
description: 'Foobar',
color: '#BADA55',
text_color: '#FFFFFF',
},
];
export const mockAncestors = [
{
id: 1,
title: 'Parent epic',
url: '/groups/gitlab-org/-/epics/6',
},
{
id: 2,
title: 'Parent epic 2',
url: '/groups/gitlab-org/-/epics/7',
},
];
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { TEST_HOST } from 'spec/test_constants';
const metaFixture = getJSONFixture('epic/mock_meta.json');
const meta = JSON.parse(metaFixture.meta);
const initial = JSON.parse(metaFixture.initial);
export const mockEpicMeta = convertObjectPropsToCamelCase(meta, {
deep: true,
});
export const mockEpicData = convertObjectPropsToCamelCase(
Object.assign({}, getJSONFixture('epic/mock_data.json'), initial, {
endpoint: TEST_HOST,
sidebarCollapsed: false,
}),
{ deep: true },
);
export const mockDatePickerProps = {
blockClass: 'epic-date',
sidebarCollapsed: false,
showToggleSidebar: false,
dateSaveInProgress: false,
canUpdate: true,
label: 'Date',
datePickerLabel: 'Fixed date',
selectedDate: null,
selectedDateIsFixed: true,
dateFromMilestones: null,
dateFixed: null,
dateFromMilestonesTooltip: 'Select an issue with milestone to set date',
isDateInvalid: false,
dateInvalidTooltip: 'Selected date is invalid',
};
export const mockLabels = [
{
id: 26,
title: 'Foo Label',
description: 'Foobar',
color: '#BADA55',
text_color: '#FFFFFF',
},
];
export const mockAncestors = [
{
id: 1,
title: 'Parent epic',
url: '/groups/gitlab-org/-/epics/6',
},
{
id: 2,
title: 'Parent epic 2',
url: '/groups/gitlab-org/-/epics/7',
},
];
export * from '../../frontend/epic/mock_data';
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment