Skip to content

Commit

Permalink
[BUGFIX beta] reimplement $.ready()
Browse files Browse the repository at this point in the history
  • Loading branch information
rreckonerr committed Oct 9, 2020
1 parent be152e2 commit d1d7181
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 47 deletions.
20 changes: 17 additions & 3 deletions packages/@ember/application/lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ENV } from '@ember/-internals/environment';
import { hasDOM } from '@ember/-internals/browser-environment';
import { assert, isTesting } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { bind, join, once, run, schedule } from '@ember/runloop';
import { join, once, run, schedule } from '@ember/runloop';
import {
libraries,
processAllNamespaces,
Expand Down Expand Up @@ -205,6 +205,15 @@ const Application = Engine.extend({
*/
rootElement: 'body',

/**
@property _document
@type Document | null
@default 'window.document'
@private
*/
_document: hasDOM ? window.document : null,

/**
The `Ember.EventDispatcher` responsible for delegating events to this
application's views.
Expand Down Expand Up @@ -483,10 +492,15 @@ const Application = Engine.extend({
@method waitForDOMReady
*/
waitForDOMReady() {
if (!this.$ || this.$.isReady) {
if (this._document === null || this._document.readyState !== 'loading') {
schedule('actions', this, 'domReady');
} else {
this.$().ready(bind(this, 'domReady'));
let callback = () => {
this._document.removeEventListener('DOMContentLoaded', callback);
run(this, 'domReady');
};

this._document.addEventListener('DOMContentLoaded', callback);
}
},

Expand Down
86 changes: 42 additions & 44 deletions packages/@ember/application/tests/readiness_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,39 @@ import { moduleFor, ModuleBasedTestResolver, ApplicationTestCase } from 'interna
import { run } from '@ember/runloop';
import EmberApplication from '..';

let jQuery, application, Application;
let readyWasCalled, domReady, readyCallbacks;
let application, Application, _document, callbacks;
let readyWasCalled = 0;

const dispatchEvent = eventName => {
callbacks[eventName].forEach(callback => callback());
};

const removeEventListener = (eventName, callbackToRemove) => {
callbacks[eventName] = callbacks[eventName].filter(callback => callback !== callbackToRemove);
};

const addEventListener = (eventName, callback) => {
callbacks[eventName] ? callbacks[eventName].push(callback) : (callbacks[eventName] = [callback]);
};

// We are using a small mock of jQuery because jQuery is third-party code with
// very well-defined semantics, and we want to confirm that a jQuery stub run
// in a more minimal server environment that implements this behavior will be
// sufficient for Ember's requirements.
moduleFor(
'Application readiness',
class extends ApplicationTestCase {
constructor() {
super();

readyWasCalled = 0;
readyCallbacks = [];

let jQueryInstance = {
ready(callback) {
readyCallbacks.push(callback);
if (jQuery.isReady) {
domReady();
}
},
};

jQuery = function() {
return jQueryInstance;
};
jQuery.isReady = false;

let domReadyCalled = 0;
domReady = function() {
if (domReadyCalled !== 0) {
return;
}
domReadyCalled++;
for (let i = 0; i < readyCallbacks.length; i++) {
readyCallbacks[i]();
}
callbacks = [];
_document = {
removeEventListener,
addEventListener,
};

Application = EmberApplication.extend({
$: jQuery,
Resolver: ModuleBasedTestResolver,
_document,

ready() {
this._super();
readyWasCalled++;
},
});
Expand All @@ -56,52 +43,59 @@ moduleFor(
teardown() {
if (application) {
run(() => application.destroy());
jQuery = readyCallbacks = domReady = Application = application = undefined;
Application = application = _document = callbacks = undefined;
readyWasCalled = 0;
}
}

// These tests are confirming that if the callbacks passed into jQuery's ready hook is called
// synchronously during the application's initialization, we get the same behavior as if
// it was triggered after initialization.

["@test Application's ready event is called right away if jQuery is already ready"](assert) {
jQuery.isReady = true;
["@test Application's ready event is called right away if DOM is already ready"](assert) {
_document.readyState = 'interactive';

run(() => {
application = Application.create({ router: false });
application = Application.create({
router: false,
});

assert.equal(readyWasCalled, 0, 'ready is not called until later');
});

assert.equal(readyWasCalled, 1, 'ready was called');

domReady();
application.domReady();

assert.equal(callbacks['DOMContentLoaded'], undefined);
assert.equal(readyWasCalled, 1, "application's ready was not called again");
}

["@test Application's ready event is called after the document becomes ready"](assert) {
_document.readyState = 'loading';

run(() => {
application = Application.create({ router: false });
assert.equal(callbacks['DOMContentLoaded'].length, 1);
});

assert.equal(readyWasCalled, 0, "ready wasn't called yet");

domReady();
dispatchEvent('DOMContentLoaded');

assert.equal(callbacks['DOMContentLoaded'].length, 0);
assert.equal(readyWasCalled, 1, 'ready was called now that DOM is ready');
}

["@test Application's ready event can be deferred by other components"](assert) {
_document.readyState = 'loading';

run(() => {
application = Application.create({ router: false });
application.deferReadiness();
assert.equal(readyWasCalled, 0, "ready wasn't called yet");
assert.equal(callbacks['DOMContentLoaded'].length, 1);
});

assert.equal(readyWasCalled, 0, "ready wasn't called yet");

domReady();
application.domReady();

assert.equal(readyWasCalled, 0, "ready wasn't called yet");

Expand All @@ -112,6 +106,10 @@ moduleFor(

assert.equal(readyWasCalled, 1, 'ready was called now all readiness deferrals are advanced');

dispatchEvent('DOMContentLoaded');

assert.equal(callbacks['DOMContentLoaded'].length, 0);

expectAssertion(() => {
application.deferReadiness();
});
Expand Down

0 comments on commit d1d7181

Please sign in to comment.