Skip to content

Commit

Permalink
Merge pull request #236 from rwjblue/make-setup-and-teardown-async
Browse files Browse the repository at this point in the history
Make setup and teardown of new API async.
  • Loading branch information
rwjblue authored Nov 5, 2017
2 parents 001e1df + e4720d9 commit d930358
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 203 deletions.
81 changes: 44 additions & 37 deletions addon-test-support/setup-context.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { run } from '@ember/runloop';
import { run, next } from '@ember/runloop';
import { set, setProperties, get, getProperties } from '@ember/object';
import buildOwner from './build-owner';
import { _setupPromiseListeners } from './ext/rsvp';
Expand Down Expand Up @@ -59,51 +59,58 @@ export default function(context, options = {}) {
Ember.testing = true;
setContext(context);

let resolver = options.resolver;
let owner = buildOwner(resolver);
return new Promise(resolve => {
// ensure "real" async and not "fake" RSVP based async
next(() => {
let resolver = options.resolver;
let owner = buildOwner(resolver);

context.owner = owner;
context.owner = owner;

context.set = function(key, value) {
let ret = run(function() {
return set(context, key, value);
});
context.set = function(key, value) {
let ret = run(function() {
return set(context, key, value);
});

return ret;
};
return ret;
};

context.setProperties = function(hash) {
let ret = run(function() {
return setProperties(context, hash);
});
context.setProperties = function(hash) {
let ret = run(function() {
return setProperties(context, hash);
});

return ret;
};
return ret;
};

context.get = function(key) {
return get(context, key);
};
context.get = function(key) {
return get(context, key);
};

context.getProperties = function(...args) {
return getProperties(context, args);
};
context.getProperties = function(...args) {
return getProperties(context, args);
};

let resume;
context.resumeTest = function resumeTest() {
assert('Testing has not been paused. There is nothing to resume.', resume);
resume();
global.resumeTest = resume = undefined;
};
let resume;
context.resumeTest = function resumeTest() {
assert('Testing has not been paused. There is nothing to resume.', resume);
resume();
global.resumeTest = resume = undefined;
};

context.pauseTest = function pauseTest() {
console.info('Testing paused. Use `resumeTest()` to continue.'); // eslint-disable-line no-console
context.pauseTest = function pauseTest() {
console.info('Testing paused. Use `resumeTest()` to continue.'); // eslint-disable-line no-console

return new Promise(resolve => {
resume = resolve;
global.resumeTest = resumeTest;
}, 'TestAdapter paused promise');
};
return new Promise(resolve => {
resume = resolve;
global.resumeTest = resumeTest;
}, 'TestAdapter paused promise');
};

_setupAJAXHooks();
_setupPromiseListeners();
_setupAJAXHooks();
_setupPromiseListeners();

resolve(context);
});
});
}
253 changes: 130 additions & 123 deletions addon-test-support/setup-rendering-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,142 +48,149 @@ export default function(context) {
},
];

let { owner } = context;

let dispatcher = owner.lookup('event_dispatcher:main') || Ember.EventDispatcher.create();
dispatcher.setup({}, '#ember-testing');

let OutletView = owner.factoryFor
? owner.factoryFor('view:-outlet')
: owner._lookupFactory('view:-outlet');
let OutletTemplate = owner.lookup('template:-outlet');
let toplevelView = OutletView.create();
RENDERING_CLEANUP[guid].push(() => toplevelView.destroy());

let hasOutletTemplate = Boolean(OutletTemplate);
let outletState = {
render: {
owner,
into: undefined,
outlet: 'main',
name: 'application',
controller: context,
ViewClass: undefined,
template: OutletTemplate,
},

outlets: {},
};

let element, hasRendered;
let templateId = 0;

if (hasOutletTemplate) {
run(() => {
toplevelView.setOutletState(outletState);
});
}

context.render = function render(template) {
if (!template) {
throw new Error('you must pass a template to `render()`');
}

// ensure context.element is reset until after rendering has completed
element = undefined;

return new Promise(function asyncRender(resolve) {
// manually enter async land, so that rendering itself is always async (even though
// Ember <= 2.18 do not require async rendering)
next(function asyncRenderSetup() {
templateId += 1;
let templateFullName = `template:-undertest-${templateId}`;
owner.register(templateFullName, template);
let stateToRender = {
return new Promise(resolve => {
// ensure "real" async and not "fake" RSVP based async
next(() => {
let { owner } = context;

let dispatcher = owner.lookup('event_dispatcher:main') || Ember.EventDispatcher.create();
dispatcher.setup({}, '#ember-testing');

let OutletView = owner.factoryFor
? owner.factoryFor('view:-outlet')
: owner._lookupFactory('view:-outlet');
let OutletTemplate = owner.lookup('template:-outlet');
let toplevelView = OutletView.create();
RENDERING_CLEANUP[guid].push(() => toplevelView.destroy());

let hasOutletTemplate = Boolean(OutletTemplate);
let outletState = {
render: {
owner,
into: undefined,
outlet: 'main',
name: 'index',
name: 'application',
controller: context,
ViewClass: undefined,
template: owner.lookup(templateFullName),
outlets: {},
};
template: OutletTemplate,
},

if (hasOutletTemplate) {
stateToRender.name = 'index';
outletState.outlets.main = { render: stateToRender, outlets: {} };
} else {
stateToRender.name = 'application';
outletState = { render: stateToRender, outlets: {} };
}
outlets: {},
};

toplevelView.setOutletState(outletState);
if (!hasRendered) {
// TODO: make this id configurable
run(toplevelView, 'appendTo', '#ember-testing');
hasRendered = true;
}
let element, hasRendered;
let templateId = 0;

// using next here because the actual rendering does not happen until
// the renderer detects it is dirty (which happens on backburner's end
// hook), see the following implementation details:
//
// * [view:outlet](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/views/outlet.js#L129-L145) manually dirties its own tag upon `setOutletState`
// * [backburner's custom end hook](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/renderer.js#L145-L159) detects that the current revision of the root is no longer the latest, and triggers a new rendering transaction
next(function asyncUpdateElementAfterRender() {
// ensure the element is based on the wrapping toplevel view
// Ember still wraps the main application template with a
// normal tagged view
//
// In older Ember versions (2.4) the element itself is not stable,
// and therefore we cannot update the `this.element` until after the
// rendering is completed
element = document.querySelector('#ember-testing > .ember-view');

resolve();
if (hasOutletTemplate) {
run(() => {
toplevelView.setOutletState(outletState);
});
});
});
};
}

Object.defineProperty(context, 'element', {
enumerable: true,
configurable: true,
get() {
return element;
},
});
context.render = function render(template) {
if (!template) {
throw new Error('you must pass a template to `render()`');
}

if (global.jQuery) {
context.$ = function $(selector) {
// emulates Ember internal behavor of `this.$` in a component
// https://github.com/emberjs/ember.js/blob/v2.5.1/packages/ember-views/lib/views/states/has_element.js#L18
return selector ? global.jQuery(selector, element) : global.jQuery(element);
};
}
// ensure context.element is reset until after rendering has completed
element = undefined;

return new Promise(function asyncRender(resolve) {
// manually enter async land, so that rendering itself is always async (even though
// Ember <= 2.18 do not require async rendering)
next(function asyncRenderSetup() {
templateId += 1;
let templateFullName = `template:-undertest-${templateId}`;
owner.register(templateFullName, template);
let stateToRender = {
owner,
into: undefined,
outlet: 'main',
name: 'index',
controller: context,
ViewClass: undefined,
template: owner.lookup(templateFullName),
outlets: {},
};

if (hasOutletTemplate) {
stateToRender.name = 'index';
outletState.outlets.main = { render: stateToRender, outlets: {} };
} else {
stateToRender.name = 'application';
outletState = { render: stateToRender, outlets: {} };
}

toplevelView.setOutletState(outletState);
if (!hasRendered) {
// TODO: make this id configurable
run(toplevelView, 'appendTo', '#ember-testing');
hasRendered = true;
}

// using next here because the actual rendering does not happen until
// the renderer detects it is dirty (which happens on backburner's end
// hook), see the following implementation details:
//
// * [view:outlet](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/views/outlet.js#L129-L145) manually dirties its own tag upon `setOutletState`
// * [backburner's custom end hook](https://github.com/emberjs/ember.js/blob/f94a4b6aef5b41b96ef2e481f35e07608df01440/packages/ember-glimmer/lib/renderer.js#L145-L159) detects that the current revision of the root is no longer the latest, and triggers a new rendering transaction
next(function asyncUpdateElementAfterRender() {
// ensure the element is based on the wrapping toplevel view
// Ember still wraps the main application template with a
// normal tagged view
//
// In older Ember versions (2.4) the element itself is not stable,
// and therefore we cannot update the `this.element` until after the
// rendering is completed
element = document.querySelector('#ember-testing > .ember-view');

resolve();
});
});
});
};

Object.defineProperty(context, 'element', {
enumerable: true,
configurable: true,
get() {
return element;
},
});

context.clearRender = function clearRender() {
return new Promise(function async_clearRender(resolve) {
element = undefined;

next(function async_clearRender() {
toplevelView.setOutletState({
render: {
owner,
into: undefined,
outlet: 'main',
name: 'application',
controller: context,
ViewClass: undefined,
template: undefined,
},
outlets: {},
if (global.jQuery) {
context.$ = function $(selector) {
// emulates Ember internal behavor of `this.$` in a component
// https://github.com/emberjs/ember.js/blob/v2.5.1/packages/ember-views/lib/views/states/has_element.js#L18
return selector ? global.jQuery(selector, element) : global.jQuery(element);
};
}

context.clearRender = function clearRender() {
return new Promise(function async_clearRender(resolve) {
element = undefined;

next(function async_clearRender() {
toplevelView.setOutletState({
render: {
owner,
into: undefined,
outlet: 'main',
name: 'application',
controller: context,
ViewClass: undefined,
template: undefined,
},
outlets: {},
});

// RE: next usage, see detailed comment above
next(resolve);
});
});
};

// RE: next usage, see detailed comment above
next(resolve);
});
resolve(context);
});
};
});
}
Loading

0 comments on commit d930358

Please sign in to comment.