From 60275319aea735f18b9ce2d2915c4e9633c8ee65 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 13 Dec 2017 17:13:28 -0500 Subject: [PATCH 1/3] Wait for pending AJAX in acceptance tests. Ember internally tracks AJAX requests in the same way that we do here for legacy style "acceptance" tests using the `ember-testing.js` asset provided by emberjs/ember.js itself. When `@ember/test-helpers`'s `settled` utility is used in a legacy acceptance test context any pending AJAX requests are not properly considered during the `isSettled` check below. This utilizes a local utility method present in Ember since around 2.8.0 to properly consider pending AJAX requests done within legacy acceptance tests. --- .../@ember/test-helpers/settled.js | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/addon-test-support/@ember/test-helpers/settled.js b/addon-test-support/@ember/test-helpers/settled.js index 7bb47662b..a998a29f6 100644 --- a/addon-test-support/@ember/test-helpers/settled.js +++ b/addon-test-support/@ember/test-helpers/settled.js @@ -8,7 +8,32 @@ import global from './global'; // TODO: refactor to use `nextTick` from #258 const SET_TIMEOUT = global.setTimeout; + +// Ember internally tracks AJAX requests in the same way that we do here for +// legacy style "acceptance" tests using the `ember-testing.js` asset provided +// by emberjs/ember.js itself. When `@ember/test-helpers`'s `settled` utility +// is used in a legacy acceptance test context any pending AJAX requests are +// not properly considered during the `isSettled` check below. +// +// This utilizes a local utility method present in Ember since around 2.8.0 to +// properly consider pending AJAX requests done within legacy acceptance tests. +const _internalPendingRequests = (() => { + if (Ember.__loader.registry['ember-testing/test/pending_requests']) { + return Ember.__loader.require('ember-testing/test/pending_requests').pendingRequests; + } + + return () => 0; +})(); + let requests; + +function pendingRequests() { + let localRequestsPending = requests !== undefined ? requests.length : 0; + let internalRequestsPending = _internalPendingRequests(); + + return localRequestsPending + internalRequestsPending; +} + function incrementAjaxPendingRequests(_, xhr) { requests.push(xhr); } @@ -71,7 +96,7 @@ function checkWaiters() { } export function getState() { - let pendingRequestCount = requests !== undefined ? requests.length : 0; + let pendingRequestCount = pendingRequests(); return { hasPendingTimers: Boolean(run.hasScheduledTimers()), From c615d7eff75f31fa32db066572d016561a440922 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 13 Dec 2017 18:43:17 -0500 Subject: [PATCH 2/3] Add monkey patch to ensure global ajaxSend listener is cleaned up. --- index.js | 4 ++++ vendor/monkey-patches.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 vendor/monkey-patches.js diff --git a/index.js b/index.js index 478241c6a..473e6337b 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,10 @@ module.exports = { name: 'ember-test-helpers', + included() { + this.import('vendor/monkey-patches.js', { type: 'test' }); + }, + treeForAddonTestSupport(tree) { // intentionally not calling _super here // so that can have our `import`'s be diff --git a/vendor/monkey-patches.js b/vendor/monkey-patches.js new file mode 100644 index 000000000..417349ce5 --- /dev/null +++ b/vendor/monkey-patches.js @@ -0,0 +1,35 @@ +/* globals require, Ember, jQuery */ + +(function() { + if (typeof jQuery !== 'undefined') { + var _Ember; + if (typeof Ember !== 'undefined') { + _Ember = Ember; + } else { + _Ember = require('ember').default; + } + + var pendingRequests; + if (Ember.__loader.registry['ember-testing/test/pending_requests']) { + pendingRequests = Ember.__loader.require('ember-testing/test/pending_requests'); + } + + if (pendingRequests) { + // This exists to ensure that the AJAX listeners setup by Ember itself + // (which as of 2.17 are not properly torn down) get cleared and released + // when the application is destroyed. Without this, any AJAX requests + // that happen _between_ when acceptance tests will always share + // `pendingRequests`. + _Ember.Application.reopen({ + willDestroy() { + jQuery(document).off('ajaxSend', pendingRequests.incrementPendingRequests); + jQuery(document).off('ajaxComplete', pendingRequests.decrementPendingRequests); + + pendingRequests.clearPendingRequests(); + + this._super.apply(this, arguments); + }, + }); + } + } +})(); From 1acecfcbdfe019c07697abc356307c454190b4c3 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 13 Dec 2017 18:43:55 -0500 Subject: [PATCH 3/3] Add test for `settled` with legacy acceptance tests. --- tests/helpers/module-for-acceptance.js | 17 ++++- .../module-for-acceptance-interop-test.js | 74 +++++++++++++++++++ vendor/monkey-patches.js | 2 +- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 tests/integration/module-for-acceptance-interop-test.js diff --git a/tests/helpers/module-for-acceptance.js b/tests/helpers/module-for-acceptance.js index 21e42d6a7..e792ebf21 100644 --- a/tests/helpers/module-for-acceptance.js +++ b/tests/helpers/module-for-acceptance.js @@ -2,11 +2,22 @@ import { resolve } from 'rsvp'; import { module } from 'qunit'; import startApp from '../helpers/start-app'; import destroyApp from '../helpers/destroy-app'; +import { setResolverRegistry } from './resolver'; +import QUnitTestAdapter from './qunit-test-adapter'; import Ember from 'ember'; export default function(name, options = {}) { module(name, { beforeEach() { + Ember.Test.adapter = QUnitTestAdapter.create(); + + if (options.registry) { + setResolverRegistry(options.registry); + } + + let testElementContainer = document.querySelector('#ember-testing-container'); + this.fixtureResetValue = testElementContainer.innerHTML; + Ember.testing = true; this.application = startApp(); @@ -19,7 +30,11 @@ export default function(name, options = {}) { let afterEach = options.afterEach && options.afterEach.apply(this, arguments); return resolve(afterEach) .then(() => destroyApp(this.application)) - .finally(() => (Ember.testing = false)); + .finally(() => { + Ember.testing = false; + + document.getElementById('ember-testing-container').innerHTML = this.fixtureResetValue; + }); }, }); } diff --git a/tests/integration/module-for-acceptance-interop-test.js b/tests/integration/module-for-acceptance-interop-test.js new file mode 100644 index 000000000..cdfa6616d --- /dev/null +++ b/tests/integration/module-for-acceptance-interop-test.js @@ -0,0 +1,74 @@ +import { test } from 'qunit'; +import { run } from '@ember/runloop'; +import EmberRouter from '@ember/routing/router'; +import Component from '@ember/component'; +import { settled } from '@ember/test-helpers'; +import hasEmberVersion from '@ember/test-helpers/has-ember-version'; + +import hbs from 'htmlbars-inline-precompile'; +import ajax from '../helpers/ajax'; +import { fireEvent } from '../helpers/events'; +import Pretender from 'pretender'; +import moduleForAcceptance from '../helpers/module-for-acceptance'; + +const TestComponent3 = Component.extend({ + layout: hbs`{{internalValue}}`, + + internalValue: '', + + click() { + ajax('/whazzits').then(data => { + let value = this.get('internalValue'); + + run(this, 'set', 'internalValue', value + data); + }); + }, +}); + +const Router = EmberRouter.extend({ location: 'none' }); +Router.map(function() { + this.route('ajax-request'); +}); + +moduleForAcceptance('Classic "moduleForAcceptance" | using settled', { + registry: { + 'router:main': Router, + 'component:x-test-3': TestComponent3, + 'template:ajax-request': hbs`{{x-test-3 class="special-thing"}}`, + }, + + beforeEach() { + this.server = new Pretender(function() { + this.get( + '/whazzits', + function() { + return [200, { 'Content-Type': 'text/plain' }, 'Remote Data!']; + }, + 25 + ); + }); + }, + + afterEach() { + this.server.shutdown(); + }, +}); + +// this requires Ember 2.8 or higher due to a refactor that exposed the +// internal mechanism that tracks pending requests in legacy acceptance tests, +// in older versions of ember legacy acceptance tests using `settled` from +// `ember-test-helpers` will **not** wait on pending requests. +if (hasEmberVersion(2, 8)) { + test('Basic acceptance test using instance test helpers', function(assert) { + return this.application.testHelpers + .visit('/ajax-request') + .then(() => { + fireEvent(document.querySelector('.special-thing'), 'click'); + return settled(); + }) + .then(() => { + let testingElement = document.getElementById('ember-testing'); + assert.equal(testingElement.textContent, 'Remote Data!'); + }); + }); +} diff --git a/vendor/monkey-patches.js b/vendor/monkey-patches.js index 417349ce5..907393c15 100644 --- a/vendor/monkey-patches.js +++ b/vendor/monkey-patches.js @@ -18,7 +18,7 @@ // This exists to ensure that the AJAX listeners setup by Ember itself // (which as of 2.17 are not properly torn down) get cleared and released // when the application is destroyed. Without this, any AJAX requests - // that happen _between_ when acceptance tests will always share + // that happen _between_ acceptance tests will always share // `pendingRequests`. _Ember.Application.reopen({ willDestroy() {