@gitlab/ui
Version:
GitLab UI Components
432 lines (349 loc) • 13 kB
JavaScript
import { mount, shallowMount } from '@vue/test-utils';
import Pikaday from 'pikaday';
import { defaultDateFormat } from '../../../utils/constants';
import GlDatepicker from './datepicker.vue';
jest.mock('pikaday');
describe('datepicker component', () => {
const mountWithOptions = ({ shallow = true, ...mountOptions } = {}) => {
const func = shallow ? shallowMount : mount;
return func(GlDatepicker, mountOptions);
};
const pikadayConfig = () => Pikaday.mock.calls[0][0];
const currentDate = new Date(2018, 0, 1);
const findInput = (wrapper) => wrapper.find('[data-testid="gl-datepicker-input"]');
const findClearButton = (wrapper) => wrapper.find('[data-testid="clear-button"]');
const findTriggerButton = (wrapper) => wrapper.findComponent({ ref: 'calendarTriggerBtn' });
const findCalendarIcon = (wrapper) => wrapper.find('[data-testid="datepicker-calendar-icon"]');
const assertDefaultDates = () => {
const { setDefaultDate, defaultDate } = pikadayConfig();
expect(defaultDate).toEqual(currentDate);
expect(setDefaultDate).toBe(true);
};
beforeEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
});
beforeEach(() => {
jest.useFakeTimers('modern');
jest.setSystemTime(currentDate.getTime());
});
it("does not set default date when 'value' and 'defaultDate' props aren't set", () => {
mountWithOptions();
expect(Pikaday).toHaveBeenCalled();
expect(pikadayConfig()).toMatchObject({
setDefaultDate: false,
});
});
it("uses 'defaultDate' over 'value' as the calendar default date", () => {
mountWithOptions({
propsData: {
value: new Date(2000, 1, 1),
defaultDate: currentDate,
},
});
expect(Pikaday).toHaveBeenCalled();
assertDefaultDates();
});
it("uses 'value' as the calendar default date", () => {
mountWithOptions({
propsData: {
value: currentDate,
},
});
expect(Pikaday).toHaveBeenCalled();
assertDefaultDates();
});
it('opens calendar after mounting when start-opened property equals true', () => {
mountWithOptions({ propsData: { startOpened: true } });
expect(Pikaday.prototype.show).toHaveBeenCalled();
});
it('opens calendar when show method is called', () => {
const wrapper = mountWithOptions();
wrapper.vm.show();
expect(Pikaday.prototype.show).toHaveBeenCalled();
});
describe('when `ariaLabel` prop is passed', () => {
it('configures pikaday with the given label', () => {
const ariaLabel = 'Meaningful description';
mountWithOptions({
propsData: {
ariaLabel,
},
});
expect(pikadayConfig().ariaLabel).toBe(ariaLabel);
});
});
describe('when `target` prop is not passed', () => {
it('sets calendar icon as `trigger` option', () => {
const wrapper = mountWithOptions();
expect(wrapper.vm.$refs.calendarTriggerBtn.$el.isSameNode(pikadayConfig().trigger)).toBe(
true
);
});
describe('when datepicker is disabled', () => {
it('does not pass the `trigger` option to Pikaday', () => {
mountWithOptions({ propsData: { disabled: true } });
expect(pikadayConfig()).not.toHaveProperty('trigger');
});
});
});
describe('when `target` prop is `null`', () => {
it('does not pass the `trigger` option to Pikaday', () => {
// This will cause the calendar to open when the `field` is focused
// https://github.com/Pikaday/Pikaday#configuration
mountWithOptions({ propsData: { target: null } });
expect(pikadayConfig()).not.toHaveProperty('trigger');
});
it('renders a svg icon instead of a button', () => {
const wrapper = mountWithOptions({ shallow: false, propsData: { target: null } });
const calendarIcon = findCalendarIcon(wrapper);
expect(findTriggerButton(wrapper).exists()).toBe(false);
expect(calendarIcon.exists()).toBe(true);
expect(calendarIcon.classes()).toContain('gl-text-gray-500');
});
});
describe('when `container` prop is not passed', () => {
it('sets component element as `container` option', () => {
const wrapper = mountWithOptions();
expect(pikadayConfig()).toHaveProperty('container', wrapper.vm.$el);
});
});
describe('when `container` prop is `null`', () => {
it('does not pass the `container` option to Pikaday', () => {
mountWithOptions({
propsData: { container: null },
});
expect(pikadayConfig()).not.toHaveProperty('container');
});
});
describe('when `showClearButton` prop is `true`', () => {
describe('when text input has a value', () => {
let wrapper;
let clearButton;
let input;
const setup = async (propsData = {}) => {
wrapper = mountWithOptions({
shallow: false,
propsData: { showClearButton: true, ...propsData },
});
input = findInput(wrapper);
await input.setValue('2020-01-15');
clearButton = findClearButton(wrapper);
};
it('renders clear button', async () => {
await setup();
expect(clearButton.exists()).toBe(true);
});
describe('when clear button is clicked', () => {
beforeEach(async () => {
await setup();
clearButton.trigger('click');
});
it('clears the input', () => {
expect(input.element.value).toBe('');
});
it('emits the `clear` event', () => {
expect(wrapper.emitted('clear')).toHaveLength(1);
});
});
describe('when datepicker is disabled', () => {
it('does not render clear button', async () => {
await setup({ disabled: true });
expect(clearButton.exists()).toBe(false);
});
});
});
describe('when text input does not have a value', () => {
it('does not render clear button', () => {
const wrapper = mountWithOptions({
shallow: false,
propsData: { showClearButton: true },
});
expect(findClearButton(wrapper).exists()).toBe(false);
});
});
});
describe('when text input is passed in the default slot', () => {
it('sets it as Pikaday `field` option', () => {
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('name', 'foo-bar');
mountWithOptions({
slots: { default: input.outerHTML },
});
expect(pikadayConfig().field).toEqual(input);
});
});
describe('when the user presses the `enter` key on the input field', () => {
describe('and the input field is not empty', () => {
it('emits no input event', async () => {
const wrapper = mountWithOptions({
shallow: false,
propsData: {
value: currentDate,
},
});
findInput(wrapper).trigger('keydown', 'Enter');
expect(wrapper.emitted('input')).toBe(undefined);
});
});
describe('and the input field is empty', () => {
it.each`
minDate | isSet
${null} | ${'is empty'}
${currentDate} | ${'is set'}
`('emits input with the value `$minDate` when the `minDate` prop $isSet', ({ minDate }) => {
const wrapper = mountWithOptions({
shallow: false,
propsData: {
minDate,
},
});
findInput(wrapper).trigger('keydown', { key: 'Enter' });
expect(wrapper.emitted('input')).toHaveLength(1);
expect(wrapper.emitted('input')[0]).toEqual([minDate]);
});
});
});
it.each`
calendarEvent | componentEvent | params
${'onSelect'} | ${'input'} | ${[currentDate]}
${'onClose'} | ${'close'} | ${[]}
${'onOpen'} | ${'open'} | ${[]}
`(
'emits $componentEvent event when calendar emits $calendarEvent event',
({ componentEvent, calendarEvent, params }) => {
const wrapper = mountWithOptions();
const config = pikadayConfig();
config[calendarEvent](...params);
expect(wrapper.emitted(componentEvent)[0]).toEqual(params);
}
);
it.each`
calendarSetter | calendarProperty | componentProperty | extraParams
${'setDate'} | ${'date'} | ${'value'} | ${[true]}
${'setMinDate'} | ${'minDate'} | ${'minDate'} | ${[]}
${'setMaxDate'} | ${'maxDate'} | ${'maxDate'} | ${[]}
`(
'sets $calendarProperty calendar property when $componentProperty component property changes',
async ({ calendarSetter, componentProperty, extraParams }) => {
const wrapper = mountWithOptions();
wrapper.setProps({ [componentProperty]: currentDate });
await wrapper.vm.$nextTick();
expect(Pikaday.prototype[calendarSetter]).toHaveBeenCalledWith(currentDate, ...extraParams);
}
);
describe('when draw event is emitted', () => {
let pikaday;
let pastDateButton;
let futureDateButton;
let wrapper;
beforeEach(() => {
pastDateButton = {
dataset: {
pikaYear: 2016,
pikaMonth: 1,
pikaDay: 1,
},
classList: {
add: jest.fn(),
},
};
futureDateButton = {
dataset: {
pikaYear: currentDate.getFullYear(),
pikaMonth: currentDate.getMonth(),
pikaDay: currentDate.getDate() + 1,
},
classList: {
add: jest.fn(),
},
};
pikaday = {
el: {
querySelectorAll() {
return [pastDateButton, futureDateButton];
},
},
};
wrapper = mountWithOptions();
const config = pikadayConfig();
config.onDraw(pikaday);
});
it('marks past days with "is-past-date" class selector', () => {
expect(pastDateButton.classList.add).toHaveBeenCalledWith('is-past-date');
});
it('does not mark future days with "is-past-date" class selector', () => {
expect(futureDateButton.classList.add).not.toHaveBeenCalled();
});
it('emits monthChange event', () => {
expect(wrapper.emitted('monthChange')).toHaveLength(1);
});
});
describe('when datepicker is disabled', () => {
it('renders a svg icon instead of a button', () => {
const wrapper = mountWithOptions({ propsData: { disabled: true } });
const calendarIcon = findCalendarIcon(wrapper);
expect(findTriggerButton(wrapper).exists()).toBe(false);
expect(calendarIcon.exists()).toBe(true);
expect(calendarIcon.classes()).toContain('gl-text-gray-400');
});
});
it.each`
propName | attribute | expectedValue
${'inputId'} | ${'id'} | ${undefined}
${'inputName'} | ${'name'} | ${undefined}
${'target'} | ${'autocomplete'} | ${undefined}
${'placeholder'} | ${'placeholder'} | ${defaultDateFormat}
`(
'when `$propName` prop is not passed sets input `$attribute` attribute to `$expectedValue`',
({ attribute, expectedValue }) => {
const wrapper = mountWithOptions();
expect(findInput(wrapper).attributes(attribute)).toBe(expectedValue);
}
);
it.each`
propName | value | attribute | expectedValue
${'inputId'} | ${'idForInput'} | ${'id'} | ${'idForInput'}
${'inputName'} | ${'nameForInput'} | ${'name'} | ${'nameForInput'}
${'target'} | ${null} | ${'autocomplete'} | ${'off'}
${'autocomplete'} | ${'on'} | ${'autocomplete'} | ${'on'}
${'placeholder'} | ${'foo bar'} | ${'placeholder'} | ${'foo bar'}
`(
'when `$propName` prop is passed sets input `$attribute` to `$expectedValue`',
({ propName, value, attribute, expectedValue }) => {
const wrapper = mountWithOptions({
propsData: {
[propName]: value,
},
});
expect(findInput(wrapper).attributes(attribute)).toBe(expectedValue);
}
);
it.each`
dateString | parsedDate
${'2021-09-27'} | ${new Date(2021, 8, 27)}
${'2021/09/27'} | ${new Date(2021, 8, 27)}
${'foobarbaz'} | ${currentDate}
`('parses $dateString correctly', ({ dateString, parsedDate }) => {
mountWithOptions();
const config = pikadayConfig();
expect(config.parse).not.toBeUndefined();
expect(config.parse(dateString)).toEqual(parsedDate);
});
it.each`
width | expectedClass
${undefined} | ${'gl-form-input-md'}
${'sm'} | ${'gl-form-input-sm'}
${'md'} | ${'gl-form-input-md'}
${'lg'} | ${'gl-form-input-lg'}
${'xl'} | ${'gl-form-input-xl'}
`('applies $expectedClass class when width is $width', ({ width, expectedClass }) => {
const wrapper = mountWithOptions({
propsData: {
width,
},
});
expect(wrapper.classes()).toContain(expectedClass);
});
});