0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

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.
This commit is contained in:
Simon Backx 2022-10-07 12:20:06 +02:00 committed by GitHub
parent e679bb4187
commit 07cb542b97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 220 additions and 74 deletions

View file

@ -953,3 +953,5 @@ remove|ember-template-lint|no-action|132|15|132|15|c7db9d737e3f06cc754d7a49a3e35
remove|ember-template-lint|no-passed-in-event-handlers|11|28|11|28|02c81dfb804e41f5b3c10730e431d1e4b0958e6f|1665014400000|1675386000000|1680566400000|app/components/settings/members/stripe-settings-form.hbs
remove|ember-template-lint|no-passed-in-event-handlers|20|28|20|28|1591adfb7d0dbab4321126ada2e2c5a4a8c66516|1665014400000|1675386000000|1680566400000|app/components/settings/members/stripe-settings-form.hbs
remove|ember-template-lint|no-passed-in-event-handlers|93|28|93|28|55dadf0e7dc5e2ed57771f46ca3cb82607d1799c|1665014400000|1675386000000|1680566400000|app/components/settings/members/stripe-settings-form.hbs
remove|ember-template-lint|no-action|5|18|5|18|7c796afb78a976ab2411eab292c02c4250e429c7|1662681600000|1673053200000|1678237200000|app/components/gh-date-time-picker.hbs
add|ember-template-lint|no-action|5|18|5|18|0c80a75b2a80d404755333991c266c81c97c9cda|1665100800000|1675472400000|1680652800000|app/components/gh-date-time-picker.hbs

View file

@ -8,7 +8,8 @@ export default class PublishAtOption extends Component {
@action
setDate(selectedDate) {
const selectedMoment = moment(selectedDate);
// selectedDate is a Date object that contains the correct date string in the blog timezone
const selectedMoment = moment.tz(selectedDate, this.settings.get('timezone'));
const {years, months, date} = selectedMoment.toObject();
// Create a new moment from existing scheduledAtUTC _in site timezone_.

View file

@ -1,8 +1,8 @@
<div class="gh-date-time-picker" data-test-component="gh-date-time-picker">
<PowerDatepicker
@selected={{this._date}}
@center={{this._date}}
@onSelect={{action "setDateInternal" value="date"}}
@selected={{readonly this.localDateValue}}
@center={{readonly this.localDateValue}}
@onSelect={{action "setLocalDate" value="date"}}
@renderInPlace={{this.renderInPlaceWithFallback}}
@disabled={{this.disabled}} as |dp|
>

View file

@ -22,9 +22,10 @@ export default class GhDateTimePicker extends Component {
timeErrorProperty = null;
isActive = true;
_time = '';
// _date is always a moment object in the blog's timezone
_previousTime = '';
_minDate = null;
_maxDate = null;
_minDate = null; // Always set to a Date object
_maxDate = null; // Always set to a Date object
_scratchDate = null;
_scratchDateError = null;
@ -46,9 +47,15 @@ export default class GhDateTimePicker extends Component {
if (this._scratchDate !== null) {
return this._scratchDate;
} else {
return moment(this._date).format(DATE_FORMAT);
return this._date?.format(DATE_FORMAT);
}
}
@computed('_date')
get localDateValue() {
// Convert the selected date to a new date in the local timezone, purely to please PowerDatepicker
return new Date(this._date.format(DATE_FORMAT));
}
@computed('blogTimezone')
get timezone() {
@ -94,7 +101,8 @@ export default class GhDateTimePicker extends Component {
let blogTimezone = this.blogTimezone;
if (!isBlank(date)) {
this.set('_date', moment(date));
// Note: input date as a string is expected to be in the blog's timezone
this.set('_date', moment.tz(date, blogTimezone));
} else {
this.set('_date', moment().tz(blogTimezone));
}
@ -113,7 +121,7 @@ export default class GhDateTimePicker extends Component {
this._lastDate = this.date;
if (isBlank(time)) {
this.set('_time', moment(this._date).format('HH:mm'));
this.set('_time', this._date.format('HH:mm'));
} else {
this.set('_time', this.time);
}
@ -121,17 +129,17 @@ export default class GhDateTimePicker extends Component {
// unless min/max date is at midnight moment will disable that day
if (minDate === 'now') {
this.set('_minDate', moment.tz(moment().tz(blogTimezone).format(DATE_FORMAT), blogTimezone));
this.set('_minDate', moment(moment().tz(blogTimezone).format(DATE_FORMAT)).toDate());
} else if (!isBlank(minDate)) {
this.set('_minDate', moment(moment(minDate).format(DATE_FORMAT)));
this.set('_minDate', moment(moment.tz(minDate, blogTimezone).format(DATE_FORMAT)).toDate());
} else {
this.set('_minDate', null);
}
if (maxDate === 'now') {
this.set('_maxDate', moment.tz(moment().tz(blogTimezone).format(DATE_FORMAT), blogTimezone));
this.set('_maxDate', moment(moment().tz(blogTimezone).format(DATE_FORMAT)).toDate());
} else if (!isBlank(maxDate)) {
this.set('_maxDate', moment(moment(maxDate).format(DATE_FORMAT)));
this.set('_maxDate', moment(moment.tz(maxDate, blogTimezone).format(DATE_FORMAT)).toDate());
} else {
this.set('_maxDate', null);
}
@ -155,6 +163,19 @@ export default class GhDateTimePicker extends Component {
}
}
/**
* This method is called by `PowerDatepicker` when a user selected a date. It is constructed like
* The difference here is that the Date object that is passed contains the date, but only when viewed in the local timezone.
* This timezone can differ between the timezone of the blog. We need to convert the date to a new date in the blog's timezone on the same day that was selected.
* Example: new Date('2000-01-01') -> a user selected 2000-01-01. In the blog timezone, this could be 1999-12-31 23:00, which is wrong.
*/
@action
setLocalDate(date) {
// Convert to a date string in the local timezone (moment is in local timezone by default)
const dateString = moment(date).format(DATE_FORMAT);
this._setDate(dateString);
}
@action
setTimeInternal(time, event) {
if (time.match(/^\d:\d\d$/)) {
@ -166,7 +187,7 @@ export default class GhDateTimePicker extends Component {
this.set('_previousTime', time);
if (isBlank(this.date)) {
this.setDate(this._date);
this.setDate(this._date.toDate());
}
}
}
@ -192,7 +213,7 @@ export default class GhDateTimePicker extends Component {
onDateBlur(event) {
// make sure we're not doing anything just because the calendar dropdown
// is opened and clicked
if (event.target.value === moment(this._date).format('YYYY-MM-DD')) {
if (event.target.value === this._date.format('YYYY-MM-DD')) {
this._resetScratchDate();
return;
}
@ -258,7 +279,7 @@ export default class GhDateTimePicker extends Component {
return false;
}
let date = moment(dateStr, DATE_FORMAT);
let date = moment.tz(dateStr, DATE_FORMAT, this.blogTimezone);
if (!date.isValid()) {
this._setScratchDateError('Invalid date');
return false;

View file

@ -209,8 +209,9 @@ export default class GhPostSettingsMenu extends Component {
@action
setPublishedAtBlogDate(date) {
// date is a Date object that contains the correct date string in the blog timezone
let post = this.post;
let dateString = moment(date).format('YYYY-MM-DD');
let dateString = moment.tz(date, this.settings.get('timezone')).format('YYYY-MM-DD');
post.get('errors').remove('publishedAtBlogDate');

View file

@ -2,11 +2,11 @@ import moment from 'moment-timezone';
import {Factory} from 'miragejs';
export default Factory.extend({
createdAt() { return moment().toISOString(); },
createdAt() { return moment.utc().toISOString(); },
createdBy: 1,
name(i) { return `Label ${i}`; },
slug(i) { return `label-${i}`; },
updatedAt() { return moment().toISOString(); },
updatedAt() { return moment.utc().toISOString(); },
updatedBy: 1,
count() {
// this gets updated automatically by the label serializer

View file

@ -10,7 +10,7 @@ export default Factory.extend({
name() { return `${faker.name.firstName()} ${faker.name.lastName()}`; },
email: faker.internet.email,
status: 'free',
createdAt() { return moment(randomDate()).format('YYYY-MM-DD HH:mm:ss'); },
createdAt() { return moment.utc(randomDate()).format('YYYY-MM-DD HH:mm:ss'); },
free: trait({
status: 'free'

View file

@ -38,7 +38,7 @@ describe('Integration: Component: gh-date-picker', function () {
it('defaults to now when @value is empty', async function () {
clock = sinon.useFakeTimers({
now: moment('2022-02-22 22:22:22.000Z').toDate()
now: moment('2022-02-22 22:22:22.000').toDate()
});
await render(hbs`<GhDatePicker />`);
@ -46,14 +46,14 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('shows passed in @value value', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
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.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
@ -64,11 +64,11 @@ describe('Integration: Component: gh-date-picker', function () {
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.000Z').toISOString());
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.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
@ -79,11 +79,11 @@ describe('Integration: Component: gh-date-picker', function () {
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.000Z').toISOString());
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.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const onChange = (newDate) => {
this.set('date', newDate);
@ -92,28 +92,28 @@ describe('Integration: Component: gh-date-picker', function () {
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.000Z').toDate());
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.000Z').toISOString());
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.000Z')).toDate();
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.000Z')).toDate();
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.000Z')).toDate();
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');
@ -121,12 +121,12 @@ describe('Integration: Component: gh-date-picker', function () {
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.000Z')).toDate();
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.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const inputSpy = sinon.spy();
this.set('onInput', inputSpy);
@ -139,7 +139,7 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('calls @onKeydown on input keydown events', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const keydownSpy = sinon.spy();
this.set('onKeydown', keydownSpy);
@ -152,7 +152,7 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('calls @onBlur on input blur events', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const blurSpy = sinon.spy();
this.set('onBlur', blurSpy);
@ -166,7 +166,7 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('resets input value on Escape', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
@ -180,7 +180,7 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('handles invalid date input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
@ -201,7 +201,7 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('handles invalid date format input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
const changeSpy = sinon.spy();
this.set('onChange', changeSpy);
@ -260,9 +260,9 @@ describe('Integration: Component: gh-date-picker', function () {
describe('min/max', function () {
it('disables datepicker dates outside of range', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000Z').toDate());
this.set('maxDate', moment('2022-02-24 12:00:00.000Z').toDate());
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]');
@ -272,8 +272,8 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('errors when date input is earlier than min', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000Z').toDate());
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);
@ -295,8 +295,8 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('allows for min date error override', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('minDate', moment('2022-02-11 12:00:00.000Z').toDate());
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}} />`);
@ -307,8 +307,8 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('errors when date input is later than max', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('maxDate', moment('2022-02-25 12:00:00.000Z').toDate());
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);
@ -330,8 +330,8 @@ describe('Integration: Component: gh-date-picker', function () {
});
it('allows for max date error override', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('maxDate', moment('2022-02-25 12:00:00.000Z').toDate());
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}} />`);
@ -345,11 +345,11 @@ describe('Integration: Component: gh-date-picker', function () {
describe('block invocation', function () {
it('exposes Nav and Days components', async function () {
clock = sinon.useFakeTimers({
now: moment('2022-02-02 22:22:22.000Z').toDate()
now: moment('2022-02-02 22:22:22.000').toDate()
});
this.set('date', moment('2022-02-02 22:22:22.000Z')).toDate();
this.set('maxDate', moment('2022-02-05 12:00:00.000Z').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>`);

View file

@ -8,12 +8,15 @@ import {describe, it} from 'mocha';
import {expect} from 'chai';
import {setupRenderingTest} from 'ember-mocha';
let timezone = 'UTC';
class SettingsStub extends Service {
timezone = 'Etc/UTC';
get timezone() {
return timezone;
}
get(key) {
if (key === 'timezone') {
return this.timezone;
return timezone;
}
}
}
@ -23,6 +26,7 @@ describe('Integration: Component: gh-date-time-picker', function () {
let clock;
beforeEach(async function () {
timezone = 'UTC';
this.owner.register('service:settings', SettingsStub);
});
@ -50,7 +54,7 @@ describe('Integration: Component: gh-date-time-picker', function () {
});
it('shows passed in @date value', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', '2022-02-22 22:22');
await render(hbs`<GhDateTimePicker @date={{this.date}} />`);
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-22');
@ -67,11 +71,13 @@ describe('Integration: Component: gh-date-time-picker', function () {
});
it('can update date via date input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('time', '22:22');
this.set('date','2022-02-22');
this.set('time', '22:22'); // = blog timezone
this.set('updateDate', (newDate) => {
expect(moment(newDate).toISOString()).to.equal('2022-02-28T00:00:00.000Z');
// Note: the newDate should be 2022-02-28 in the current blog timezone, this is not the same timezone as the user timezone
// Blog timezone is UTC, so ending of Z is needed here
expect(moment.utc(newDate).toISOString()).to.equal('2022-02-28T00:00:00.000Z');
this.set('date', newDate);
});
this.set('updateTime', (newTime) => {
@ -84,12 +90,38 @@ describe('Integration: Component: gh-date-time-picker', function () {
await blur('[data-test-date-time-picker-date-input]');
});
it('can update time via time input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
it('can update date via date input with +12 timezone', async function () {
timezone = 'Pacific/Kwajalein'; // +12
// Current date/time doesn't really matter
this.set('date','2022-02-22');
this.set('time', '22:22');
this.set('updateDate', (newDate) => {
expect(moment(newDate).toISOString()).to.equal('2022-02-28T00:00:00.000Z');
// Note: the newDate should be 2022-02-28 in the current blog timezone, this is not the same timezone as the user timezone
// Blog timezone is +12
expect(moment.utc(newDate).toISOString()).to.equal('2022-02-27T12:00:00.000Z');
this.set('date', newDate);
});
this.set('updateTime', (newTime) => {
expect(newTime).to.equal('22:22');
this.set('time', newTime);
});
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} @setDate={{this.updateDate}} @setTime={{this.updateTime}} />`);
// We enter 2022-02-28. In UTC this is 2022-02-28 00:00:00.000Z. But we should call updateDate to be 2022-02-28 in the blog timezone
await fillIn('[data-test-date-time-picker-date-input]', '2022-02-28');
await blur('[data-test-date-time-picker-date-input]');
});
it('can update time via time input', async function () {
this.set('date', '2022-02-22');
this.set('time', '22:22');
this.set('updateDate', (newDate) => {
expect(moment.utc(newDate).toISOString()).to.equal('2022-02-28T00:00:00.000');
this.set('date', newDate);
});
this.set('updateTime', (newTime) => {
@ -103,11 +135,31 @@ describe('Integration: Component: gh-date-time-picker', function () {
});
it('can update date via datepicker', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', '2022-02-22');
this.set('time', '12:00');
this.set('updateDate', (newDate) => {
expect(moment(newDate).toISOString()).to.equal('2022-02-27T00:00:00.000Z');
expect(moment.utc(newDate).toISOString()).to.equal('2022-02-27T00:00:00.000Z');
this.set('date', newDate);
});
this.set('updateTime', (newTime) => {
expect(newTime).to.equal('12:00');
this.set('time', newTime);
});
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} @setDate={{this.updateDate}} @setTime={{this.updateTime}} />`);
await datepickerSelect('[data-test-date-time-picker-datepicker]', moment('2022-02-27T13:00:00.000Z').toDate());
});
it('can update date via datepicker with +12 timezone', async function () {
timezone = 'Pacific/Kwajalein'; // +12
this.set('date', '2022-02-22');
this.set('time', '12:00');
this.set('updateDate', (newDate) => {
// 12 hours earlier in UTC
expect(moment.utc(newDate).toISOString()).to.equal('2022-02-26T12:00:00.000Z');
this.set('date', newDate);
});
this.set('updateTime', (newTime) => {
@ -120,21 +172,53 @@ describe('Integration: Component: gh-date-time-picker', function () {
});
it('updates when @date is changed externally', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date','2022-02-22');
this.set('time', '12:00');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} />`);
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-22');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
this.set('date', moment('2022-02-28 10:00:00.000Z')).toDate();
this.set('date', '2022-02-28');
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-28');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
});
it('updates when @date is changed externally with +12 timezone', async function () {
timezone = 'Pacific/Kwajalein'; // +12
this.set('date','2022-02-22');
this.set('time', '12:00');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} />`);
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-22');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
this.set('date', '2022-02-28');
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-28');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
});
it('updates when @date is changed externally with -11 timezone', async function () {
timezone = 'Pacific/Pago_Pago'; // -11
this.set('date','2022-02-22');
this.set('time', '12:00');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} />`);
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-22');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
this.set('date', '2022-02-28');
expect(find('[data-test-date-time-picker-date-input]'), 'date input').to.have.value('2022-02-28');
expect(find('[data-test-date-time-picker-time-input]'), 'time input').to.have.value('12:00');
});
it('updates when @time is changed externally', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', '2022-02-22');
this.set('time', '12:00');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} />`);
@ -148,7 +232,7 @@ describe('Integration: Component: gh-date-time-picker', function () {
});
it('handles invalid date input', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', '2022-02-22');
this.set('time', '12:00');
const dateSpy = sinon.spy();
@ -174,7 +258,7 @@ describe('Integration: Component: gh-date-time-picker', function () {
// TODO: move time format handling into component?
// it('handles invalid time input', async function () {
// this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
// this.set('date', '2022-02-22');
// this.set('time', '12:00');
// const dateSpy = sinon.spy();
@ -200,10 +284,10 @@ describe('Integration: Component: gh-date-time-picker', function () {
describe('min/max', function () {
it('disables datepicker dates outside of range', async function () {
this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
this.set('date', '2022-02-22');
this.set('time', '12:00');
this.set('minDate', moment('2022-02-11 12:00:00.000Z').toDate());
this.set('maxDate', moment('2022-02-24 12:00:00.000Z').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`<GhDateTimePicker @date={{this.date}} @time={{this.time}} @minDate={{this.minDate}} @maxDate={{this.maxDate}} />`);
await click('[data-test-date-time-picker-datepicker]');
@ -212,11 +296,48 @@ describe('Integration: Component: gh-date-time-picker', function () {
expect(find('[data-date="2022-02-25"]')).to.have.attribute('disabled');
});
it('Handles timezone of minimum date correctly', async function () {
clock = sinon.useFakeTimers({
now: moment('2022-01-01 10:00:00.000Z').toDate()
});
// Blog timezone is -11, so current date over there is 2021-12-31
timezone = 'Pacific/Pago_Pago'; // GMT-11
this.set('date', '2021-12-31');
this.set('time', '22:00');
this.set('minDate', 'now');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} @minDate={{this.minDate}} />`);
await click('[data-test-date-time-picker-datepicker]');
expect(find('[data-date="2021-12-30"]')).to.have.attribute('disabled');
expect(find('[data-date="2021-12-31"]')).not.to.have.attribute('disabled');
});
it('Handles timezone of maximum date correctly', async function () {
clock = sinon.useFakeTimers({
now: moment('2021-12-31 10:00:00.000Z').toDate()
});
// Blog timezone is -11, so current date over there is 2021-12-30
timezone = 'Pacific/Pago_Pago'; // GMT-11
this.set('date', '2021-12-30');
this.set('time', '22:00');
this.set('maxDate', 'now');
await render(hbs`<GhDateTimePicker @date={{this.date}} @time={{this.time}} @maxDate={{this.maxDate}} />`);
await click('[data-test-date-time-picker-datepicker]');
expect(find('[data-date="2021-12-30"]')).not.to.have.attribute('disabled');
expect(find('[data-date="2021-12-31"]')).to.have.attribute('disabled');
});
// TODO: move date validation into component?
// it('errors when date input is earlier than min', async function () {
// this.set('date', moment('2022-02-22 22:22:22.000Z')).toDate();
// this.set('date', moment('2022-02-22 22:22:22.000')).toDate();
// this.set('time', '12:00');
// this.set('minDate', moment('2022-02-11 12:00:00.000Z').toDate());
// this.set('minDate', moment('2022-02-11 12:00:00.000').toDate());
// const dateSpy = sinon.spy();
// this.set('updateDate', dateSpy);