mirror of
https://github.com/discourse/discourse.git
synced 2024-12-02 20:45:17 +08:00
0e09c5837f
In my original PR (#9647) I attempted to solve the problem of using fake timers in acceptance tests by using the new sinon clock.tickAsync methods. This way of doing things seems to be flawed, however, as we are getting random spec timeouts starting with the bookmark acceptance test where this was introduced. I think I was going about things the wrong way. This commit introduces a new function with callback (acceptanceUseFakeClock) that sets up the fake timers using sinon.useFakeTimers with the shouldAdvanceTime option set to true. This advances time at a normal rate of 20ms per tick, which means that we are not freezing any time and existing setTimeout funcs. should proceed as normal. Along with this the callback passed will run clock.reset() at the end to make sure all the timers are cleaned up correctly. There is an optional third parameter after the callback, which is the timezone. If the user is logged in for the acceptance test then their timezone is used, otherwise we default to America/Denver. Usage is (inside an acceptance test): ``` test("Name of the test", async assert => { // first parameter is time to start fake clock at await acceptanceUseFakeClock("2020-05-04T13:00:00", async () => { // test code goes here e.g. await visit("/url"); }); }); ```
240 lines
6.1 KiB
JavaScript
240 lines
6.1 KiB
JavaScript
import { isEmpty } from "@ember/utils";
|
|
import { later } from "@ember/runloop";
|
|
/* global QUnit, resetSite */
|
|
|
|
import sessionFixtures from "fixtures/session-fixtures";
|
|
import HeaderComponent from "discourse/components/site-header";
|
|
import { forceMobile, resetMobile } from "discourse/lib/mobile";
|
|
import { resetPluginApi } from "discourse/lib/plugin-api";
|
|
import {
|
|
clearCache as clearOutletCache,
|
|
resetExtraClasses
|
|
} from "discourse/lib/plugin-connectors";
|
|
import { clearHTMLCache } from "discourse/helpers/custom-html";
|
|
import { flushMap } from "discourse/models/store";
|
|
import { clearRewrites } from "discourse/lib/url";
|
|
import { initSearchData } from "discourse/widgets/search-menu";
|
|
import { resetDecorators } from "discourse/widgets/widget";
|
|
import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget";
|
|
import { resetTopicTitleDecorators } from "discourse/components/topic-title";
|
|
import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked";
|
|
import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector";
|
|
import { resetCache as resetOneboxCache } from "pretty-text/oneboxer";
|
|
import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic";
|
|
import User from "discourse/models/user";
|
|
|
|
export function currentUser() {
|
|
return User.create(sessionFixtures["/session/current.json"].current_user);
|
|
}
|
|
|
|
export function updateCurrentUser(properties) {
|
|
User.current().setProperties(properties);
|
|
}
|
|
|
|
// Note: do not use this in acceptance tests. Use `loggedIn: true` instead
|
|
export function logIn() {
|
|
User.resetCurrent(currentUser());
|
|
}
|
|
|
|
// Note: Only use if `loggedIn: true` has been used in an acceptance test
|
|
export function loggedInUser() {
|
|
return User.current();
|
|
}
|
|
|
|
export function fakeTime(timeString, timezone = null, advanceTime = false) {
|
|
let now = moment.tz(timeString, timezone);
|
|
return sandbox.useFakeTimers({
|
|
now: now.valueOf(),
|
|
shouldAdvanceTime: advanceTime
|
|
});
|
|
}
|
|
|
|
export async function acceptanceUseFakeClock(
|
|
timeString,
|
|
callback,
|
|
timezone = null
|
|
) {
|
|
if (!timezone) {
|
|
let user = loggedInUser();
|
|
if (user) {
|
|
timezone = user.resolvedTimezone(user);
|
|
} else {
|
|
timezone = "America/Denver";
|
|
}
|
|
}
|
|
let clock = fakeTime(timeString, timezone, true);
|
|
await callback();
|
|
clock.reset();
|
|
}
|
|
|
|
const Plugin = $.fn.modal;
|
|
const Modal = Plugin.Constructor;
|
|
|
|
function AcceptanceModal(option, _relatedTarget) {
|
|
return this.each(function() {
|
|
var $this = $(this);
|
|
var data = $this.data("bs.modal");
|
|
var options = $.extend(
|
|
{},
|
|
Modal.DEFAULTS,
|
|
$this.data(),
|
|
typeof option === "object" && option
|
|
);
|
|
|
|
if (!data) $this.data("bs.modal", (data = new Modal(this, options)));
|
|
data.$body = $("#ember-testing");
|
|
|
|
if (typeof option === "string") data[option](_relatedTarget);
|
|
else if (options.show) data.show(_relatedTarget);
|
|
});
|
|
}
|
|
|
|
window.bootbox.$body = $("#ember-testing");
|
|
$.fn.modal = AcceptanceModal;
|
|
|
|
let _pretenderCallbacks = {};
|
|
|
|
export function applyPretender(name, server, helper) {
|
|
const cb = _pretenderCallbacks[name];
|
|
if (cb) cb(server, helper);
|
|
}
|
|
|
|
export function acceptance(name, options) {
|
|
options = options || {};
|
|
|
|
if (options.pretend) {
|
|
_pretenderCallbacks[name] = options.pretend;
|
|
}
|
|
|
|
QUnit.module("Acceptance: " + name, {
|
|
beforeEach() {
|
|
resetMobile();
|
|
|
|
// For now don't do scrolling stuff in Test Mode
|
|
HeaderComponent.reopen({ examineDockHeader: function() {} });
|
|
|
|
resetExtraClasses();
|
|
if (options.beforeEach) {
|
|
options.beforeEach.call(this);
|
|
}
|
|
|
|
if (options.mobileView) {
|
|
forceMobile();
|
|
}
|
|
|
|
if (options.loggedIn) {
|
|
logIn();
|
|
}
|
|
|
|
if (options.settings) {
|
|
Discourse.SiteSettings = jQuery.extend(
|
|
true,
|
|
Discourse.SiteSettings,
|
|
options.settings
|
|
);
|
|
}
|
|
|
|
if (options.site) {
|
|
resetSite(Discourse.SiteSettings, options.site);
|
|
}
|
|
|
|
clearOutletCache();
|
|
clearHTMLCache();
|
|
resetPluginApi();
|
|
Discourse.reset();
|
|
},
|
|
|
|
afterEach() {
|
|
if (options && options.afterEach) {
|
|
options.afterEach.call(this);
|
|
}
|
|
flushMap();
|
|
localStorage.clear();
|
|
User.resetCurrent();
|
|
resetSite(Discourse.SiteSettings);
|
|
resetExtraClasses();
|
|
clearOutletCache();
|
|
clearHTMLCache();
|
|
resetPluginApi();
|
|
clearRewrites();
|
|
initSearchData();
|
|
resetDecorators();
|
|
resetPostCookedDecorators();
|
|
resetPluginOutletDecorators();
|
|
resetTopicTitleDecorators();
|
|
resetOneboxCache();
|
|
resetCustomPostMessageCallbacks();
|
|
Discourse._runInitializer("instanceInitializers", function(
|
|
initName,
|
|
initializer
|
|
) {
|
|
if (initializer && initializer.teardown) {
|
|
initializer.teardown(Discourse.__container__);
|
|
}
|
|
});
|
|
Discourse.reset();
|
|
|
|
// We do this after reset so that the willClearRender will have already fired
|
|
resetWidgetCleanCallbacks();
|
|
}
|
|
});
|
|
}
|
|
|
|
export function controllerFor(controller, model) {
|
|
controller = Discourse.__container__.lookup("controller:" + controller);
|
|
if (model) {
|
|
controller.set("model", model);
|
|
}
|
|
return controller;
|
|
}
|
|
|
|
export function fixture(selector) {
|
|
if (selector) {
|
|
return $("#qunit-fixture").find(selector);
|
|
}
|
|
return $("#qunit-fixture");
|
|
}
|
|
|
|
QUnit.assert.not = function(actual, message) {
|
|
this.pushResult({
|
|
result: !actual,
|
|
actual,
|
|
expected: !actual,
|
|
message
|
|
});
|
|
};
|
|
|
|
QUnit.assert.blank = function(actual, message) {
|
|
this.pushResult({
|
|
result: isEmpty(actual),
|
|
actual,
|
|
message
|
|
});
|
|
};
|
|
|
|
QUnit.assert.present = function(actual, message) {
|
|
this.pushResult({
|
|
result: !isEmpty(actual),
|
|
actual,
|
|
message
|
|
});
|
|
};
|
|
|
|
QUnit.assert.containsInstance = function(collection, klass, message) {
|
|
const result = klass.detectInstance(_.first(collection));
|
|
this.pushResult({
|
|
result,
|
|
message
|
|
});
|
|
};
|
|
|
|
export function waitFor(assert, callback, timeout) {
|
|
timeout = timeout || 500;
|
|
|
|
const done = assert.async();
|
|
later(() => {
|
|
callback();
|
|
done();
|
|
}, timeout);
|
|
}
|