Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make setup and teardown of new API async. #236

Merged
merged 1 commit into from
Nov 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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