vanillajs-datepicker
Version:
A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks
705 lines (522 loc) • 22.4 kB
JavaScript
describe('keyboard operation - edit mode', function () {
let input;
beforeEach(function () {
input = document.createElement('input');
testContainer.appendChild(input);
});
afterEach(function () {
if (input.datepicker) {
input.datepicker.destroy();
}
testContainer.removeChild(input);
});
it('turns on when Datepicker.enterEditMode() is called', function () {
const dp = new Datepicker(input);
input.focus();
dp.enterEditMode();
expect(dp.editMode, 'to be true');
expect(input.classList.contains('in-edit'), 'to be true');
dp.destroy();
input.classList.remove('in-edit');
});
it('turns on when a printable letter, backspace or delete key is pressed without ctrl/meta', function () {
const dp = new Datepicker(input);
input.focus();
simulant.fire(input, 'keydown', {key: '1'});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'J'});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: '/'});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: ' '});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'Backspace'});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'Delete'});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// with modifier key
simulant.fire(input, 'keydown', {key: '1', shiftKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: '1', altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: '1', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
simulant.fire(input, 'keydown', {key: '1', metaKey: true});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
// non-pritable-letter key
simulant.fire(input, 'keydown', {key: 'PageDown'});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
simulant.fire(input, 'keydown', {key: 'Escape', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
dp.destroy();
});
it('turns on when ctrl/meta + ArrowDown key is pressed', function () {
const dp = new Datepicker(input);
input.focus();
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true});
expect(dp.editMode, 'to be true');
dp.destroy();
});
it('does not turn on when ctrl/meta + ArrowLeft/Right/up key is pressed', function () {
const dp = new Datepicker(input);
input.focus();
// arrow-left
simulant.fire(input, 'keydown', {key: 'ArrowLeft', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'keydown', {key: 'ArrowLeft', metaKey: true});
expect(dp.editMode, 'to be undefined');
// arrow-right
simulant.fire(input, 'keydown', {key: 'ArrowRight', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'keydown', {key: 'ArrowRight', metaKey: true});
expect(dp.editMode, 'to be undefined');
// arrow-up
simulant.fire(input, 'keydown', {key: 'ArrowUp', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'keydown', {key: 'ArrowUp', metaKey: true});
expect(dp.editMode, 'to be undefined');
dp.destroy();
});
it('turns on when shift or alt + either of arrow keys is pressed', function () {
const dp = new Datepicker(input);
input.focus();
// shift + arrow
simulant.fire(input, 'keydown', {key: 'ArrowLeft', shiftKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowRight', shiftKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// alt + arrow
simulant.fire(input, 'keydown', {key: 'ArrowLeft', altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowRight', altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// arrow with multiple modifier keys
// arrow-left
simulant.fire(input, 'keydown', {key: 'ArrowLeft', shiftKey: true, altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft', shiftKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft', shiftKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft', altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft', altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft',shiftKey: true , altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowLeft',shiftKey: true , altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// arrow-right
simulant.fire(input, 'keydown', {key: 'ArrowRight', shiftKey: true, altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowRight', shiftKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowRight', shiftKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// arrow-up
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true, altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true, altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowUp', shiftKey: true, altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
// arrow-down
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true, altKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true, altKey: true, ctrlKey: true});
expect(dp.editMode, 'to be true');
delete dp.editMode;
input.classList.remove('in-edit');
simulant.fire(input, 'keydown', {key: 'ArrowDown', shiftKey: true, altKey: true, metaKey: true});
expect(dp.editMode, 'to be true');
dp.destroy();
});
it('turns on when input is clicked', function () {
const dp = new Datepicker(input);
input.focus();
simulant.fire(input, 'mousedown');
input.click();
expect(dp.editMode, 'to be true');
expect(input.classList.contains('in-edit'), 'to be true');
dp.destroy();
input.classList.remove('in-edit');
});
it('does not turn on when the picker is hidden', function () {
const dp = new Datepicker(input);
dp.enterEditMode();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
simulant.fire(input, 'keydown', {key: '1'});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'keydown', {key: 'J'});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'mousedown');
input.click();
expect(dp.editMode, 'to be undefined');
dp.destroy();
});
it('does not turn on when the input has readonly attribute', function () {
const dp = new Datepicker(input);
input.readOnly = true;
input.focus();
dp.enterEditMode();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
simulant.fire(input, 'keydown', {key: '1'});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'keydown', {key: 'J'});
expect(dp.editMode, 'to be undefined');
simulant.fire(input, 'mousedown');
input.click();
expect(dp.editMode, 'to be undefined');
dp.destroy();
});
it('disables the arrow-key operation of the picker', function () {
const clock = sinon.useFakeTimers({now: new Date(2020, 1, 14), shouldAdvanceTime: true});
const {dp, picker} = createDP(input);
const viewSwitch = getViewSwitch(picker);
input.focus();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowLeft'});
expect(viewSwitch.textContent, 'to be', 'February 2020');
let cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [19]);
expect(cells[19].textContent, 'to be', '14');
simulant.fire(input, 'keydown', {key: 'ArrowRight'});
expect(viewSwitch.textContent, 'to be', 'February 2020');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [19]);
simulant.fire(input, 'keydown', {key: 'ArrowUp'});
expect(viewSwitch.textContent, 'to be', 'February 2020');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [19]);
simulant.fire(input, 'keydown', {key: 'ArrowDown'});
expect(viewSwitch.textContent, 'to be', 'February 2020');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [19]);
viewSwitch.click();
simulant.fire(input, 'keydown', {key: 'ArrowLeft'});
expect(viewSwitch.textContent, 'to be', '2020');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [1]);
viewSwitch.click();
simulant.fire(input, 'keydown', {key: 'ArrowRight'});
expect(viewSwitch.textContent, 'to be', '2020-2029');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [1]);
viewSwitch.click();
simulant.fire(input, 'keydown', {key: 'ArrowDown'});
expect(viewSwitch.textContent, 'to be', '2000-2090');
cells = getCells(picker);
expect(getCellIndices(cells, '.focused'), 'to equal', [3]);
// keydown event is not canceled
const spyKeydown = sinon.spy();
input.addEventListener('keydown', spyKeydown);
simulant.fire(input, 'keydown', {key: 'ArrowLeft'});
expect(spyKeydown.args[0][0].defaultPrevented, 'to be false');
simulant.fire(input, 'keydown', {key: 'ArrowRight'});
expect(spyKeydown.args[1][0].defaultPrevented, 'to be false');
simulant.fire(input, 'keydown', {key: 'ArrowUp'});
expect(spyKeydown.args[2][0].defaultPrevented, 'to be false');
simulant.fire(input, 'keydown', {key: 'ArrowDown'});
expect(spyKeydown.args[3][0].defaultPrevented, 'to be false');
input.removeEventListener('keydown', spyKeydown);
dp.destroy();
clock.restore();
});
it('turns off when Datepicker.exitEditMode() is called', function () {
const {dp, picker} = createDP(input);
input.focus();
dp.enterEditMode();
dp.exitEditMode();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
// exitEditMode() does not hide the picker
expect(isVisible(picker), 'to be true');
dp.destroy();
});
it('turns off when ctrl/metaKey + ArrowDown is pressed', function () {
const {dp, picker} = createDP(input);
input.focus();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
// picker is kept open
expect(isVisible(picker), 'to be true');
dp.show();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
dp.show();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true, altKey: true});
expect(dp.editMode, 'to be true');
expect(isVisible(picker), 'to be true');
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true, altKey: true});
expect(dp.editMode, 'to be true');
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true, shiftKey: true});
expect(dp.editMode, 'to be true');
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true, shiftKey: true});
expect(dp.editMode, 'to be true');
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true, altKey: true, shiftKey: true});
expect(dp.editMode, 'to be true');
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true, altKey: true, shiftKey: true});
expect(dp.editMode, 'to be true');
dp.destroy();
});
it('keydown event is canceled and does not bubble when turning off by ctrl/metaKey + ArrowDown key press', function () {
const outer = document.createElement('div');
testContainer.replaceChild(outer, input);
outer.appendChild(input);
const dp = new Datepicker(input);
const spyInputKeydown = sinon.spy();
const spyOuterKeydown = sinon.spy();
input.addEventListener('keydown', spyInputKeydown);
outer.addEventListener('keydown', spyOuterKeydown);
input.focus();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowDown', ctrlKey: true});
expect(spyInputKeydown.called, 'to be true');
expect(spyInputKeydown.args[0][0].defaultPrevented, 'to be true');
expect(spyOuterKeydown.called, 'to be false');
spyInputKeydown.resetHistory();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'ArrowDown', metaKey: true});
expect(spyInputKeydown.called, 'to be true');
expect(spyInputKeydown.args[0][0].defaultPrevented, 'to be true');
expect(spyOuterKeydown.called, 'to be false');
input.removeEventListener('keydown', spyInputKeydown);
outer.removeEventListener('keydown', spyOuterKeydown);
dp.destroy();
outer.removeChild(input);
testContainer.replaceChild(input, outer);
});
it('turns off when the picker hides', function () {
const {dp, picker} = createDP(input);
input.focus();
dp.enterEditMode();
dp.hide();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
dp.show();
dp.enterEditMode();
// by escape key press
simulant.fire(input, 'keydown', {key: 'Escape'});
expect(isVisible(picker), 'to be false');
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
dp.destroy();
});
it('keydown event is canceled and does not bubble when turning off by escape key press', function () {
const outer = document.createElement('div');
testContainer.replaceChild(outer, input);
outer.appendChild(input);
const dp = new Datepicker(input);
const spyInputKeydown = sinon.spy();
const spyOuterKeydown = sinon.spy();
input.addEventListener('keydown', spyInputKeydown);
outer.addEventListener('keydown', spyOuterKeydown);
input.focus();
dp.enterEditMode();
simulant.fire(input, 'keydown', {key: 'Escape'});
expect(spyInputKeydown.called, 'to be true');
expect(spyInputKeydown.args[0][0].defaultPrevented, 'to be true');
expect(spyOuterKeydown.called, 'to be false');
input.removeEventListener('keydown', spyInputKeydown);
outer.removeEventListener('keydown', spyOuterKeydown);
dp.destroy();
outer.removeChild(input);
testContainer.replaceChild(input, outer);
});
it('leaves the edit on the input as-is by default when turning off', function () {
const dp = new Datepicker(input);
const date = dateValue(2020, 1, 14);
dp.setDate(date);
input.focus();
dp.enterEditMode();
input.value = '4/22/2020';
dp.exitEditMode();
expect(input.value, 'to be', '4/22/2020');
expect(dp.dates, 'to equal', [date]);
dp.show();
dp.enterEditMode();
input.value = '3/8/2020';
dp.hide();
expect(input.value, 'to be', '3/8/2020');
expect(dp.dates, 'to equal', [date]);
dp.show();
dp.enterEditMode();
input.value = '02/14/2020';
simulant.fire(input, 'keydown', {key: 'Escape'});
expect(input.value, 'to be', '02/14/2020');
expect(dp.dates, 'to equal', [date]);
dp.destroy();
});
it('updates the selection with the input when turning off by exitEditMode() call with update: true option', function () {
const dp = new Datepicker(input);
const date = dateValue(2020, 3, 22);
dp.setDate('02/14/2020');
input.focus();
dp.enterEditMode();
input.value = '4/22/2020';
dp.exitEditMode({update: true});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
expect(input.value, 'to be', '04/22/2020');
expect(dp.dates, 'to equal', [date]);
dp.destroy();
});
it('updates the selection with the input when turning off by enter key press', function () {
const dp = new Datepicker(input);
const date = dateValue(2020, 3, 22);
dp.setDate('02/14/2020');
input.focus();
dp.enterEditMode();
input.value = '4/22/2020';
simulant.fire(input, 'keydown', {key: 'Enter'});
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
expect(input.value, 'to be', '04/22/2020');
expect(dp.dates, 'to equal', [date]);
dp.destroy();
});
it('updates the selection with the input when turning off being induced by unfocusing input element', function () {
const outsider = document.createElement('p');
testContainer.appendChild(outsider);
const dp = new Datepicker(input);
const date = dateValue(2020, 3, 22);
// by tab key-press
dp.setDate('02/14/2020');
input.focus();
dp.enterEditMode();
input.value = '4/22/2020';
simulant.fire(input, 'keydown', {key: 'Tab'});
input.blur();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
expect(input.value, 'to be', '04/22/2020');
expect(dp.dates, 'to equal', [date]);
//by clicking outside
dp.setDate('02/14/2020');
input.focus();
dp.enterEditMode();
input.value = '4/22/2020';
simulant.fire(outsider, 'mousedown');
input.blur();
expect(dp.editMode, 'to be undefined');
expect(input.classList.contains('in-edit'), 'to be false');
expect(input.value, 'to be', '04/22/2020');
expect(dp.dates, 'to equal', [date]);
dp.destroy();
testContainer.removeChild(outsider);
});
});