diff --git a/addon-test-support/asserts/deprecations-include.js b/addon-test-support/assertions/deprecations-include.js similarity index 68% rename from addon-test-support/asserts/deprecations-include.js rename to addon-test-support/assertions/deprecations-include.js index 0e584b12..322c0145 100644 --- a/addon-test-support/asserts/deprecations-include.js +++ b/addon-test-support/assertions/deprecations-include.js @@ -1,8 +1,8 @@ import { getDeprecations } from '@ember/test-helpers'; -import toAssertionMessage from './utils/to-assertion-message'; export default function deprecationsInclude(expected) { - const deprecations = getDeprecations().map(toAssertionMessage); + const deprecations = getDeprecations().map(deprecation => deprecation.message); + this.pushResult({ result: deprecations.indexOf(expected) > -1, actual: deprecations, diff --git a/addon-test-support/asserts/deprecations.js b/addon-test-support/assertions/deprecations.js similarity index 84% rename from addon-test-support/asserts/deprecations.js rename to addon-test-support/assertions/deprecations.js index 2b597529..082efb13 100644 --- a/addon-test-support/asserts/deprecations.js +++ b/addon-test-support/assertions/deprecations.js @@ -1,12 +1,11 @@ import { getDeprecationsDuringCallback } from '@ember/test-helpers'; -import toAssertionMessage from './utils/to-assertion-message'; export default async function deprecations(callback, expectedDeprecations) { const maybeThenable = getDeprecationsDuringCallback(callback); const operation = (deprecations) => { this.deepEqual( - deprecations.map(toAssertionMessage), + deprecations.map(deprecation => deprecation.message), expectedDeprecations, 'Expected deprecations during test.' ); diff --git a/addon-test-support/assertions/expect-deprecation.js b/addon-test-support/assertions/expect-deprecation.js new file mode 100644 index 00000000..998bb954 --- /dev/null +++ b/addon-test-support/assertions/expect-deprecation.js @@ -0,0 +1,29 @@ +import { getDeprecationsForCallback, getDeprecations } from '@ember/test-helpers'; +import checkMatcher from './utils/check-matcher'; + +export default function expectDeprecation(cb, matcher) { + const test = deprecations => { + const matchedDeprecations = deprecations.filter(deprecation => { + return checkMatcher(deprecation.message, matcher); + }); + + this.pushResult({ + result: matchedDeprecations.length !== 0, + actual: matchedDeprecations, + expected: null, + message: 'Expected deprecations during test, but no deprecations were found.' + }); + } + + if (typeof cb !== 'function') { + test(getDeprecations()); + } else { + const maybeThenable = getDeprecationsForCallback(cb); + if (maybeThenable !== null && typeof maybeThenable === 'object' && typeof maybeThenable.then === 'function') { + return maybeThenable.then(test); + } else { + test(maybeThenable); + } + } + +} diff --git a/addon-test-support/assertions/expect-no-runloop.js b/addon-test-support/assertions/expect-no-runloop.js new file mode 100644 index 00000000..3d3ee41d --- /dev/null +++ b/addon-test-support/assertions/expect-no-runloop.js @@ -0,0 +1,27 @@ +import { end, _currentRunloop, _hasScheduledTimers, _cancelTimers } from '@ember/runloop'; + +export default function expectNoRunloop() { + if (_currentRunLoop) { + this.pushResult({ + result: false, + actual: run.currentRunLoop, + expected: null, + message: 'Should not be in a run loop at end of test' + }); + + while (_currentRunLoop) { + end(); + } + } + + if (_hasScheduledTimers()) { + this.pushResult({ + result: false, + actual: true, + expected: false, + message: 'Ember run should not have scheduled timers at end of test' + }); + + _cancelTimers(); + } +} diff --git a/addon-test-support/assertions/expect-no-warnings.js b/addon-test-support/assertions/expect-no-warnings.js new file mode 100644 index 00000000..b8c5b437 --- /dev/null +++ b/addon-test-support/assertions/expect-no-warnings.js @@ -0,0 +1,16 @@ +import { getWarnings, getWarningsDuringCallback } from '@ember/test-helpers'; + +export default function expectNoWarnings(callback) { + const warnings = typeof callback === 'function' ? getWarningsDuringCallback(callback) : getWarnings(); + + let warningStr = warnings.reduce((a, b) => { + return `${b}${a.message}\n`; + }, ''); + + this.pushResult({ + result: warnings.length === 0, + actual: warnings, + expected: [], + message: `Expected no warnings during test, but warnings were found.\n${warningStr}` + }); +} diff --git a/addon-test-support/assertions/expect-warnings.js b/addon-test-support/assertions/expect-warnings.js new file mode 100644 index 00000000..b8da7965 --- /dev/null +++ b/addon-test-support/assertions/expect-warnings.js @@ -0,0 +1,21 @@ +import checkMatcher from './utils/check-matcher'; +import { getWarningsDuringCallback } from '@ember/test-helpers'; + +export default function expectWarnings(callback, matcher) { + let warnings; + if (typeof callback === 'function') { + warnings = getWarningsDuringCallback(callback); + } else { + matcher = callback; + warnings = getWarnings(); + } + + const matchedWarnings = warnings.filter(warning => checkMatcher(warning.message, matcher); + + this.pushResult({ + result: matchedWarnings.length !== 0, + actual: matchedWarnings, + expected: null, + message: 'Expected warnings during test, but no warnings were found.' + }); +} diff --git a/addon-test-support/assertions/no-deprecations.js b/addon-test-support/assertions/no-deprecations.js new file mode 100644 index 00000000..a0a89879 --- /dev/null +++ b/addon-test-support/assertions/no-deprecations.js @@ -0,0 +1,11 @@ +import { getDeprecations } from '@ember/test-helpers'; + +export default function noDeprecations() { + // TODO: implement + this.pushResult({ + result: getDeprecations(), + actual: [], + expected: null, + message: 'Expected no deprecations during test, but deprecations did occure.' + }); +} diff --git a/addon-test-support/assertions/utils/check-matcher.js b/addon-test-support/assertions/utils/check-matcher.js new file mode 100644 index 00000000..608ce80e --- /dev/null +++ b/addon-test-support/assertions/utils/check-matcher.js @@ -0,0 +1,12 @@ +export function checkMatcher(message, matcher) { + if (typeof matcher === 'string') { + return includes(message, matcher); + } else if (matcher instanceof RegExp) { + return !!message.match(matcher); + } else if (matcher) { + throw new Error(`[ember-qunit] can only match Strings and RegExps. "${typeof matcher}" was provided.`); + } + + // No matcher always returns true. Makes the code easier elsewhere. + return true; +} diff --git a/addon-test-support/asserts/utils/to-assertion-message.js b/addon-test-support/assertions/utils/to-assertion-message.js similarity index 100% rename from addon-test-support/asserts/utils/to-assertion-message.js rename to addon-test-support/assertions/utils/to-assertion-message.js diff --git a/addon-test-support/asserts/no-depreactions.js b/addon-test-support/asserts/no-depreactions.js deleted file mode 100644 index bd72d1bf..00000000 --- a/addon-test-support/asserts/no-depreactions.js +++ /dev/null @@ -1,10 +0,0 @@ -import { getDeprecations } from '@ember/test-helpers'; -import toAssertionMessage from './utils/to-assertion-message'; - -export default function noDeprecations() { - this.deepEqual( - getDeprecations().map(toAssertionMessage), - [], - 'Expected no deprecations during test.' - ); -} diff --git a/addon-test-support/index.js b/addon-test-support/index.js index c99488a8..54dd988b 100644 --- a/addon-test-support/index.js +++ b/addon-test-support/index.js @@ -29,12 +29,21 @@ let waitForSettled = true; import deprecationsInclude from './asserts/deprecations-include'; import deprecations from './asserts/deprecations'; -import noDeprecations from './asserts/no-depreactions'; +import noDeprecations from './asserts/no-deprecations'; +import expectDeprecation from './asserts/expect-deprecation'; +import expectNoRunloop from './asserts/expect-no-runloop'; +import expectWarning from './asserts/expect-warning'; export function setup(assert) { + // TODO: decide which of these we should keep, which depreacte and which drop. assert.deprecationsInclude = deprecationsInclude; assert.deprecations = deprecations; + assert.expectNoDeprecations = noDeprecations; // compat assert.noDeprecations = noDeprecations; + assert.expectDeprecation = expectDeprecation; // compat + assert.expectNoRunloop = expectNoRunloop; // compat but fixed name + // around for compat + assert.exepectNoRunLoop = expectNoRunloop; // compat but wrong camelization } export function setupTest(hooks, _options) { diff --git a/tests/unit/assertions/assertion-test.js b/tests/unit/assertions/assertion-test.js new file mode 100644 index 00000000..57b0702c --- /dev/null +++ b/tests/unit/assertions/assertion-test.js @@ -0,0 +1,46 @@ +import { module, test } from 'qunit'; +import expectAssertion from 'ember-qunit/assertions/expect-assertion'; +import { assert as emberAssert } from '@ember/debug'; + +module('expectAssertion', function(hooks) { + let mockAssert; + + hooks.beforeEach(() => { + mockAssert = { + pushedResults: [], + expectAssertion + }; + }); + + test('called with assert', function(assert) { + mockAssert.expectAssertion(() => { + emberAssert('testing assert'); + }); + + assert.ok(mockAssert.pushedResults[0].result, '`expectAssertion` captured deprecation call'); + }); + + test('called without deprecation', function(assert) { + mockAssert.expectAssertion(() => { + emberAssert('testing assert', true); + }); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectAssertion` logged failed result'); + }); + + test('called with deprecation and matched assert', function(assert) { + mockAssert.expectAssertion(() => { + emberAssert('testing assert'); + }, /testing/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectAssertion` captured deprecation call'); + }); + + test('called with deprecation and unmatched assert', function(assert) { + mockAssert.expectAssertion(() => { + emberAssert('testing assert'); + }, /different/); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectAssertion` logged failed result'); + }); +}); diff --git a/tests/unit/assertions/expect-deprecation-test.js b/tests/unit/assertions/expect-deprecation-test.js new file mode 100644 index 00000000..9331a829 --- /dev/null +++ b/tests/unit/assertions/expect-deprecation-test.js @@ -0,0 +1,145 @@ +import { module, test } from 'qunit'; +import expectDeprecation from 'ember-qunit/assertions/expect-deprecation'; +import { deprecate } from '@ember/debug'; + +// ............................................................ +// Deprecation outside of a test. Should not cause test failures. +deprecate('Deprecation outside of a test', false, { id: 'deprecation-test', until: '3.0.0' }); +// ............................................................ + +module('expectDeprecation', function(hooks) { + let mockAssert; + + hooks.beforeEach(() => { + mockAssert = { + pushedResults: [], + expectDeprecation, + }; + }); + + test('expectDeprecation called after test and with deprecation', function(assert) { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + + mockAssert.expectDeprecation(); + + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` captured deprecation call'); + }); + + test('expectDeprecation called after test and without deprecation', function(assert) { + mockAssert.expectDeprecation(); + assert.notOk(mockAssert.pushedResults[0].result, '`expectDeprecation` logged failed result'); + }); + + test('expectDeprecation called with callback and with deprecation', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }); + + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` captured deprecation call'); + }); + + test('expectDeprecation called with callback and without deprecation', function(assert) { + mockAssert.expectDeprecation(() => { }); + assert.notOk(mockAssert.pushedResults[0].result, '`expectDeprecation` logged failed result'); + }); + + test('expectDeprecation called with callback and after test', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }); + + mockAssert.expectDeprecation(); + assert.ok(mockAssert.pushedResults[0].result, 'first `expectDeprecation` captured deprecation call'); + assert.notOk(mockAssert.pushedResults[1].result, 'second `expectDeprecation` logged failed result'); + }); + + test('expectDeprecation called after test, with matcher and matched deprecation', function(assert) { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + + mockAssert.expectDeprecation(/Something deprecated/); + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` captured deprecation call'); + }); + + test('expectDeprecation called after test, with matcher and unmatched deprecation', function(assert) { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + + mockAssert.expectDeprecation(/different deprecation/); + assert.notOk(mockAssert.pushedResults[0].result, '`expectDeprecation` logged failed result'); + }); + + test('expectDeprecation called with callback, matcher and matched deprecation', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }, /Something deprecated/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` captured deprecation call'); + }); + + test('expectDeprecation called with callback, matcher and unmatched deprecation', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }, /different deprecation/); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectDeprecation` logged failed result'); + }); + + test('expectNoDeprecation called after test and without deprecation', function(assert) { + assert.expectNoDeprecation(); + assert.ok(mockAssert.pushedResults[0].result, '`expectNoDeprecation` caught no deprecation'); + }); + + test('expectNoDeprecation called after test and with deprecation', function(assert) { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + + assert.expectNoDeprecation(); + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoDeprecation` caught logged failed result'); + }); + + test('expectNoDeprecation called with callback and with deprecation', function(assert) { + assert.expectNoDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoDeprecation` caught logged failed result'); + }); + + test('expectNoDeprecation called with callback and without deprecation', function(assert) { + assert.expectNoDeprecation(() => { }); + assert.ok(mockAssert.pushedResults[0].result, '`expectNoDeprecation` caught no deprecation'); + }); + + test('expectNoDeprecation called with callback and after test', function(assert) { + assert.expectNoDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }); + + assert.expectNoDeprecation(); + assert.notOk(mockAssert.pushedResults[0].result, 'first `expectNoDeprecation` caught logged failed result'); + assert.ok(mockAssert.pushedResults[1].result, 'second `expectNoDeprecation` caught no deprecation'); + }); + + test('expectDeprecation with regex matcher', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }, /Somethi[a-z ]*ecated/); + mockAssert.expectDeprecation(() => { + deprecate('/Something* deprecated/', false, { id: 'deprecation-test', until: '3.0.0' }); + }, /Something* deprecated/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` matched RegExp'); + assert.notOk(mockAssert.pushedResults[1].result, '`expectDeprecation` didn\'t RegExp as String match'); + }); + + test('expectDeprecation with string matcher', function(assert) { + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }, 'Something'); + + mockAssert.expectDeprecation(() => { + deprecate('Something deprecated', false, { id: 'deprecation-test', until: '3.0.0' }); + }, 'Something.*'); + + assert.ok(mockAssert.pushedResults[0].result, '`expectDeprecation` captured deprecation for partial String match'); + assert.notOk(mockAssert.pushedResults[1].result, '`expectDeprecation` didn\'t test a String match as RegExp'); + }); +}); diff --git a/tests/unit/assertions/runloop-test.js b/tests/unit/assertions/runloop-test.js new file mode 100644 index 00000000..f82e815c --- /dev/null +++ b/tests/unit/assertions/runloop-test.js @@ -0,0 +1,39 @@ +import { begin, end, later }from '@ember/runloop'; +import { test, module } from 'qunit'; +import expectNoRunloop from 'ember-qunit/assertions/expect-no-runloop'; + +module('expectNoRunLoop', function(hooks) { + let mockAssert; + hooks.beforeEach(function() { + mockAssert = { + pushedResults: [], + expectNoRunloop, + }; + }); + + test('in a run loop', function(assert) { + mockAssert.expectNoRunloop(); + assert.ok(mockAssert.pushedResults.length, 0, '`expectNoRunLoop` detected NO active runloop'); + + begin(); + + mockAssert.expectNoRunloop(); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoRunLoop` detected active runloop'); + assert.ok(mockAssert.pushedResults.length, 1, '`expectNoRunLoop` detected ONE active runloop'); + + end(); + assert.ok(mockAssert.pushedResults.length, 1, '`expectNoRunLoop` detected no new active runloop'); + }); + + test('`expectNoRunLoop` when timers are active', function(assert) { + later(() => { + assert.ok(false, 'should not execute'); + }); + + mockAssert.expectNoRunloop(); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoRunLoop` detected active runloop'); + assert.notOk(Ember.run.hasScheduledTimers(), 'ends run loop'); + }); +}); diff --git a/tests/unit/assertions/warning-test.js b/tests/unit/assertions/warning-test.js new file mode 100644 index 00000000..9963de80 --- /dev/null +++ b/tests/unit/assertions/warning-test.js @@ -0,0 +1,150 @@ +import { module, test } from 'qunit'; +import expectWarning from 'ember-qunit/assertions/expect-warning'; +import { warn } from '@ember/warn'; + +// ............................................................ +// Warning outside of a test. Should not cause test failures. +warn('Warning outside of a test', false, { id: 'warning-test', until: '3.0.0' }); +// ............................................................ + +module('expectWarning', function() { + let mockAssert; + + hooks.beforeEach(() => { + mockAssert = { + pushedResults: [], + expectWarning, + }; + }); + + test('expectWarning called after test and with warning', function(assert) { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + + mockAssert.expectWarning(); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` captured warning call'); + }); + + test('expectWarning called after test and without warning', function(assert) { + mockAssert.expectWarning(); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectWarning` logged failed result'); + }); + + test('expectWarning called with callback and with warning', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` captured warning call'); + }); + + test('expectWarning called with callback and without warning', function(assert) { + mockAssert.expectWarning(() => { }); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectWarning` logged failed result'); + }); + + test('expectWarning called with callback and after test', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }); + + mockAssert.expectWarning(); + + assert.ok(mockAssert.pushedResults[0].result, 'first `expectWarning` captured warning call'); + assert.notOk(mockAssert.pushedResults[1].result, 'second `expectWarning` logged failed result'); + }); + + test('expectWarning called after test, with matcher and matched warning', function(assert) { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + + mockAssert.expectWarning(/Something warned/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` captured warning call'); + }); + + test('expectWarning called after test, with matcher and unmatched warning', function(assert) { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + + mockAssert.expectWarning(/different warning/); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectWarning` logged failed result'); + }); + + test('expectWarning called with callback, matcher and matched warning', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }, /Something warned/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` captured warning call'); + }); + + test('expectWarning called with callback, matcher and unmatched warning', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }, /different warning/); + + assert.notOk(mockAssert.pushedResults[0].result, '`expectWarning` logged failed result'); + }); + + test('expectNoWarning called after test and without warning', function(assert) { + assert.expectNoWarning(); + assert.ok(mockAssert.pushedResults[0].result, '`expectNoWarning` caught no warning'); + }); + + test('expectNoWarning called after test and with warning', function(assert) { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + + assert.expectNoWarning(); + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoWarning` caught logged failed result'); + }); + + test('expectNoWarning called with callback and with warning', function(assert) { + assert.expectNoWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }); + assert.notOk(mockAssert.pushedResults[0].result, '`expectNoWarning` caught logged failed result'); + }); + + test('expectNoWarning called with callback and without warning', function(assert) { + assert.expectNoWarning(() => { }); + assert.ok(mockAssert.pushedResults[0].result, '`expectNoWarning` caught no warning'); + }); + + test('expectNoWarning called with callback and after test', function(assert) { + assert.expectNoWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }); + + assert.expectNoWarning(); + assert.notOk(mockAssert.pushedResults[0].result, 'first `expectNoWarning` caught logged failed result'); + assert.ok(mockAssert.pushedResults[1].result, 'second `expectNoWarning` caught no warning'); + }); + + test('expectWarning with regex matcher', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }, /Somethi[a-z ]*rned/); + + mockAssert.expectWarning(() => { + Ember.deprecate('/Something* warned/', false, { id: 'warning-test', until: '3.0.0' }); + }, /Something* warned/); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` matched RegExp'); + assert.notOk(mockAssert.pushedResults[1].result, '`expectWarning` didn\'t RegExp as String match'); + }); + + test('expectWarning with string matcher', function(assert) { + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }, 'Something'); + + mockAssert.expectWarning(() => { + warn('Something warned', false, { id: 'warning-test', until: '3.0.0' }); + }, 'Something.*'); + + assert.ok(mockAssert.pushedResults[0].result, '`expectWarning` captured warning for partial string match'); + assert.notOk(mockAssert.pushedResults[1].result, '`expectWarning` didn\'t test a string match as RegExp'); + }); +});