FIX: flaky javascript tests with fake timers (#13235)

The problem was happening in component integration tests on the rendering stage, sometimes the rendering would never finish.

Using time moments in the future when faking time solves the problem. Unfortunately, I don't know why exactly it helps. It was just a lucky guess after some hours I spent trying to figure out what's going on. But I've done a lot of testings, so looks like it really works. I'll be monitoring builds for some time after merging this anyway.

Unit tests seem to work alright with moments in the past. And we don't fake time in acceptance tests at the moment but I guess they would very likely be flaky with time moments from the past since they also do rendering.

I'm actually thinking of moving all fake time moments to the future (including moments in unit tests) to decrease the chances of flakiness. But I don't want to do everything in one PR, because I can accidentally introduce new flakiness.

A pretty easy way of picking time moments in the future for tests is to use the 2100 year. It has the same calendar as 2021. If a day is Monday in 2021 it's Monday in 2100 too.
This commit is contained in:
Andrei Prigorshnev 2021-06-11 13:51:27 +04:00 committed by GitHub
parent cd6ab7bdd7
commit 178b294a62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 88 deletions

View File

@ -76,6 +76,15 @@ export function fakeTime(timeString, timezone = null, advanceTime = false) {
});
}
export function withFrozenTime(timeString, timezone, callback) {
const clock = fakeTime(timeString, timezone, false);
try {
callback();
} finally {
clock.restore();
}
}
let _pretenderCallbacks = {};
export function resetSite(siteSettings, extras) {

View File

@ -6,13 +6,6 @@ import {
fakeTime,
query,
} from "discourse/tests/helpers/qunit-helpers";
import sinon from "sinon";
let clock = null;
function mockMomentTz(dateString, timezone) {
clock = fakeTime(dateString, timezone, true);
}
discourseModule("Integration | Component | bookmark", function (hooks) {
setupRenderingTest(hooks);
@ -32,18 +25,17 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
});
hooks.afterEach(function () {
if (clock) {
clock.restore();
if (this.clock) {
this.clock.restore();
}
sinon.restore();
});
componentTest("show later this week option if today is < Thursday", {
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-10T08:00:00", this.currentUser._timezone);
const monday = "2100-06-07T08:00:00";
this.clock = fakeTime(monday, this.currentUser._timezone, true);
},
test(assert) {
@ -55,10 +47,10 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
"does not show later this week option if today is >= Thursday",
{
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-13T08:00:00", this.currentUser._timezone);
const thursday = "2100-06-10T08:00:00";
this.clock = fakeTime(thursday, this.currentUser._timezone, true);
},
test(assert) {
@ -72,10 +64,13 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("later today does not show if later today is tomorrow", {
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-11T22:00:00", this.currentUser._timezone);
this.clock = fakeTime(
"2100-12-11T22:00:00",
this.currentUser._timezone,
true
);
},
test(assert) {
@ -88,10 +83,13 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("later today shows if it is after 5pm but before 6pm", {
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-11T14:30:00", this.currentUser._timezone);
this.clock = fakeTime(
"2100-12-11T14:30:00",
this.currentUser._timezone,
true
);
},
test(assert) {
@ -101,10 +99,13 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("later today does not show if it is after 5pm", {
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-11T17:00:00", this.currentUser._timezone);
this.clock = fakeTime(
"2100-12-11T17:00:00",
this.currentUser._timezone,
true
);
},
test(assert) {
@ -117,10 +118,13 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("later today does show if it is before the end of the day", {
template,
skip: true,
beforeEach() {
mockMomentTz("2019-12-11T13:00:00", this.currentUser._timezone);
this.clock = fakeTime(
"2100-12-11T13:00:00",
this.currentUser._timezone,
true
);
},
test(assert) {
@ -130,7 +134,6 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("prefills the custom reminder type date and time", {
template,
skip: true,
beforeEach() {
let name = "test";
@ -147,7 +150,6 @@ discourseModule("Integration | Component | bookmark", function (hooks) {
componentTest("defaults to 08:00 for custom time", {
template,
skip: true,
async test(assert) {
await click("#tap_tile_custom");

View File

@ -17,7 +17,6 @@ discourseModule(
hooks.beforeEach(function () {
this.set("subject", selectKit());
this.clock = fakeTime("2021-05-03T08:00:00", "UTC", true); // Monday
});
hooks.afterEach(function () {
@ -25,9 +24,13 @@ discourseModule(
});
componentTest("shows default options", {
skip: true,
template: hbs`{{future-date-input-selector}}`,
beforeEach() {
const monday = fakeTime("2100-06-07T08:00:00", "UTC", true);
this.clock = monday;
},
async test(assert) {
await this.subject.expand();
@ -48,11 +51,11 @@ discourseModule(
});
componentTest("doesn't show 'Next Week' on Sundays", {
skip: true,
template: hbs`{{future-date-input-selector}}`,
beforeEach() {
this.clock = fakeTime("2021-05-02T08:00:00", "UTC", true); // Sunday
const sunday = fakeTime("2100-06-13T08:00:00", "UTC", true);
this.clock = sunday;
},
async test(assert) {

View File

@ -1,15 +1,14 @@
import { module, test } from "qunit";
import { fakeTime } from "discourse/tests/helpers/qunit-helpers";
import { formattedReminderTime } from "discourse/lib/bookmark";
import sinon from "sinon";
module("Unit | Utility | bookmark", function (hooks) {
hooks.beforeEach(function () {
fakeTime("2020-04-11 08:00:00", "Australia/Brisbane");
this.clock = fakeTime("2020-04-11 08:00:00", "Australia/Brisbane");
});
hooks.afterEach(function () {
sinon.restore();
this.clock.restore();
});
test("formattedReminderTime works when the reminder time is tomorrow", function (assert) {

View File

@ -1,6 +1,6 @@
import {
discourseModule,
fakeTime,
withFrozenTime,
} from "discourse/tests/helpers/qunit-helpers";
import {
@ -15,33 +15,29 @@ import { test } from "qunit";
const timezone = "Australia/Brisbane";
function mockMomentTz(dateString) {
fakeTime(dateString, timezone);
}
discourseModule("Unit | lib | timeUtils", function () {
test("nextWeek gets next week correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
assert.equal(nextWeek(timezone).format("YYYY-MM-DD"), "2019-12-18");
withFrozenTime("2019-12-11T08:00:00", timezone, () => {
assert.equal(nextWeek(timezone).format("YYYY-MM-DD"), "2019-12-18");
});
});
test("nextMonth gets next month correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
assert.equal(nextMonth(timezone).format("YYYY-MM-DD"), "2020-01-11");
withFrozenTime("2019-12-11T08:00:00", timezone, () => {
assert.equal(nextMonth(timezone).format("YYYY-MM-DD"), "2020-01-11");
});
});
test("laterThisWeek gets 2 days from now", function (assert) {
mockMomentTz("2019-12-10T08:00:00");
assert.equal(laterThisWeek(timezone).format("YYYY-MM-DD"), "2019-12-12");
withFrozenTime("2019-12-10T08:00:00", timezone, () => {
assert.equal(laterThisWeek(timezone).format("YYYY-MM-DD"), "2019-12-12");
});
});
test("tomorrow gets tomorrow correctly", function (assert) {
mockMomentTz("2019-12-11T08:00:00");
assert.equal(tomorrow(timezone).format("YYYY-MM-DD"), "2019-12-12");
withFrozenTime("2019-12-11T08:00:00", timezone, () => {
assert.equal(tomorrow(timezone).format("YYYY-MM-DD"), "2019-12-12");
});
});
test("startOfDay changes the time of the provided date to 8:00am correctly", function (assert) {
@ -54,54 +50,54 @@ discourseModule("Unit | lib | timeUtils", function () {
});
test("laterToday gets 3 hours from now and if before half-past, it rounds down", function (assert) {
mockMomentTz("2019-12-11T08:13:00");
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 11:00:00"
);
withFrozenTime("2019-12-11T08:13:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 11:00:00"
);
});
});
test("laterToday gets 3 hours from now and if after half-past, it rounds up to the next hour", function (assert) {
mockMomentTz("2019-12-11T08:43:00");
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 12:00:00"
);
withFrozenTime("2019-12-11T08:43:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 12:00:00"
);
});
});
test("laterToday is capped to 6pm. later today at 3pm = 6pm, 3:30pm = 6pm, 4pm = 6pm, 4:59pm = 6pm", function (assert) {
mockMomentTz("2019-12-11T15:00:00");
withFrozenTime("2019-12-11T15:00:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3pm should max to 6pm"
);
});
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3pm should max to 6pm"
);
withFrozenTime("2019-12-11T15:31:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3:30pm should max to 6pm"
);
});
mockMomentTz("2019-12-11T15:31:00");
withFrozenTime("2019-12-11T16:00:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4pm should max to 6pm"
);
});
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"3:30pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:00:00");
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4pm should max to 6pm"
);
mockMomentTz("2019-12-11T16:59:00");
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4:59pm should max to 6pm"
);
withFrozenTime("2019-12-11T16:59:00", timezone, () => {
assert.equal(
laterToday(timezone).format("YYYY-MM-DD HH:mm:ss"),
"2019-12-11 18:00:00",
"4:59pm should max to 6pm"
);
});
});
});