Ghost/ghost/admin/tests/integration/components/gh-date-picker-test.js
Simon Backx 07cb542b97
Improved timezone support date picker and improved tests (#15545)
fixes https://github.com/TryGhost/Team/issues/1946

Problem:
- When running the admin tests in a timezone that is later than UTC, the tests failed.

Causes:
- Some tests needed some adjustements
- The DateTimePicker did not always use the correct timezone.
- Test models createdAt times sometimes depended on the timezone of the test runner

Solution:
- All the input DateTimePicker gets should be processed in the blog's timezone.
- Make sure that all communication (properties, setters, minDate...) with `PowerDatepicker` happens in the local timezone. When setting, convert that date to the blog timezone and use that as the real value.
2022-10-07 12:20:06 +02:00

364 lines
16 KiB
JavaScript

// import Service from '@ember/service';
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment-timezone';
import sinon from 'sinon';
import {blur, click, fillIn, find, focus, render, triggerKeyEvent, typeIn} from '@ember/test-helpers';
import {datepickerSelect} from 'ember-power-datepicker/test-support';
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {setupRenderingTest} from 'ember-mocha';
// class SettingsStub extends Service {
// timezone = 'Etc/UTC';
// get(key) {
// if (key === 'timezone') {
// return this.timezone;
// }
// }
// }
describe('Integration: Component: gh-date-picker', function () {
setupRenderingTest();
let clock;
// beforeEach(async function () {
// this.owner.register('service:settings', SettingsStub);
// });
afterEach(function () {
clock?.restore();
});
it('renders', async function () {
await render(hbs`<GhDatePicker />`);
expect(find('[data-test-date-picker-trigger]'), 'datepicker trigger').to.exist;
expect(find('[data-test-date-picker-input]'), 'datepicker input').to.exist;
});
it('defaults to now when @value is empty', async function () {
clock = sinon.useFakeTimers({
now: moment('2022-02-22 22:22:22.000').toDate()
});
await render(hbs`<GhDatePicker />`);
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-22');
});
it('shows passed in @value value', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
await render(hbs`<GhDatePicker @value={{this.date}} />`);
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-22');
});
it('updates date via input blur', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-28');
await blur('[data-test-date-picker-input]');
expect(changeSpy.callCount).to.equal(1);
expect(changeSpy.firstCall.args[0]).to.be.an.instanceof(Date);
expect(changeSpy.firstCall.args[0].toISOString()).to.equal(moment('2022-02-28T00:00:00.000').toISOString());
});
it('updates date via input Enter keydown', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-28');
await triggerKeyEvent('[data-test-date-picker-input]', 'keydown', 'Enter');
expect(changeSpy.callCount).to.equal(1);
expect(changeSpy.firstCall.args[0]).to.be.an.instanceof(Date);
expect(changeSpy.firstCall.args[0].toISOString()).to.equal(moment('2022-02-28T00:00:00.000').toISOString());
});
it('updates date via datepicker selection', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const onChange = (newDate) => {
this.set('date', newDate);
};
const changeSpy = sinon.spy(onChange);
this.set('onChange', changeSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} />`);
await datepickerSelect('[data-test-date-picker-trigger]', moment('2022-02-27T13:00:00.000').toDate());
expect(find('[data-test-date-picker-input]')).to.have.value('2022-02-27');
expect(changeSpy.callCount).to.equal(1);
expect(changeSpy.firstCall.args[0]).to.be.an.instanceof(Date);
expect(changeSpy.firstCall.args[0].toISOString()).to.equal(moment('2022-02-27T00:00:00.000').toISOString());
});
it('updates when @value is changed externally', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
await render(hbs`<GhDatePicker @value={{this.date}} />`);
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-22');
this.set('date', moment('2022-02-28 10:00:00.000')).toDate();
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-28');
});
it('updates when @value is changed externally when we have a scratch date', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
await render(hbs`<GhDatePicker @value={{this.date}} />`);
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-22');
await fillIn('[data-test-date-picker-input]', '2022-02-27');
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-27');
this.set('date', moment('2022-02-28 10:00:00.000')).toDate();
expect(find('[data-test-date-picker-input]'), 'date input').to.have.value('2022-02-28');
});
it('calls @onInput on input events', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const inputSpy = sinon.spy();
this.set('onInput', inputSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onInput={{this.onInput}} />`);
await typeIn('[data-test-date-picker-input]', 'lo');
expect(inputSpy.callCount).to.equal(2);
expect(inputSpy.firstCall.args[0]).to.be.instanceOf(Event);
});
it('calls @onKeydown on input keydown events', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const keydownSpy = sinon.spy();
this.set('onKeydown', keydownSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onKeydown={{this.onKeydown}} />`);
await typeIn('[data-test-date-picker-input]', 'lo');
expect(keydownSpy.callCount).to.equal(2);
expect(keydownSpy.firstCall.args[0]).to.be.instanceOf(Event);
});
it('calls @onBlur on input blur events', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const blurSpy = sinon.spy();
this.set('onBlur', blurSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onBlur={{this.onBlur}} />`);
await focus('[data-test-date-picker-input]');
await blur('[data-test-date-picker-input]');
expect(blurSpy.callCount).to.equal(1);
expect(blurSpy.firstCall.args[0]).to.be.instanceOf(Event);
});
it('resets input value on Escape', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-28');
await triggerKeyEvent('[data-test-date-picker-input]', 'keydown', 'Escape');
expect(changeSpy.callCount).to.equal(0);
expect(find('[data-test-date-picker-input]')).to.have.value('2022-02-22');
});
it('handles invalid date input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
const errorSpy = sinon.spy();
this.set('onError', errorSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-31');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.have.text('Invalid date');
expect(changeSpy.callCount, '@onChange call count').to.equal(0);
expect(errorSpy.callCount, '@onError call count').to.equal(1);
expect(errorSpy.firstCall.args[0]).to.be.instanceof(Error);
expect(errorSpy.firstCall.args[0].message).to.equal('Invalid date');
});
it('handles invalid date format input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
const errorSpy = sinon.spy();
this.set('onError', errorSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', 'narp');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.contain.text('Date must be YYYY-MM-DD');
expect(changeSpy.callCount, '@onChange call count').to.equal(0);
expect(errorSpy.callCount, '@onError call count').to.equal(1);
expect(errorSpy.firstCall.args[0]).to.be.instanceof(Error);
expect(errorSpy.firstCall.args[0].message).to.contain('Date must be YYYY-MM-DD');
});
it('clears error on internal change to valid', async function () {
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', 'narp');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.exist;
await fillIn('[data-test-date-picker-input]', '2022-02-22');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.not.exist;
});
it('clears error on external @value change to valid', async function () {
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', 'narp');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.exist;
this.set('date', moment('2022-02-22'));
expect(find('[data-test-date-picker-error]')).to.not.exist;
});
it('clears error on reset', async function () {
await render(hbs`<GhDatePicker @value={{this.date}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', 'narp');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.exist;
await triggerKeyEvent('[data-test-date-picker-input]', 'keydown', 'Escape');
expect(find('[data-test-date-picker-error]')).to.not.exist;
});
describe('min/max', function () {
it('disables datepicker dates outside of range', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000').toDate());
this.set('maxDate', moment('2022-02-24 12:00:00.000').toDate());
await render(hbs`<GhDatePicker @value={{this.date}} @minDate={{this.minDate}} @maxDate={{this.maxDate}} />`);
await click('[data-test-date-picker-trigger]');
expect(find('[data-date="2022-02-10"]')).to.have.attribute('disabled');
expect(find('[data-date="2022-02-25"]')).to.have.attribute('disabled');
});
it('errors when date input is earlier than min', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000').toDate());
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
const errorSpy = sinon.spy();
this.set('onError', errorSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @minDate={{this.minDate}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-10');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.have.text('Must be on or after 2022-02-11');
expect(changeSpy.callCount, '@onChange call count').to.equal(0);
expect(errorSpy.callCount, '@onError call count').to.equal(1);
expect(errorSpy.firstCall.args[0]).to.be.instanceof(Error);
expect(errorSpy.firstCall.args[0].message).to.equal('Must be on or after 2022-02-11');
expect(errorSpy.firstCall.args[0].date).to.be.equal('2022-02-10');
});
it('allows for min date error override', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000').toDate());
await render(hbs`<GhDatePicker @value={{this.date}} @minDate={{this.minDate}} @minDateError="Must be in the future" @onChange={{this.onChange}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-10');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.have.text('Must be in the future');
});
it('errors when date input is later than max', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
this.set('maxDate', moment('2022-02-25 12:00:00.000').toDate());
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
const errorSpy = sinon.spy();
this.set('onError', errorSpy);
await render(hbs`<GhDatePicker @value={{this.date}} @maxDate={{this.maxDate}} @onChange={{this.onChange}} @onError={{this.onError}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-28');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.have.text('Must be on or before 2022-02-25');
expect(changeSpy.callCount, '@onChange call count').to.equal(0);
expect(errorSpy.callCount, '@onError call count').to.equal(1);
expect(errorSpy.firstCall.args[0]).to.be.instanceof(Error);
expect(errorSpy.firstCall.args[0].message).to.equal('Must be on or before 2022-02-25');
expect(errorSpy.firstCall.args[0].date).to.be.equal('2022-02-28');
});
it('allows for max date error override', async function () {
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
this.set('maxDate', moment('2022-02-25 12:00:00.000').toDate());
await render(hbs`<GhDatePicker @value={{this.date}} @maxDate={{this.maxDate}} @maxDateError="Must be in the past" @onChange={{this.onChange}} />`);
await fillIn('[data-test-date-picker-input]', '2022-02-28');
await blur('[data-test-date-picker-input]');
expect(find('[data-test-date-picker-error]')).to.have.text('Must be in the past');
});
});
describe('block invocation', function () {
it('exposes Nav and Days components', async function () {
clock = sinon.useFakeTimers({
now: moment('2022-02-02 22:22:22.000').toDate()
});
this.set('date', moment('2022-02-02 22:22:22.000')).toDate();
this.set('maxDate', moment('2022-02-05 12:00:00.000').toDate());
await render(hbs`<GhDatePicker @value={{this.date}} @maxDate={{this.maxDate}} as |dp|><dp.Nav /><dp.Days /></GhDatePicker>`);
await click('[data-test-date-picker-trigger]');
// calendar is rendered with the right maxDate value curried
expect(find('[data-date="2022-02-10"]')).to.have.attribute('disabled');
expect(find('[data-date="2022-02-25"]')).to.have.attribute('disabled');
});
});
});