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

Adding tests not settled detection support in tests #314

Merged
merged 8 commits into from
Feb 23, 2018
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
29 changes: 26 additions & 3 deletions addon-test-support/ember-qunit/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { default as moduleFor } from './legacy-2-x/module-for';
export { default as moduleForComponent } from './legacy-2-x/module-for-component';
export {
default as moduleForComponent,
} from './legacy-2-x/module-for-component';
export { default as moduleForModel } from './legacy-2-x/module-for-model';
export { default as QUnitAdapter } from './adapter';
export { module, test, skip, only, todo } from 'qunit';
Expand All @@ -25,6 +27,10 @@ import {
teardownApplicationContext,
validateErrorHandler,
} from '@ember/test-helpers';
import {
detectIfTestNotIsolated,
reportIfTestNotIsolated,
} from './test-isolation-validation';

export function setResolver() {
deprecate(
Expand Down Expand Up @@ -164,7 +170,8 @@ export function setupTestContainer() {
let params = QUnit.urlParams;

let containerVisibility = params.nocontainer ? 'hidden' : 'visible';
let containerPosition = params.dockcontainer || params.devmode ? 'fixed' : 'relative';
let containerPosition =
params.dockcontainer || params.devmode ? 'fixed' : 'relative';

if (params.devmode) {
testContainer.className = ' full-screen';
Expand All @@ -175,7 +182,9 @@ export function setupTestContainer() {

let qunitContainer = document.getElementById('qunit');
if (params.dockcontainer) {
qunitContainer.style.marginBottom = window.getComputedStyle(testContainer).height;
qunitContainer.style.marginBottom = window.getComputedStyle(
testContainer
).height;
}
}

Expand Down Expand Up @@ -228,6 +237,11 @@ export function setupEmberOnerrorValidation() {
});
}

export function setupTestIsolationValidation() {
QUnit.testDone(detectIfTestNotIsolated);
QUnit.done(reportIfTestNotIsolated);
}

/**
@method start
@param {Object} [options] Options to be used for enabling/disabling behaviors
Expand All @@ -243,6 +257,8 @@ export function setupEmberOnerrorValidation() {
back to `false` after each test will.
@param {Boolean} [options.setupEmberOnerrorValidation] If `false` validation
of `Ember.onerror` will be disabled.
@param {Boolean} [options.setupTestIsolationValidation] If `false` test isolation validation
will be disabled.
*/
export function start(options = {}) {
if (options.loadTests !== false) {
Expand All @@ -265,6 +281,13 @@ export function start(options = {}) {
setupEmberOnerrorValidation();
}

if (
typeof options.setupTestIsolationValidation !== 'undefined' &&
options.setupTestIsolationValidation !== false
) {
setupTestIsolationValidation();
}

if (options.startTests !== false) {
startTests();
}
Expand Down
48 changes: 48 additions & 0 deletions addon-test-support/ember-qunit/test-isolation-validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { run } from '@ember/runloop';
import { isSettled } from '@ember/test-helpers';

const TESTS_NOT_ISOLATED = [];

/**
* Detects if a specific test isn't isolated. A test is considered
* not isolated if it:
*
* - has no pending timers
* - is not in a runloop
* - has no pending AJAX requests
* - has no pending test waiters
*
* @function detectIfTestNotIsolated
* @param {Object} testInfo
* @param {string} testInfo.module The name of the test module
* @param {string} testInfo.name The test name
*/
export function detectIfTestNotIsolated({ module, name }) {
if (!isSettled()) {
TESTS_NOT_ISOLATED.push(`${module}: ${name}`);
run.cancelTimers();
}
}

/**
* Reports if a test isn't isolated. Please see above for what
* constitutes a test being isolated.
*
* @function reportIfTestNotIsolated
* @throws Error if tests are not isolated
*/
export function reportIfTestNotIsolated() {
if (TESTS_NOT_ISOLATED.length > 0) {
let leakyTests = TESTS_NOT_ISOLATED.slice();
TESTS_NOT_ISOLATED.length = 0;

throw new Error(getMessage(leakyTests.length, leakyTests.join('\n')));
}
}

export function getMessage(testCount, testsToReport) {
return `TESTS ARE NOT ISOLATED
The following (${testCount}) tests have one or more of pending timers, pending AJAX requests, pending test waiters, or are still in a runloop: \n
${testsToReport}
`;
}
17 changes: 10 additions & 7 deletions testem.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ module.exports = {
'Chrome'
],
browser_args: {
Chrome: [
'--disable-gpu',
'--headless',
'--no-sandbox',
'--remote-debugging-port=9222',
'--window-size=1440,900'
]
Chrome: {
mode: 'ci',
args: [
'--disable-gpu',
'--headless',
'--no-sandbox',
'--remote-debugging-port=9222',
'--window-size=1440,900'
]
}
}
};
74 changes: 74 additions & 0 deletions tests/unit/setup-test-isolation-validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Ember from 'ember';
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import {
detectIfTestNotIsolated,
reportIfTestNotIsolated,
getMessage,
} from 'ember-qunit/test-isolation-validation';

module('setupTestIsolationValidation', function(hooks) {
hooks.beforeEach(function() {
this.cancelId = 0;

this._waiter = () => {
return !this.isWaiterPending;
};

// In Ember < 2.8 `registerWaiter` expected to be bound to
// `Ember.Test` 😭
//
// Once we have dropped support for < 2.8 we should swap this to
// use:
//
// import { registerWaiter } from '@ember/test';
Ember.Test.registerWaiter(this._waiter);
});

hooks.afterEach(function() {
Ember.Test.unregisterWaiter(this._waiter);

run.cancel(this.cancelId);
});

test('reportIfTestNotIsolated does not throw when test is isolated', function(assert) {
assert.expect(1);

detectIfTestNotIsolated({ module: 'foo', name: 'bar' });
reportIfTestNotIsolated();

assert.ok(true);
});

test('reportIfTestNotIsolated throws when test has pending timers', function(assert) {
assert.expect(1);

this.cancelId = run.later(() => {}, 10);

detectIfTestNotIsolated({ module: 'foo', name: 'bar' });

assert.throws(
function() {
reportIfTestNotIsolated();
},
Error,
getMessage(1, 'foo: bar')
);
});

test('reportIfTestNotIsolated throws when test has test waiters', function(assert) {
assert.expect(1);

this.isWaiterPending = true;

detectIfTestNotIsolated({ module: 'foo', name: 'bar' });

assert.throws(
function() {
reportIfTestNotIsolated();
},
Error,
getMessage(1, 'foo: bar')
);
});
});