vanillajs-datepicker
Version:
A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks
815 lines (606 loc) • 23.9 kB
JavaScript
describe('options', function () {
let clock;
let input;
beforeEach(function () {
clock = sinon.useFakeTimers({now: new Date(2020, 1, 14), shouldAdvanceTime: true});
input = document.createElement('input');
testContainer.appendChild(input);
});
afterEach(function () {
if (input.datepicker) {
input.datepicker.destroy();
}
testContainer.removeChild(input);
clock.restore();
});
describe('autohide', function () {
it('makes the picker hide automatically on selection when true', function () {
const {dp, picker} = createDP(input, {autohide: true});
dp.show();
// by satDate()
dp.setDate('2/4/2020');
expect(isVisible(picker), 'to be false');
dp.show();
// by click on day cell
getCells(picker)[25].click();
expect(isVisible(picker), 'to be false');
dp.show();
// by typing enter key in edit mode
dp.enterEditMode();
input.value = '2/14/2020';
simulant.fire(input, 'keydown', {key: 'Enter'});
expect(isVisible(picker), 'to be false');
// focus is kept on input field after auto-hidng by clicking day cell
// (issue #21)
dp.show();
getCells(picker)[25].click();
expect(document.activeElement, 'to be', input);
dp.destroy();
input.value = '';
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({autohide: true});
dp.show();
dp.setDate('2/4/2020');
expect(isVisible(picker), 'to be false');
dp.setOptions({autohide: false});
dp.show();
dp.setDate('2/14/2020');
expect(isVisible(picker), 'to be true');
dp.destroy();
input.value = '';
});
});
describe('buttonClass', function () {
it('specifies the main class used for the button elements', function () {
const {dp, picker} = createDP(input, {buttonClass: 'btn'});
const [viewSwitch, prevButton, nextButton, todayButton, clearButton] = getParts(picker, [
'.view-switch',
'.prev-button',
'.next-button',
'.today-button',
'.clear-button',
]);
expect(viewSwitch.className, 'to be', 'btn view-switch');
expect(prevButton.className, 'to be', 'btn prev-button prev-btn');
expect(nextButton.className, 'to be', 'btn next-button next-btn');
expect(todayButton.className, 'to be', 'btn today-button today-btn');
expect(clearButton.className, 'to be', 'btn clear-button clear-btn');
dp.destroy();
});
it('cannot be update with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({buttonClass: 'btn'});
const [viewSwitch, prevButton, nextButton, todayButton, clearButton] = getParts(picker, [
'.view-switch',
'.prev-button',
'.next-button',
'.today-button',
'.clear-button',
]);
expect(viewSwitch.className, 'to be', 'button view-switch');
expect(prevButton.className, 'to be', 'button prev-button prev-btn');
expect(nextButton.className, 'to be', 'button next-button next-btn');
expect(todayButton.className, 'to be', 'button today-button today-btn');
expect(clearButton.className, 'to be', 'button clear-button clear-btn');
dp.destroy();
});
});
describe('container', function () {
let foo;
beforeEach(function () {
// foo = parseHTML('<div id="foo"><div>').firstChild;
foo = document.createElement('div');
foo.id = 'foo';
testContainer.appendChild(foo);
});
afterEach(function () {
testContainer.removeChild(foo);
});
it('specifies the element to attach the picker', function () {
// with css selector
let {dp, picker} = createDP(input, {container: '#foo'});
expect(picker.parentElement, 'to be', foo);
dp.destroy();
// the picker should be removed from the container when destroyed
expect(Array.from(foo.children).includes(picker), 'to be false');
({dp, picker} = createDP(input, {container: 'body'}));
expect(picker.parentElement, 'to be', document.body);
dp.destroy();
expect(Array.from(document.body.children).includes(picker), 'to be false');
// with DOM
({dp, picker} = createDP(input, {container: foo}));
expect(picker.parentElement, 'to be', foo);
dp.destroy();
({dp, picker} = createDP(input, {container: document.body}));
expect(picker.parentElement, 'to be', document.body);
dp.destroy();
});
it('cannot be update with setOptions()', function () {
const dp = new Datepicker(input);
dp.setOptions({container: '#foo'});
expect(document.querySelector('.datepicker').parentElement, 'to be', input.parentElement);
dp.destroy();
});
describe('picker\'s text direction', function () {
it('is adjussted to match the input field in the same way as without container', function (done) {
// input's direction differs from the document's and the container's
input.dir = 'rtl';
const {dp, picker} = createDP(input, {container: foo});
dp.show();
expect(picker.dir, 'to be', 'rtl');
dp.hide();
// container's direction becomes the same as the input's but differnet from the document's
foo.dir = 'rtl';
dp.show();
expect(picker.hasAttribute('dir'), 'to be false');
dp.hide();
input.removeAttribute('dir');
foo.removeAttribute('dir');
// container's direction differs from the document's and input's
const htmlElem = document.querySelector('html');
htmlElem.dir = 'rtl';
foo.style.direction = 'ltr';
dp.show();
expect(picker.dir, 'to be', 'rtl');
dp.hide();
// container's direction becomes the same as the document's and input's
foo.removeAttribute('style');
dp.show();
expect(picker.hasAttribute('dir'), 'to be false');
dp.destroy();
htmlElem.removeAttribute('dir');
htmlElem.style.direction = 'ltr';
const checkDirChange = () => {
if (window.getComputedStyle(htmlElem).direction === 'ltr') {
htmlElem.removeAttribute('style');
done();
} else {
setTimeout(checkDirChange, 10);
}
};
checkDirChange();
});
});
});
describe('daysOfWeekHighlighted', function () {
const highlightedCellIndices = (picker) => {
const cells = getCells(picker);
return getCellIndices(cells, '.highlighted');
};
const highlighted1stWeekIndices = picker => highlightedCellIndices(picker).filter(val => val < 7);
it('specifies the days of week to highlight by dey numbers', function () {
const {dp, picker} = createDP(input, {daysOfWeekHighlighted: [1, 5]});
dp.show();
expect(highlightedCellIndices(picker), 'to equal', [1, 5, 8, 12, 15, 19, 22, 26, 29, 33, 36, 40]);
const viewSwitch = getViewSwitch(picker);
// months view
viewSwitch.click();
expect(highlightedCellIndices(picker), 'to equal', []);
// years view
viewSwitch.click();
expect(highlightedCellIndices(picker), 'to equal', []);
// decades view
viewSwitch.click();
expect(highlightedCellIndices(picker), 'to equal', []);
dp.destroy();
});
it('can contain values of 0 - 6 and max 6 items', function () {
const {dp, picker} = createDP(input, {daysOfWeekHighlighted: [0, -1, 1, 2, 3, 2, 4, 5, 6, 7]});
dp.show();
expect(highlighted1stWeekIndices(picker), 'to equal', [0, 1, 2, 3, 4, 5]);
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({daysOfWeekHighlighted: [6, 0, 3]});
dp.show();
expect(highlighted1stWeekIndices(picker), 'to equal', [0, 3, 6]);
dp.setOptions({daysOfWeekHighlighted: []});
expect(highlightedCellIndices(picker), 'to equal', []);
dp.destroy();
});
});
describe('defaultViewDate', function () {
it('specifies the start view date in the case no selection is made', function () {
const date = new Date(1984, 0, 24);
const {dp, picker} = createDP(input, {defaultViewDate: date});
dp.show();
expect(getViewSwitch(picker).textContent, 'to be', 'January 1984');
let cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [23]);
expect(cells[23].textContent, 'to be', '24');
dp.setDate('7/4/2020');
dp.setDate({clear: true});
expect(getViewSwitch(picker).textContent, 'to be', 'January 1984');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [23]);
expect(cells[23].textContent, 'to be', '24');
picker.querySelector('.prev-button').click();
dp.hide();
dp.show();
expect(getViewSwitch(picker).textContent, 'to be', 'January 1984');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [23]);
expect(cells[23].textContent, 'to be', '24');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.show();
dp.setOptions({defaultViewDate: new Date(1984, 0, 24)});
dp.hide();
dp.show();
expect(getViewSwitch(picker).textContent, 'to be', 'January 1984');
let cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [23]);
expect(cells[23].textContent, 'to be', '24');
dp.setOptions({defaultViewDate: new Date(2007, 5, 29)});
dp.setDate('7/4/2020');
dp.setDate({clear: true});
expect(getViewSwitch(picker).textContent, 'to be', 'June 2007');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [33]);
expect(cells[33].textContent, 'to be', '29');
dp.destroy();
});
});
describe('disableTouchKeyboard', function () {
const ontouchstartSupported = 'ontouchstart' in document;
before(function () {
if (!ontouchstartSupported) {
document.ontouchstart = null;
}
});
after(function () {
if (!ontouchstartSupported) {
delete document.ontouchstart;
}
});
it('unfocuses the input after showing the picker', function () {
const dp = new Datepicker(input, {disableTouchKeyboard: true});
input.focus();
expect(document.activeElement, 'not to be', input);
dp.hide();
// input is unfocused when tapped to shou picker (bugfix)
simulant.fire(input, 'mousedown');
input.focus();
input.click();
expect(document.activeElement, 'not to be', input);
dp.destroy();
});
it('prevents the input from getting focus after an element in the picker is clicked', function () {
const {dp, picker} = createDP(input, {disableTouchKeyboard: true});
const [viewSwitch, prevButton] = getParts(picker, ['.view-switch', '.prev-button']);
dp.show();
input.blur();
prevButton.click();
expect(document.activeElement, 'not to be', input);
simulant.fire(getCells(picker)[15], 'click');
expect(document.activeElement, 'not to be', input);
viewSwitch.click();
expect(document.activeElement, 'not to be', input);
simulant.fire(getCells(picker)[6], 'click');
expect(document.activeElement, 'not to be', input);
dp.destroy();
});
it('is ignored if the browser does not support document.ontouchstart', function () {
if (ontouchstartSupported) {
return;
}
delete document.ontouchstart;
const {dp, picker} = createDP(input, {disableTouchKeyboard: true});
const [viewSwitch, prevButton] = getParts(picker, ['.view-switch', '.prev-button']);
input.focus();
expect(document.activeElement, 'to be', input);
dp.hide();
input.blur();
simulant.fire(input, 'mousedown');
input.focus();
input.click();
expect(document.activeElement, 'to be', input);
prevButton.click();
expect(document.activeElement, 'to be', input);
simulant.fire(getCells(picker)[15], 'click');
expect(document.activeElement, 'to be', input);
viewSwitch.click();
expect(document.activeElement, 'to be', input);
simulant.fire(getCells(picker)[6], 'click');
expect(document.activeElement, 'to be', input);
dp.destroy();
document.ontouchstart = null;
});
it('can be updated with setOptions()', function () {
const dp = new Datepicker(input);
dp.setOptions({disableTouchKeyboard: true});
input.focus();
expect(document.activeElement, 'not to be', input);
dp.hide();
dp.setOptions({disableTouchKeyboard: false});
input.focus();
expect(document.activeElement, 'to be', input);
dp.destroy();
});
});
describe('enableOnReadonly', function () {
beforeEach(function () {
input.readOnly = true;
});
after(function () {
input.readOnly = false;
});
it('disables the picker of the input with readonly attribute to be shown when false', function () {
const {dp, picker} = createDP(input, {enableOnReadonly: false});
input.focus();
expect(isVisible(picker), 'to be false');
dp.show();
expect(isVisible(picker), 'to be false');
input.blur();
input.readOnly = false;
input.focus();
expect(isVisible(picker), 'to be true');
dp.hide();
dp.show();
expect(isVisible(picker), 'to be true');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.show();
expect(isVisible(picker), 'to be true');
dp.hide();
dp.setOptions({enableOnReadonly: false});
dp.show();
expect(isVisible(picker), 'to be false');
dp.destroy();
});
});
describe('nextArrow', function () {
it('specifies the label of the next button in HTML (or plain text)', function () {
const html = '<i class="icn icn-arrow-right"></i>';
const {dp, picker} = createDP(input, {nextArrow: html});
const nextButton = picker.querySelector('.next-button');
dp.show();
expect(nextButton.innerHTML, 'to be', html);
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
const nextButton = picker.querySelector('.next-button');
dp.setOptions({nextArrow: 'N'});
dp.show();
expect(nextButton.textContent, 'to be', 'N');
dp.setOptions({nextArrow: '>'});
expect(nextButton.textContent, 'to be', '>');
dp.destroy();
});
});
describe('prevArrow', function () {
it('specifies the label of the next button in HTML (or plain text)', function () {
const html = '<i class="icn icn-arrow-left"></i>';
const {dp, picker} = createDP(input, {prevArrow: html});
const prevButton = picker.querySelector('.prev-button');
dp.show();
expect(prevButton.innerHTML, 'to be', html);
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
const prevButton = picker.querySelector('.prev-button');
dp.setOptions({prevArrow: 'P'});
dp.show();
expect(prevButton.textContent, 'to be', 'P');
dp.setOptions({prevArrow: '<'});
expect(prevButton.textContent, 'to be', '<');
dp.destroy();
});
});
describe('showDaysOfWeek', function () {
it('hides day names of week when false', function () {
const {dp, picker} = createDP(input, {showDaysOfWeek: false});
dp.show();
expect(isVisible(picker.querySelector('.days-of-week')), 'to be false');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({showDaysOfWeek: false});
dp.show();
expect(isVisible(picker.querySelector('.days-of-week')), 'to be false');
dp.setOptions({showDaysOfWeek: true});
expect(isVisible(picker.querySelector('.days-of-week')), 'to be true');
dp.destroy();
});
});
describe('showOnClick', function () {
it('disables the picker to auto-open on clicking input when false', function () {
const {dp, picker} = createDP(input, {showOnClick: false});
input.focus();
dp.hide();
simulant.fire(input, 'mousedown');
input.click();
expect(isVisible(picker), 'to be false');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({showOnClick: false});
input.focus();
dp.hide();
simulant.fire(input, 'mousedown');
input.click();
expect(isVisible(picker), 'to be false');
dp.setOptions({showOnClick: true});
simulant.fire(input, 'mousedown');
input.click();
expect(isVisible(picker), 'to be true');
dp.destroy();
});
});
describe('showOnFocus', function () {
it('disables the picker to auto-open on focus when false', function () {
const {dp, picker} = createDP(input, {showOnFocus: false});
input.focus();
expect(isVisible(picker), 'to be false');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({showOnFocus: false});
input.focus();
expect(isVisible(picker), 'to be false');
input.blur();
dp.setOptions({showOnFocus: true});
input.focus();
expect(isVisible(picker), 'to be true');
dp.destroy();
});
});
describe('title', function () {
it('specifies the title of the picker and shows it when not empty', function () {
const {dp, picker} = createDP(input, {title: 'Foo Bar'});
const title = picker.querySelector('.datepicker-title');
dp.show();
expect(title.textContent, 'to be', 'Foo Bar');
expect(isVisible(title), 'to be true');
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
const title = picker.querySelector('.datepicker-title');
dp.setOptions({title: 'My Datepicker'});
dp.show();
expect(title.textContent, 'to be', 'My Datepicker');
expect(isVisible(title), 'to be true');
dp.setOptions({title: ''});
expect(title.textContent, 'to be', '');
expect(isVisible(title), 'to be false');
dp.destroy();
});
});
describe('todayHighlight', function () {
it('highlights the current date in days view when true', function () {
const {dp, picker} = createDP(input, {todayHighlight: true});
const viewSwitch = getViewSwitch(picker);
dp.show();
let cells = getCells(picker);
expect(getCellIndices(cells, '.today'), 'to equal', [19]);
picker.querySelector('.prev-button').click();
expect(getCellIndices(getCells(picker), '.today'), 'to equal', []);
picker.querySelector('.next-button').click();
viewSwitch.click();
expect(getCellIndices(getCells(picker), '.today'), 'to equal', []);
viewSwitch.click();
expect(getCellIndices(getCells(picker), '.today'), 'to equal', []);
viewSwitch.click();
expect(getCellIndices(getCells(picker), '.today'), 'to equal', []);
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({todayHighlight: true});
dp.show();
let cells = getCells(picker);
expect(getCellIndices(cells, '.today'), 'to equal', [19]);
dp.setOptions({todayHighlight: false});
cells = getCells(picker);
expect(getCellIndices(cells, '.today'), 'to equal', []);
dp.destroy();
});
});
describe('updateOnBlur', function () {
it('discards unparsed input on losing focus when false', function () {
const outsider = document.createElement('p');
testContainer.appendChild(outsider);
const {dp, picker} = createDP(input, {updateOnBlur: false});
input.focus();
input.value = 'foo';
// on tab key press
simulant.fire(input, 'keydown', {key: 'Tab'});
input.blur();
expect(input.value, 'to be', '');
dp.setDate('04/22/2020');
input.focus();
dp.enterEditMode();
input.value = 'foo';
simulant.fire(input, 'keydown', {key: 'Tab'});
input.blur();
expect(input.value, 'to be', '04/22/2020');
// on click outside
input.focus();
input.value = 'foo';
simulant.fire(picker.querySelector('.dow'), 'mousedown');
input.blur();
expect(input.value, 'to be', 'foo');
input.focus();
simulant.fire(input, 'mousedown');
input.blur();
expect(input.value, 'to be', 'foo');
input.focus();
simulant.fire(outsider, 'mousedown');
input.blur();
expect(input.value, 'to be', '04/22/2020');
dp.setDate({clear: true});
input.focus();
input.value = 'foo';
simulant.fire(outsider, 'mousedown');
input.blur();
expect(input.value, 'to be', '');
dp.destroy();
testContainer.removeChild(outsider);
});
it('can be updated with setOptions()', function () {
const dp = new Datepicker(input);
dp.setOptions({updateOnBlur: false});
input.focus();
input.value = '04/22/2020';
simulant.fire(input, 'keydown', {key: 'Tab'});
input.blur();
expect(input.value, 'to be', '');
dp.setOptions({updateOnBlur: true});
input.focus();
input.value = '04/22/2020';
simulant.fire(input, 'keydown', {key: 'Tab'});
input.blur();
expect(input.value, 'to be', '04/22/2020');
dp.destroy();
});
});
describe('weekStart', function () {
const getDayNames = (picker) => {
const daysOfWeek = picker.querySelector('.days-of-week');
return Array.from(daysOfWeek.children).map(el => el.textContent);
};
const getDatesInColumn = (picker, colIndex) => {
const cells = getCells(picker);
return cells.reduce((dates, el, ix) => {
if (ix % 7 === colIndex) {
dates.push(el.textContent);
}
return dates;
}, []);
};
it('specifies the day of week to display in the first column', function () {
const {dp, picker} = createDP(input, {weekStart: 1});
dp.show();
expect(getDayNames(picker), 'to equal', ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']);
expect(getDatesInColumn(picker, 0), 'to equal', ['27', '3', '10', '17', '24', '2']);
expect(getDatesInColumn(picker, 6), 'to equal', ['2', '9', '16', '23', '1', '8']);
dp.destroy();
});
it('can be updated with setOptions()', function () {
const {dp, picker} = createDP(input);
dp.setOptions({weekStart: 4});
dp.show();
expect(getDayNames(picker), 'to equal', ['Th', 'Fr', 'Sa', 'Su', 'Mo', 'Tu', 'We']);
expect(getDatesInColumn(picker, 0), 'to equal', ['30', '6', '13', '20', '27', '5']);
expect(getDatesInColumn(picker, 6), 'to equal', ['5', '12', '19', '26', '4', '11']);
dp.setOptions({weekStart: 0});
expect(getDayNames(picker), 'to equal', ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']);
expect(getDatesInColumn(picker, 0), 'to equal', ['26', '2', '9', '16', '23', '1']);
expect(getDatesInColumn(picker, 6), 'to equal', ['1', '8', '15', '22', '29', '7']);
dp.destroy();
});
});
});