From 1007f9be5b9e72cd0cfea0c6246508e17f278136 Mon Sep 17 00:00:00 2001 From: Adi Cohen Date: Thu, 15 Oct 2020 00:53:53 +0300 Subject: [PATCH 01/64] feat(): #135 add after() hook --- README.md | 17 +++++++++++++ README_v4.md | 17 +++++++++++++ lib/test_runner.js | 4 +++ lib/test_suite.js | 13 ++++++++++ tests/core/test_suite_test.js | 20 ++++++++++++++- tests/examples/basic_features_test.js | 35 ++++++++++++++++++++++++++- testy.js | 6 ++++- 7 files changed, 109 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 505499d1..d9840bc5 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,23 @@ If you don't have a NPM project you can install testy globally using `npm instal }); }); ``` +* **Running code after every test**: just like many testing frameworks have, there is a way to execute some code after (cleanp) every test in a suite using the `after()` function. Example: + + ```javascript + const { suite, test, before, after, assert } = require('@pmoo/testy'); + + suite('using the after() helper', () => { + let answer; + + before(() => { + answer = 42; + }); + + after(() => { + answer = undefined; + }); + }); + ``` * **Support for pending tests**: if a test has no body, it will be reported as `[WIP]` and it won't be considered a failure. * **Fail-Fast mode**: if enabled, it stops execution in the first test that fails (or has an error). Remaining tests will be marked as skipped. * **Run tests and suites in random order**: a good test suite does not depend on a particular order. Enabling this setting is a good way to ensure that. diff --git a/README_v4.md b/README_v4.md index 1638307b..91472e5c 100644 --- a/README_v4.md +++ b/README_v4.md @@ -176,6 +176,23 @@ Please take a look at the `tests` folder, you'll find examples of each possible }); }); ``` +* **Running code after every test**: just like many testing frameworks have, there is a way to execute some code after (cleanp) every test in a suite using the `after()` function. Example: + + ```javascript + const { suite, test, before, after, assert } = require('@pmoo/testy'); + + suite('using the after() helper', () => { + let answer; + + before(() => { + answer = 42; + }); + + after(() => { + answer = undefined; + }); + }); + ``` * **Support for pending tests**: if a test has no body, it will be reported as `[WIP]` and it won't be considered a failure. * **Fail-Fast mode**: if enabled, it stops execution in the first test that fails (or has an error). Remaining tests will be marked as skipped. * **Run tests and suites in random order**: a good test suite does not depend on a particular order. Enabling this setting is a good way to ensure that. diff --git a/lib/test_runner.js b/lib/test_runner.js index db6037d1..9dc224a5 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -30,6 +30,10 @@ class TestRunner { this.currentSuite().before(beforeBlock); } + registerAfter(afterBlock) { + this.currentSuite().after(afterBlock); + } + addSuite(suiteToAdd) { this.suites().push(suiteToAdd); this._setCurrentSuite(suiteToAdd); diff --git a/lib/test_suite.js b/lib/test_suite.js index 08fe53c5..3c7b1dcb 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -10,6 +10,7 @@ class TestSuite { this._tests = []; this._callbacks = callbacks; this._before = undefined; + this._after = undefined; } // Initializing / Configuring @@ -25,6 +26,13 @@ class TestSuite { throw 'There is already a before() block. Please leave just one before() block and run again the tests.'; } + after(initialization) { + if (this._after === undefined) + this._after = initialization; + else + throw 'There is already a after() block. Please leave just one after() block and run again the tests.'; + } + // Executing run(failFastMode = FailFast.default(), randomOrderMode = false) { @@ -116,6 +124,10 @@ class TestSuite { this._before && this._before.call(); } + _evaluateAfterBlock() { + this._after && this._after.call(); + } + // TODO: reify configuration instead of many boolean flags _runTests(failFastMode, randomOrderMode) { if (randomOrderMode) { @@ -125,6 +137,7 @@ class TestSuite { this._currentTest = test; this._evaluateBeforeBlock(); test.run(failFastMode); + this._evaluateAfterBlock(); }); } } diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index 2a3213c3..f73387e5 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -1,6 +1,6 @@ 'use strict'; -const { suite, test, before, assert } = require('../../testy'); +const { suite, test, before, after, assert } = require('../../testy'); const TestSuite = require('../../lib/test_suite'); const { Asserter } = require('../../lib/asserter'); const TestRunner = require('../../lib/test_runner'); @@ -28,6 +28,16 @@ suite('test suite behavior', () => { erroredTest = anErroredTest(); pendingTest = aPendingTest(); }); + + after(() => { + runner = undefined; + asserter = undefined; + mySuite = undefined; + passingTest = undefined; + failingTest = undefined; + erroredTest = undefined; + pendingTest = undefined; + }); test('more than one before block is not allowed', () => { mySuite.before(() => 3 + 4); @@ -37,6 +47,14 @@ suite('test suite behavior', () => { .raises('There is already a before() block. Please leave just one before() block and run again the tests.'); }); + test('more than one after block is not allowed', () => { + mySuite.after(() => 3 + 4); + + assert + .that(() => mySuite.after(() => 5 + 6)) + .raises('There is already a after() block. Please leave just one after() block and run again the tests.'); + }); + test('reporting failures and errors', () => { mySuite.addTest(passingTest); mySuite.addTest(failingTest); diff --git a/tests/examples/basic_features_test.js b/tests/examples/basic_features_test.js index cbfe8bd8..a6d9e6d7 100644 --- a/tests/examples/basic_features_test.js +++ b/tests/examples/basic_features_test.js @@ -1,12 +1,17 @@ 'use strict'; -const { suite, test, before, assert } = require('../../testy'); +const { suite, test, before, after, assert } = require('../../testy'); +const TestSuite = require('../../lib/test_suite'); +const { Asserter } = require('../../lib/asserter'); +const TestRunner = require('../../lib/test_runner'); +const { aPassingTest } = require('../support/tests_factory'); suite('testing testy - basic features', () => { const circular = {}; circular.yourself = circular; let myVar = 8; before(() => myVar = 7); + after(() => myVar = undefined); test('tests with body', () => { const pepe = { nombre: 'pepe' }; @@ -14,7 +19,35 @@ suite('testing testy - basic features', () => { }); test('before hook can be used', () => assert.areEqual(myVar, 7)); + test('after hook can be used', () => { + const noop = () => {}; + const emptyRunnerCallbacks = { onFinish: noop }; + const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; + + const newEmptySuite = () => suiteNamed('myTestSuite'); + const suiteNamed = suiteName => new TestSuite(suiteName, () => {}, emptySuiteCallbacks); + + const asserter = new Asserter(runner); + const suite = newEmptySuite(); + const passingTest = aPassingTest(asserter); + + let afterTestVar = 10; + + const runner = new TestRunner(emptyRunnerCallbacks); + runner.addSuite(suite); + + suite.before(() => { + afterTestVar = 9; + }); + suite.after(() => { + afterTestVar = 0; + }); + suite.addTest(passingTest); + runner.run(); + assert.that(afterTestVar).isEqualTo(0); + }); + test('many assertions', () => { assert.areEqual(2, 1 + 1); assert.isTrue(true || false); diff --git a/testy.js b/testy.js index 1c2c5076..83f50e7e 100644 --- a/testy.js +++ b/testy.js @@ -24,6 +24,10 @@ function before(initialization) { testRunner.registerBefore(initialization); } +function after(initialization) { + testRunner.registerAfter(initialization); +} + class Testy { // instance creation @@ -90,4 +94,4 @@ class Testy { } } -module.exports = { Testy, suite, test, before, assert, fail, pending }; +module.exports = { Testy, suite, test, before, after, assert, fail, pending }; From 9ff6650a6af8250355810503286d82e7de10f881 Mon Sep 17 00:00:00 2001 From: Askar Imran Date: Sat, 17 Oct 2020 01:37:44 +1100 Subject: [PATCH 02/64] :bug: throw error when suite or test names are empty --- lib/test.js | 6 +++++- lib/test_suite.js | 6 +++++- lib/utils.js | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 9d7ad117..7b820a9e 100644 --- a/lib/test.js +++ b/lib/test.js @@ -2,7 +2,7 @@ const TestResult = require('./test_result'); const FailFast = require('./fail_fast'); -const { isString, isFunction, isUndefined } = require('./utils'); +const { isString, isStringNullOrWhiteSpace, isFunction, isUndefined } = require('./utils'); class Test { constructor(name, body, callbacks) { @@ -121,6 +121,10 @@ class Test { if (!isString(name)) { throw 'Test does not have a valid name'; } + + if(isStringNullOrWhiteSpace(name)) { + throw 'Suite and test names cannot be empty' + } } _ensureBodyIsValid(body) { diff --git a/lib/test_suite.js b/lib/test_suite.js index 08fe53c5..b799ebb2 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -1,7 +1,7 @@ 'use strict'; const FailFast = require('./fail_fast'); -const { shuffle, isString, isFunction } = require('./utils'); +const { shuffle, isString, isStringNullOrWhiteSpace, isFunction } = require('./utils'); class TestSuite { constructor(name, body, callbacks) { @@ -94,6 +94,10 @@ class TestSuite { if (!isString(name)) { throw 'Suite does not have a valid name'; } + + if(isStringNullOrWhiteSpace(name)) { + throw 'Suite and test names cannot be empty' + } } _ensureBodyIsValid(body) { diff --git a/lib/utils.js b/lib/utils.js index 4dbb4fc8..0bb83dfd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -72,6 +72,9 @@ const isUndefined = object => const notNullOrUndefined = object => !isUndefined(object) && object !== null; +const isStringNullOrWhiteSpace = string => + string.replace(/\s/g, '').length < 1; + const respondsTo = (object, methodName) => notNullOrUndefined(object) && isFunction(object[methodName]); @@ -95,6 +98,7 @@ module.exports = { prettyPrint, // types isString, + isStringNullOrWhiteSpace, isFunction, isUndefined, isRegex, From 04ea4d186a38731fc9ca994ccad617737fabfd19 Mon Sep 17 00:00:00 2001 From: Askar Imran Date: Sat, 17 Oct 2020 01:48:17 +1100 Subject: [PATCH 03/64] :bug: run eslint --- lib/test.js | 2 +- lib/test_suite.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 7b820a9e..c0b249cc 100644 --- a/lib/test.js +++ b/lib/test.js @@ -123,7 +123,7 @@ class Test { } if(isStringNullOrWhiteSpace(name)) { - throw 'Suite and test names cannot be empty' + throw 'Suite and test names cannot be empty'; } } diff --git a/lib/test_suite.js b/lib/test_suite.js index b799ebb2..8b3d556a 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -96,7 +96,7 @@ class TestSuite { } if(isStringNullOrWhiteSpace(name)) { - throw 'Suite and test names cannot be empty' + throw 'Suite and test names cannot be empty'; } } From 385fa98a9a3c8f4fc32891571ea3dfb10f5fe495 Mon Sep 17 00:00:00 2001 From: Askar Imran Date: Sat, 17 Oct 2020 14:10:04 +1100 Subject: [PATCH 04/64] :bug: add unit test for Test with name empty --- tests/core/test_test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/core/test_test.js b/tests/core/test_test.js index cf70ae11..2fe5e871 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -33,4 +33,8 @@ suite('tests behavior', () => { test('a test cannot be created with a body that is not a function', () => { assert.that(() => new Test('hey', 'ho')).raises('Test does not have a valid body'); }); + + test('a test cannot be created with name empty', () => { + assert.that(() => new Test('', undefined)).raises('Suite and test names cannot be empty'); + }); }); From ee6bfbd94834859a8e1be0e77cf41542449268a4 Mon Sep 17 00:00:00 2001 From: Askar Imran Date: Sat, 17 Oct 2020 14:42:54 +1100 Subject: [PATCH 05/64] =?UTF-8?q?=F0=9F=90=9B=20add=20unit=20test=20for=20?= =?UTF-8?q?suite=20with=20name=20empty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/core/test_suite_test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index 2a3213c3..5430d2b5 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -63,6 +63,10 @@ suite('test suite behavior', () => { test('a suite cannot be created without a name', () => { assert.that(() => new TestSuite()).raises('Suite does not have a valid name'); }); + + test('a suite cannot be created with name empty', () => { + assert.that(() => new TestSuite('')).raises('Suite and test names cannot be empty'); + }); test('a suite cannot be created with a name that is not a string', () => { assert.that(() => new TestSuite(new Date())).raises('Suite does not have a valid name'); From 7f0fb24d2dcd2a8e3b7679a57245b221acce53b5 Mon Sep 17 00:00:00 2001 From: Askar Imran Date: Sun, 18 Oct 2020 00:08:10 +1100 Subject: [PATCH 06/64] :sparkles: add new assertion - isIncludedIn and isNotIncludedIn --- lib/asserter.js | 14 ++++++++++++++ lib/translations.json | 2 ++ tests/examples/basic_assertions_test.js | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/lib/asserter.js b/lib/asserter.js index c9a24929..42a06bba 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -135,6 +135,13 @@ class Assertion extends TestResultReporter { this._reportAssertionResult(resultIsSuccessful, failureMessage); } + isIncludedIn(expectedCollection, equalityCriteria) { + const resultIsSuccessful = expectedCollection.find(element => + this._areConsideredEqual(element, this._actual, equalityCriteria)); + const failureMessage = `${this.translated('be_included_in')} ${Utils.prettyPrint(expectedCollection)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !this._actual.find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); @@ -142,6 +149,13 @@ class Assertion extends TestResultReporter { this._reportAssertionResult(resultIsSuccessful, failureMessage); } + isNotIncludedIn(expectedCollection, equalityCriteria) { + const resultIsSuccessful = !expectedCollection.find(element => + this._areConsideredEqual(element, this._actual, equalityCriteria)); + const failureMessage = `${this.translated('be_not_included_in')} ${Utils.prettyPrint(expectedCollection)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); const failureMessage = `${this.translated('include_exactly')} ${Utils.prettyPrint(objects)}`; diff --git a/lib/translations.json b/lib/translations.json index b6a264e4..619a586e 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -32,6 +32,8 @@ "be_null": "be null", "be_not_null": "be not null", "include": "include", + "be_included_in": "be included in", + "be_not_included_in": "be not included in", "not_include": "not include", "include_exactly": "include exactly", "be_empty": "be empty", diff --git a/tests/examples/basic_assertions_test.js b/tests/examples/basic_assertions_test.js index d47c5574..8fd4b721 100644 --- a/tests/examples/basic_assertions_test.js +++ b/tests/examples/basic_assertions_test.js @@ -26,8 +26,12 @@ suite('testing testy - basic assertions', () => { }); test('inclusion in collection', () => assert.that([1, 2, 3]).includes(2)); + + test('value is included in collection', () => assert.that(2).isIncludedIn([1, 2, 3])); test('not inclusion in collection', () => assert.that([1, 2, 3]).doesNotInclude(4)); + + test('value is not included in collection', () => assert.that(4).isNotIncludedIn([1, 2, 3])); test('inclusion of all elements in collection', () => { assert.that([1, 2, 3]).includesExactly(2, 3, 1); From 2b04bacd00037af27b0b078f4ed5574bbf671a3c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2020 00:06:28 +0000 Subject: [PATCH 07/64] docs: update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 505499d1..8fd2fda1 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Tomer Ben-Rachel

⚠️ 💻
Abraão Duarte

💻
adico

💻 +
Askar Imran

💻 ⚠️ From a64cc5765a987ed7182153ec73b209e9f35d45f0 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2020 00:06:29 +0000 Subject: [PATCH 08/64] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3427bd34..35cd4657 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -43,6 +43,16 @@ "contributions": [ "code" ] + }, + { + "login": "ask-imran", + "name": "Askar Imran", + "avatar_url": "https://avatars0.githubusercontent.com/u/20487103?v=4", + "profile": "https://github.com/ask-imran", + "contributions": [ + "code", + "test" + ] } ], "contributorsPerLine": 7, From a138c87807806b8a381565a3102f5fbb27b2cb86 Mon Sep 17 00:00:00 2001 From: Adi Cohen Date: Tue, 20 Oct 2020 23:06:45 +0300 Subject: [PATCH 09/64] feat(): #135 add after() hook --- README_v4.md | 17 -------------- lib/test_suite.js | 2 +- tests/core/test_suite_test.js | 16 +++++++++++++- tests/examples/basic_features_test.js | 32 --------------------------- 4 files changed, 16 insertions(+), 51 deletions(-) diff --git a/README_v4.md b/README_v4.md index 91472e5c..1638307b 100644 --- a/README_v4.md +++ b/README_v4.md @@ -176,23 +176,6 @@ Please take a look at the `tests` folder, you'll find examples of each possible }); }); ``` -* **Running code after every test**: just like many testing frameworks have, there is a way to execute some code after (cleanp) every test in a suite using the `after()` function. Example: - - ```javascript - const { suite, test, before, after, assert } = require('@pmoo/testy'); - - suite('using the after() helper', () => { - let answer; - - before(() => { - answer = 42; - }); - - after(() => { - answer = undefined; - }); - }); - ``` * **Support for pending tests**: if a test has no body, it will be reported as `[WIP]` and it won't be considered a failure. * **Fail-Fast mode**: if enabled, it stops execution in the first test that fails (or has an error). Remaining tests will be marked as skipped. * **Run tests and suites in random order**: a good test suite does not depend on a particular order. Enabling this setting is a good way to ensure that. diff --git a/lib/test_suite.js b/lib/test_suite.js index 3c7b1dcb..0a2a107b 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -30,7 +30,7 @@ class TestSuite { if (this._after === undefined) this._after = initialization; else - throw 'There is already a after() block. Please leave just one after() block and run again the tests.'; + throw 'There is already an after() block. Please leave just one after() block and run again the tests.'; } // Executing diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index f73387e5..d73f24c1 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -52,7 +52,21 @@ suite('test suite behavior', () => { assert .that(() => mySuite.after(() => 5 + 6)) - .raises('There is already a after() block. Please leave just one after() block and run again the tests.'); + .raises('There is already an after() block. Please leave just one after() block and run again the tests.'); + }); + + test('after hook can be used', () => { + let afterTestVar = 10; + + mySuite.before(() => { + afterTestVar = 9; + }); + mySuite.after(() => { + afterTestVar = 0; + }); + mySuite.addTest(passingTest); + runner.run(); + assert.that(afterTestVar).isEqualTo(0); }); test('reporting failures and errors', () => { diff --git a/tests/examples/basic_features_test.js b/tests/examples/basic_features_test.js index a6d9e6d7..b54a1d23 100644 --- a/tests/examples/basic_features_test.js +++ b/tests/examples/basic_features_test.js @@ -1,10 +1,6 @@ 'use strict'; const { suite, test, before, after, assert } = require('../../testy'); -const TestSuite = require('../../lib/test_suite'); -const { Asserter } = require('../../lib/asserter'); -const TestRunner = require('../../lib/test_runner'); -const { aPassingTest } = require('../support/tests_factory'); suite('testing testy - basic features', () => { const circular = {}; circular.yourself = circular; @@ -19,35 +15,7 @@ suite('testing testy - basic features', () => { }); test('before hook can be used', () => assert.areEqual(myVar, 7)); - test('after hook can be used', () => { - const noop = () => {}; - const emptyRunnerCallbacks = { onFinish: noop }; - const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; - - const newEmptySuite = () => suiteNamed('myTestSuite'); - const suiteNamed = suiteName => new TestSuite(suiteName, () => {}, emptySuiteCallbacks); - - const asserter = new Asserter(runner); - const suite = newEmptySuite(); - const passingTest = aPassingTest(asserter); - - let afterTestVar = 10; - - const runner = new TestRunner(emptyRunnerCallbacks); - runner.addSuite(suite); - - suite.before(() => { - afterTestVar = 9; - }); - suite.after(() => { - afterTestVar = 0; - }); - suite.addTest(passingTest); - runner.run(); - assert.that(afterTestVar).isEqualTo(0); - }); - test('many assertions', () => { assert.areEqual(2, 1 + 1); assert.isTrue(true || false); From eeaaf95bb6e1ecfe09290489441b74a6a00fc5fa Mon Sep 17 00:00:00 2001 From: niyonx Date: Tue, 20 Oct 2020 20:04:41 -0400 Subject: [PATCH 10/64] Warning message when no tests found. --- lib/configuration.js | 4 ++++ lib/console_ui.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/lib/configuration.js b/lib/configuration.js index c949a1a0..cf552f5d 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -34,6 +34,10 @@ class Configuration { filter() { return new RegExp(this._configurationOptions.filter); } + + filterRaw() { + return this._configurationOptions.filter; + } language() { return this._configurationOptions.language; diff --git a/lib/console_ui.js b/lib/console_ui.js index 007853ec..23d469d0 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -13,6 +13,7 @@ const yellow = '\x1b[33m'; class ConsoleUI { constructor() { this.useLanguage(I18n.defaultLanguage()); + this.filter = '' } // Callbacks for runner/suite/test @@ -93,6 +94,7 @@ class ConsoleUI { const failFast = this.translated('fail_fast'); const randomOrder = this.translated('random_order'); const padding = Math.max(testPaths.length, failFast.length, randomOrder.length); + this.filter = configuration.filterRaw(); console.log(`${testPaths.padEnd(padding)} : ${paths}`); console.log(`${failFast.padEnd(padding)} : ${this._humanBoolean(configuration.failFastMode().enabled())}`); console.log(`${randomOrder.padEnd(padding)} : ${this._humanBoolean(configuration.randomOrder())}`); @@ -121,6 +123,7 @@ class ConsoleUI { const pendingCount = this._displayIfNonZero(runner.pendingCount(), this.translated('pending')); const skippedCount = this._displayIfNonZero(runner.skippedCount(), this.translated('skipped'), yellow); console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); + if(runner.totalCount() == 0) {console.log(this._withColor(`\nWarning: Make sure your files matches the `+this.filter+` naming filter.`, yellow))}; } displayErrorsAndFailuresSummary(runner) { From 2cd6d7a3df44e908067e43c16c33efdd197477df Mon Sep 17 00:00:00 2001 From: niyonx Date: Tue, 20 Oct 2020 20:21:50 -0400 Subject: [PATCH 11/64] typo --- lib/console_ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/console_ui.js b/lib/console_ui.js index 23d469d0..2aa27a12 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -13,7 +13,7 @@ const yellow = '\x1b[33m'; class ConsoleUI { constructor() { this.useLanguage(I18n.defaultLanguage()); - this.filter = '' + this.filter = ''; } // Callbacks for runner/suite/test @@ -123,7 +123,7 @@ class ConsoleUI { const pendingCount = this._displayIfNonZero(runner.pendingCount(), this.translated('pending')); const skippedCount = this._displayIfNonZero(runner.skippedCount(), this.translated('skipped'), yellow); console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); - if(runner.totalCount() == 0) {console.log(this._withColor(`\nWarning: Make sure your files matches the `+this.filter+` naming filter.`, yellow))}; + if(runner.totalCount() === 0) {console.log(this._withColor(`\nWarning: Make sure your files matches the ${this.filter} naming filter.`, yellow));} } displayErrorsAndFailuresSummary(runner) { From b0470c4a35d76c4a180e7f4f281dae201a8a5d83 Mon Sep 17 00:00:00 2001 From: chelsieng Date: Tue, 20 Oct 2020 21:07:23 -0400 Subject: [PATCH 12/64] Issue #158 done --- testy.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/testy.js b/testy.js index 1c2c5076..eb6e82b9 100644 --- a/testy.js +++ b/testy.js @@ -12,6 +12,8 @@ const assert = new Asserter(testRunner); const fail = new FailureGenerator(testRunner); const pending = new PendingMarker(testRunner); +const red = '\x1b[31m'; + function test(name, testBody) { testRunner.registerTest(name, testBody, ui.testCallbacks()); } @@ -65,11 +67,16 @@ class Testy { } _loadAllRequestedFiles() { - this._resolvedTestFilesPathsToRun().forEach(path => - Utils.allFilesMatching(path, this._testFilesFilter()).forEach(file => - require(file) - ) - ); + try{ + this._resolvedTestFilesPathsToRun().forEach(path => + Utils.allFilesMatching(path, this._testFilesFilter()).forEach(file => + require(file) + ) + ); + }catch(err){ + console.log(`${red}Error: the requested file/folder does not exist.`); + process.exit(1); + } } _testFilesPathsToRun() { From 9899bb7e4cec561268c7056a3bdac017ddd962a7 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 02:58:18 +0000 Subject: [PATCH 13/64] docs: update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8fd2fda1..b5ccf93f 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Abraão Duarte

💻
adico

💻
Askar Imran

💻 ⚠️ +
Nigel Yong

💻 From 0ea636d576f6f90844d8217774ad8551a80fb785 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 02:58:19 +0000 Subject: [PATCH 14/64] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 35cd4657..90e4f765 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -53,6 +53,15 @@ "code", "test" ] + }, + { + "login": "niyonx", + "name": "Nigel Yong", + "avatar_url": "https://avatars2.githubusercontent.com/u/23243585?v=4", + "profile": "http://www.nigelyong.com/", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, From b4cd672c43bdb45bff6b2e826c19318f6fa60a16 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 03:01:41 +0000 Subject: [PATCH 15/64] docs: update README.md [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0debe5d8..02dd0b42 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Facundo Javier Gelatti

⚠️ 💻
Tomer Ben-Rachel

⚠️ 💻
Abraão Duarte

💻 -
adico

💻 +
adico

💻 ⚠️
Askar Imran

💻 ⚠️
Nigel Yong

💻 From c2b18f2c15acbc74252fdb2fbaccf5c3ab71e256 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 03:01:42 +0000 Subject: [PATCH 16/64] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 90e4f765..b121848a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -41,7 +41,8 @@ "avatar_url": "https://avatars0.githubusercontent.com/u/5412270?v=4", "profile": "http://adico.tech", "contributions": [ - "code" + "code", + "test" ] }, { From 2ce80afdb2ef60c5217a019f4485abc2bab1e821 Mon Sep 17 00:00:00 2001 From: chelsieng Date: Sat, 24 Oct 2020 01:55:22 -0400 Subject: [PATCH 17/64] Specified which file failed and used ui object to display error --- lib/console_ui.js | 4 ++++ testy.js | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/console_ui.js b/lib/console_ui.js index 007853ec..b4054e6c 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -113,6 +113,10 @@ class ConsoleUI { _displaySeparator(character = '=') { console.log(character.repeat(80)); } + + _displayError(text, color) { + console.log(`${this._withColor(text, color)}`); + } _displayCountFor(runner) { const passedCount = this._displayIfNonZero(runner.successCount(), this.translated('passed'), green); diff --git a/testy.js b/testy.js index eb6e82b9..36d1b9a9 100644 --- a/testy.js +++ b/testy.js @@ -68,13 +68,14 @@ class Testy { _loadAllRequestedFiles() { try{ + this._resolvedTestFilesPathsToRun().forEach(path => Utils.allFilesMatching(path, this._testFilesFilter()).forEach(file => require(file) ) ); }catch(err){ - console.log(`${red}Error: the requested file/folder does not exist.`); + ui._displayError(`Error: ${err.path} does not exist.`, red); process.exit(1); } } @@ -84,8 +85,9 @@ class Testy { return requestedPaths.length > 0 ? requestedPaths : [this._pathForAllTests()]; } - _resolvedTestFilesPathsToRun() { + _resolvedTestFilesPathsToRun(){ return this._testFilesPathsToRun().map(path => Utils.resolvePathFor(path)); + } _pathForAllTests() { From d346a0280636cb827652a36ade7f211c25eb1ade Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2020 18:47:38 +0000 Subject: [PATCH 18/64] docs: update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 02dd0b42..9b750ab4 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
adico

💻 ⚠️
Askar Imran

💻 ⚠️
Nigel Yong

💻 +
Chelsie Ng

💻 From 813f966ebe621236a875506284aab7d4d5bea2ea Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2020 18:47:39 +0000 Subject: [PATCH 19/64] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index b121848a..11b10849 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -63,6 +63,15 @@ "contributions": [ "code" ] + }, + { + "login": "chelsieng", + "name": "Chelsie Ng", + "avatar_url": "https://avatars1.githubusercontent.com/u/60008262?v=4", + "profile": "https://github.com/chelsieng", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, From 04309b1386e2ab6c508de52334d96dbd01369033 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sun, 2 Aug 2020 12:16:00 -0300 Subject: [PATCH 20/64] :construction_worker: add more eslint rules and fix offenses --- .eslintrc.json | 14 +++++++++----- .github/workflows/nodejs.yml | 2 -- lib/test_runner.js | 2 +- .../core/assertions/collection_assertions_test.js | 2 +- tests/examples/basic_assertions_test.js | 6 +++--- tests/examples/basic_features_test.js | 14 +++++++------- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8fb7f00e..ab861cc0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,23 +6,27 @@ }, "extends": "eslint:recommended", "env": { - "es6": true, "browser": false, + "es6": true, "node": true }, "rules": { - "semi": 2, + "arrow-spacing": "warn", "block-scoped-var": "error", "dot-notation": "error", + "dot-location": ["error", "property"], "eqeqeq": "error", "no-eval": "error", "no-multi-spaces": "error", "no-param-reassign": "error", "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "no-var": "error", + "object-curly-spacing": ["warn", "always"], "prefer-const": "error", - "arrow-spacing": "warn", - "dot-location": ["error", "property"], - "prefer-template": "warn" + "prefer-template": "warn", + "quotes": [2, "single", "avoid-escape"], + "semi": 2, + "space-before-function-paren": ["error", "never"], + "space-infix-ops": "error" } } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index deda84d4..8d5025f0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -5,11 +5,9 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest - strategy: matrix: node-version: [8.x, 10.x, 12.x, 14.x] - steps: - name: Checkout repo uses: actions/checkout@v1 diff --git a/lib/test_runner.js b/lib/test_runner.js index 9dc224a5..5d2de797 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -2,7 +2,7 @@ const Test = require('./test'); const TestSuite = require('./test_suite'); -const FailFast = require("./fail_fast"); +const FailFast = require('./fail_fast'); const I18n = require('./i18n'); const { shuffle } = require('./utils'); diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index f8439925..471d870a 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -119,7 +119,7 @@ suite('collection assertions', () => { test('isNotEmpty does not pass if the array is empty', () => { asserter.that([]).isNotEmpty(); - expectFailureDueTo("Expected [] to be not empty"); + expectFailureDueTo('Expected [] to be not empty'); }); test('isNotEmpty passes with a string with content', () => { diff --git a/tests/examples/basic_assertions_test.js b/tests/examples/basic_assertions_test.js index 8fd4b721..43471281 100644 --- a/tests/examples/basic_assertions_test.js +++ b/tests/examples/basic_assertions_test.js @@ -37,17 +37,17 @@ suite('testing testy - basic assertions', () => { assert.that([1, 2, 3]).includesExactly(2, 3, 1); }); - test('error checking', () => assert.that(() => { throw 'hey!'; }).raises("hey!")); + test('error checking', () => assert.that(() => { throw 'hey!'; }).raises('hey!')); // commented so CI can pass - uncomment to see the failure // test('tests can fail as well :)', () => assert.that(() => { throw 'hey!'; }).raises("ho!")); test('no specific error happened', () => - assert.that(emptyFunction).doesNotRaise("hey!") + assert.that(emptyFunction).doesNotRaise('hey!') ); test('testing that no specific error happened - even if other error occurs', () => - assert.that(() => { throw "ho!"; }).doesNotRaise("hey!") + assert.that(() => { throw 'ho!'; }).doesNotRaise('hey!') ); test('testing that no error happens at all', () => diff --git a/tests/examples/basic_features_test.js b/tests/examples/basic_features_test.js index b54a1d23..4f0e76db 100644 --- a/tests/examples/basic_features_test.js +++ b/tests/examples/basic_features_test.js @@ -24,23 +24,23 @@ suite('testing testy - basic features', () => { // commented so CI can pass - uncomment to see the failure // test("unexpected errors don't break the suite", () => assert.isTrue(notAFunction())); - test("successful test after the failure", () => assert.isTrue(true)); + test('successful test after the failure', () => assert.isTrue(true)); - test("custom equality check", () => { + test('custom equality check', () => { const criteria = (o1, o2) => o1.a === o2.a; - assert.areEqual({ a: 'a', b: 'b1'}, { a: 'a', b: 'b2'}, criteria); - assert.that({ a: 'a', b: 'b1'}).isEqualTo({ a: 'a', b: 'b2'}, criteria); - assert.that({ a: 'a', b: 'b1'}).isNotEqualTo({ a: 'a2', b: 'b1'}, criteria); + assert.areEqual({ a: 'a', b: 'b1' }, { a: 'a', b: 'b2' }, criteria); + assert.that({ a: 'a', b: 'b1' }).isEqualTo({ a: 'a', b: 'b2' }, criteria); + assert.that({ a: 'a', b: 'b1' }).isNotEqualTo({ a: 'a2', b: 'b1' }, criteria); }); - test("equality check when objects understand equals()", () => { + test('equality check when objects understand equals()', () => { const objectOne = { a: 'a', b: 'b1', equals: function(another) { return this.a === another.a; } }; const objectTwo = { a: 'a', b: 'b2', equals: function(another) { return this.b === another.b; } }; assert.that(objectOne).isEqualTo(objectTwo); assert.that(objectTwo).isNotEqualTo(objectOne); }); - test("equality check using custom message name", () => { + test('equality check using custom message name', () => { const objectOne = { a: 'a', b: 'b1', sameAs: function(another) { return this.a === another.a; } }; const objectTwo = { a: 'a', b: 'b2', sameAs: function(another) { return this.b === another.b; } }; assert.that(objectOne).isEqualTo(objectTwo, 'sameAs'); From d014ac50f59b6d98ada55d392e1278e77441666d Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sun, 25 Oct 2020 01:43:31 -0300 Subject: [PATCH 21/64] :construction: add more eslint configurations --- .eslintrc.json | 16 ++++++ lib/asserter.js | 49 +++++++++++-------- lib/configuration.js | 2 +- lib/console_ui.js | 7 ++- lib/equality_assertion_strategy.js | 16 +++--- lib/i18n.js | 4 +- lib/test.js | 10 ++-- lib/test_runner.js | 8 +-- lib/test_suite.js | 23 ++++----- lib/utils.js | 8 ++- package.json | 1 + .../assertions/equality_assertions_test.js | 16 ++++-- .../assertions/exception_assertions_test.js | 20 ++++++-- tests/core/i18n_test.js | 14 +++--- tests/examples/basic_assertions_test.js | 8 ++- tests/examples/basic_features_test.js | 16 ++++-- tests/support/tests_factory.js | 8 ++- tests/utils_test.js | 8 +-- testy.js | 12 ++--- 19 files changed, 158 insertions(+), 88 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index ab861cc0..3e20a0a0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,12 +12,28 @@ }, "rules": { "arrow-spacing": "warn", + "block-spacing": "warn", "block-scoped-var": "error", + "brace-style": "warn", + "camelcase": "warn", + "comma-spacing": "warn", + "constructor-super": "error", + "curly": "error", "dot-notation": "error", "dot-location": ["error", "property"], "eqeqeq": "error", + "eol-last": "error", + "func-call-spacing": "error", + "indent": ["error", 2], + "keyword-spacing": "error", + "new-cap": "error", + "new-parens": "error", + "no-cond-assign": "error", "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", "no-multi-spaces": "error", + "no-multiple-empty-lines": "error", "no-param-reassign": "error", "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "no-var": "error", diff --git a/lib/asserter.js b/lib/asserter.js index 42a06bba..ae2379ff 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -1,6 +1,6 @@ 'use strict'; -const Utils = require('./utils'); +const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements } = require('./utils'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); @@ -131,40 +131,39 @@ class Assertion extends TestResultReporter { includes(expectedObject, equalityCriteria) { const resultIsSuccessful = this._actual.find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('include')} ${Utils.prettyPrint(expectedObject)}`; + const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); } isIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_included_in')} ${Utils.prettyPrint(expectedCollection)}`; + const failureMessage = `${this.translated('be_included_in')} ${prettyPrint(expectedCollection)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); } doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !this._actual.find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('not_include')} ${Utils.prettyPrint(expectedObject)}`; + const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = !expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_not_included_in')} ${Utils.prettyPrint(expectedCollection)}`; + const failureMessage = `${this.translated('be_not_included_in')} ${prettyPrint(expectedCollection)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); } includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = `${this.translated('include_exactly')} ${Utils.prettyPrint(objects)}`; + const failureMessage = `${this.translated('include_exactly')} ${prettyPrint(objects)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); } isEmpty() { - const resultIsSuccessful = Utils.numberOfElements(this._actual || {}) === 0 - && Utils.notNullOrUndefined(this._actual); + const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); const failureMessage = this.translated('be_empty'); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -172,7 +171,7 @@ class Assertion extends TestResultReporter { isNotEmpty() { const setValueWhenUndefined = this._actual || {}; - const resultIsSuccessful = Utils.numberOfElements(setValueWhenUndefined) > 0; + const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; const failureMessage = this.translated('be_not_empty'); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -195,7 +194,7 @@ class Assertion extends TestResultReporter { noErrorsOccurred = true; } catch (error) { noErrorsOccurred = false; - failureMessage = `${this.translated('expected_no_errors')}, ${this.translated('but')} ${Utils.prettyPrint(error)} ${this.translated('was_raised')}`; + failureMessage = `${this.translated('expected_no_errors')}, ${this.translated('but')} ${prettyPrint(error)} ${this.translated('was_raised')}`; } finally { this._reportAssertionResult(noErrorsOccurred, failureMessage, true); } @@ -228,7 +227,7 @@ class Assertion extends TestResultReporter { this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage, true); } else { const expectationMessage = shouldBeEqual ? this.translated('be_equal_to') : this.translated('be_not_equal_to'); - const failureMessage = `${expectationMessage} ${Utils.prettyPrint(expected)}${additionalFailureMessage}`; + const failureMessage = `${expectationMessage} ${prettyPrint(expected)}${additionalFailureMessage}`; this._reportAssertionResult(resultIsSuccessful, failureMessage, false); } } @@ -243,12 +242,12 @@ class Assertion extends TestResultReporter { } _undefinedAssertion(failureMessage) { - const resultIsSuccessful = this._actual === undefined; + const resultIsSuccessful = isUndefined(this._actual); this._reportAssertionResult(resultIsSuccessful, failureMessage); } _notUndefinedAssertion(failureMessage) { - const resultIsSuccessful = this._actual !== undefined; + const resultIsSuccessful = !isUndefined(this._actual); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -264,7 +263,7 @@ class Assertion extends TestResultReporter { _exceptionAssertion(errorExpectation, shouldFail) { let hasFailed = false; - let actualError = undefined; + let actualError; try { this._actual(); hasFailed = !shouldFail; @@ -274,15 +273,19 @@ class Assertion extends TestResultReporter { hasFailed = shouldFail ? errorCheck : !errorCheck; } finally { const toHappenOrNot = shouldFail ? this.translated('to_happen') : this.translated('not_to_happen'); - const expectedErrorIntroduction = `${this.translated('expected')} ${this.translated('expecting_error')} ${Utils.prettyPrint(errorExpectation)} ${toHappenOrNot}`; - const failureMessage = typeof actualError !== 'undefined' ? - `${expectedErrorIntroduction}, ${this.translated('but_got')} ${Utils.prettyPrint(actualError)} ${this.translated('instead')}` : expectedErrorIntroduction; + const expectedErrorIntroduction = `${this.translated('expected')} ${this.translated('expecting_error')} ${prettyPrint(errorExpectation)} ${toHappenOrNot}`; + let failureMessage; + if (isUndefined(actualError)) { + failureMessage = expectedErrorIntroduction; + } else { + failureMessage = `${expectedErrorIntroduction}, ${this.translated('but_got')} ${prettyPrint(actualError)} ${this.translated('instead')}`; + } this._reportAssertionResult(hasFailed, failureMessage, true); } } _checkIfErrorMatchesExpectation(errorExpectation, actualError) { - if (Utils.isRegex(errorExpectation)) { + if (isRegex(errorExpectation)) { return errorExpectation.test(actualError); } else { return this._areConsideredEqual(actualError, errorExpectation); @@ -299,19 +302,23 @@ class Assertion extends TestResultReporter { } _actualResultAsString() { - return Utils.prettyPrint(this._actual); + return prettyPrint(this._actual); } _haveElementsConsideredEqual(collectionOne, collectionTwo) { const collectionOneArray = Array.from(collectionOne); const collectionTwoArray = Array.from(collectionTwo); - if (collectionOneArray.length !== collectionTwoArray.length) return false; + if (collectionOneArray.length !== collectionTwoArray.length) { + return false; + } for (let i = 0; i < collectionOne.length; i++) { const includedInOne = collectionOne.find(element => this._areConsideredEqual(element, collectionTwoArray[i])); const includedInTwo = collectionTwo.find(element => this._areConsideredEqual(element, collectionOneArray[i])); - if (!includedInOne || !includedInTwo) return false; + if (!includedInOne || !includedInTwo) { + return false; + } } return true; } diff --git a/lib/configuration.js b/lib/configuration.js index cf552f5d..472ae785 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -12,7 +12,7 @@ class Configuration { let userConfiguration; try { userConfiguration = require(resolvePathFor(CONFIGURATION_FILE_NAME)); - } catch(error) { + } catch (error) { userConfiguration = {}; } const defaultConfiguration = require('./default_configuration'); diff --git a/lib/console_ui.js b/lib/console_ui.js index 13ca6403..77e48dc8 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -22,8 +22,9 @@ class ConsoleUI { return { whenPending: test => { this._displayResult(this.translated('wip'), test, yellow); - if (test.isExplicitlyMarkedPending()) + if (test.isExplicitlyMarkedPending()) { this._displayResultDetail(test.result().reason()); + } }, whenSkipped: test => { this._displayResult(this.translated('skip'), test, grey); @@ -127,7 +128,9 @@ class ConsoleUI { const pendingCount = this._displayIfNonZero(runner.pendingCount(), this.translated('pending')); const skippedCount = this._displayIfNonZero(runner.skippedCount(), this.translated('skipped'), yellow); console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); - if(runner.totalCount() === 0) {console.log(this._withColor(`\nWarning: Make sure your files matches the ${this.filter} naming filter.`, yellow));} + if (runner.totalCount() === 0) { + console.log(this._withColor(`\nWarning: Make sure your files matches the ${this.filter} naming filter.`, yellow)); + } } displayErrorsAndFailuresSummary(runner) { diff --git a/lib/equality_assertion_strategy.js b/lib/equality_assertion_strategy.js index 19cbff00..31b4f220 100644 --- a/lib/equality_assertion_strategy.js +++ b/lib/equality_assertion_strategy.js @@ -1,6 +1,6 @@ 'use strict'; -const Utils = require('./utils'); +const { isString, isFunction, isUndefined, respondsTo, isCyclic, deepStrictEqual } = require('./utils'); const EqualityAssertionStrategy = { availableStrategies() { @@ -25,7 +25,7 @@ const BothPartsUndefined = { __proto__: EqualityAssertionStrategy, appliesTo(actual, expected) { - return actual === undefined && expected === undefined; + return isUndefined(actual) && isUndefined(expected); }, evaluate() { @@ -40,7 +40,7 @@ const CustomFunction = { __proto__: EqualityAssertionStrategy, appliesTo(actual, expected, criteria) { - return Utils.isFunction(criteria); + return isFunction(criteria); }, evaluate(actual, expected, criteria) { @@ -55,7 +55,7 @@ const CustomPropertyName = { __proto__: EqualityAssertionStrategy, appliesTo(actual, expected, criteria) { - return Utils.isString(criteria); + return isString(criteria); }, evaluate(actual, expected, criteria) { @@ -67,7 +67,7 @@ const CustomPropertyName = { }, _comparisonCanBeMade(actual, expected, criteria) { - return Utils.respondsTo(actual, criteria) && Utils.respondsTo(expected, criteria); + return respondsTo(actual, criteria) && respondsTo(expected, criteria); }, _compareUsingCustomCriteria(actual, expected, criteria) { @@ -89,7 +89,7 @@ const ObjectWithEqualsProperty = { __proto__: EqualityAssertionStrategy, appliesTo(actual, _expected) { - return Utils.respondsTo(actual, 'equals'); + return respondsTo(actual, 'equals'); }, evaluate(actual, expected) { @@ -104,7 +104,7 @@ const ObjectWithCyclicReference = { __proto__: EqualityAssertionStrategy, appliesTo(actual, expected) { - return Utils.isCyclic(actual) || Utils.isCyclic(expected); + return isCyclic(actual) || isCyclic(expected); }, evaluate(_actual, _expected) { @@ -124,7 +124,7 @@ const DefaultEquality = { evaluate(actual, expected) { return { - comparisonResult: Utils.deepStrictEqual(actual, expected), + comparisonResult: deepStrictEqual(actual, expected), additionalFailureMessage: '' }; }, diff --git a/lib/i18n.js b/lib/i18n.js index da4d6cfb..7ec063f1 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -1,6 +1,7 @@ 'use strict'; const TRANSLATIONS = require('./translations'); +const { isUndefined } = require('./utils'); class I18n { static defaultLanguage() { @@ -15,8 +16,9 @@ class I18n { translate(key) { const languageTranslations = this.translationsForCurrentLanguage(); const translatedText = languageTranslations[key] || this.defaultTranslationFor(key); - if (translatedText === undefined) + if (isUndefined(translatedText)) { throw this.keyNotFoundMessage(key); + } return translatedText; } diff --git a/lib/test.js b/lib/test.js index c0b249cc..2646cac3 100644 --- a/lib/test.js +++ b/lib/test.js @@ -37,8 +37,9 @@ class Test { } setResult(result) { - if(this.hasNoResult() || this.isSuccess()) + if (this.hasNoResult() || this.isSuccess()) { this._result = result; + } } finishWithSuccess() { @@ -60,11 +61,11 @@ class Test { // Testing hasDefinition() { - return this.body() !== undefined; + return !isUndefined(this.body()); } hasNoResult() { - return this.result() === undefined; + return isUndefined(this.result()); } isSuccess() { @@ -121,8 +122,7 @@ class Test { if (!isString(name)) { throw 'Test does not have a valid name'; } - - if(isStringNullOrWhiteSpace(name)) { + if (isStringNullOrWhiteSpace(name)) { throw 'Suite and test names cannot be empty'; } } diff --git a/lib/test_runner.js b/lib/test_runner.js index 5d2de797..897d9713 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -68,9 +68,11 @@ class TestRunner { } finish(callbacks) { - return this._considerResultAsSucceeded() - ? callbacks.success() - : callbacks.failure(); + if (this._considerResultAsSucceeded()) { + return callbacks.success(); + } else { + return callbacks.failure(); + } } // Counting diff --git a/lib/test_suite.js b/lib/test_suite.js index fa40028d..aac12de1 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -1,7 +1,7 @@ 'use strict'; const FailFast = require('./fail_fast'); -const { shuffle, isString, isStringNullOrWhiteSpace, isFunction } = require('./utils'); +const { shuffle, isString, isUndefined, isStringNullOrWhiteSpace, isFunction } = require('./utils'); class TestSuite { constructor(name, body, callbacks) { @@ -19,18 +19,20 @@ class TestSuite { this._tests.push(test); } - before(initialization) { - if (this._before === undefined) - this._before = initialization; - else + before(initializationBlock) { + if (isUndefined(this._before)) { + this._before = initializationBlock; + } else { throw 'There is already a before() block. Please leave just one before() block and run again the tests.'; + } } - after(initialization) { - if (this._after === undefined) - this._after = initialization; - else + after(releasingBlock) { + if (isUndefined(this._after)) { + this._after = releasingBlock; + } else { throw 'There is already an after() block. Please leave just one after() block and run again the tests.'; + } } // Executing @@ -102,8 +104,7 @@ class TestSuite { if (!isString(name)) { throw 'Suite does not have a valid name'; } - - if(isStringNullOrWhiteSpace(name)) { + if (isStringNullOrWhiteSpace(name)) { throw 'Suite and test names cannot be empty'; } } diff --git a/lib/utils.js b/lib/utils.js index 0bb83dfd..ff72e6c6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,12 +6,16 @@ const path = require('path'); const assert = require('assert'); const isCyclic = object => { - if (object === null) { return false; } + if (object === null) { + return false; + } const seenObjects = []; function detect(obj) { if (typeof obj === 'object') { - if (seenObjects.includes(obj)) return true; + if (seenObjects.includes(obj)) { + return true; + } seenObjects.push(obj); return !!Object.keys(obj).find(key => detect(obj[key])); } diff --git a/package.json b/package.json index c6dededa..3cfb0bf7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "scripts": { "coverage": "npx nyc@latest --reporter=lcov --reporter=text-summary npm test", "lint": "npx eslint@6.8.0 .", + "lint-fix": "npx eslint@6.8.0 . --fix", "test": "bin/testy_cli.js" }, "bin": "bin/testy_cli.js", diff --git a/tests/core/assertions/equality_assertions_test.js b/tests/core/assertions/equality_assertions_test.js index 1a164247..14530a3d 100644 --- a/tests/core/assertions/equality_assertions_test.js +++ b/tests/core/assertions/equality_assertions_test.js @@ -84,8 +84,12 @@ suite('equality assertions', () => { test('isEqualTo with custom criteria passes if the criteria evaluates to true, and we are comparing instances of the same class', () => { class AClass { - constructor(a) { this.a = a; } - myEqualMessage() { return true; } + constructor(a) { + this.a = a; + } + myEqualMessage() { + return true; + } } const objectOne = new AClass(); const objectTwo = new AClass(); @@ -96,8 +100,12 @@ suite('equality assertions', () => { test('isEqualTo with equals() default criteria passes if it evaluates to true, and we are comparing instances of the same class', () => { class AClass { - constructor(a) { this.a = a; } - equals() { return true; } + constructor(a) { + this.a = a; + } + equals() { + return true; + } } const objectOne = new AClass(1); const objectTwo = new AClass(2); diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index fa36cdfe..f60c44bf 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -5,31 +5,41 @@ const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/a suite('exception assertions', () => { test('raises() can receive a string and it passes when the exact string is expected', () => { - asserter.that(() => { throw 'an error happened'; }).raises('an error happened'); + asserter.that(() => { + throw 'an error happened'; + }).raises('an error happened'); expectSuccess(); }); test('raises() can receive a regex and it passes when it matches the thrown string', () => { - asserter.that(() => { throw 'an error happened'; }).raises(/error/); + asserter.that(() => { + throw 'an error happened'; + }).raises(/error/); expectSuccess(); }); test('raises() can receive an arbitrary object and it passes when the exact object is expected', () => { - asserter.that(() => { throw { an: 'object' }; }).raises({ an: 'object' }); + asserter.that(() => { + throw { an: 'object' }; + }).raises({ an: 'object' }); expectSuccess(); }); test('raises() can receive a regex and it passes when it matches the thrown error with message', () => { - asserter.that(() => { throw new TypeError('things happened'); }).raises(/happened/); + asserter.that(() => { + throw new TypeError('things happened'); + }).raises(/happened/); expectSuccess(); }); test('raises() can receive a regex and it fails if there is not a match in the error message', () => { - asserter.that(() => { throw 'a terrible error'; }).raises(/happiness/); + asserter.that(() => { + throw 'a terrible error'; + }).raises(/happiness/); expectFailureDueTo('Expected error /happiness/ to happen, but got \'a terrible error\' instead'); }); diff --git a/tests/core/i18n_test.js b/tests/core/i18n_test.js index 4b1e6b83..9449a668 100644 --- a/tests/core/i18n_test.js +++ b/tests/core/i18n_test.js @@ -9,21 +9,21 @@ suite('i18n', () => { }); test('translating a existing message in the default language', () => { - const translations = { en: { a_key: 'a text' } }; + const translations = { en: { aKey: 'a text' } }; const i18n = new I18n('en', translations); - assert.areEqual(i18n.translate('a_key'), 'a text'); + assert.areEqual(i18n.translate('aKey'), 'a text'); }); test('translating a existing message in another language', () => { - const translations = { es: { a_key: 'un texto' } }; + const translations = { es: { aKey: 'un texto' } }; const i18n = new I18n('es', translations); - assert.areEqual(i18n.translate('a_key'), 'un texto'); + assert.areEqual(i18n.translate('aKey'), 'un texto'); }); test('an error is raised if the key is not found in the default language', () => { - const translations = { en: { a_key: 'un texto' } }; + const translations = { en: { aKey: 'un texto' } }; const i18n = new I18n('en', translations); assert @@ -32,9 +32,9 @@ suite('i18n', () => { }); test('falls back to default language if the key is not found in the given language', () => { - const translations = { en: { a_key: 'a text' } }; + const translations = { en: { aKey: 'a text' } }; const i18n = new I18n('es', translations); - assert.areEqual(i18n.translate('a_key'), 'a text'); + assert.areEqual(i18n.translate('aKey'), 'a text'); }); }); diff --git a/tests/examples/basic_assertions_test.js b/tests/examples/basic_assertions_test.js index 43471281..487f528c 100644 --- a/tests/examples/basic_assertions_test.js +++ b/tests/examples/basic_assertions_test.js @@ -37,7 +37,9 @@ suite('testing testy - basic assertions', () => { assert.that([1, 2, 3]).includesExactly(2, 3, 1); }); - test('error checking', () => assert.that(() => { throw 'hey!'; }).raises('hey!')); + test('error checking', () => assert.that(() => { + throw 'hey!'; + }).raises('hey!')); // commented so CI can pass - uncomment to see the failure // test('tests can fail as well :)', () => assert.that(() => { throw 'hey!'; }).raises("ho!")); @@ -47,7 +49,9 @@ suite('testing testy - basic assertions', () => { ); test('testing that no specific error happened - even if other error occurs', () => - assert.that(() => { throw 'ho!'; }).doesNotRaise('hey!') + assert.that(() => { + throw 'ho!'; + }).doesNotRaise('hey!') ); test('testing that no error happens at all', () => diff --git a/tests/examples/basic_features_test.js b/tests/examples/basic_features_test.js index 4f0e76db..7f4c9da6 100644 --- a/tests/examples/basic_features_test.js +++ b/tests/examples/basic_features_test.js @@ -34,15 +34,23 @@ suite('testing testy - basic features', () => { }); test('equality check when objects understand equals()', () => { - const objectOne = { a: 'a', b: 'b1', equals: function(another) { return this.a === another.a; } }; - const objectTwo = { a: 'a', b: 'b2', equals: function(another) { return this.b === another.b; } }; + const objectOne = { a: 'a', b: 'b1', equals: function(another) { + return this.a === another.a; + } }; + const objectTwo = { a: 'a', b: 'b2', equals: function(another) { + return this.b === another.b; + } }; assert.that(objectOne).isEqualTo(objectTwo); assert.that(objectTwo).isNotEqualTo(objectOne); }); test('equality check using custom message name', () => { - const objectOne = { a: 'a', b: 'b1', sameAs: function(another) { return this.a === another.a; } }; - const objectTwo = { a: 'a', b: 'b2', sameAs: function(another) { return this.b === another.b; } }; + const objectOne = { a: 'a', b: 'b1', sameAs: function(another) { + return this.a === another.a; + } }; + const objectTwo = { a: 'a', b: 'b2', sameAs: function(another) { + return this.b === another.b; + } }; assert.that(objectOne).isEqualTo(objectTwo, 'sameAs'); assert.that(objectTwo).isNotEqualTo(objectOne, 'sameAs'); }); diff --git a/tests/support/tests_factory.js b/tests/support/tests_factory.js index 107f4b7f..c7bbaa34 100644 --- a/tests/support/tests_factory.js +++ b/tests/support/tests_factory.js @@ -18,13 +18,17 @@ const aFailingTest = asserter => new Test('a true failure', () => asserter.isFalse(true), emptyTestCallbacks); const anErroredTest = () => - new Test('an unexpected error', () => { throw 'oops'; }, emptyTestCallbacks); + new Test('an unexpected error', () => { + throw 'oops'; + }, emptyTestCallbacks); const aPendingTest = () => new Test('a work in progress', undefined, emptyTestCallbacks); const aTestWithNoAssertions = () => - new Test('wrong', () => { return 1 + 2; }, emptyTestCallbacks); + new Test('wrong', () => { + return 1 + 2; + }, emptyTestCallbacks); module.exports = { aPassingTest, diff --git a/tests/utils_test.js b/tests/utils_test.js index 0aaab1cd..586ccd3b 100644 --- a/tests/utils_test.js +++ b/tests/utils_test.js @@ -16,11 +16,11 @@ suite('utility functions', () => { }); test('number of elements of Set', () => { - assert.areEqual(Utils.numberOfElements(new Set([1,2])), 2); + assert.areEqual(Utils.numberOfElements(new Set([1, 2])), 2); }); test('number of elements of Array', () => { - assert.areEqual(Utils.numberOfElements([1,2, 3]), 3); + assert.areEqual(Utils.numberOfElements([1, 2, 3]), 3); }); test('isRegex returns true for regexes', () => { @@ -39,7 +39,9 @@ suite('utility functions', () => { }); test('respondsTo() is true when the property exists as a function in the object', () => { - const thingThatKnowsHowToDance = { dance() { return 'I am dancing!'; } }; + const thingThatKnowsHowToDance = { dance() { + return 'I am dancing!'; + } }; assert.isTrue(Utils.respondsTo(thingThatKnowsHowToDance, 'dance')); }); diff --git a/testy.js b/testy.js index 523d383a..7e3e4da6 100644 --- a/testy.js +++ b/testy.js @@ -4,7 +4,7 @@ const libDir = './lib'; const TestRunner = require(`${libDir}/test_runner`); const { Asserter, FailureGenerator, PendingMarker } = require(`${libDir}/asserter`); const ConsoleUI = require(`${libDir}/console_ui`); -const Utils = require(`${libDir}/utils`); +const { allFilesMatching, resolvePathFor } = require(`${libDir}/utils`); const ui = new ConsoleUI(); const testRunner = new TestRunner(ui.testRunnerCallbacks()); @@ -71,14 +71,13 @@ class Testy { } _loadAllRequestedFiles() { - try{ - + try { this._resolvedTestFilesPathsToRun().forEach(path => - Utils.allFilesMatching(path, this._testFilesFilter()).forEach(file => + allFilesMatching(path, this._testFilesFilter()).forEach(file => require(file) ) ); - }catch(err){ + } catch (err){ ui._displayError(`Error: ${err.path} does not exist.`, red); process.exit(1); } @@ -90,8 +89,7 @@ class Testy { } _resolvedTestFilesPathsToRun(){ - return this._testFilesPathsToRun().map(path => Utils.resolvePathFor(path)); - + return this._testFilesPathsToRun().map(path => resolvePathFor(path)); } _pathForAllTests() { From 628931ce6988ec36ad04a160633ef30885f6d22c Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sun, 25 Oct 2020 22:18:14 -0300 Subject: [PATCH 22/64] :recycle: small improvements on test runner, console and testy modules; add a test for the runner --- lib/console_ui.js | 28 ++++++++++++++++++++++++---- lib/test_runner.js | 12 ++++-------- tests/core/test_runner_test.js | 29 +++++++++++++++++++++++++++++ testy.js | 14 ++++---------- 4 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 tests/core/test_runner_test.js diff --git a/lib/console_ui.js b/lib/console_ui.js index 77e48dc8..2a73d823 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -10,6 +10,11 @@ const red = '\x1b[31m'; const green = '\x1b[32m'; const yellow = '\x1b[33m'; +const consoleWidth = 80; + +const successfulExitCode = 0; +const failedExitCode = 1; + class ConsoleUI { constructor() { this.useLanguage(I18n.defaultLanguage()); @@ -61,8 +66,14 @@ class ConsoleUI { testRunnerCallbacks() { return { onFinish: runner => { - this.displayErrorsAndFailuresSummary(runner); + this._displayErrorsAndFailuresSummary(runner); this._displaySummary(runner); + }, + onSuccess: () => { + this._exitWithCode(successfulExitCode); + }, + onFailure: () => { + this._exitWithCode(failedExitCode); } }; } @@ -78,7 +89,7 @@ class ConsoleUI { measuringTotalTime(code) { const name = this.translated('total_time'); console.time(name); - code(); + code.call(); console.timeEnd(name); } @@ -90,6 +101,15 @@ class ConsoleUI { return this._i18n.translate(key); } + exitWithError(errorMessage) { + this._displayError(errorMessage, red); + this._exitWithCode(failedExitCode); + } + + _exitWithCode(exitCode) { + process.exit(exitCode); + } + _displayRunConfiguration(paths, configuration) { const testPaths = this.translated('running_tests_in'); const failFast = this.translated('fail_fast'); @@ -114,7 +134,7 @@ class ConsoleUI { } _displaySeparator(character = '=') { - console.log(character.repeat(80)); + console.log(character.repeat(consoleWidth)); } _displayError(text, color) { @@ -133,7 +153,7 @@ class ConsoleUI { } } - displayErrorsAndFailuresSummary(runner) { + _displayErrorsAndFailuresSummary(runner) { if (runner.hasErrorsOrFailures()) { console.log(`\n${this.translated('failures_summary')}`); runner.allFailuresAndErrors().forEach(test => { diff --git a/lib/test_runner.js b/lib/test_runner.js index 897d9713..53eb6a9c 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -67,11 +67,11 @@ class TestRunner { this.currentSuite().currentTest().setResult(result); } - finish(callbacks) { - if (this._considerResultAsSucceeded()) { - return callbacks.success(); + finish() { + if (this.hasErrorsOrFailures()) { + return this._callbacks.onFailure(); } else { - return callbacks.failure(); + return this._callbacks.onSuccess(); } } @@ -127,10 +127,6 @@ class TestRunner { this._currentSuite = suite; } - _considerResultAsSucceeded() { - return this.errorsCount() + this.failuresCount() === 0; - } - _countEach(property) { return this.suites().reduce((count, suite) => count + suite[property](), 0); } diff --git a/tests/core/test_runner_test.js b/tests/core/test_runner_test.js new file mode 100644 index 00000000..f7e4306c --- /dev/null +++ b/tests/core/test_runner_test.js @@ -0,0 +1,29 @@ +'use strict'; + +const { suite, test, assert } = require('../../testy'); +const TestRunner = require('../../lib/test_runner'); + +suite('test runner', () => { + test('with no tests, it finishes with success', () => { + let result = 'not called'; + let finished = false; + const callbacks = { + onFinish: () => { + finished = true; + }, + onSuccess: () => { + result = 'success'; + }, + onFailure: () => { + result = 'failure'; + } + }; + const runner = new TestRunner(callbacks); + + runner.run(); + runner.finish(); + + assert.isTrue(finished); + assert.that(result).isEqualTo('success'); + }); +}); diff --git a/testy.js b/testy.js index 7e3e4da6..488b37c9 100644 --- a/testy.js +++ b/testy.js @@ -12,14 +12,12 @@ const assert = new Asserter(testRunner); const fail = new FailureGenerator(testRunner); const pending = new PendingMarker(testRunner); -const red = '\x1b[31m'; - function test(name, testBody) { testRunner.registerTest(name, testBody, ui.testCallbacks()); } function suite(name, suiteBody) { - return testRunner.registerSuite(name, suiteBody, ui.suiteCallbacks()); + testRunner.registerSuite(name, suiteBody, ui.suiteCallbacks()); } function before(initialization) { @@ -50,10 +48,7 @@ class Testy { ui.measuringTotalTime(() => testRunner.run() ); - testRunner.finish({ - success: () => process.exit(0), - failure: () => process.exit(1), - }); + testRunner.finish(); } // initialization @@ -77,9 +72,8 @@ class Testy { require(file) ) ); - } catch (err){ - ui._displayError(`Error: ${err.path} does not exist.`, red); - process.exit(1); + } catch (err) { + ui.exitWithError(`Error: ${err.path} does not exist.`); } } From 7cf2dad8a1dd330ccc431ecd65c08290308ace0e Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Wed, 28 Oct 2020 23:46:36 -0300 Subject: [PATCH 23/64] :recycle: improvement: decouple the console UI from the formatter logic --- lib/configuration.js | 2 +- lib/console_ui.js | 164 ++++++------------------------------------- lib/formatter.js | 162 ++++++++++++++++++++++++++++++++++++++++++ testy.js | 3 +- 4 files changed, 187 insertions(+), 144 deletions(-) create mode 100644 lib/formatter.js diff --git a/lib/configuration.js b/lib/configuration.js index 472ae785..ff7f1fa5 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -32,7 +32,7 @@ class Configuration { } filter() { - return new RegExp(this._configurationOptions.filter); + return new RegExp(this.filterRaw()); } filterRaw() { diff --git a/lib/console_ui.js b/lib/console_ui.js index 2a73d823..95fbdbaf 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -1,73 +1,42 @@ 'use strict'; const I18n = require('./i18n'); - -// Colors and emphasis -const off = '\x1b[0m'; -const bold = '\x1b[1m'; -const grey = '\x1b[30m'; -const red = '\x1b[31m'; -const green = '\x1b[32m'; -const yellow = '\x1b[33m'; - -const consoleWidth = 80; +const Formatter = require('./formatter'); const successfulExitCode = 0; const failedExitCode = 1; class ConsoleUI { - constructor() { - this.useLanguage(I18n.defaultLanguage()); - this.filter = ''; - } - // Callbacks for runner/suite/test testCallbacks() { return { - whenPending: test => { - this._displayResult(this.translated('wip'), test, yellow); - if (test.isExplicitlyMarkedPending()) { - this._displayResultDetail(test.result().reason()); - } - }, - whenSkipped: test => { - this._displayResult(this.translated('skip'), test, grey); - }, - whenSuccess: test => { - this._displayResult(this.translated('ok'), test, green); - }, - whenFailed: test => { - this._displayResult(this.translated('fail'), test, red); - this._displayResultDetail(test.result().failureMessage()); - }, - whenErrored: test => { - this._displayResult(this.translated('error'), test, red); - this._displayResultDetail(test.result().failureMessage()); - } + whenPending: test => + this._formatter.displayPendingResult(test), + whenSkipped: test => + this._formatter.displaySkippedResult(test), + whenSuccess: test => + this._formatter.displaySuccessResult(test), + whenFailed: test => + this._formatter.displayFailureResult(test), + whenErrored: test => + this._formatter.displayErrorResult(test), }; } suiteCallbacks() { return { - onStart: suite => { - console.log(`\n${suite.name()}:`); - this._displaySeparator('-'); - }, - onFinish: suite => { - this._displaySeparator('-'); - console.log(`${this.translated('summary_of')} ${suite.name()}:`); - this._displayCountFor(suite); - this._displaySeparator(); - } + onStart: suite => + this._formatter.displaySuiteStart(suite), + onFinish: suite => + this._formatter.displaySuiteEnd(suite), }; } testRunnerCallbacks() { return { onFinish: runner => { - this._displayErrorsAndFailuresSummary(runner); - this._displaySummary(runner); + this._formatter.displayRunnerEnd(runner); }, onSuccess: () => { this._exitWithCode(successfulExitCode); @@ -78,113 +47,26 @@ class ConsoleUI { }; } - displayInitialSummary(configuration, paths) { - this._displaySeparator(); - - console.log(this._inBold(this.translated('starting_testy'))); - this._displayRunConfiguration(paths, configuration); - this._displaySeparator(); - } - - measuringTotalTime(code) { - const name = this.translated('total_time'); - console.time(name); - code.call(); - console.timeEnd(name); + start(configuration, paths, runnerBlock) { + this._formatter.start(); + this._formatter.displayInitialInformation(configuration, paths); + runnerBlock.call(); + this._formatter.end(); } useLanguage(language) { this._i18n = new I18n(language); - } - - translated(key) { - return this._i18n.translate(key); + this._formatter = new Formatter(this._i18n); } exitWithError(errorMessage) { - this._displayError(errorMessage, red); + this._formatter.displayError(errorMessage); this._exitWithCode(failedExitCode); } _exitWithCode(exitCode) { process.exit(exitCode); } - - _displayRunConfiguration(paths, configuration) { - const testPaths = this.translated('running_tests_in'); - const failFast = this.translated('fail_fast'); - const randomOrder = this.translated('random_order'); - const padding = Math.max(testPaths.length, failFast.length, randomOrder.length); - this.filter = configuration.filterRaw(); - console.log(`${testPaths.padEnd(padding)} : ${paths}`); - console.log(`${failFast.padEnd(padding)} : ${this._humanBoolean(configuration.failFastMode().enabled())}`); - console.log(`${randomOrder.padEnd(padding)} : ${this._humanBoolean(configuration.randomOrder())}`); - } - - _displayIfNonZero(quantity, word, color = off) { - return quantity > 0 ? `, ${this._withColor(`${quantity} ${word}`, color)}` : ''; - } - - _displayResult(result, test, color) { - console.log(`[${color}${this._inBold(result)}] ${this._withColor(test.name(), color)}`); - } - - _displayResultDetail(detail) { - console.log(` => ${detail}`); - } - - _displaySeparator(character = '=') { - console.log(character.repeat(consoleWidth)); - } - - _displayError(text, color) { - console.log(`${this._withColor(text, color)}`); - } - - _displayCountFor(runner) { - const passedCount = this._displayIfNonZero(runner.successCount(), this.translated('passed'), green); - const failureCount = this._displayIfNonZero(runner.failuresCount(), this.translated('failed'), red); - const errorCount = this._displayIfNonZero(runner.errorsCount(), this.translated('errors'), red); - const pendingCount = this._displayIfNonZero(runner.pendingCount(), this.translated('pending')); - const skippedCount = this._displayIfNonZero(runner.skippedCount(), this.translated('skipped'), yellow); - console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); - if (runner.totalCount() === 0) { - console.log(this._withColor(`\nWarning: Make sure your files matches the ${this.filter} naming filter.`, yellow)); - } - } - - _displayErrorsAndFailuresSummary(runner) { - if (runner.hasErrorsOrFailures()) { - console.log(`\n${this.translated('failures_summary')}`); - runner.allFailuresAndErrors().forEach(test => { - if (test.isFailure()) { - this._displayResult(this.translated('fail'), test, red); - } else { - this._displayResult(this.translated('error'), test, red); - } - this._displayResultDetail(test.result().failureMessage()); - }); - this._displaySeparator(); - } - } - - _displaySummary(runner) { - console.log(`\n${this.translated('total')}`); - this._displayCountFor(runner); - this._displaySeparator(); - } - - _inBold(text) { - return `${bold}${text}${off}`; - } - - _withColor(text, color) { - return `${color}${text}${off}`; - } - - _humanBoolean(boolean) { - return boolean === true ? this.translated('yes') : this.translated('no'); - } } module.exports = ConsoleUI; diff --git a/lib/formatter.js b/lib/formatter.js new file mode 100644 index 00000000..d61fc30a --- /dev/null +++ b/lib/formatter.js @@ -0,0 +1,162 @@ +'use strict'; + +// Colors and emphasis +const off = '\x1b[0m'; +const bold = '\x1b[1m'; +const grey = '\x1b[30m'; +const red = '\x1b[31m'; +const green = '\x1b[32m'; +const yellow = '\x1b[33m'; + +const consoleWidth = 80; + +class Formatter { + constructor(i18n) { + this._i18n = i18n; + this._timerName = this._translated('total_time'); + } + + start() { + console.time(this._timerName); + } + + end() { + console.timeEnd(this._timerName); + } + + displayInitialInformation(configuration, paths) { + this._filter = configuration.filterRaw(); + this._displaySeparator(); + this._displayConfigurationSummary(paths, configuration); + this._displaySeparator(); + } + + displayRunnerEnd(runner) { + this._displayErrorsAndFailuresSummary(runner); + this._displayGeneralSummary(runner); + } + + // displaying suites + + displaySuiteStart(suite) { + console.log(`\n${suite.name()}:`); + this._displaySeparator('-'); + } + + displaySuiteEnd(suite) { + this._displaySeparator('-'); + console.log(`${this._translated('summary_of')} ${suite.name()}:`); + this._displayCountFor(suite); + this._displaySeparator(); + } + + // displaying test results + + displayPendingResult(test) { + this._displayResult(this._translated('wip'), test, yellow); + if (test.isExplicitlyMarkedPending()) { + this._displayResultDetail(test.result().reason()); + } + } + + displaySkippedResult(test) { + this._displayResult(this._translated('skip'), test, grey); + } + + displaySuccessResult(test) { + this._displayResult(this._translated('ok'), test, green); + } + + displayFailureResult(test) { + this._displayResult(this._translated('fail'), test, red); + this._displayResultDetail(test.result().failureMessage()); + } + + displayErrorResult(test) { + this._displayResult(this._translated('error'), test, red); + this._displayResultDetail(test.result().failureMessage()); + } + + // displaying other messages + + displayError(message) { + console.log(`${this._withColor(message, red)}`); + } + + _displayConfigurationSummary(paths, configuration) { + const testPathsLabel = this._translated('running_tests_in'); + const failFastLabel = this._translated('fail_fast'); + const randomOrderLabel = this._translated('random_order'); + console.log(this._inBold(this._translated('starting_testy'))); + const padding = Math.max(testPathsLabel.length, failFastLabel.length, randomOrderLabel.length); + console.log(`${testPathsLabel.padEnd(padding)} : ${paths}`); + console.log(`${failFastLabel.padEnd(padding)} : ${this._humanBoolean(configuration.failFastMode().enabled())}`); + console.log(`${randomOrderLabel.padEnd(padding)} : ${this._humanBoolean(configuration.randomOrder())}`); + } + + _displayResult(result, test, color) { + console.log(`[${color}${this._inBold(result)}] ${this._withColor(test.name(), color)}`); + } + + _displayResultDetail(detail) { + console.log(` => ${detail}`); + } + + _displayErrorsAndFailuresSummary(runner) { + if (runner.hasErrorsOrFailures()) { + console.log(`\n${this._translated('failures_summary')}`); + runner.allFailuresAndErrors().forEach(test => { + if (test.isFailure()) { + this.displayFailureResult(test); + } else { + this.displayErrorResult(test); + } + }); + this._displaySeparator(); + } + } + + _displayGeneralSummary(runner) { + console.log(`\n${this._translated('total')}`); + this._displayCountFor(runner); + this._displaySeparator(); + } + + _displayCountFor(runner) { + const passedCount = this._displayIfNonZero(runner.successCount(), this._translated('passed'), green); + const failureCount = this._displayIfNonZero(runner.failuresCount(), this._translated('failed'), red); + const errorCount = this._displayIfNonZero(runner.errorsCount(), this._translated('errors'), red); + const pendingCount = this._displayIfNonZero(runner.pendingCount(), this._translated('pending')); + const skippedCount = this._displayIfNonZero(runner.skippedCount(), this._translated('skipped'), yellow); + console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); + if (runner.totalCount() === 0) { + console.log(this._withColor(`\nWarning: Make sure your files matches the ${this._filter} naming filter.`, yellow)); + } + } + + _displayIfNonZero(quantity, word, color = off) { + return quantity > 0 ? `, ${this._withColor(`${quantity} ${word}`, color)}` : ''; + } + + _displaySeparator(character = '=') { + console.log(character.repeat(consoleWidth)); + } + + _inBold(text) { + return `${bold}${text}${off}`; + } + + _withColor(text, color) { + return `${color}${text}${off}`; + } + + _humanBoolean(boolean) { + return boolean === true ? this._translated('yes') : this._translated('no'); + } + + _translated(key) { + return this._i18n.translate(key); + } +} + +module.exports = Formatter; diff --git a/testy.js b/testy.js index 488b37c9..2e95d8e6 100644 --- a/testy.js +++ b/testy.js @@ -44,8 +44,7 @@ class Testy { run(requestedPaths) { this._requestedPaths = requestedPaths; this._loadAllRequestedFiles(); - ui.displayInitialSummary(this._configuration, this._testFilesPathsToRun()); - ui.measuringTotalTime(() => + ui.start(this._configuration, this._testFilesPathsToRun(), () => testRunner.run() ); testRunner.finish(); From 9ab5c55fd4738054effef1e1aab15824a62c6750 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Thu, 29 Oct 2020 08:10:05 -0300 Subject: [PATCH 24/64] :memo: add ADR to explain the existence of Formatter and its differences with ConsoleUI --- .../0004-console-ui-and-formatter.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 doc/decisions/0004-console-ui-and-formatter.md diff --git a/doc/decisions/0004-console-ui-and-formatter.md b/doc/decisions/0004-console-ui-and-formatter.md new file mode 100644 index 00000000..60996722 --- /dev/null +++ b/doc/decisions/0004-console-ui-and-formatter.md @@ -0,0 +1,21 @@ +# 4. Console UI and Formatter + +Date: 2020-10-28 + +## Status + +Accepted + +## Context + +Reducing complexity in the `ConsoleUI` object and make it more reusable and testable. + +## Decision + +`ConsoleUI` now only knows when to output things, but not the contents of messages, which is now responsibility of a `Formatter`. This object can be replaced by other formatters in the future. + +## Consequences + +* `Formatter` becomes testable, as well as `ConsoleUI` +* Responsibilities are better split +* There are more control on Node built-in modules, `Formatter` now talks to `console` object, and `ConsoleUI` to `process` From 3ebb45d85629675b1eaeb1f8d9a9aedabca76f23 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Thu, 29 Oct 2020 08:51:09 -0300 Subject: [PATCH 25/64] :recycle: make the formatter testable by not depending on node console module, and add a test for it --- lib/console_ui.js | 4 ++-- lib/formatter.js | 35 ++++++++++++++++++----------------- tests/ui/formatter_test.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 tests/ui/formatter_test.js diff --git a/lib/console_ui.js b/lib/console_ui.js index 95fbdbaf..fe870fab 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -55,8 +55,8 @@ class ConsoleUI { } useLanguage(language) { - this._i18n = new I18n(language); - this._formatter = new Formatter(this._i18n); + const i18n = new I18n(language); + this._formatter = new Formatter(console, i18n); } exitWithError(errorMessage) { diff --git a/lib/formatter.js b/lib/formatter.js index d61fc30a..e0fa3768 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -11,17 +11,18 @@ const yellow = '\x1b[33m'; const consoleWidth = 80; class Formatter { - constructor(i18n) { + constructor(console, i18n) { + this._console = console; this._i18n = i18n; this._timerName = this._translated('total_time'); } start() { - console.time(this._timerName); + this._console.time(this._timerName); } end() { - console.timeEnd(this._timerName); + this._console.timeEnd(this._timerName); } displayInitialInformation(configuration, paths) { @@ -39,13 +40,13 @@ class Formatter { // displaying suites displaySuiteStart(suite) { - console.log(`\n${suite.name()}:`); + this._console.log(`\n${suite.name()}:`); this._displaySeparator('-'); } displaySuiteEnd(suite) { this._displaySeparator('-'); - console.log(`${this._translated('summary_of')} ${suite.name()}:`); + this._console.log(`${this._translated('summary_of')} ${suite.name()}:`); this._displayCountFor(suite); this._displaySeparator(); } @@ -80,31 +81,31 @@ class Formatter { // displaying other messages displayError(message) { - console.log(`${this._withColor(message, red)}`); + this._console.log(`${this._withColor(message, red)}`); } _displayConfigurationSummary(paths, configuration) { const testPathsLabel = this._translated('running_tests_in'); const failFastLabel = this._translated('fail_fast'); const randomOrderLabel = this._translated('random_order'); - console.log(this._inBold(this._translated('starting_testy'))); + this._console.log(this._inBold(this._translated('starting_testy'))); const padding = Math.max(testPathsLabel.length, failFastLabel.length, randomOrderLabel.length); - console.log(`${testPathsLabel.padEnd(padding)} : ${paths}`); - console.log(`${failFastLabel.padEnd(padding)} : ${this._humanBoolean(configuration.failFastMode().enabled())}`); - console.log(`${randomOrderLabel.padEnd(padding)} : ${this._humanBoolean(configuration.randomOrder())}`); + this._console.log(`${testPathsLabel.padEnd(padding)} : ${paths}`); + this._console.log(`${failFastLabel.padEnd(padding)} : ${this._humanBoolean(configuration.failFastMode().enabled())}`); + this._console.log(`${randomOrderLabel.padEnd(padding)} : ${this._humanBoolean(configuration.randomOrder())}`); } _displayResult(result, test, color) { - console.log(`[${color}${this._inBold(result)}] ${this._withColor(test.name(), color)}`); + this._console.log(`[${color}${this._inBold(result)}] ${this._withColor(test.name(), color)}`); } _displayResultDetail(detail) { - console.log(` => ${detail}`); + this._console.log(` => ${detail}`); } _displayErrorsAndFailuresSummary(runner) { if (runner.hasErrorsOrFailures()) { - console.log(`\n${this._translated('failures_summary')}`); + this._console.log(`\n${this._translated('failures_summary')}`); runner.allFailuresAndErrors().forEach(test => { if (test.isFailure()) { this.displayFailureResult(test); @@ -117,7 +118,7 @@ class Formatter { } _displayGeneralSummary(runner) { - console.log(`\n${this._translated('total')}`); + this._console.log(`\n${this._translated('total')}`); this._displayCountFor(runner); this._displaySeparator(); } @@ -128,9 +129,9 @@ class Formatter { const errorCount = this._displayIfNonZero(runner.errorsCount(), this._translated('errors'), red); const pendingCount = this._displayIfNonZero(runner.pendingCount(), this._translated('pending')); const skippedCount = this._displayIfNonZero(runner.skippedCount(), this._translated('skipped'), yellow); - console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); + this._console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); if (runner.totalCount() === 0) { - console.log(this._withColor(`\nWarning: Make sure your files matches the ${this._filter} naming filter.`, yellow)); + this._console.log(this._withColor(`\nWarning: Make sure your files matches the ${this._filter} naming filter.`, yellow)); } } @@ -139,7 +140,7 @@ class Formatter { } _displaySeparator(character = '=') { - console.log(character.repeat(consoleWidth)); + this._console.log(character.repeat(consoleWidth)); } _inBold(text) { diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js new file mode 100644 index 00000000..4fd29843 --- /dev/null +++ b/tests/ui/formatter_test.js @@ -0,0 +1,32 @@ +'use strict'; + +const { suite, test, before, assert } = require('../../testy'); +const Formatter = require('../../lib/formatter'); +const I18n = require('../../lib/i18n'); + +suite('formatter', () => { + let formatter, dummyConsole, i18n; + + before(() => { + dummyConsole = { + _messages: [], + + log(message) { + this._messages.push(message); + }, + + messages() { + return Array.from(this._messages); + } + }; + + i18n = new I18n(); + formatter = new Formatter(dummyConsole, i18n); + }); + + test('display errors in red', () => { + formatter.displayError('things happened'); + const expectedErrorMessage = '\x1b[31mthings happened\x1b[0m'; + assert.that(dummyConsole.messages()).includesExactly(expectedErrorMessage); + }); +}); From d2cc1dcde9daa8469cbc204331edb6765cf1ca7f Mon Sep 17 00:00:00 2001 From: Pablo Troche Date: Tue, 20 Oct 2020 20:27:36 -0300 Subject: [PATCH 26/64] :rotating_light: fix: #130 add convert to array includes and doesNotIncludeAdd utils for convert to array a parameter incoming in the includes and doesNotInclude test --- lib/asserter.js | 8 ++-- lib/utils.js | 14 +++++++ .../assertions/collection_assertions_test.js | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/asserter.js b/lib/asserter.js index ae2379ff..d318df91 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -1,6 +1,6 @@ 'use strict'; -const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements } = require('./utils'); +const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, builderArrayByType } = require('./utils'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); @@ -129,7 +129,8 @@ class Assertion extends TestResultReporter { // Collection assertions includes(expectedObject, equalityCriteria) { - const resultIsSuccessful = this._actual.find(element => + const convertToArray = builderArrayByType[this._actual.constructor.name]; + const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); @@ -143,7 +144,8 @@ class Assertion extends TestResultReporter { } doesNotInclude(expectedObject, equalityCriteria) { - const resultIsSuccessful = !this._actual.find(element => + const convertToArray = builderArrayByType[this._actual.constructor.name]; + const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; this._reportAssertionResult(resultIsSuccessful, failureMessage); diff --git a/lib/utils.js b/lib/utils.js index ff72e6c6..bf96d2c8 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -94,6 +94,18 @@ const prettyPrint = object => { const numberOfElements = object => Array.from(object).length; +const stringToArray = object => Array.from(object); +const arrayToArray = object => object; +const setToArray = object => Array.from(object); +const mapToArray = object => Array.from(object.values()); + +const builderArrayByType = { + String: stringToArray, + Array: arrayToArray, + Set: setToArray, + Map: mapToArray +}; + module.exports = { // comparison on objects isCyclic, @@ -114,4 +126,6 @@ module.exports = { // files resolvePathFor, allFilesMatching, + // converts + builderArrayByType, }; diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index 471d870a..905a2514 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -169,4 +169,41 @@ suite('collection assertions', () => { expectFailureDueTo('Expected undefined to be not empty'); }); + + test('includes works with Sets', () => { + asserter.that(new Set([42])).includes(42); + + expectSuccess(); + }); + + test('includes works with Maps', () => { + asserter.that(new Map([['key', 42]])).includes(42); + + expectSuccess(); + }); + + test('includes works with Strings', () => { + asserter.that('42').includes('4'); + + expectSuccess(); + }); + + test('doesNotInclude works with Sets', () => { + asserter.that(new Set([24])).doesNotInclude(42); + + expectSuccess(); + }); + + test('doesNotInclude works with Maps', () => { + asserter.that(new Map([['key', 24]])).doesNotInclude(42); + + expectSuccess(); + }); + + test('doesNotInclude works with Strings', () => { + asserter.that('24').doesNotInclude('5'); + + expectSuccess(); + }); + }); From ad52afdd83d577a4f48a9641825b054ca7dd5433 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2020 03:31:36 +0000 Subject: [PATCH 27/64] docs: update README.md [skip ci] --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9b750ab4..bd69840b 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Nigel Yong

💻
Chelsie Ng

💻 + +
Pablo T

⚠️ 💻 + From 78a7a3e50dc7d084ed6f12e705898daa6b9476e7 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2020 03:31:37 +0000 Subject: [PATCH 28/64] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 11b10849..a22b545d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -72,6 +72,16 @@ "contributions": [ "code" ] + }, + { + "login": "trochepablo", + "name": "Pablo T", + "avatar_url": "https://avatars2.githubusercontent.com/u/18213369?v=4", + "profile": "https://github.com/trochepablo", + "contributions": [ + "test", + "code" + ] } ], "contributorsPerLine": 7, From d4ca1fa7804b2353458eb214d1f302fefc9fed9d Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 10:48:43 -0300 Subject: [PATCH 29/64] :construction_worker: fix some lint issues; add some useful run scripts --- .eslintrc.json | 5 ++++- .gitignore | 3 ++- lib/console_ui.js | 28 +++++++++++++------------ lib/equality_assertion_strategy.js | 12 +++++------ lib/test_runner.js | 12 ++++++----- lib/utils.js | 4 ++-- package.json | 2 ++ tests/core/test_runner_test.js | 2 +- tests/examples/basic_assertions_test.js | 8 +++---- tests/ui/formatter_test.js | 2 +- testy.js | 6 +++--- 11 files changed, 47 insertions(+), 37 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3e20a0a0..fa0a1b82 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,9 @@ "block-scoped-var": "error", "brace-style": "warn", "camelcase": "warn", + "comma-dangle": ["warn", "always-multiline"], "comma-spacing": "warn", + "comma-style": "warn", "constructor-super": "error", "curly": "error", "dot-notation": "error", @@ -43,6 +45,7 @@ "quotes": [2, "single", "avoid-escape"], "semi": 2, "space-before-function-paren": ["error", "never"], - "space-infix-ops": "error" + "space-infix-ops": "error", + "space-in-parens": "warn" } } diff --git a/.gitignore b/.gitignore index 94fb99e3..38511ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .idea/ -coverage/ \ No newline at end of file +coverage/ +.nyc_output/ diff --git a/lib/console_ui.js b/lib/console_ui.js index fe870fab..54e4f180 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -3,10 +3,15 @@ const I18n = require('./i18n'); const Formatter = require('./formatter'); -const successfulExitCode = 0; -const failedExitCode = 1; - class ConsoleUI { + static successfulExitCode() { + return 0; + } + + static failedExitCode() { + return 1; + } + // Callbacks for runner/suite/test testCallbacks() { @@ -35,15 +40,12 @@ class ConsoleUI { testRunnerCallbacks() { return { - onFinish: runner => { - this._formatter.displayRunnerEnd(runner); - }, - onSuccess: () => { - this._exitWithCode(successfulExitCode); - }, - onFailure: () => { - this._exitWithCode(failedExitCode); - } + onFinish: runner => + this._formatter.displayRunnerEnd(runner), + onSuccess: _runner => + this._exitWithCode(ConsoleUI.successfulExitCode()), + onFailure: _runner => + this._exitWithCode(ConsoleUI.failedExitCode()), }; } @@ -61,7 +63,7 @@ class ConsoleUI { exitWithError(errorMessage) { this._formatter.displayError(errorMessage); - this._exitWithCode(failedExitCode); + this._exitWithCode(ConsoleUI.failedExitCode()); } _exitWithCode(exitCode) { diff --git a/lib/equality_assertion_strategy.js b/lib/equality_assertion_strategy.js index 31b4f220..8a618a58 100644 --- a/lib/equality_assertion_strategy.js +++ b/lib/equality_assertion_strategy.js @@ -46,7 +46,7 @@ const CustomFunction = { evaluate(actual, expected, criteria) { return { comparisonResult: criteria(actual, expected), - additionalFailureMessage: '' + additionalFailureMessage: '', }; }, }; @@ -73,14 +73,14 @@ const CustomPropertyName = { _compareUsingCustomCriteria(actual, expected, criteria) { return { comparisonResult: actual[criteria](expected), - additionalFailureMessage: '' + additionalFailureMessage: '', }; }, _failWithCriteriaNotFoundMessage(criteria) { return { comparisonResult: false, - additionalFailureMessage: ` Equality check failed. Objects do not have '${criteria}' property` + additionalFailureMessage: ` Equality check failed. Objects do not have '${criteria}' property`, }; }, }; @@ -95,7 +95,7 @@ const ObjectWithEqualsProperty = { evaluate(actual, expected) { return { comparisonResult: actual.equals(expected), - additionalFailureMessage: '' + additionalFailureMessage: '', }; }, }; @@ -110,7 +110,7 @@ const ObjectWithCyclicReference = { evaluate(_actual, _expected) { return { comparisonResult: false, - additionalFailureMessage: ' (circular references found, equality check cannot be done. Please compare objects\' properties individually)' + additionalFailureMessage: ' (circular references found, equality check cannot be done. Please compare objects\' properties individually)', }; }, }; @@ -125,7 +125,7 @@ const DefaultEquality = { evaluate(actual, expected) { return { comparisonResult: deepStrictEqual(actual, expected), - additionalFailureMessage: '' + additionalFailureMessage: '', }; }, }; diff --git a/lib/test_runner.js b/lib/test_runner.js index 53eb6a9c..33aa21ee 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -69,9 +69,9 @@ class TestRunner { finish() { if (this.hasErrorsOrFailures()) { - return this._callbacks.onFailure(); + return this._callbacks.onFailure(this); } else { - return this._callbacks.onSuccess(); + return this._callbacks.onSuccess(this); } } @@ -112,8 +112,8 @@ class TestRunner { } allFailuresAndErrors() { - return this.suites().reduce( - (failures, suite) => failures.concat(suite.allFailuresAndErrors()), [] + return this.suites().reduce((failures, suite) => + failures.concat(suite.allFailuresAndErrors()), [], ); } @@ -128,7 +128,9 @@ class TestRunner { } _countEach(property) { - return this.suites().reduce((count, suite) => count + suite[property](), 0); + return this.suites().reduce((count, suite) => + count + suite[property](), 0, + ); } _randomizeSuites() { diff --git a/lib/utils.js b/lib/utils.js index bf96d2c8..aca3834b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,7 +43,7 @@ const allFilesMatching = (dir, regex, results = []) => { } fs.readdirSync(dir).forEach(entry => - results.push(...allFilesMatching(path.join(dir, entry), regex, results)) + results.push(...allFilesMatching(path.join(dir, entry), regex, results)), ); return results; }; @@ -103,7 +103,7 @@ const builderArrayByType = { String: stringToArray, Array: arrayToArray, Set: setToArray, - Map: mapToArray + Map: mapToArray, }; module.exports = { diff --git a/package.json b/package.json index 3cfb0bf7..e0785a49 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "main": "testy.js", "scripts": { "coverage": "npx nyc@latest --reporter=lcov --reporter=text-summary npm test", + "open-coverage-report": "xdg-open coverage/lcov-report/index.html", + "generate-dependencies-graph": "npx madge -i testy-dependencies.png lib/ bin/", "lint": "npx eslint@6.8.0 .", "lint-fix": "npx eslint@6.8.0 . --fix", "test": "bin/testy_cli.js" diff --git a/tests/core/test_runner_test.js b/tests/core/test_runner_test.js index f7e4306c..d9ca4251 100644 --- a/tests/core/test_runner_test.js +++ b/tests/core/test_runner_test.js @@ -16,7 +16,7 @@ suite('test runner', () => { }, onFailure: () => { result = 'failure'; - } + }, }; const runner = new TestRunner(callbacks); diff --git a/tests/examples/basic_assertions_test.js b/tests/examples/basic_assertions_test.js index 487f528c..6c518579 100644 --- a/tests/examples/basic_assertions_test.js +++ b/tests/examples/basic_assertions_test.js @@ -45,21 +45,21 @@ suite('testing testy - basic assertions', () => { // test('tests can fail as well :)', () => assert.that(() => { throw 'hey!'; }).raises("ho!")); test('no specific error happened', () => - assert.that(emptyFunction).doesNotRaise('hey!') + assert.that(emptyFunction).doesNotRaise('hey!'), ); test('testing that no specific error happened - even if other error occurs', () => assert.that(() => { throw 'ho!'; - }).doesNotRaise('hey!') + }).doesNotRaise('hey!'), ); test('testing that no error happens at all', () => - assert.that(emptyFunction).doesNotRaiseAnyErrors() + assert.that(emptyFunction).doesNotRaiseAnyErrors(), ); test('object comparison', () => - assert.areEqual({ a: 2, b: [1, 2, 3] }, { a: 2, b: [1, 2, 3] }) + assert.areEqual({ a: 2, b: [1, 2, 3] }, { a: 2, b: [1, 2, 3] }), ); // commented so CI can pass - uncomment to see the failure diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js index 4fd29843..87e94e66 100644 --- a/tests/ui/formatter_test.js +++ b/tests/ui/formatter_test.js @@ -17,7 +17,7 @@ suite('formatter', () => { messages() { return Array.from(this._messages); - } + }, }; i18n = new I18n(); diff --git a/testy.js b/testy.js index 2e95d8e6..9acf2159 100644 --- a/testy.js +++ b/testy.js @@ -45,7 +45,7 @@ class Testy { this._requestedPaths = requestedPaths; this._loadAllRequestedFiles(); ui.start(this._configuration, this._testFilesPathsToRun(), () => - testRunner.run() + testRunner.run(), ); testRunner.finish(); } @@ -68,8 +68,8 @@ class Testy { try { this._resolvedTestFilesPathsToRun().forEach(path => allFilesMatching(path, this._testFilesFilter()).forEach(file => - require(file) - ) + require(file), + ), ); } catch (err) { ui.exitWithError(`Error: ${err.path} does not exist.`); From 4913b5a187bc0700b3de4b5b1a9adc0e2a8dc57e Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 10:57:21 -0300 Subject: [PATCH 30/64] :recycle: refactor the asserter module to separate the assertions piece of it --- lib/asserter.js | 264 +----------------------------------- lib/assertion.js | 255 ++++++++++++++++++++++++++++++++++ lib/test_result_reporter.js | 17 +++ 3 files changed, 274 insertions(+), 262 deletions(-) create mode 100644 lib/assertion.js create mode 100644 lib/test_result_reporter.js diff --git a/lib/asserter.js b/lib/asserter.js index d318df91..4886b42b 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -1,22 +1,8 @@ 'use strict'; -const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, builderArrayByType } = require('./utils'); -const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); - -class TestResultReporter { - constructor(runner) { - this._runner = runner; - } - - report(result) { - this._runner.setResultForCurrentTest(result); - } - - translated(key) { - return this._runner._i18n.translate(key); - } -} +const TestResultReporter = require('./test_result_reporter'); +const Assertion = require('./assertion'); class FailureGenerator extends TestResultReporter { with(description) { @@ -80,250 +66,4 @@ class Asserter extends TestResultReporter { } } -class Assertion extends TestResultReporter { - constructor(runner, actual) { - super(runner); - this._actual = actual; - } - - // Boolean assertions - - isTrue() { - this._booleanAssertion(true, this.translated('be_true')); - } - - isFalse() { - this._booleanAssertion(false, this.translated('be_false')); - } - - // Undefined value assertions - - isUndefined() { - this._undefinedAssertion(this.translated('be_undefined')); - } - - isNotUndefined() { - this._notUndefinedAssertion(this.translated('be_defined')); - } - - // Null value assertions - - isNull() { - this._nullAssertion(this.translated('be_null')); - } - - isNotNull() { - this._notNullAssertion(this.translated('be_not_null')); - } - - // Equality assertions - - isEqualTo(expected, criteria) { - this._equalityAssertion(expected, criteria, true); - } - - isNotEqualTo(expected, criteria) { - this._equalityAssertion(expected, criteria, false); - } - - // Collection assertions - - includes(expectedObject, equalityCriteria) { - const convertToArray = builderArrayByType[this._actual.constructor.name]; - const resultIsSuccessful = convertToArray(this._actual).find(element => - this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - isIncludedIn(expectedCollection, equalityCriteria) { - const resultIsSuccessful = expectedCollection.find(element => - this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_included_in')} ${prettyPrint(expectedCollection)}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - doesNotInclude(expectedObject, equalityCriteria) { - const convertToArray = builderArrayByType[this._actual.constructor.name]; - const resultIsSuccessful = !convertToArray(this._actual).find(element => - this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - isNotIncludedIn(expectedCollection, equalityCriteria) { - const resultIsSuccessful = !expectedCollection.find(element => - this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_not_included_in')} ${prettyPrint(expectedCollection)}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - includesExactly(...objects) { - const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = `${this.translated('include_exactly')} ${prettyPrint(objects)}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - isEmpty() { - const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); - const failureMessage = this.translated('be_empty'); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - isNotEmpty() { - const setValueWhenUndefined = this._actual || {}; - - const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; - const failureMessage = this.translated('be_not_empty'); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - // Exception assertions - - raises(errorExpectation) { - this._exceptionAssertion(errorExpectation, true); - } - - doesNotRaise(notExpectedError) { - this._exceptionAssertion(notExpectedError, false); - } - - doesNotRaiseAnyErrors() { - let noErrorsOccurred = false; - let failureMessage = ''; - try { - this._actual.call(); - noErrorsOccurred = true; - } catch (error) { - noErrorsOccurred = false; - failureMessage = `${this.translated('expected_no_errors')}, ${this.translated('but')} ${prettyPrint(error)} ${this.translated('was_raised')}`; - } finally { - this._reportAssertionResult(noErrorsOccurred, failureMessage, true); - } - } - - // Numeric assertions - - isNearTo(number, precisionDigits = 4) { - const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; - const failureMessage = `be near to ${number} (using ${precisionDigits} precision digits)`; - this._reportAssertionResult(result, failureMessage, false); - } - - // String assertions - - matches(regex) { - const result = this._actual.match(regex); - const failureMessage = `${this.translated('match')} ${regex}`; - this._reportAssertionResult(result, failureMessage, false); - } - - // Private - - _equalityAssertion(expected, criteria, shouldBeEqual) { - const { comparisonResult, additionalFailureMessage, overrideFailureMessage } = - EqualityAssertionStrategy.evaluate(this._actual, expected, criteria); - - const resultIsSuccessful = shouldBeEqual ? comparisonResult : !comparisonResult; - if (overrideFailureMessage) { - this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage, true); - } else { - const expectationMessage = shouldBeEqual ? this.translated('be_equal_to') : this.translated('be_not_equal_to'); - const failureMessage = `${expectationMessage} ${prettyPrint(expected)}${additionalFailureMessage}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage, false); - } - } - - _areConsideredEqual(objectOne, objectTwo, equalityCriteria) { - return EqualityAssertionStrategy.evaluate(objectOne, objectTwo, equalityCriteria).comparisonResult; - } - - _booleanAssertion(expectedBoolean, failureMessage) { - const resultIsSuccessful = this._actual === expectedBoolean; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _undefinedAssertion(failureMessage) { - const resultIsSuccessful = isUndefined(this._actual); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _notUndefinedAssertion(failureMessage) { - const resultIsSuccessful = !isUndefined(this._actual); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _nullAssertion(failureMessage) { - const resultIsSuccessful = this._actual === null; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _notNullAssertion(failureMessage) { - const resultIsSuccessful = this._actual !== null; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _exceptionAssertion(errorExpectation, shouldFail) { - let hasFailed = false; - let actualError; - try { - this._actual(); - hasFailed = !shouldFail; - } catch (error) { - actualError = error; - const errorCheck = this._checkIfErrorMatchesExpectation(errorExpectation, actualError); - hasFailed = shouldFail ? errorCheck : !errorCheck; - } finally { - const toHappenOrNot = shouldFail ? this.translated('to_happen') : this.translated('not_to_happen'); - const expectedErrorIntroduction = `${this.translated('expected')} ${this.translated('expecting_error')} ${prettyPrint(errorExpectation)} ${toHappenOrNot}`; - let failureMessage; - if (isUndefined(actualError)) { - failureMessage = expectedErrorIntroduction; - } else { - failureMessage = `${expectedErrorIntroduction}, ${this.translated('but_got')} ${prettyPrint(actualError)} ${this.translated('instead')}`; - } - this._reportAssertionResult(hasFailed, failureMessage, true); - } - } - - _checkIfErrorMatchesExpectation(errorExpectation, actualError) { - if (isRegex(errorExpectation)) { - return errorExpectation.test(actualError); - } else { - return this._areConsideredEqual(actualError, errorExpectation); - } - } - - _reportAssertionResult(wasSuccess, matcherFailureMessage, overrideFailureMessage) { - if (wasSuccess) { - this.report(TestResult.success()); - } else { - const defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; - this.report(TestResult.failure(overrideFailureMessage ? matcherFailureMessage : defaultFailureMessage)); - } - } - - _actualResultAsString() { - return prettyPrint(this._actual); - } - - _haveElementsConsideredEqual(collectionOne, collectionTwo) { - const collectionOneArray = Array.from(collectionOne); - const collectionTwoArray = Array.from(collectionTwo); - if (collectionOneArray.length !== collectionTwoArray.length) { - return false; - } - for (let i = 0; i < collectionOne.length; i++) { - const includedInOne = collectionOne.find(element => - this._areConsideredEqual(element, collectionTwoArray[i])); - const includedInTwo = collectionTwo.find(element => - this._areConsideredEqual(element, collectionOneArray[i])); - if (!includedInOne || !includedInTwo) { - return false; - } - } - return true; - } -} - module.exports = { Asserter, FailureGenerator, PendingMarker }; diff --git a/lib/assertion.js b/lib/assertion.js new file mode 100644 index 00000000..5fff76f6 --- /dev/null +++ b/lib/assertion.js @@ -0,0 +1,255 @@ +'use strict'; + +const TestResultReporter = require('./test_result_reporter'); +const EqualityAssertionStrategy = require('./equality_assertion_strategy'); +const TestResult = require('./test_result'); + +const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, builderArrayByType } = require('./utils'); + +class Assertion extends TestResultReporter { + constructor(runner, actual) { + super(runner); + this._actual = actual; + } + + // Boolean assertions + + isTrue() { + this._booleanAssertion(true, this.translated('be_true')); + } + + isFalse() { + this._booleanAssertion(false, this.translated('be_false')); + } + + // Undefined value assertions + + isUndefined() { + this._undefinedAssertion(this.translated('be_undefined')); + } + + isNotUndefined() { + this._notUndefinedAssertion(this.translated('be_defined')); + } + + // Null value assertions + + isNull() { + this._nullAssertion(this.translated('be_null')); + } + + isNotNull() { + this._notNullAssertion(this.translated('be_not_null')); + } + + // Equality assertions + + isEqualTo(expected, criteria) { + this._equalityAssertion(expected, criteria, true); + } + + isNotEqualTo(expected, criteria) { + this._equalityAssertion(expected, criteria, false); + } + + // Collection assertions + + includes(expectedObject, equalityCriteria) { + const convertToArray = builderArrayByType[this._actual.constructor.name]; + const resultIsSuccessful = convertToArray(this._actual).find(element => + this._areConsideredEqual(element, expectedObject, equalityCriteria)); + const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + isIncludedIn(expectedCollection, equalityCriteria) { + const resultIsSuccessful = expectedCollection.find(element => + this._areConsideredEqual(element, this._actual, equalityCriteria)); + const failureMessage = `${this.translated('be_included_in')} ${prettyPrint(expectedCollection)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + doesNotInclude(expectedObject, equalityCriteria) { + const convertToArray = builderArrayByType[this._actual.constructor.name]; + const resultIsSuccessful = !convertToArray(this._actual).find(element => + this._areConsideredEqual(element, expectedObject, equalityCriteria)); + const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + isNotIncludedIn(expectedCollection, equalityCriteria) { + const resultIsSuccessful = !expectedCollection.find(element => + this._areConsideredEqual(element, this._actual, equalityCriteria)); + const failureMessage = `${this.translated('be_not_included_in')} ${prettyPrint(expectedCollection)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + includesExactly(...objects) { + const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); + const failureMessage = `${this.translated('include_exactly')} ${prettyPrint(objects)}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + isEmpty() { + const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); + const failureMessage = this.translated('be_empty'); + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + isNotEmpty() { + const setValueWhenUndefined = this._actual || {}; + + const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; + const failureMessage = this.translated('be_not_empty'); + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + // Exception assertions + + raises(errorExpectation) { + this._exceptionAssertion(errorExpectation, true); + } + + doesNotRaise(notExpectedError) { + this._exceptionAssertion(notExpectedError, false); + } + + doesNotRaiseAnyErrors() { + let noErrorsOccurred = false; + let failureMessage = ''; + try { + this._actual.call(); + noErrorsOccurred = true; + } catch (error) { + noErrorsOccurred = false; + failureMessage = `${this.translated('expected_no_errors')}, ${this.translated('but')} ${prettyPrint(error)} ${this.translated('was_raised')}`; + } finally { + this._reportAssertionResult(noErrorsOccurred, failureMessage, true); + } + } + + // Numeric assertions + + isNearTo(number, precisionDigits = 4) { + const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; + const failureMessage = `be near to ${number} (using ${precisionDigits} precision digits)`; + this._reportAssertionResult(result, failureMessage, false); + } + + // String assertions + + matches(regex) { + const result = this._actual.match(regex); + const failureMessage = `${this.translated('match')} ${regex}`; + this._reportAssertionResult(result, failureMessage, false); + } + + // Private + + _equalityAssertion(expected, criteria, shouldBeEqual) { + const { comparisonResult, additionalFailureMessage, overrideFailureMessage } = + EqualityAssertionStrategy.evaluate(this._actual, expected, criteria); + + const resultIsSuccessful = shouldBeEqual ? comparisonResult : !comparisonResult; + if (overrideFailureMessage) { + this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage, true); + } else { + const expectationMessage = shouldBeEqual ? this.translated('be_equal_to') : this.translated('be_not_equal_to'); + const failureMessage = `${expectationMessage} ${prettyPrint(expected)}${additionalFailureMessage}`; + this._reportAssertionResult(resultIsSuccessful, failureMessage, false); + } + } + + _areConsideredEqual(objectOne, objectTwo, equalityCriteria) { + return EqualityAssertionStrategy.evaluate(objectOne, objectTwo, equalityCriteria).comparisonResult; + } + + _booleanAssertion(expectedBoolean, failureMessage) { + const resultIsSuccessful = this._actual === expectedBoolean; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + _undefinedAssertion(failureMessage) { + const resultIsSuccessful = isUndefined(this._actual); + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + _notUndefinedAssertion(failureMessage) { + const resultIsSuccessful = !isUndefined(this._actual); + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + _nullAssertion(failureMessage) { + const resultIsSuccessful = this._actual === null; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + _notNullAssertion(failureMessage) { + const resultIsSuccessful = this._actual !== null; + this._reportAssertionResult(resultIsSuccessful, failureMessage); + } + + _exceptionAssertion(errorExpectation, shouldFail) { + let hasFailed = false; + let actualError; + try { + this._actual(); + hasFailed = !shouldFail; + } catch (error) { + actualError = error; + const errorCheck = this._checkIfErrorMatchesExpectation(errorExpectation, actualError); + hasFailed = shouldFail ? errorCheck : !errorCheck; + } finally { + const toHappenOrNot = shouldFail ? this.translated('to_happen') : this.translated('not_to_happen'); + const expectedErrorIntroduction = `${this.translated('expected')} ${this.translated('expecting_error')} ${prettyPrint(errorExpectation)} ${toHappenOrNot}`; + let failureMessage; + if (isUndefined(actualError)) { + failureMessage = expectedErrorIntroduction; + } else { + failureMessage = `${expectedErrorIntroduction}, ${this.translated('but_got')} ${prettyPrint(actualError)} ${this.translated('instead')}`; + } + this._reportAssertionResult(hasFailed, failureMessage, true); + } + } + + _checkIfErrorMatchesExpectation(errorExpectation, actualError) { + if (isRegex(errorExpectation)) { + return errorExpectation.test(actualError); + } else { + return this._areConsideredEqual(actualError, errorExpectation); + } + } + + _reportAssertionResult(wasSuccess, matcherFailureMessage, overrideFailureMessage) { + if (wasSuccess) { + this.report(TestResult.success()); + } else { + const defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; + this.report(TestResult.failure(overrideFailureMessage ? matcherFailureMessage : defaultFailureMessage)); + } + } + + _actualResultAsString() { + return prettyPrint(this._actual); + } + + _haveElementsConsideredEqual(collectionOne, collectionTwo) { + const collectionOneArray = Array.from(collectionOne); + const collectionTwoArray = Array.from(collectionTwo); + if (collectionOneArray.length !== collectionTwoArray.length) { + return false; + } + for (let i = 0; i < collectionOne.length; i++) { + const includedInOne = collectionOne.find(element => + this._areConsideredEqual(element, collectionTwoArray[i])); + const includedInTwo = collectionTwo.find(element => + this._areConsideredEqual(element, collectionOneArray[i])); + if (!includedInOne || !includedInTwo) { + return false; + } + } + return true; + } +} + +module.exports = Assertion; diff --git a/lib/test_result_reporter.js b/lib/test_result_reporter.js new file mode 100644 index 00000000..cdad89c8 --- /dev/null +++ b/lib/test_result_reporter.js @@ -0,0 +1,17 @@ +'use strict'; + +class TestResultReporter { + constructor(runner) { + this._runner = runner; + } + + report(result) { + this._runner.setResultForCurrentTest(result); + } + + translated(key) { + return this._runner._i18n.translate(key); + } +} + +module.exports = TestResultReporter; From e619f86fa3857e4d173a1d2d75f751f3a55b057d Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 11:09:49 -0300 Subject: [PATCH 31/64] :recycle: inline methods in Assertion class --- lib/assertion.js | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/lib/assertion.js b/lib/assertion.js index 5fff76f6..9a68c790 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -15,31 +15,31 @@ class Assertion extends TestResultReporter { // Boolean assertions isTrue() { - this._booleanAssertion(true, this.translated('be_true')); + this._reportAssertionResult(this._actual === true, this.translated('be_true')); } isFalse() { - this._booleanAssertion(false, this.translated('be_false')); + this._reportAssertionResult(this._actual === false, this.translated('be_false')); } // Undefined value assertions isUndefined() { - this._undefinedAssertion(this.translated('be_undefined')); + this._reportAssertionResult(isUndefined(this._actual), this.translated('be_undefined')); } isNotUndefined() { - this._notUndefinedAssertion(this.translated('be_defined')); + this._reportAssertionResult(!isUndefined(this._actual), this.translated('be_defined')); } // Null value assertions isNull() { - this._nullAssertion(this.translated('be_null')); + this._reportAssertionResult(this._actual === null, this.translated('be_null')); } isNotNull() { - this._notNullAssertion(this.translated('be_not_null')); + this._reportAssertionResult(this._actual !== null, this.translated('be_not_null')); } // Equality assertions @@ -164,36 +164,11 @@ class Assertion extends TestResultReporter { return EqualityAssertionStrategy.evaluate(objectOne, objectTwo, equalityCriteria).comparisonResult; } - _booleanAssertion(expectedBoolean, failureMessage) { - const resultIsSuccessful = this._actual === expectedBoolean; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _undefinedAssertion(failureMessage) { - const resultIsSuccessful = isUndefined(this._actual); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _notUndefinedAssertion(failureMessage) { - const resultIsSuccessful = !isUndefined(this._actual); - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _nullAssertion(failureMessage) { - const resultIsSuccessful = this._actual === null; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - - _notNullAssertion(failureMessage) { - const resultIsSuccessful = this._actual !== null; - this._reportAssertionResult(resultIsSuccessful, failureMessage); - } - _exceptionAssertion(errorExpectation, shouldFail) { let hasFailed = false; let actualError; try { - this._actual(); + this._actual.call(); hasFailed = !shouldFail; } catch (error) { actualError = error; From 7fe1ec9a83d36778d1140898287808c011f4c761 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 12:27:58 -0300 Subject: [PATCH 32/64] :white_check_mark: increase coverage by testing the explicit mark as pending/fail --- .../assertions/exception_assertions_test.js | 20 +++++++++++++ ...porting_failures_and_pending_tests_test.js | 30 +++++++++++++++++++ tests/support/assertion_helpers.js | 21 +++++++++++-- 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/core/assertions/reporting_failures_and_pending_tests_test.js diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index f60c44bf..76191819 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -43,4 +43,24 @@ suite('exception assertions', () => { expectFailureDueTo('Expected error /happiness/ to happen, but got \'a terrible error\' instead'); }); + + test('raises() fails when no errors occur in the given function', () => { + asserter.that(() => 1 + 2).raises('a weird error'); + + expectFailureDueTo('Expected error \'a weird error\' to happen'); + }); + + test('doesNoRaiseAnyErrors() passes when no errors occur in the given function', () => { + asserter.that(() => 1 + 2).doesNotRaiseAnyErrors(); + + expectSuccess(); + }); + + test('doesNoRaiseAnyErrors() fails when an error happens', () => { + asserter.that(() => { + throw 'an unexpected error'; + }).doesNotRaiseAnyErrors(); + + expectFailureDueTo('Expected no errors to happen, but \'an unexpected error\' was raised'); + }); }); diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js new file mode 100644 index 00000000..90703ca1 --- /dev/null +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -0,0 +1,30 @@ +'use strict'; + +const { suite, test, pending } = require('../../../testy'); +const { failGenerator, pendingMarker, expectFailureDueTo, expectPendingResultDueTo } = require('../../support/assertion_helpers'); + +suite('reporting failures and pending tests', () => { + test('marking a test as explicitly failed with no message', () => { + failGenerator.with(); + + expectFailureDueTo('Explicitly failed'); + }); + + test('marking a test as explicitly failed with no message', () => { + failGenerator.with('I should not be here!'); + + expectFailureDueTo('I should not be here!'); + }); + + test('marking a test as pending with no message', () => { + pending.dueTo('Need to fix this bug: https://github.com/ngarbezza/testy/issues/172'); + // pendingMarker.dueTo(); + // expectErrorDueTo('aaaaaa'); + }); + + test('marking a test as pending with a custom message', () => { + pendingMarker.dueTo('No time to fix!'); + + expectPendingResultDueTo('No time to fix!'); + }); +}); diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index da883a70..3e074651 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -1,7 +1,7 @@ 'use strict'; const { assert } = require('../../testy'); -const { Asserter } = require('../../lib/asserter'); +const { Asserter, FailureGenerator, PendingMarker } = require('../../lib/asserter'); const TestResult = require('../../lib/test_result'); const I18n = require('../../lib/i18n'); @@ -19,6 +19,8 @@ const fakeRunner = { }; const asserter = new Asserter(fakeRunner); +const failGenerator = new FailureGenerator(fakeRunner); +const pendingMarker = new PendingMarker(fakeRunner); const expectSuccess = () => { assert.areEqual(fakeRunner.result(), TestResult.success()); @@ -30,6 +32,12 @@ const expectFailureDueTo = failureMessage => { fakeRunner.reset(); }; +const expectPendingResultDueTo = reason => { + assert.isTrue(fakeRunner.result().isPending()); + assert.areEqual(fakeRunner.result().reason(), reason); + fakeRunner.reset(); +}; + const expectFailureOn = (test, failureMessage) => { assert.isTrue(test.result().isFailure()); assert.areEqual(test.result().failureMessage(), failureMessage); @@ -40,4 +48,13 @@ const expectErrorOn = (test, errorMessage) => { assert.areEqual(test.result().failureMessage(), errorMessage); }; -module.exports = { asserter, expectSuccess, expectFailureDueTo, expectFailureOn, expectErrorOn }; +module.exports = { + asserter, + failGenerator, + pendingMarker, + expectSuccess, + expectFailureDueTo, + expectPendingResultDueTo, + expectFailureOn, + expectErrorOn, +}; From 40408a4f2716995766b3ac78affb5887564848a3 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 12:47:30 -0300 Subject: [PATCH 33/64] :recycle: refactor array conversion function by hiding implementation details --- lib/assertion.js | 4 +--- lib/utils.js | 16 ++++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/assertion.js b/lib/assertion.js index 9a68c790..7710f00b 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -4,7 +4,7 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); -const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, builderArrayByType } = require('./utils'); +const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); class Assertion extends TestResultReporter { constructor(runner, actual) { @@ -55,7 +55,6 @@ class Assertion extends TestResultReporter { // Collection assertions includes(expectedObject, equalityCriteria) { - const convertToArray = builderArrayByType[this._actual.constructor.name]; const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; @@ -70,7 +69,6 @@ class Assertion extends TestResultReporter { } doesNotInclude(expectedObject, equalityCriteria) { - const convertToArray = builderArrayByType[this._actual.constructor.name]; const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; diff --git a/lib/utils.js b/lib/utils.js index aca3834b..82b74639 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -99,11 +99,15 @@ const arrayToArray = object => object; const setToArray = object => Array.from(object); const mapToArray = object => Array.from(object.values()); -const builderArrayByType = { - String: stringToArray, - Array: arrayToArray, - Set: setToArray, - Map: mapToArray, +const convertToArray = object => { + const conversionFunctions = { + String: stringToArray, + Array: arrayToArray, + Set: setToArray, + Map: mapToArray, + }; + const conversionFunction = conversionFunctions[object.constructor.name]; + return conversionFunction(object); }; module.exports = { @@ -127,5 +131,5 @@ module.exports = { resolvePathFor, allFilesMatching, // converts - builderArrayByType, + convertToArray, }; From fc8f2e83607c53b9d6cf92c56f58245c175e378c Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sat, 28 Nov 2020 13:09:04 -0300 Subject: [PATCH 34/64] :bug: set the right color for pending tests summary (fixes #173) --- lib/formatter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/formatter.js b/lib/formatter.js index e0fa3768..6bdca7e7 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -127,7 +127,7 @@ class Formatter { const passedCount = this._displayIfNonZero(runner.successCount(), this._translated('passed'), green); const failureCount = this._displayIfNonZero(runner.failuresCount(), this._translated('failed'), red); const errorCount = this._displayIfNonZero(runner.errorsCount(), this._translated('errors'), red); - const pendingCount = this._displayIfNonZero(runner.pendingCount(), this._translated('pending')); + const pendingCount = this._displayIfNonZero(runner.pendingCount(), this._translated('pending'), yellow); const skippedCount = this._displayIfNonZero(runner.skippedCount(), this._translated('skipped'), yellow); this._console.log(`${runner.totalCount()} test(s)${passedCount}${failureCount}${errorCount}${pendingCount}${skippedCount}`); if (runner.totalCount() === 0) { From bfb648f2d9c6f1900ba67499ce310ed73134759c Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 30 Nov 2020 00:09:42 -0300 Subject: [PATCH 35/64] :bug: show error message when marking as pending with an invalid reason (fixes #172) --- lib/asserter.js | 12 +++++++++++- lib/test.js | 7 ++----- lib/test_suite.js | 7 ++----- lib/translations.json | 6 ++++-- lib/utils.js | 7 +++---- .../reporting_failures_and_pending_tests_test.js | 10 +++++----- tests/core/test_suite_test.js | 4 ++-- tests/core/test_test.js | 4 ++-- tests/support/assertion_helpers.js | 6 ++++++ 9 files changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/asserter.js b/lib/asserter.js index 4886b42b..3be744aa 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -4,6 +4,8 @@ const TestResult = require('./test_result'); const TestResultReporter = require('./test_result_reporter'); const Assertion = require('./assertion'); +const { isStringWithContent } = require('./utils'); + class FailureGenerator extends TestResultReporter { with(description) { this.report(TestResult.failure(description || this.translated('explicitly_failed'))); @@ -12,7 +14,15 @@ class FailureGenerator extends TestResultReporter { class PendingMarker extends TestResultReporter { dueTo(reason) { - this.report(TestResult.explicitlyMarkedAsPending(reason)); + if (isStringWithContent(reason)) { + this.report(TestResult.explicitlyMarkedAsPending(reason)); + } else { + this.report(TestResult.error(this.invalidReasonErrorMessage())); + } + } + + invalidReasonErrorMessage() { + return this.translated('invalid_pending_reason'); } } diff --git a/lib/test.js b/lib/test.js index 2646cac3..a401fe5c 100644 --- a/lib/test.js +++ b/lib/test.js @@ -2,7 +2,7 @@ const TestResult = require('./test_result'); const FailFast = require('./fail_fast'); -const { isString, isStringNullOrWhiteSpace, isFunction, isUndefined } = require('./utils'); +const { isStringWithContent, isFunction, isUndefined } = require('./utils'); class Test { constructor(name, body, callbacks) { @@ -119,12 +119,9 @@ class Test { } _ensureNameIsValid(name) { - if (!isString(name)) { + if (!isStringWithContent(name)) { throw 'Test does not have a valid name'; } - if (isStringNullOrWhiteSpace(name)) { - throw 'Suite and test names cannot be empty'; - } } _ensureBodyIsValid(body) { diff --git a/lib/test_suite.js b/lib/test_suite.js index aac12de1..1144bc36 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -1,7 +1,7 @@ 'use strict'; const FailFast = require('./fail_fast'); -const { shuffle, isString, isUndefined, isStringNullOrWhiteSpace, isFunction } = require('./utils'); +const { shuffle, isUndefined, isStringWithContent, isFunction } = require('./utils'); class TestSuite { constructor(name, body, callbacks) { @@ -101,12 +101,9 @@ class TestSuite { } _ensureNameIsValid(name) { - if (!isString(name)) { + if (!isStringWithContent(name)) { throw 'Suite does not have a valid name'; } - if (isStringNullOrWhiteSpace(name)) { - throw 'Suite and test names cannot be empty'; - } } _ensureBodyIsValid(body) { diff --git a/lib/translations.json b/lib/translations.json index 619a586e..b1ede735 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -46,7 +46,8 @@ "running_tests_in": "Running tests in", "fail_fast": "Fail fast", "random_order": "Random order", - "starting_testy": "Starting Testy!" + "starting_testy": "Starting Testy!", + "invalid_pending_reason": "In order to mark a test as pending, you need to specify a reason." }, "es": { "yes": "sí", @@ -93,6 +94,7 @@ "running_tests_in": "Ejecutando tests en", "fail_fast": "Fallo rápido", "random_order": "Orden aleatorio", - "starting_testy": "Comenzando a ejecutar Testy!" + "starting_testy": "Comenzando a ejecutar Testy!", + "invalid_pending_reason": "Para marcar un test como pendiente, es necesario especificar un motivo." } } diff --git a/lib/utils.js b/lib/utils.js index 82b74639..cade69a3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -41,7 +41,6 @@ const allFilesMatching = (dir, regex, results = []) => { if (fs.lstatSync(dir).isFile()) { return dir.match(regex) ? [dir] : []; } - fs.readdirSync(dir).forEach(entry => results.push(...allFilesMatching(path.join(dir, entry), regex, results)), ); @@ -76,8 +75,8 @@ const isUndefined = object => const notNullOrUndefined = object => !isUndefined(object) && object !== null; -const isStringNullOrWhiteSpace = string => - string.replace(/\s/g, '').length < 1; +const isStringWithContent = string => + isString(string) && string.replace(/\s/g, '').length > 1; const respondsTo = (object, methodName) => notNullOrUndefined(object) && isFunction(object[methodName]); @@ -118,7 +117,7 @@ module.exports = { prettyPrint, // types isString, - isStringNullOrWhiteSpace, + isStringWithContent, isFunction, isUndefined, isRegex, diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js index 90703ca1..9f45ed7e 100644 --- a/tests/core/assertions/reporting_failures_and_pending_tests_test.js +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -1,7 +1,7 @@ 'use strict'; -const { suite, test, pending } = require('../../../testy'); -const { failGenerator, pendingMarker, expectFailureDueTo, expectPendingResultDueTo } = require('../../support/assertion_helpers'); +const { suite, test } = require('../../../testy'); +const { failGenerator, pendingMarker, expectErrorDueTo, expectFailureDueTo, expectPendingResultDueTo } = require('../../support/assertion_helpers'); suite('reporting failures and pending tests', () => { test('marking a test as explicitly failed with no message', () => { @@ -17,9 +17,9 @@ suite('reporting failures and pending tests', () => { }); test('marking a test as pending with no message', () => { - pending.dueTo('Need to fix this bug: https://github.com/ngarbezza/testy/issues/172'); - // pendingMarker.dueTo(); - // expectErrorDueTo('aaaaaa'); + pendingMarker.dueTo(); + + expectErrorDueTo('In order to mark a test as pending, you need to specify a reason.'); }); test('marking a test as pending with a custom message', () => { diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index 9ef98477..a5461fc3 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -96,8 +96,8 @@ suite('test suite behavior', () => { assert.that(() => new TestSuite()).raises('Suite does not have a valid name'); }); - test('a suite cannot be created with name empty', () => { - assert.that(() => new TestSuite('')).raises('Suite and test names cannot be empty'); + test('a suite cannot be created with an empty name', () => { + assert.that(() => new TestSuite(' ')).raises('Suite does not have a valid name'); }); test('a suite cannot be created with a name that is not a string', () => { diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 2fe5e871..7cb8b1b4 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -34,7 +34,7 @@ suite('tests behavior', () => { assert.that(() => new Test('hey', 'ho')).raises('Test does not have a valid body'); }); - test('a test cannot be created with name empty', () => { - assert.that(() => new Test('', undefined)).raises('Suite and test names cannot be empty'); + test('a test cannot be created with an empty name', () => { + assert.that(() => new Test(' ', undefined)).raises('Test does not have a valid name'); }); }); diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index 3e074651..aa1d2a12 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -32,6 +32,11 @@ const expectFailureDueTo = failureMessage => { fakeRunner.reset(); }; +const expectErrorDueTo = failureMessage => { + expectErrorOn(fakeRunner, failureMessage); + fakeRunner.reset(); +}; + const expectPendingResultDueTo = reason => { assert.isTrue(fakeRunner.result().isPending()); assert.areEqual(fakeRunner.result().reason(), reason); @@ -54,6 +59,7 @@ module.exports = { pendingMarker, expectSuccess, expectFailureDueTo, + expectErrorDueTo, expectPendingResultDueTo, expectFailureOn, expectErrorOn, From b07df6189b678ab387b10f2450b7276899cc3607 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 30 Nov 2020 00:24:54 -0300 Subject: [PATCH 36/64] :pencil: update badges and remove v4 docs --- README.md | 5 +- README_es.md | 5 +- README_v4.md | 234 ------------------------------------------------ README_v4_es.md | 234 ------------------------------------------------ 4 files changed, 4 insertions(+), 474 deletions(-) delete mode 100644 README_v4.md delete mode 100644 README_v4_es.md diff --git a/README.md b/README.md index bd69840b..6e23223a 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,13 @@ ![dependencies](https://img.shields.io/david/ngarbezza/testy.svg?logo=dependabot) \ ![package-size](https://img.shields.io/bundlephobia/min/@pmoo/testy.svg?logo=npm) -![activity](https://img.shields.io/github/commit-activity/w/ngarbezza/testy.svg?logo=npm) +![activity](https://img.shields.io/github/commit-activity/m/ngarbezza/testy?logo=npm) ![release-date](https://img.shields.io/github/release-date/ngarbezza/testy.svg?logo=npm) \ -[![all-contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?logo=open-source-initiative)](#contributors) +[![all-contributors](https://img.shields.io/github/all-contributors/ngarbezza/testy?logo=open-source-initiative)](#Contributors) A very simple JS testing framework, for educational purposes. Live at npm at [@pmoo/testy](https://www.npmjs.com/package/@pmoo/testy). -:arrow_right: [v4 (legacy version) documentation here](README_v4.md) \ :arrow_right: [Documentación en español aquí](README_es.md) ## Getting started diff --git a/README_es.md b/README_es.md index 7e754cd4..be6eaeb4 100644 --- a/README_es.md +++ b/README_es.md @@ -15,14 +15,13 @@ ![dependencies](https://img.shields.io/david/ngarbezza/testy.svg?logo=dependabot) \ ![package-size](https://img.shields.io/bundlephobia/min/@pmoo/testy.svg?logo=npm) -![activity](https://img.shields.io/github/commit-activity/w/ngarbezza/testy.svg?logo=npm) +![activity](https://img.shields.io/github/commit-activity/m/ngarbezza/testy?logo=npm) ![release-date](https://img.shields.io/github/release-date/ngarbezza/testy.svg?logo=npm) \ -[![all-contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?logo=open-source-initiative)](#Contribuyentes) +[![all-contributors](https://img.shields.io/github/all-contributors/ngarbezza/testy?logo=open-source-initiative)](#Contribuyentes) Una simple herramienta de testeo en Javascript, para propósitos educativos. Disponible en npm: [@pmoo/testy](https://www.npmjs.com/package/@pmoo/testy). -:arrow_right: [Documentación de v4 (versión anterior) aquí](README_v4_es.md) \ :arrow_right: [English version here](README.md) ## Para comenzar diff --git a/README_v4.md b/README_v4.md deleted file mode 100644 index 1638307b..00000000 --- a/README_v4.md +++ /dev/null @@ -1,234 +0,0 @@ -# Testy - -![ci](https://img.shields.io/github/workflow/status/ngarbezza/testy/Node%20CI/develop?logo=github) -\ -[![maintainability](https://img.shields.io/codeclimate/maintainability/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -[![tech-debt](https://img.shields.io/codeclimate/tech-debt/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -[![coverage](https://img.shields.io/codeclimate/coverage/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -\ -![open-issues](https://img.shields.io/github/issues-raw/ngarbezza/testy?logo=github) -![closed-issues](https://img.shields.io/github/issues-closed-raw/ngarbezza/testy?logo=github) -![open-prs](https://img.shields.io/github/issues-pr-raw/ngarbezza/testy?logo=github) - \ -![vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/@pmoo/testy.svg?logo=npm) -![downloads](https://img.shields.io/npm/dt/@pmoo/testy.svg?logo=npm) -![dependencies](https://img.shields.io/david/ngarbezza/testy.svg?logo=dependabot) -\ -![package-size](https://img.shields.io/bundlephobia/min/@pmoo/testy.svg?logo=npm) -![activity](https://img.shields.io/github/commit-activity/w/ngarbezza/testy.svg?logo=npm) -![release-date](https://img.shields.io/github/release-date/ngarbezza/testy.svg?logo=npm) -\ -[![all-contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?logo=open-source-initiative)](#contributors) - -A very simple JS testing framework, for educational purposes. Live at npm at [@pmoo/testy](https://www.npmjs.com/package/@pmoo/testy). - -:warning: **This version is deprecated. [See current documentation here](README.md)** :warning: - -:arrow_right: [Documentación en español aquí](README_v4_es.md) - -## Installation - -`npm install --save-dev @pmoo/testy` - -**Supported Node versions**: 8.x or higher - -## Usage - -### Writing test suites - -A test suite is a file that looks like this: - -```javascript -const { suite, test, assert } = require('@pmoo/testy'); - -suite('a boring test suite', () => { - test('42 is 42, not surprising', () => { - assert.that(42).isEqualTo(42); - }); -}); -``` - -A test suite represents a grouping of tests and it is implemented as a function call to `suite` passing a name and a zero-argument function, which is the suite body. - -A test is implemented as a function call to `test()`, passing a name and the test body as a zero-argument function. - -Inside the test you can call different assertions that are documented in detail later on. - -### Setting up the configuration - -This is the recommended setup. Add a file `tests.js` (or whatever name you like) with the following content: - -```javascript -const { Testy } = require('@pmoo/testy'); - -Testy.configuredWith({ - // Relative or absolute path to all the test files. Default './tests' - directory: './tests', - // Regular expression to filter test files to run - filter: /.*test.js$/, - // 'en' is the default. For example, you can try 'es' to see output in Spanish - language: 'en', - // Stops at the first failed or errored test. false by default - failFast: false, - // Enforces randomness in the tests inside each suite. false by default - randomOrder: false, -}).run(); -``` - -These are all the configuration parameters you can set. Feel free to change it according to your needs. -When declaring this configuration, every test suite under the `tests` directory (matching files ending with `*test.js`) will be executed. - -### Running Testy - -Assuming you created `tests.js` with the Testy configuration, you can run it with: - -``` -$ node tests.js -``` - -Or you can add it as the `test` script for npm in your `package.json`: - -``` -{ - ... - "scripts": { - "test": "node tests.js" - }, - ... -} -``` - -And then run the tests using: - -``` -$ npm test -``` - -### Running a single file - -**Note:** this could be good for prototyping or running small examples but it is not the recommended setup. It will be deprecated at some point. - -```javascript -const { suite, test, assert } = require('@pmoo/testy'); - -suite('a boring test suite', () => { - test('true is obviously true', () => assert.isTrue(true)) -}).run(); -``` - -(notice the `run()` at the end) - -### Examples and available assertions - -* Boolean assertions: - * `assert.that(boolean).isTrue()` or `assert.isTrue(boolean)`. It does a strict comparison against `true` (`object === true`) - * `assert.that(boolean).isFalse()` or `assert.isFalse(boolean)`. It does a strict comparison against `false` (`object === false`) -* Equality assertions: - * `assert.that(actual).isEqualTo(expected)` or `assert.areEqual(actual, expected)`. - * `assert.that(actual).isNotEqualTo(expected)` or `assert.areNotEqual(actual, expected)` - * Equality assertions use a deep object comparison (based on Node's `assert` module) and fail if objects under comparison have circular references. - * Equality criteria on non-primitive objects can be specified: - * Passing an extra two-arg comparator function to `isEqualTo(expected, criteria)` or `areEqual(actual, expected, criteria)` - * Passing a method name that `actual` object understands: `isEqualTo(expected, 'myEqMessage')` or `areEqual(actual, expected, 'myEqMessage')` - * By default, if `actual` has an `equals` method it will be used. - * If we compare `undefined` with `undefined` using `isEqualTo()`, it will make the test fail. For explicit check for `undefined`, use the `isUndefined()`/`isNotUndefined()` assertions documented above. -* Check for `undefined` presence/absence: - * `assert.that(aValue).isUndefined()` or `assert.isUndefined(aValue)` - * `assert.that(aValue).isNotUndefined()` or `assert.isNotUndefined(aValue)` -* Check for `null` presence/absence: - * `assert.that(aValue).isNull()` or `assert.isNull(aValue)` - * `assert.that(aValue).isNotNull()` or `assert.isNotNull(aValue)` -* Exception testing: - * `assert.that(() => { ... }).raises(error)` or with regex `.raises(/part of message/)` - * `assert.that(() => { ... }).doesNotRaise(error)` - * `assert.that(() => { ... }).doesNotRaiseAnyErrors()` -* Numeric assertions: - * `assert.that(aNumber).isNearTo(anotherNumber)`. There's a second optional argument that indicates the number of digits to be used for precision. Default is `4`. -* String assertions: - * `assert.that(string).matches(regexOrString)` or `assert.isMatching(string, regexOrString)` -* Array inclusion: - * `assert.that(collection).includes(object)` - * `assert.that(collection).doesNotInclude(object)` - * `assert.that(collection).includesExactly(...objects)` -* Emptiness - * `assert.that(collection).isEmpty()` or `assert.isEmpty(collection)` - * `assert.that(collection).isNotEmpty()` or `assert.isNotEmpty(collection)` - * the collection under test can be an `Array`, a `String` or a `Set` - -Please take a look at the `tests` folder, you'll find examples of each possible test you can write. Testy is self-tested. - -### Other features - -* **Running code before every test**: just like many testing frameworks have, there is a way to execute some code before every test in a suite using the `before()` function. Example: - - ```javascript - const { suite, test, before, assert } = require('@pmoo/testy'); - - suite('using the before() helper', () => { - let answer; - - before(() => { - answer = 42; - }); - - test('checking the answer', () => { - assert.that(answer).isEqualTo(42); - }); - }); - ``` -* **Support for pending tests**: if a test has no body, it will be reported as `[WIP]` and it won't be considered a failure. -* **Fail-Fast mode**: if enabled, it stops execution in the first test that fails (or has an error). Remaining tests will be marked as skipped. -* **Run tests and suites in random order**: a good test suite does not depend on a particular order. Enabling this setting is a good way to ensure that. -* **Strict check for assertions**: if a test does not evaluate any assertion while it is executed, the result is considered an error. Basically, a test with no assertion is considered a "bad" test. -* **Explicitly failing or marking a test as pending**: there's a possibility of marking a test as failed or pending, for example: - - ```javascript - const { suite, test, fail, pending } = require('@pmoo/testy'); - - suite('marking tests as failed and pending', () => { - test('marking as failed', () => - fail.with('should not be here')); - - test('marking as pending', () => - pending.dueTo('did not have time to finish')); - }); - ``` - - The output includes the messages provided: - ``` - [FAIL] marking as failed - => should not be here - [WIP] marking as pending - => did not have time to finish - ``` - -## Why? - -Why another testing tool? The main reason is that we want to keep simplicity, something it's hard to see in the main testing tools out there. - -* **Zero dependencies:** right now, this project does not depend on any npm package, making the tool easy to install, and fast: essential to have immediate feedback while doing TDD. This is also good for installing on places where the internet connection is not good and we don't want to download hundreds of libraries. -* **Understandable object-oriented code:** we want to use this tool for teaching, so eventually we'll look at the code during lessons, and students should be able to see what is going on, and even contributing at it, with no dark magic involved. Also, we try to follow good OO practices. -* **Unique set of features:** we are not following any specification nor trying to copy behavior from other approaches (like the "xUnit" or "xSpec" way). - -["Design Principles Behind Smalltalk"](https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html) is a source of inspiration for this work. We try to follow the same principles here. - -## Contributing - -Please take a look at the [Contributing section](CONTRIBUTING.md). - -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - -
Facundo Javier Gelatti
Facundo Javier Gelatti

⚠️ 💻
Tomer Ben-Rachel
Tomer Ben-Rachel

⚠️ 💻
- - - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/README_v4_es.md b/README_v4_es.md deleted file mode 100644 index 9810841a..00000000 --- a/README_v4_es.md +++ /dev/null @@ -1,234 +0,0 @@ -# Testy - -![ci](https://img.shields.io/github/workflow/status/ngarbezza/testy/Node%20CI/develop?logo=github) -\ -[![maintainability](https://img.shields.io/codeclimate/maintainability/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -[![tech-debt](https://img.shields.io/codeclimate/tech-debt/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -[![coverage](https://img.shields.io/codeclimate/coverage/ngarbezza/testy?logo=code-climate)](https://codeclimate.com/github/ngarbezza/testy) -\ -![open-issues](https://img.shields.io/github/issues-raw/ngarbezza/testy?logo=github) -![closed-issues](https://img.shields.io/github/issues-closed-raw/ngarbezza/testy?logo=github) -![open-prs](https://img.shields.io/github/issues-pr-raw/ngarbezza/testy?logo=github) - \ -![vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/@pmoo/testy.svg?logo=npm) -![downloads](https://img.shields.io/npm/dt/@pmoo/testy.svg?logo=npm) -![dependencies](https://img.shields.io/david/ngarbezza/testy.svg?logo=dependabot) -\ -![package-size](https://img.shields.io/bundlephobia/min/@pmoo/testy.svg?logo=npm) -![activity](https://img.shields.io/github/commit-activity/w/ngarbezza/testy.svg?logo=npm) -![release-date](https://img.shields.io/github/release-date/ngarbezza/testy.svg?logo=npm) -\ -[![all-contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?logo=open-source-initiative)](#contribuyentes) - -Una simple herramienta de testeo en Javascript, para propósitos educativos. Disponible en npm: [@pmoo/testy](https://www.npmjs.com/package/@pmoo/testy). - -:warning: **Esta versión se encuentra deprecada. [Ver la documentación de la versión actual aquí](README_es.md)** :warning: - -:arrow_right: [English version here](README_v4.md) - -## Instalación - -`npm install --save-dev @pmoo/testy` - -**Versiones de Node soportadas**: 8.x o mayor - -## Uso - -### Escribiendo suites de test - -Una suite de test no es más que un archivo de la siguiente forma: - -```javascript -const { suite, test, assert } = require('@pmoo/testy'); - -suite('una suite de tests aburrida', () => { - test('42 es 42, no nos sorprende', () => { - assert.that(42).isEqualTo(42); - }); -}); -``` - -Una suite representa un agrupamiento de tests, y se define llamando a la función `suite(name, body)`, que toma como parámetro el nombre de este agrupamiendo y una función sin argumentos, que representa el contenido de la suite. - -Un test se escribe llamando a la función `test(name, body)`, que toma como parámetro el nombre del caso de test y una función sin parámetros que representa el cuerpo del test. - -Dentro del test se pueden evaluar diferentes aserciones que están documentadas más adelante. - -### Configurando Testy - -Esta es la configuración recomendada. Agrega un archivo `tests.js` (o el nombre que gustes) con el siguiente contenido: - -```javascript -const { Testy } = require('@pmoo/testy'); - -Testy.configuredWith({ - // ruta absoluta o relativa a la carpeta en donde vamos a ubicar nuestras suites de tests - directory: './tests', - // una expresión regular para indicar qué archivos se deben interpretar como suites de tests - filter: /.*test.js$/, - // idioma de los mensajes de salida. 'en' (inglés) es el valor por defecto; 'es' para Español es también una opción posible - language: 'en', - // Cuando es true, se detiene apenas encuentra un test que no pasa. Por defecto, es false - failFast: false, - // Fuerza a que los tests se ejecuten en un orden aleatorio. Por defecto, es false - randomOrder: false, -}).run(); -``` - -Estos son todos los parámetros de configuración que existen, ajustalos de acuerdo a tus necesidades. -Siguiendo este ejemplo de configuración, lo que se va a ejecutar es cada suite de test dentro del directorio `tests`, cuyos nombres de archivos finalicen con `*test.js`. - -### Ejecutando Testy - -Asumiendo que en el archivo `tests.js` tenemos la configuración de Testy creada anteriorente, podemos ejecutar los tests con: - -``` -$ node tests.js -``` - -Además se puede agregar como script de `test` para tu `package.json`: - -``` -{ - ... - "scripts": { - "test": "node tests.js" - }, - ... -} -``` - -Y luego ejecutar los tests utilizando `npm`: - -``` -$ npm test -``` - -### Ejecutar un único archivo de suite - -**Nota:** esto puede ser util para ejecutar ejemplos pequeños o realizar pruebas rápidas; no es la configuración recomendada, en próximas versiones será deprecado. - -```javascript -const { suite, test, assert } = require('@pmoo/testy'); - -suite('una suite aburrida', () => { - test('true es obviamente true', () => assert.isTrue(true)) -}).run(); -``` - -(es similar al ejemplo inicial, pero con un `run()` al final) - -### Ejemplos y aserciones disponibles - -* Aserciones sobre valores booleanos: - * `assert.that(boolean).isTrue()` o `assert.isTrue(boolean)`. Realiza una comparación estricta contra `true` (`object === true`) - * `assert.that(boolean).isFalse()` o `assert.isFalse(boolean)`. Realiza una comparación estricta contra `false` (`object === false`) -* Aserciones de igualdad de objetos: - * `assert.that(actual).isEqualTo(expected)` o `assert.areEqual(actual, expected)`. - * `assert.that(actual).isNotEqualTo(expected)` o `assert.areNotEqual(actual, expected)` - * Las aserciones de igualdad utilizan una comparación (_deep_) basada en el módulo `assert` de Node, y falla si los objetos que están siendo comparados tienen referencias cíclicas. - * El criterio de igualdad en objetos no primitivos puede ser especificado: - * Pasando una función adicional de comparación de dos parámetros a `isEqualTo(expected, criteria)` o `areEqual(actual, expected, criteria)` - * Pasando un nombre de método que el objeto `actual` comprenda: `isEqualTo(expected, 'myEqMessage')` o `areEqual(actual, expected, 'myEqMessage')` - * Por defecto, si `actual` entiende el mensaje `equals`, será utilizado para determinar la comparación - * Si comparamos `undefined` con `undefined` usando `isEqualTo()`, el test fallará. Para chequear explícitamente por el valor `undefined`, se debe utilizar las aserciones `isUndefined()` o `isNotUndefined()` documentadas más adelante. -* Validar si un objeto es o no `undefined`: - * `assert.that(aValue).isUndefined()` o `assert.isUndefined(aValue)` - * `assert.that(aValue).isNotUndefined()` o `assert.isNotUndefined(aValue)` -* Validar si un objeto es o no `null`: - * `assert.that(aValue).isNull()` o `assert.isNull(aValue)` - * `assert.that(aValue).isNotNull()` o `assert.isNotNull(aValue)` -* Testeo de errores: - * `assert.that(() => { ... }).raises(error)` o con una expresión regular `.raises(/part of message/)` - * `assert.that(() => { ... }).doesNotRaise(error)` - * `assert.that(() => { ... }).doesNotRaiseAnyErrors()` -* Aserciones numéricas: - * `assert.that(aNumber).isNearTo(anotherNumber)`. Se puede pasar un segundo parámetro adicional que indica el número de dígitos de precisión que se van a considerar. Por defecto, son `4`. -* Aserciones sobre strings: - * `assert.that(string).matches(regexOrString)` o `assert.isMatching(string, regexOrString)` -* Inclusión de objetos en colecciones (`Array` y `Set`): - * `assert.that(collection).includes(object)` - * `assert.that(collection).doesNotInclude(object)` - * `assert.that(collection).includesExactly(...objects)` -* Verificar si una colección es o no vacía: - * `assert.that(collection).isEmpty()` or `assert.isEmpty(collection)` - * `assert.that(collection).isNotEmpty()` or `assert.isNotEmpty(collection)` - * la colección a verificar puede ser un `Array`, un `String` o un `Set` - -En la carpeta `tests` podrás encontrar más ejemplos y todas las posibles aserciones que puedes escribir. Testy está testeado en sí mismo. - -### Otras funcionalidades - -* **Ejecutar código antes de cada test**: como todas las bibliotecas y frameworks de testing poseen, existe una forma de ejecutar un código siempre antes dde cada test en una suite utilizando la función `before()` Ejemplo: - - ```javascript - const { suite, test, before, assert } = require('@pmoo/testy'); - - suite('usando la función before()', () => { - let answer; - - before(() => { - answer = 42; - }); - - test('la respuesta es 42', () => { - assert.that(answer).isEqualTo(42); - }); - }); - ``` -* **Soporte para tests "pendientes"**: Un test que no tenga cuerpo, será reportado como pendiente (`[WIP]`) y no se considerará una falla. -* **Modo "fail-fast"**: Cuando está habilitado, se detiene apenas encuentra un test que falle o lance un error. Los tests restantes serán marcados como no ejecutados (_skipped_). -* **Ejecutar tests en orden aleatorio**: Una buena suite de tests no depende de un orden particular de tests para ejecutarse correctamentee. Activando esta configuración es una buena forma de asegurar eso. -* **Chequeo estricto de presencia de aserciones**: Si un test no evalúa ninguna aserción durante su ejecución, el resultado se considera un error. Básicamente, un test que no tiene aserciones es un "mal" test. -* **Explícitamente marcar un test como fallido o pendiente**: Ejemplos: - - ```javascript - const { suite, test, fail, pending } = require('@pmoo/testy'); - - suite('marcando tests explícitamente como fallidos o pendientes', () => { - test('marcando como fallido', () => - fail.with('no debería estar aquí')); - - test('marcando como pendiente', () => - pending.dueTo('no hubo tiempo de finalizarlo')); - }); - ``` - - Al ejecutar veremos los siguientes mensajes: - ``` - [FAIL] marcando como fallido - => no debería estar aquí - [WIP] marcando como pendiente - => no hubo tiempo de finalizarlo - ``` - -## ¿Por qué? - -¿Por qué tener una herramienta de tests cuando ya existen otras? La razón principal es que deseamos mantener la simplicidad, algo que no se puede encontrar en las principales herramientas de testing conocidas. - -* **Cero dependencias:** Este proyecto no depende de ningún otro paquete de npm para funcionar, lo que facilita su instalación, y lo hace más rápido: esencial para obtener feedback inmediato desarrollando con TDD. Esto es algo bueno también para instalar en lugares donde la conexión a internet no es buena y no queremos perder tiempo descargando múltiples dependencias. -* **Código orientado a objetos entendible:** Esta herramienta es utilizada para enseñar, así que es muy común durante las clases mirar el código para entender cómo se ejecutan los tests, para entender lo que sucede. El objetivo es que los alumnos puedan comprender la herramienta e incluso realizar contribuciones a ella. Intentamos seguir buenas prácticas de diseño con objetos y de _clean code_ en general. -* **Conjunto único de funcionalidad:** Esta herramienta no sigue ninguna especificación ni trata de copiar la funcionalidad de enfoques conocidos de testing (como la forma "xUnit" la forma "xSpec"). La funcionalidad que existe, es la que tiene sentido que esté. - -["Design Principles Behind Smalltalk"](https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html) es una gran fuente de inspiración para este trabajo. Intentamos seguir los mismos principios aquí. - -## Para contribuir - -Por favor revisar la [guía para contribuciones](CONTRIBUTING_es.md). - -## Contribuyentes ✨ - -Muchas gracias a estas maravillosas personas ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - -
Facundo Javier Gelatti
Facundo Javier Gelatti

⚠️ 💻
Tomer Ben-Rachel
Tomer Ben-Rachel

⚠️ 💻
- - - -Este proyecto sigue la convención de [all-contributors](https://github.com/all-contributors/all-contributors). Se aceptan contribuciones de todo tipo! From 0cbf1db899b36c48252f429b525462f77961fc74 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 18:34:50 -0300 Subject: [PATCH 37/64] :recycle: introduce the context object to abstract details of configurations --- lib/test.js | 5 ++--- lib/test_result.js | 26 +++++++++++++------------- lib/test_runner.js | 5 +++-- lib/test_suite.js | 12 +++++------- tests/core/test_test.js | 4 +++- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/test.js b/lib/test.js index a401fe5c..5a05ee9d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,7 +1,6 @@ 'use strict'; const TestResult = require('./test_result'); -const FailFast = require('./fail_fast'); const { isStringWithContent, isFunction, isUndefined } = require('./utils'); class Test { @@ -14,8 +13,8 @@ class Test { // Execution - run(failFastMode = FailFast.default()) { - TestResult.evaluate(this, failFastMode); + run(context) { + TestResult.evaluate(this, context); } evaluate() { diff --git a/lib/test_result.js b/lib/test_result.js index c6f1628b..ecb31dba 100644 --- a/lib/test_result.js +++ b/lib/test_result.js @@ -21,10 +21,10 @@ class TestResult { // checking for test status - static evaluate(test, failFastMode) { + static evaluate(test, context) { return [SkippedTest, TestWithoutDefinition, TestWithDefinition] - .find(result => result.canHandle(test, failFastMode)) - .handle(test, failFastMode); + .find(result => result.canHandle(test, context)) + .handle(test, context); } // statuses @@ -55,8 +55,8 @@ class TestResult { } class SkippedTest extends TestResult { - static canHandle(test, failFastMode) { - return failFastMode.hasFailed(); + static canHandle(test, context) { + return context.failFastMode.hasFailed(); } static handle(test) { @@ -87,12 +87,12 @@ class TestWithDefinition extends TestResult { return test.hasDefinition(); } - static handle(test, failFastMode) { + static handle(test, context) { test.evaluate(); const possibleResults = [TestWithoutAssertion, TestErrored, TestExplicitlyMarkedPending, TestSucceeded, TestFailed]; possibleResults .find(result => result.canHandle(test)) - .handle(test, failFastMode); + .handle(test, context); } } @@ -128,8 +128,8 @@ class TestErrored extends TestWithDefinition { return test.isError(); } - static handle(test, failFastMode) { - failFastMode.registerFailure(); + static handle(test, context) { + context.failFastMode.registerFailure(); test.finishWithError(); } @@ -152,9 +152,9 @@ class TestWithoutAssertion extends TestErrored { return test.hasNoResult(); } - static handle(test, failFastMode) { + static handle(test, context) { test.setResult(new this()); - super.handle(test, failFastMode); + super.handle(test, context); } constructor() { @@ -181,8 +181,8 @@ class TestFailed extends TestWithDefinition { return true; } - static handle(test, failFastMode) { - failFastMode.registerFailure(); + static handle(test, context) { + context.failFastMode.registerFailure(); test.finishWithFailure(); } diff --git a/lib/test_runner.js b/lib/test_runner.js index 33aa21ee..029d5153 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -16,7 +16,7 @@ class TestRunner { // Configuration - registerSuite(name, suiteBody = () => {}, callbacks = {}) { + registerSuite(name, suiteBody, callbacks) { const suiteToAdd = new TestSuite(name, suiteBody, callbacks); return this.addSuite(suiteToAdd); } @@ -58,7 +58,8 @@ class TestRunner { this._randomizeSuites(); this.suites().forEach(suite => { this._setCurrentSuite(suite); - suite.run(this._failFastMode, this._randomOrder); + const context = { failFastMode: this._failFastMode, randomOrderMode: this._randomOrder }; + suite.run(context); }); this._callbacks.onFinish(this); } diff --git a/lib/test_suite.js b/lib/test_suite.js index 1144bc36..f2c46f52 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -1,6 +1,5 @@ 'use strict'; -const FailFast = require('./fail_fast'); const { shuffle, isUndefined, isStringWithContent, isFunction } = require('./utils'); class TestSuite { @@ -37,10 +36,10 @@ class TestSuite { // Executing - run(failFastMode = FailFast.default(), randomOrderMode = false) { + run(context) { this._callbacks.onStart(this); this._evaluateSuiteDefinition(); - this._runTests(failFastMode, randomOrderMode); + this._runTests(context); this._callbacks.onFinish(this); } @@ -130,15 +129,14 @@ class TestSuite { this._after && this._after.call(); } - // TODO: reify configuration instead of many boolean flags - _runTests(failFastMode, randomOrderMode) { - if (randomOrderMode) { + _runTests(context) { + if (context.randomOrder) { this._randomizeTests(); } this.tests().forEach(test => { this._currentTest = test; this._evaluateBeforeBlock(); - test.run(failFastMode); + test.run(context); this._evaluateAfterBlock(); }); } diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 7cb8b1b4..277a118d 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -2,14 +2,16 @@ const { suite, test, assert } = require('../../testy'); const Test = require('../../lib/test'); +const FailFast = require('../../lib/fail_fast'); const { aTestWithNoAssertions } = require('../support/tests_factory'); const { expectErrorOn } = require('../support/assertion_helpers'); suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { const testToRun = aTestWithNoAssertions(); + const context = { failFastMode: FailFast.default() }; - testToRun.run(); + testToRun.run(context); expectErrorOn(testToRun, 'This test does not have any assertions'); }); From 4e9629e189100c69622005ae2520335dccff6244 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 20:29:19 -0300 Subject: [PATCH 38/64] :bug: fix isStringWithContent to take into account single-char strings --- lib/utils.js | 2 +- tests/utils_test.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index cade69a3..cfa031ee 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -76,7 +76,7 @@ const notNullOrUndefined = object => !isUndefined(object) && object !== null; const isStringWithContent = string => - isString(string) && string.replace(/\s/g, '').length > 1; + isString(string) && string.replace(/\s/g, '').length > 0; const respondsTo = (object, methodName) => notNullOrUndefined(object) && isFunction(object[methodName]); diff --git a/tests/utils_test.js b/tests/utils_test.js index 586ccd3b..4e40cfb7 100644 --- a/tests/utils_test.js +++ b/tests/utils_test.js @@ -102,4 +102,16 @@ suite('utility functions', () => { assert.isFalse(Utils.isUndefined('')); assert.isFalse(Utils.isUndefined([])); }); + + test('isStringWithContent() is true for a string containing one or more characters', () => { + assert.isTrue(Utils.isStringWithContent('a')); + assert.isTrue(Utils.isStringWithContent('an')); + assert.isTrue(Utils.isStringWithContent('an object')); + }); + + test('isStringWithContent() is false for an empty string or string containing only separators', () => { + assert.isFalse(Utils.isStringWithContent('')); + assert.isFalse(Utils.isStringWithContent(' ')); + assert.isFalse(Utils.isStringWithContent(' ')); + }); }); From ca0336ab6241b7822b8565c61ac1f5627dbc753d Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 20:36:12 -0300 Subject: [PATCH 39/64] :recycle: introduce a new way to test tests using an actual runner instead of a fake one --- tests/core/test_test.js | 30 +++++++++++++++++++++--------- tests/support/assertion_helpers.js | 22 ++++++++++++++++++++++ tests/support/tests_factory.js | 8 +++++--- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 277a118d..0b198eb7 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -2,18 +2,17 @@ const { suite, test, assert } = require('../../testy'); const Test = require('../../lib/test'); -const FailFast = require('../../lib/fail_fast'); -const { aTestWithNoAssertions } = require('../support/tests_factory'); -const { expectErrorOn } = require('../support/assertion_helpers'); +const { aTestWithNoAssertions, aTestWithBody } = require('../support/tests_factory'); +const { withRunner, runSingleTest, expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { - const testToRun = aTestWithNoAssertions(); - const context = { failFastMode: FailFast.default() }; - - testToRun.run(context); - - expectErrorOn(testToRun, 'This test does not have any assertions'); + withRunner(runner => { + const testToRun = aTestWithNoAssertions(); + runSingleTest(runner, testToRun); + + expectErrorOn(testToRun, 'This test does not have any assertions'); + }); }); test('a test cannot be created without a name', () => { @@ -39,4 +38,17 @@ suite('tests behavior', () => { test('a test cannot be created with an empty name', () => { assert.that(() => new Test(' ', undefined)).raises('Test does not have a valid name'); }); + + test('a test fails on the first assertion failed', () => { + withRunner((runner, asserter) => { + const test = aTestWithBody(() => { + asserter.isNotEmpty([]); + asserter.areEqual(2, 3); + }); + + runSingleTest(runner, test); + + expectFailureOn(test, 'Expected [] to be not empty'); + }); + }); }); diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index aa1d2a12..8f3a0834 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -3,6 +3,8 @@ const { assert } = require('../../testy'); const { Asserter, FailureGenerator, PendingMarker } = require('../../lib/asserter'); const TestResult = require('../../lib/test_result'); +const TestRunner = require('../../lib/test_runner'); +const TestSuite = require('../../lib/test_suite'); const I18n = require('../../lib/i18n'); const fakeRunner = { @@ -53,7 +55,27 @@ const expectErrorOn = (test, errorMessage) => { assert.areEqual(test.result().failureMessage(), errorMessage); }; +const runSingleTest = (runner, test) => { + const noop = () => {}; + const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; + const suite = new TestSuite(`suite for ${test.name()}`, noop, emptySuiteCallbacks); + suite.addTest(test); + runner.addSuite(suite); + runner.run(); + return test.result(); +}; + +const withRunner = testBlock => { + const noop = () => {}; + const emptyRunnerCallbacks = { onFinish: noop }; + const runner = new TestRunner(emptyRunnerCallbacks); + const asserter = new Asserter(runner); + testBlock(runner, asserter); +}; + module.exports = { + withRunner, + runSingleTest, asserter, failGenerator, pendingMarker, diff --git a/tests/support/tests_factory.js b/tests/support/tests_factory.js index c7bbaa34..02fb8ebd 100644 --- a/tests/support/tests_factory.js +++ b/tests/support/tests_factory.js @@ -26,11 +26,13 @@ const aPendingTest = () => new Test('a work in progress', undefined, emptyTestCallbacks); const aTestWithNoAssertions = () => - new Test('wrong', () => { - return 1 + 2; - }, emptyTestCallbacks); + aTestWithBody(() => 1 + 2); + +const aTestWithBody = body => + new Test('just a test', body, emptyTestCallbacks); module.exports = { + aTestWithBody, aPassingTest, aFailingTest, anErroredTest, From d8f0ef25f52d36387d5395af90ed7610937bdad8 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 20:47:46 -0300 Subject: [PATCH 40/64] :recycle: separate runner helpers from assertion helpers --- tests/core/test_suite_test.js | 35 ++++++++++-------------------- tests/core/test_test.js | 3 ++- tests/support/assertion_helpers.js | 22 ------------------- tests/support/runner_helpers.js | 28 ++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 46 deletions(-) create mode 100644 tests/support/runner_helpers.js diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index a5461fc3..c5d3781f 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -1,42 +1,31 @@ 'use strict'; -const { suite, test, before, after, assert } = require('../../testy'); +const { suite, test, before, assert } = require('../../testy'); const TestSuite = require('../../lib/test_suite'); -const { Asserter } = require('../../lib/asserter'); -const TestRunner = require('../../lib/test_runner'); +const { withRunner } = require('../support/runner_helpers'); const FailFast = require('../../lib/fail_fast'); const { aPassingTest, aFailingTest, anErroredTest, aPendingTest } = require('../support/tests_factory'); const noop = () => {}; -const emptyRunnerCallbacks = { onFinish: noop }; const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; const newEmptySuite = () => suiteNamed('myTestSuite'); const suiteNamed = suiteName => new TestSuite(suiteName, () => {}, emptySuiteCallbacks); suite('test suite behavior', () => { - let runner, asserter, mySuite; + let runner, mySuite; let passingTest, failingTest, erroredTest, pendingTest; before(() => { - runner = new TestRunner(emptyRunnerCallbacks); - asserter = new Asserter(runner); - mySuite = newEmptySuite(); - runner.addSuite(mySuite); - passingTest = aPassingTest(asserter); - failingTest = aFailingTest(asserter); - erroredTest = anErroredTest(); - pendingTest = aPendingTest(); - }); - - after(() => { - runner = undefined; - asserter = undefined; - mySuite = undefined; - passingTest = undefined; - failingTest = undefined; - erroredTest = undefined; - pendingTest = undefined; + withRunner((runnerToUse, asserterToUse) => { + runner = runnerToUse; + mySuite = newEmptySuite(); + runner.addSuite(mySuite); + passingTest = aPassingTest(asserterToUse); + failingTest = aFailingTest(asserterToUse); + erroredTest = anErroredTest(); + pendingTest = aPendingTest(); + }); }); test('more than one before block is not allowed', () => { diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 0b198eb7..9b0b4bf5 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -3,7 +3,8 @@ const { suite, test, assert } = require('../../testy'); const Test = require('../../lib/test'); const { aTestWithNoAssertions, aTestWithBody } = require('../support/tests_factory'); -const { withRunner, runSingleTest, expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); +const { withRunner, runSingleTest } = require('../support/runner_helpers'); +const { expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index 8f3a0834..aa1d2a12 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -3,8 +3,6 @@ const { assert } = require('../../testy'); const { Asserter, FailureGenerator, PendingMarker } = require('../../lib/asserter'); const TestResult = require('../../lib/test_result'); -const TestRunner = require('../../lib/test_runner'); -const TestSuite = require('../../lib/test_suite'); const I18n = require('../../lib/i18n'); const fakeRunner = { @@ -55,27 +53,7 @@ const expectErrorOn = (test, errorMessage) => { assert.areEqual(test.result().failureMessage(), errorMessage); }; -const runSingleTest = (runner, test) => { - const noop = () => {}; - const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; - const suite = new TestSuite(`suite for ${test.name()}`, noop, emptySuiteCallbacks); - suite.addTest(test); - runner.addSuite(suite); - runner.run(); - return test.result(); -}; - -const withRunner = testBlock => { - const noop = () => {}; - const emptyRunnerCallbacks = { onFinish: noop }; - const runner = new TestRunner(emptyRunnerCallbacks); - const asserter = new Asserter(runner); - testBlock(runner, asserter); -}; - module.exports = { - withRunner, - runSingleTest, asserter, failGenerator, pendingMarker, diff --git a/tests/support/runner_helpers.js b/tests/support/runner_helpers.js new file mode 100644 index 00000000..e6d683a1 --- /dev/null +++ b/tests/support/runner_helpers.js @@ -0,0 +1,28 @@ +'use strict'; + +const { Asserter } = require('../../lib/asserter'); +const TestRunner = require('../../lib/test_runner'); +const TestSuite = require('../../lib/test_suite'); + +const runSingleTest = (runner, test) => { + const noop = () => {}; + const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; + const suite = new TestSuite(`suite for ${test.name()}`, noop, emptySuiteCallbacks); + suite.addTest(test); + runner.addSuite(suite); + runner.run(); + return test.result(); +}; + +const withRunner = testBlock => { + const noop = () => {}; + const emptyRunnerCallbacks = { onFinish: noop }; + const runner = new TestRunner(emptyRunnerCallbacks); + const asserter = new Asserter(runner); + testBlock(runner, asserter); +}; + +module.exports = { + withRunner, + runSingleTest, +}; From a7750e4099f47b50087252c85735447bac29f153 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 21:08:37 -0300 Subject: [PATCH 41/64] :recycle: adapt boolean assertion tests to the new assertions w/o fake objects --- .../assertions/boolean_assertions_test.js | 27 ++++++++++--------- tests/core/test_test.js | 10 +++---- tests/support/assertion_helpers.js | 12 ++++----- tests/support/runner_helpers.js | 11 +++++++- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tests/core/assertions/boolean_assertions_test.js b/tests/core/assertions/boolean_assertions_test.js index 40de7989..bd0a980f 100644 --- a/tests/core/assertions/boolean_assertions_test.js +++ b/tests/core/assertions/boolean_assertions_test.js @@ -1,42 +1,43 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('boolean assertions', () => { test('isTrue passes with true', () => { - asserter.that(true).isTrue(); + const result = resultOfATestWith(assert => assert.that(true).isTrue()); - expectSuccess(); + expectSuccess(result); }); test('isTrue does not pass with false', () => { - asserter.that(false).isTrue(); + const result = resultOfATestWith(assert => assert.that(false).isTrue()); - expectFailureDueTo('Expected false to be true'); + expectFailureOn(result, 'Expected false to be true'); }); test('isTrue does not pass with another value', () => { - asserter.that(null).isTrue(); + const result = resultOfATestWith(assert => assert.that(null).isTrue()); - expectFailureDueTo('Expected null to be true'); + expectFailureOn(result, 'Expected null to be true'); }); test('isFalse passes with false', () => { - asserter.that(false).isFalse(); + const result = resultOfATestWith(assert => assert.that(false).isFalse()); - expectSuccess(); + expectSuccess(result); }); test('isFalse does not pass with true', () => { - asserter.that(true).isFalse(); + const result = resultOfATestWith(assert => assert.that(true).isFalse()); - expectFailureDueTo('Expected true to be false'); + expectFailureOn(result, 'Expected true to be false'); }); test('isFalse does not pass with another value', () => { - asserter.that(null).isFalse(); + const result = resultOfATestWith(assert => assert.that(null).isFalse()); - expectFailureDueTo('Expected null to be false'); + expectFailureOn(result, 'Expected null to be false'); }); }); diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 9b0b4bf5..847ad4d7 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -41,15 +41,15 @@ suite('tests behavior', () => { }); test('a test fails on the first assertion failed', () => { - withRunner((runner, asserter) => { + withRunner((runner, assert) => { const test = aTestWithBody(() => { - asserter.isNotEmpty([]); - asserter.areEqual(2, 3); + assert.isNotEmpty([]); + assert.areEqual(2, 3); }); - runSingleTest(runner, test); + const result = runSingleTest(runner, test); - expectFailureOn(test, 'Expected [] to be not empty'); + expectFailureOn(result, 'Expected [] to be not empty'); }); }); }); diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index aa1d2a12..9ff2b47b 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -22,13 +22,13 @@ const asserter = new Asserter(fakeRunner); const failGenerator = new FailureGenerator(fakeRunner); const pendingMarker = new PendingMarker(fakeRunner); -const expectSuccess = () => { - assert.areEqual(fakeRunner.result(), TestResult.success()); +const expectSuccess = (result) => { + assert.areEqual(result || fakeRunner.result(), TestResult.success()); fakeRunner.reset(); }; const expectFailureDueTo = failureMessage => { - expectFailureOn(fakeRunner, failureMessage); + expectFailureOn(fakeRunner.result(), failureMessage); fakeRunner.reset(); }; @@ -43,9 +43,9 @@ const expectPendingResultDueTo = reason => { fakeRunner.reset(); }; -const expectFailureOn = (test, failureMessage) => { - assert.isTrue(test.result().isFailure()); - assert.areEqual(test.result().failureMessage(), failureMessage); +const expectFailureOn = (result, failureMessage) => { + assert.isTrue(result.isFailure()); + assert.areEqual(result.failureMessage(), failureMessage); }; const expectErrorOn = (test, errorMessage) => { diff --git a/tests/support/runner_helpers.js b/tests/support/runner_helpers.js index e6d683a1..ac7fe588 100644 --- a/tests/support/runner_helpers.js +++ b/tests/support/runner_helpers.js @@ -3,6 +3,7 @@ const { Asserter } = require('../../lib/asserter'); const TestRunner = require('../../lib/test_runner'); const TestSuite = require('../../lib/test_suite'); +const { aTestWithBody } = require('./tests_factory'); const runSingleTest = (runner, test) => { const noop = () => {}; @@ -19,10 +20,18 @@ const withRunner = testBlock => { const emptyRunnerCallbacks = { onFinish: noop }; const runner = new TestRunner(emptyRunnerCallbacks); const asserter = new Asserter(runner); - testBlock(runner, asserter); + return testBlock(runner, asserter); }; +const resultOfATestWith = assertBlock => + withRunner((runner, asserter) => { + const testToRun = aTestWithBody(() => assertBlock(asserter)); + runSingleTest(runner, testToRun); + return testToRun.result(); + }); + module.exports = { withRunner, runSingleTest, + resultOfATestWith, }; From 351f06a7ab83e4755924f0ffa32371527ef4b5f9 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 21:14:51 -0300 Subject: [PATCH 42/64] :recycle: adapt collection assertion tests to the new assertions w/o fake objects --- .../assertions/collection_assertions_test.js | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index 905a2514..6136ec43 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -2,208 +2,208 @@ const Utils = require('../../../lib/utils'); const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('collection assertions', () => { const nonEmptySet = new Set([1]); const emptySet = new Set([]); test('includes passes if the object is in the array', () => { - asserter.that(['hey']).includes('hey'); + const result = resultOfATestWith(assert => assert.that(['hey']).includes('hey')); - expectSuccess(); + expectSuccess(result); }); test('includes does not pass if the actual object is not an array', () => { - asserter.that([]).includes('hey'); + const result = resultOfATestWith(assert => assert.that([]).includes('hey')); - expectFailureDueTo("Expected [] to include 'hey'"); + expectFailureOn(result, "Expected [] to include 'hey'"); }); test('includes works with non-primitives', () => { - asserter.that([{ a: '1' }]).includes({ a: '1' }); + const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).includes({ a: '1' })); - expectSuccess(); + expectSuccess(result); }); test('doesNotInclude fails if the object is in the array', () => { - asserter.that(['hey']).doesNotInclude('hey'); + const result = resultOfATestWith(assert => assert.that(['hey']).doesNotInclude('hey')); - expectFailureDueTo("Expected [ 'hey' ] to not include 'hey'"); + expectFailureOn(result, "Expected [ 'hey' ] to not include 'hey'"); }); test('doesNotInclude passes if the object is not an array', () => { - asserter.that([]).doesNotInclude('hey'); + const result = resultOfATestWith(assert => assert.that([]).doesNotInclude('hey')); - expectSuccess(); + expectSuccess(result); }); test('doesNotInclude fails properly with non-primitives', () => { - asserter.that([{ a: '1' }]).doesNotInclude({ a: '1' }); + const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).doesNotInclude({ a: '1' })); - expectFailureDueTo("Expected [ { a: '1' } ] to not include { a: '1' }"); + expectFailureOn(result, "Expected [ { a: '1' } ] to not include { a: '1' }"); }); test('includesExactly passes with a single object included', () => { - asserter.that(['hey']).includesExactly('hey'); + const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('hey')); - expectSuccess(); + expectSuccess(result); }); test('includesExactly passes using a non-primitive single object', () => { - asserter.that([{ a: '1' }]).includesExactly({ a: '1' }); + const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).includesExactly({ a: '1' })); - expectSuccess(); + expectSuccess(result); }); test('includesExactly fails if the collection has more objects than expected', () => { - asserter.that(['hey', 'ho']).includesExactly('hey'); + const result = resultOfATestWith(assert => assert.that(['hey', 'ho']).includesExactly('hey')); - expectFailureDueTo("Expected [ 'hey', 'ho' ] to include exactly [ 'hey' ]"); + expectFailureOn(result, "Expected [ 'hey', 'ho' ] to include exactly [ 'hey' ]"); }); test('includesExactly fails if the collection has less objects than expected', () => { - asserter.that(['hey']).includesExactly('hey', 'ho'); + const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('hey', 'ho')); - expectFailureDueTo("Expected [ 'hey' ] to include exactly [ 'hey', 'ho' ]"); + expectFailureOn(result, "Expected [ 'hey' ] to include exactly [ 'hey', 'ho' ]"); }); test('includesExactly fails if none of the objects are included at all', () => { - asserter.that(['hey']).includesExactly('ho'); + const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('ho')); - expectFailureDueTo("Expected [ 'hey' ] to include exactly [ 'ho' ]"); + expectFailureOn(result, "Expected [ 'hey' ] to include exactly [ 'ho' ]"); }); test('includesExactly passes with many items no matter the order', () => { - asserter.that(['hey', 'ho']).includesExactly('ho', 'hey'); + const result = resultOfATestWith(assert => assert.that(['hey', 'ho']).includesExactly('ho', 'hey')); - expectSuccess(); + expectSuccess(result); }); test('includesExactly passes on a Set', () => { - asserter.that(new Set(['hey', 'ho'])).includesExactly('ho', 'hey'); + const result = resultOfATestWith(assert => assert.that(new Set(['hey', 'ho'])).includesExactly('ho', 'hey')); - expectSuccess(); + expectSuccess(result); }); test('isEmpty passes on an empty array', () => { - asserter.that([]).isEmpty(); + const result = resultOfATestWith(assert => assert.that([]).isEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isEmpty does not pass if the array has elements', () => { - asserter.that(['hey']).isEmpty(); + const result = resultOfATestWith(assert => assert.that(['hey']).isEmpty()); - expectFailureDueTo("Expected [ 'hey' ] to be empty"); + expectFailureOn(result, "Expected [ 'hey' ] to be empty"); }); test('isEmpty passes with an empty string', () => { - asserter.that('').isEmpty(); + const result = resultOfATestWith(assert => assert.that('').isEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isEmpty shorthand works', () => { - asserter.isEmpty([]); + const result = resultOfATestWith(assert => assert.isEmpty([])); - expectSuccess(); + expectSuccess(result); }); test('isNotEmpty passes on an array with element', () => { - asserter.that(['hey']).isNotEmpty(); + const result = resultOfATestWith(assert => assert.that(['hey']).isNotEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isNotEmpty does not pass if the array is empty', () => { - asserter.that([]).isNotEmpty(); + const result = resultOfATestWith(assert => assert.that([]).isNotEmpty()); - expectFailureDueTo('Expected [] to be not empty'); + expectFailureOn(result, 'Expected [] to be not empty'); }); test('isNotEmpty passes with a string with content', () => { - asserter.that('hey').isNotEmpty(); + const result = resultOfATestWith(assert => assert.that('hey').isNotEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isNotEmpty shorthand works', () => { - asserter.isNotEmpty(['hey']); + const result = resultOfATestWith(assert => assert.isNotEmpty(['hey'])); - expectSuccess(); + expectSuccess(result); }); test('isEmpty passes on an empty set', () => { - asserter.that(emptySet).isEmpty(); + const result = resultOfATestWith(assert => assert.that(emptySet).isEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isEmpty does not pass on a set with elements', () => { - asserter.that(nonEmptySet).isEmpty(); + const result = resultOfATestWith(assert => assert.that(nonEmptySet).isEmpty()); - expectFailureDueTo(`Expected ${Utils.prettyPrint(nonEmptySet)} to be empty`); + expectFailureOn(result, `Expected ${Utils.prettyPrint(nonEmptySet)} to be empty`); }); test('isNotEmpty does not pass on an empty set', () => { - asserter.that(emptySet).isNotEmpty(); + const result = resultOfATestWith(assert => assert.that(emptySet).isNotEmpty()); - expectFailureDueTo(`Expected ${Utils.prettyPrint(emptySet)} to be not empty`); + expectFailureOn(result, `Expected ${Utils.prettyPrint(emptySet)} to be not empty`); }); test('isNotEmpty passes on a set with elements', () => { - asserter.that(nonEmptySet).isNotEmpty(); + const result = resultOfATestWith(assert => assert.that(nonEmptySet).isNotEmpty()); - expectSuccess(); + expectSuccess(result); }); test('isEmpty throwing error instead of failure', () => { - asserter.isEmpty(undefined); + const result = resultOfATestWith(assert => assert.isEmpty(undefined)); - expectFailureDueTo('Expected undefined to be empty'); + expectFailureOn(result, 'Expected undefined to be empty'); }); test('isNotEmpty throwing error instead of failure', () => { - asserter.isNotEmpty(undefined); + const result = resultOfATestWith(assert => assert.isNotEmpty(undefined)); - expectFailureDueTo('Expected undefined to be not empty'); + expectFailureOn(result, 'Expected undefined to be not empty'); }); test('includes works with Sets', () => { - asserter.that(new Set([42])).includes(42); + const result = resultOfATestWith(assert => assert.that(new Set([42])).includes(42)); - expectSuccess(); + expectSuccess(result); }); test('includes works with Maps', () => { - asserter.that(new Map([['key', 42]])).includes(42); + const result = resultOfATestWith(assert => assert.that(new Map([['key', 42]])).includes(42)); - expectSuccess(); + expectSuccess(result); }); test('includes works with Strings', () => { - asserter.that('42').includes('4'); + const result = resultOfATestWith(assert => assert.that('42').includes('4')); - expectSuccess(); + expectSuccess(result); }); test('doesNotInclude works with Sets', () => { - asserter.that(new Set([24])).doesNotInclude(42); + const result = resultOfATestWith(assert => assert.that(new Set([24])).doesNotInclude(42)); - expectSuccess(); + expectSuccess(result); }); test('doesNotInclude works with Maps', () => { - asserter.that(new Map([['key', 24]])).doesNotInclude(42); + const result = resultOfATestWith(assert => assert.that(new Map([['key', 24]])).doesNotInclude(42)); - expectSuccess(); + expectSuccess(result); }); test('doesNotInclude works with Strings', () => { - asserter.that('24').doesNotInclude('5'); + const result = resultOfATestWith(assert => assert.that('24').doesNotInclude('5')); - expectSuccess(); + expectSuccess(result); }); - }); From 9a4c717608619838e482db7a5cac43b45f6ff47a Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 22:02:54 -0300 Subject: [PATCH 43/64] :recycle: adapt equality assertion tests to the new assertions w/o fake objects --- .../assertions/equality_assertions_test.js | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/tests/core/assertions/equality_assertions_test.js b/tests/core/assertions/equality_assertions_test.js index 14530a3d..7b72dec0 100644 --- a/tests/core/assertions/equality_assertions_test.js +++ b/tests/core/assertions/equality_assertions_test.js @@ -1,85 +1,86 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('equality assertions', () => { test('isEqualTo pass with equal primitive objects', () => { - asserter.that(42).isEqualTo(42); + const result = resultOfATestWith(assert => assert.that(42).isEqualTo(42)); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo fails with different primitive objects', () => { - asserter.that(42).isEqualTo(21); + const result = resultOfATestWith(assert => assert.that(42).isEqualTo(21)); - expectFailureDueTo('Expected 42 to be equal to 21'); + expectFailureOn(result, 'Expected 42 to be equal to 21'); }); test('isEqualTo passes with boxed and unboxed numbers', () => { - asserter.that(42).isEqualTo((42)); + const result = resultOfATestWith(assert => assert.that(42).isEqualTo((42))); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo passes with arrays in the same order', () => { - asserter.that([1, 2, 3]).isEqualTo([1, 2, 3]); + const result = resultOfATestWith(assert => assert.that([1, 2, 3]).isEqualTo([1, 2, 3])); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo fails with arrays in different order', () => { - asserter.that([1, 2, 3]).isEqualTo([1, 3, 2]); + const result = resultOfATestWith(assert => assert.that([1, 2, 3]).isEqualTo([1, 3, 2])); - expectFailureDueTo('Expected [ 1, 2, 3 ] to be equal to [ 1, 3, 2 ]'); + expectFailureOn(result, 'Expected [ 1, 2, 3 ] to be equal to [ 1, 3, 2 ]'); }); test('isEqualTo passes with objects having the same property values', () => { const objectOne = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; const objectTwo = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; - asserter.that(objectOne).isEqualTo(objectTwo); + const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo fails with objects having different property values', () => { const objectOne = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; const objectTwo = { a: 'a', b: { b1: 'b1', b2: '' } }; - asserter.that(objectOne).isEqualTo(objectTwo); + const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureDueTo("Expected { a: 'a', b: { b1: 'b1', b2: 'b2' } } to be equal to { a: 'a', b: { b1: 'b1', b2: '' } }"); + expectFailureOn(result, "Expected { a: 'a', b: { b1: 'b1', b2: 'b2' } } to be equal to { a: 'a', b: { b1: 'b1', b2: '' } }"); }); test('isEqualTo fails if one object has less properties than the other', () => { const objectOne = { a: 'a', b: 'b' }; const objectTwo = { a: 'a', b: 'b', c: 'c' }; - asserter.that(objectOne).isEqualTo(objectTwo); + const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureDueTo("Expected { a: 'a', b: 'b' } to be equal to { a: 'a', b: 'b', c: 'c' }"); + expectFailureOn(result, "Expected { a: 'a', b: 'b' } to be equal to { a: 'a', b: 'b', c: 'c' }"); }); test('isEqualTo fails if one object has more properties than the other', () => { const objectOne = { a: 'a', b: 'b', c: 'c' }; const objectTwo = { a: 'a', b: 'b' }; - asserter.that(objectOne).isEqualTo(objectTwo); + const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureDueTo("Expected { a: 'a', b: 'b', c: 'c' } to be equal to { a: 'a', b: 'b' }"); + expectFailureOn(result, "Expected { a: 'a', b: 'b', c: 'c' } to be equal to { a: 'a', b: 'b' }"); }); test('isEqualTo with custom criteria fails if objects do not have that property', () => { const objectOne = { a: 'a', b: 'b' }; const objectTwo = { a: 'a', b: 'b' }; - asserter.areEqual(objectOne, objectTwo, 'notFound'); + const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'notFound')); - expectFailureDueTo('Expected { a: \'a\', b: \'b\' } to be equal to { a: \'a\', b: \'b\' } Equality check failed. Objects do not have \'notFound\' property'); + expectFailureOn(result, 'Expected { a: \'a\', b: \'b\' } to be equal to { a: \'a\', b: \'b\' } Equality check failed. Objects do not have \'notFound\' property'); }); test('isEqualTo with custom criteria passes if the criteria evaluates to true', () => { const objectOne = { a: 'a', b: 'b1', myEqualMessage: () => true }; const objectTwo = { a: 'a', b: 'b2', myEqualMessage: () => true }; - asserter.areEqual(objectOne, objectTwo, 'myEqualMessage'); + const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'myEqualMessage')); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo with custom criteria passes if the criteria evaluates to true, and we are comparing instances of the same class', () => { @@ -93,9 +94,9 @@ suite('equality assertions', () => { } const objectOne = new AClass(); const objectTwo = new AClass(); - asserter.areEqual(objectOne, objectTwo, 'myEqualMessage'); + const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'myEqualMessage')); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo with equals() default criteria passes if it evaluates to true, and we are comparing instances of the same class', () => { @@ -109,27 +110,29 @@ suite('equality assertions', () => { } const objectOne = new AClass(1); const objectTwo = new AClass(2); - asserter.areEqual(objectOne, objectTwo); + const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo)); - expectSuccess(); + expectSuccess(result); }); test('isEqualTo fails when comparing undefined with an object', () => { - asserter.areEqual(undefined, {}); - expectFailureDueTo('Expected undefined to be equal to {}'); - asserter.areEqual({}, undefined); - expectFailureDueTo('Expected {} to be equal to undefined'); + const resultOne = resultOfATestWith(assert => assert.areEqual(undefined, {})); + const resultTwo = resultOfATestWith(assert => assert.areEqual({}, undefined)); + + expectFailureOn(resultOne, 'Expected undefined to be equal to {}'); + expectFailureOn(resultTwo, 'Expected {} to be equal to undefined'); }); test('isEqualTo fails when comparing null with an object', () => { - asserter.areEqual(null, {}); - expectFailureDueTo('Expected null to be equal to {}'); - asserter.areEqual({}, null); - expectFailureDueTo('Expected {} to be equal to null'); + const resultOne = resultOfATestWith(assert => assert.areEqual(null, {})); + const resultTwo = resultOfATestWith(assert => assert.areEqual({}, null)); + + expectFailureOn(resultOne, 'Expected null to be equal to {}'); + expectFailureOn(resultTwo, 'Expected {} to be equal to null'); }); test('isEqualTo fails if both parts are undefined', () => { - asserter.areEqual(undefined, undefined); - expectFailureDueTo('Equality cannot be determined. Both parts are undefined'); + const result = resultOfATestWith(assert => assert.areEqual(undefined, undefined)); + expectFailureOn(result, 'Equality cannot be determined. Both parts are undefined'); }); }); From 9d0e6475c2a0f714b245dc2e49780408747b1842 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 22:09:36 -0300 Subject: [PATCH 44/64] :recycle: adapt exception, null and numeric assertion tests to the new assertions w/o fake objects --- .../assertions/exception_assertions_test.js | 71 +++++++++++-------- tests/core/assertions/null_assertions_test.js | 19 ++--- .../assertions/numeric_assertions_test.js | 27 +++---- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index 76191819..2227b621 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -1,66 +1,79 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('exception assertions', () => { test('raises() can receive a string and it passes when the exact string is expected', () => { - asserter.that(() => { - throw 'an error happened'; - }).raises('an error happened'); + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'an error happened'; + }).raises('an error happened'), + ); - expectSuccess(); + expectSuccess(result); }); test('raises() can receive a regex and it passes when it matches the thrown string', () => { - asserter.that(() => { - throw 'an error happened'; - }).raises(/error/); + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'an error happened'; + }).raises(/error/), + ); - expectSuccess(); + expectSuccess(result); }); test('raises() can receive an arbitrary object and it passes when the exact object is expected', () => { - asserter.that(() => { - throw { an: 'object' }; - }).raises({ an: 'object' }); + const result = resultOfATestWith(assert => + assert.that(() => { + throw { an: 'object' }; + }).raises({ an: 'object' }), + ); - expectSuccess(); + expectSuccess(result); }); test('raises() can receive a regex and it passes when it matches the thrown error with message', () => { - asserter.that(() => { - throw new TypeError('things happened'); - }).raises(/happened/); + const result = resultOfATestWith(assert => + assert.that(() => { + throw new TypeError('things happened'); + }).raises(/happened/), + ); - expectSuccess(); + expectSuccess(result); }); test('raises() can receive a regex and it fails if there is not a match in the error message', () => { - asserter.that(() => { - throw 'a terrible error'; - }).raises(/happiness/); + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'a terrible error'; + }).raises(/happiness/), + ); - expectFailureDueTo('Expected error /happiness/ to happen, but got \'a terrible error\' instead'); + expectFailureOn(result, 'Expected error /happiness/ to happen, but got \'a terrible error\' instead'); }); test('raises() fails when no errors occur in the given function', () => { - asserter.that(() => 1 + 2).raises('a weird error'); + const result = resultOfATestWith(assert => assert.that(() => 1 + 2).raises('a weird error')); - expectFailureDueTo('Expected error \'a weird error\' to happen'); + expectFailureOn(result, 'Expected error \'a weird error\' to happen'); }); test('doesNoRaiseAnyErrors() passes when no errors occur in the given function', () => { - asserter.that(() => 1 + 2).doesNotRaiseAnyErrors(); + const result = resultOfATestWith(assert => assert.that(() => 1 + 2).doesNotRaiseAnyErrors()); - expectSuccess(); + expectSuccess(result); }); test('doesNoRaiseAnyErrors() fails when an error happens', () => { - asserter.that(() => { - throw 'an unexpected error'; - }).doesNotRaiseAnyErrors(); + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'an unexpected error'; + }).doesNotRaiseAnyErrors(), + ); - expectFailureDueTo('Expected no errors to happen, but \'an unexpected error\' was raised'); + expectFailureOn(result, 'Expected no errors to happen, but \'an unexpected error\' was raised'); }); }); diff --git a/tests/core/assertions/null_assertions_test.js b/tests/core/assertions/null_assertions_test.js index ec7a072c..015b1f0c 100644 --- a/tests/core/assertions/null_assertions_test.js +++ b/tests/core/assertions/null_assertions_test.js @@ -1,30 +1,31 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('assertions about null', () => { test('isNull passes with a null value', () => { - asserter.isNull(null); + const result = resultOfATestWith(assert => assert.isNull(null)); - expectSuccess(); + expectSuccess(result); }); test('isNull does not pass with a another value', () => { - asserter.isNull(undefined); + const result = resultOfATestWith(assert => assert.isNull(undefined)); - expectFailureDueTo('Expected undefined to be null'); + expectFailureOn(result, 'Expected undefined to be null'); }); test('isNotNull passes with a non-null value', () => { - asserter.isNotNull(3); + const result = resultOfATestWith(assert => assert.isNotNull(3)); - expectSuccess(); + expectSuccess(result); }); test('isNotNull does not pass when the value is null', () => { - asserter.isNotNull(null); + const result = resultOfATestWith(assert => assert.isNotNull(null)); - expectFailureDueTo('Expected null to be not null'); + expectFailureOn(result, 'Expected null to be not null'); }); }); diff --git a/tests/core/assertions/numeric_assertions_test.js b/tests/core/assertions/numeric_assertions_test.js index eb39697d..9f5329b7 100644 --- a/tests/core/assertions/numeric_assertions_test.js +++ b/tests/core/assertions/numeric_assertions_test.js @@ -1,42 +1,43 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('numeric assertions', () => { test('isNearTo passes if an exact integer matches', () => { - asserter.that(42).isNearTo(42); + const result = resultOfATestWith(assert => assert.that(42).isNearTo(42)); - expectSuccess(); + expectSuccess(result); }); test('isNearTo fails if the integer part of the number is not equal', () => { - asserter.that(42).isNearTo(43); + const result = resultOfATestWith(assert => assert.that(42).isNearTo(43)); - expectFailureDueTo('Expected 42 to be near to 43 (using 4 precision digits)'); + expectFailureOn(result, 'Expected 42 to be near to 43 (using 4 precision digits)'); }); test('isNearTo passes if the actual number rounded using the specified decimals matches the expected number', () => { - asserter.that(42.0001).isNearTo(42, 3); + const result = resultOfATestWith(assert => assert.that(42.0001).isNearTo(42, 3)); - expectSuccess(); + expectSuccess(result); }); test('isNearTo fails if the actual number rounded using the specified decimals does not match the expected number', () => { - asserter.that(42.001).isNearTo(42, 3); + const result = resultOfATestWith(assert => assert.that(42.001).isNearTo(42, 3)); - expectFailureDueTo('Expected 42.001 to be near to 42 (using 3 precision digits)'); + expectFailureOn(result, 'Expected 42.001 to be near to 42 (using 3 precision digits)'); }); test('isNearTo passes with a default precision of 4', () => { - asserter.that(42.00001).isNearTo(42); + const result = resultOfATestWith(assert => assert.that(42.00001).isNearTo(42)); - expectSuccess(); + expectSuccess(result); }); test('isNearTo passes with a classical floating point representation issue', () => { - asserter.that(0.1 + 0.2).isNearTo(0.3); + const result = resultOfATestWith(assert => assert.that(0.1 + 0.2).isNearTo(0.3)); - expectSuccess(); + expectSuccess(result); }); }); From 806f199de8d26ce4fb29cd39924bf16a7ad31f64 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 22:20:48 -0300 Subject: [PATCH 45/64] :recycle: adapt the rest of assertion tests to the new assertions w/o fake objects; goodbye fake runner! :wave: --- ...porting_failures_and_pending_tests_test.js | 19 +++---- .../string_match_assertions_test.js | 19 +++---- .../assertions/undefined_assertions_test.js | 19 +++---- tests/core/test_test.js | 4 +- tests/support/assertion_helpers.js | 54 ++++--------------- tests/support/runner_helpers.js | 12 +++-- 6 files changed, 48 insertions(+), 79 deletions(-) diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js index 9f45ed7e..ccb8bba0 100644 --- a/tests/core/assertions/reporting_failures_and_pending_tests_test.js +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -1,30 +1,31 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { failGenerator, pendingMarker, expectErrorDueTo, expectFailureDueTo, expectPendingResultDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectErrorOn, expectFailureOn, expectPendingResultOn } = require('../../support/assertion_helpers'); suite('reporting failures and pending tests', () => { test('marking a test as explicitly failed with no message', () => { - failGenerator.with(); + const result = resultOfATestWith((_assert, fail, _pending) => fail.with()); - expectFailureDueTo('Explicitly failed'); + expectFailureOn(result, 'Explicitly failed'); }); test('marking a test as explicitly failed with no message', () => { - failGenerator.with('I should not be here!'); + const result = resultOfATestWith((_assert, fail, _pending) => fail.with('I should not be here!')); - expectFailureDueTo('I should not be here!'); + expectFailureOn(result, 'I should not be here!'); }); test('marking a test as pending with no message', () => { - pendingMarker.dueTo(); + const result = resultOfATestWith((_assert, _fail, pending) => pending.dueTo()); - expectErrorDueTo('In order to mark a test as pending, you need to specify a reason.'); + expectErrorOn(result, 'In order to mark a test as pending, you need to specify a reason.'); }); test('marking a test as pending with a custom message', () => { - pendingMarker.dueTo('No time to fix!'); + const result = resultOfATestWith((_assert, _fail, pending) => pending.dueTo('No time to fix!')); - expectPendingResultDueTo('No time to fix!'); + expectPendingResultOn(result, 'No time to fix!'); }); }); diff --git a/tests/core/assertions/string_match_assertions_test.js b/tests/core/assertions/string_match_assertions_test.js index e24bc93c..ac0a5cea 100644 --- a/tests/core/assertions/string_match_assertions_test.js +++ b/tests/core/assertions/string_match_assertions_test.js @@ -1,30 +1,31 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('assertions about strings match', () => { test('matches() passes when the regex match the actual string', () => { - asserter.that('hello').matches(/ll/); + const result = resultOfATestWith(assert => assert.that('hello').matches(/ll/)); - expectSuccess(); + expectSuccess(result); }); test('matches() fails when the regex does the actual string', () => { - asserter.that('goodbye').matches(/ll/); + const result = resultOfATestWith(assert => assert.that('goodbye').matches(/ll/)); - expectFailureDueTo("Expected 'goodbye' to match /ll/"); + expectFailureOn(result, "Expected 'goodbye' to match /ll/"); }); test('isMatching() shortcut works', () => { - asserter.isMatching('hello', /ll/); + const result = resultOfATestWith(assert => assert.isMatching('hello', /ll/)); - expectSuccess(); + expectSuccess(result); }); test('matches() passes with a exact string', () => { - asserter.that('hola').matches('hola'); + const result = resultOfATestWith(assert => assert.that('hola').matches('hola')); - expectSuccess(); + expectSuccess(result); }); }); diff --git a/tests/core/assertions/undefined_assertions_test.js b/tests/core/assertions/undefined_assertions_test.js index a6e92ff3..fb6ff5cd 100644 --- a/tests/core/assertions/undefined_assertions_test.js +++ b/tests/core/assertions/undefined_assertions_test.js @@ -1,30 +1,31 @@ 'use strict'; const { suite, test } = require('../../../testy'); -const { asserter, expectSuccess, expectFailureDueTo } = require('../../support/assertion_helpers'); +const { resultOfATestWith } = require('../../support/runner_helpers'); +const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); suite('undefined assertions', () => { test('isUndefined passes with an undefined value', () => { - asserter.isUndefined(undefined); + const result = resultOfATestWith(assert => assert.isUndefined(undefined)); - expectSuccess(); + expectSuccess(result); }); test('isUndefined does not pass with a another value', () => { - asserter.isUndefined(null); + const result = resultOfATestWith(assert => assert.isUndefined(null)); - expectFailureDueTo('Expected null to be undefined'); + expectFailureOn(result, 'Expected null to be undefined'); }); test('isNotUndefined passes with a not-undefined value', () => { - asserter.isNotUndefined(null); + const result = resultOfATestWith(assert => assert.isNotUndefined(null)); - expectSuccess(); + expectSuccess(result); }); test('isNotUndefined does not pass when the value is undefined', () => { - asserter.isNotUndefined(undefined); + const result = resultOfATestWith(assert => assert.isNotUndefined(undefined)); - expectFailureDueTo('Expected undefined to be defined'); + expectFailureOn(result, 'Expected undefined to be defined'); }); }); diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 847ad4d7..369d9a47 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -10,9 +10,9 @@ suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { withRunner(runner => { const testToRun = aTestWithNoAssertions(); - runSingleTest(runner, testToRun); + const result = runSingleTest(runner, testToRun); - expectErrorOn(testToRun, 'This test does not have any assertions'); + expectErrorOn(result, 'This test does not have any assertions'); }); }); diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index 9ff2b47b..4d90a89a 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -1,46 +1,15 @@ 'use strict'; const { assert } = require('../../testy'); -const { Asserter, FailureGenerator, PendingMarker } = require('../../lib/asserter'); const TestResult = require('../../lib/test_result'); -const I18n = require('../../lib/i18n'); -const fakeRunner = { - setResultForCurrentTest(result) { - this._result = result; - }, - result() { - return this._result; - }, - reset() { - this.setResultForCurrentTest(undefined); - }, - _i18n: new I18n(), +const expectSuccess = result => { + assert.areEqual(result, TestResult.success()); }; -const asserter = new Asserter(fakeRunner); -const failGenerator = new FailureGenerator(fakeRunner); -const pendingMarker = new PendingMarker(fakeRunner); - -const expectSuccess = (result) => { - assert.areEqual(result || fakeRunner.result(), TestResult.success()); - fakeRunner.reset(); -}; - -const expectFailureDueTo = failureMessage => { - expectFailureOn(fakeRunner.result(), failureMessage); - fakeRunner.reset(); -}; - -const expectErrorDueTo = failureMessage => { - expectErrorOn(fakeRunner, failureMessage); - fakeRunner.reset(); -}; - -const expectPendingResultDueTo = reason => { - assert.isTrue(fakeRunner.result().isPending()); - assert.areEqual(fakeRunner.result().reason(), reason); - fakeRunner.reset(); +const expectPendingResultOn = (result, reason) => { + assert.isTrue(result.isPending()); + assert.areEqual(result.reason(), reason); }; const expectFailureOn = (result, failureMessage) => { @@ -48,19 +17,14 @@ const expectFailureOn = (result, failureMessage) => { assert.areEqual(result.failureMessage(), failureMessage); }; -const expectErrorOn = (test, errorMessage) => { - assert.isTrue(test.result().isError()); - assert.areEqual(test.result().failureMessage(), errorMessage); +const expectErrorOn = (result, errorMessage) => { + assert.isTrue(result.isError()); + assert.areEqual(result.failureMessage(), errorMessage); }; module.exports = { - asserter, - failGenerator, - pendingMarker, expectSuccess, - expectFailureDueTo, - expectErrorDueTo, - expectPendingResultDueTo, expectFailureOn, expectErrorOn, + expectPendingResultOn, }; diff --git a/tests/support/runner_helpers.js b/tests/support/runner_helpers.js index ac7fe588..09767289 100644 --- a/tests/support/runner_helpers.js +++ b/tests/support/runner_helpers.js @@ -1,8 +1,8 @@ 'use strict'; -const { Asserter } = require('../../lib/asserter'); const TestRunner = require('../../lib/test_runner'); const TestSuite = require('../../lib/test_suite'); +const { Asserter, FailureGenerator, PendingMarker } = require('../../lib/asserter'); const { aTestWithBody } = require('./tests_factory'); const runSingleTest = (runner, test) => { @@ -20,12 +20,14 @@ const withRunner = testBlock => { const emptyRunnerCallbacks = { onFinish: noop }; const runner = new TestRunner(emptyRunnerCallbacks); const asserter = new Asserter(runner); - return testBlock(runner, asserter); + const failGenerator = new FailureGenerator(runner); + const pendingMarker = new PendingMarker(runner); + return testBlock(runner, asserter, failGenerator, pendingMarker); }; -const resultOfATestWith = assertBlock => - withRunner((runner, asserter) => { - const testToRun = aTestWithBody(() => assertBlock(asserter)); +const resultOfATestWith = (assertBlock) => + withRunner((runner, assert, fail, pending) => { + const testToRun = aTestWithBody(() => assertBlock(assert, fail, pending)); runSingleTest(runner, testToRun); return testToRun.result(); }); From faa3e71c836256a75101e33cf8081f4f5b184d32 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 22:24:49 -0300 Subject: [PATCH 46/64] :recycle: add missing assertion --- tests/support/assertion_helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/support/assertion_helpers.js b/tests/support/assertion_helpers.js index 4d90a89a..8d234f00 100644 --- a/tests/support/assertion_helpers.js +++ b/tests/support/assertion_helpers.js @@ -9,6 +9,7 @@ const expectSuccess = result => { const expectPendingResultOn = (result, reason) => { assert.isTrue(result.isPending()); + assert.isTrue(result.isExplicitlyMarkedPending()); assert.areEqual(result.reason(), reason); }; From 5a65fbc6e6e58b1f03f996c381240d4a1b8c3875 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 22:31:00 -0300 Subject: [PATCH 47/64] :pencil: add decision to track the idea of avoiding test doubles as much as possible --- doc/decisions/0005-avoiding-test-doubles.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 doc/decisions/0005-avoiding-test-doubles.md diff --git a/doc/decisions/0005-avoiding-test-doubles.md b/doc/decisions/0005-avoiding-test-doubles.md new file mode 100644 index 00000000..94f03532 --- /dev/null +++ b/doc/decisions/0005-avoiding-test-doubles.md @@ -0,0 +1,21 @@ +# 5. Avoiding test doubles + +Date: 2020-12-15 + +## Status + +Accepted + +## Context + +Tests against the real system instead of test doubles was proven to be useful detecting bugs and exercising better our system. + +## Decision + +Avoid introducing test doubles as much as possible. The only allowed exception is to stub/simulate external systems we can't control. Library code should never be stubbed. + +## Consequences + +- Testing with all the real objects that are used in the tool. +- No need to maintain test doubles and their protocol that should match the real objects' protocols. +- Potentially more setup code, objects could be hard to setup because there are no shortcuts. This can be solved by factory methods and/or test helpers. From 28b2ee51078300382c7398cb40203d6e40ca26d1 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 15 Dec 2020 23:08:51 -0300 Subject: [PATCH 48/64] :recycle: increase coverage by testing uncovered paths --- lib/test_suite.js | 2 +- .../assertions/equality_assertions_test.js | 10 ++++++ tests/core/test_suite_test.js | 15 ++++++++ tests/ui/formatter_test.js | 34 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/test_suite.js b/lib/test_suite.js index f2c46f52..78cc4af8 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -130,7 +130,7 @@ class TestSuite { } _runTests(context) { - if (context.randomOrder) { + if (context.randomOrderMode) { this._randomizeTests(); } this.tests().forEach(test => { diff --git a/tests/core/assertions/equality_assertions_test.js b/tests/core/assertions/equality_assertions_test.js index 7b72dec0..c9c4b3d4 100644 --- a/tests/core/assertions/equality_assertions_test.js +++ b/tests/core/assertions/equality_assertions_test.js @@ -135,4 +135,14 @@ suite('equality assertions', () => { const result = resultOfATestWith(assert => assert.areEqual(undefined, undefined)); expectFailureOn(result, 'Equality cannot be determined. Both parts are undefined'); }); + + test('isEqualTo fails with object with circular references', () => { + const objectOne = { toString() { + return 'circular!'; + } }; + objectOne.self = objectOne; + const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectOne)); + + expectFailureOn(result, "Expected circular! to be equal to circular! (circular references found, equality check cannot be done. Please compare objects' properties individually)"); + }); }); diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index c5d3781f..ffb1c3aa 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -115,4 +115,19 @@ suite('test suite behavior', () => { assert.isTrue(erroredTest.isSkipped()); assert.isTrue(pendingTest.isSkipped()); }); + + test('tests can be randomized based on a setting', () => { + mySuite.addTest(passingTest); + mySuite.addTest(failingTest); + mySuite.addTest(erroredTest); + mySuite.addTest(pendingTest); + runner.setTestRandomness(true); + + const testsBefore = mySuite.tests(); + runner.run(); + const testsAfter = mySuite.tests(); + + // we cannot test how the random process was done, but at least we ensure we keep the same tests + assert.areEqual(new Set(testsBefore), new Set(testsAfter)); + }); }); diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js index 87e94e66..23f0b361 100644 --- a/tests/ui/formatter_test.js +++ b/tests/ui/formatter_test.js @@ -3,6 +3,8 @@ const { suite, test, before, assert } = require('../../testy'); const Formatter = require('../../lib/formatter'); const I18n = require('../../lib/i18n'); +const { withRunner, runSingleTest } = require('../support/runner_helpers'); +const { aTestWithBody, aPendingTest } = require('../support/tests_factory'); suite('formatter', () => { let formatter, dummyConsole, i18n; @@ -29,4 +31,36 @@ suite('formatter', () => { const expectedErrorMessage = '\x1b[31mthings happened\x1b[0m'; assert.that(dummyConsole.messages()).includesExactly(expectedErrorMessage); }); + + test('display pending status in yellow including reason', () => { + withRunner((runner, _assert, _fail, pending) => { + const test = aTestWithBody(() => pending.dueTo('in a rush')); + runSingleTest(runner, test); + formatter.displayPendingResult(test); + const testResultMessage = '[\u001b[33m\u001b[1mWIP\u001b[0m] \u001b[33mjust a test\u001b[0m'; + const pendingReasonMessage = ' => in a rush'; + assert.that(dummyConsole.messages()).isEqualTo([testResultMessage, pendingReasonMessage]); + }); + }); + + test('display failure status in red including reason', () => { + withRunner((runner, _assert, fail) => { + const test = aTestWithBody(() => fail.with('I wanted to fail')); + runSingleTest(runner, test); + formatter.displayFailureResult(test); + const testResultMessage = '[\u001b[31m\u001b[1mFAIL\u001b[0m] \u001b[31mjust a test\u001b[0m'; + const failureDetailMessage = ' => I wanted to fail'; + assert.that(dummyConsole.messages()).isEqualTo([testResultMessage, failureDetailMessage]); + }); + }); + + test('display pending status in yellow and no reason if the test is empty', () => { + withRunner(runner => { + const test = aPendingTest(); + runSingleTest(runner, test); + formatter.displayPendingResult(test); + const testResultMessage = '[\u001b[33m\u001b[1mWIP\u001b[0m] \u001b[33ma work in progress\u001b[0m'; + assert.that(dummyConsole.messages()).includesExactly(testResultMessage); + }); + }); }); From 7e1b168940a37c4139f38e4983e862efa1013532 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sun, 27 Dec 2020 23:36:09 -0300 Subject: [PATCH 49/64] :sparkles: allow i18n texts to have arguments; implement basic validations --- lib/i18n.js | 59 ++++++++++++++++++++++++++++++++++------- tests/core/i18n_test.js | 38 ++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/lib/i18n.js b/lib/i18n.js index 7ec063f1..73458ad3 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -8,31 +8,70 @@ class I18n { return 'en'; } - constructor(languageCode = I18n.defaultLanguage(), translations = TRANSLATIONS) { + constructor(languageCode, translations = TRANSLATIONS) { + this._assertLanguageIsSupported(languageCode, translations); this._languageCode = languageCode; this._translations = translations; } - translate(key) { - const languageTranslations = this.translationsForCurrentLanguage(); - const translatedText = languageTranslations[key] || this.defaultTranslationFor(key); - if (isUndefined(translatedText)) { - throw this.keyNotFoundMessage(key); + translate(key, ...params) { + const languageTranslations = this._translationsForCurrentLanguage(); + const translatedText = languageTranslations[key] || this._defaultTranslationFor(key); + this._validateResultingTextExists(translatedText, key); + return this._evaluateWithPotentialArguments(translatedText, params); + } + + // assertions + + _assertLanguageIsSupported(languageCode, translations) { + const supportedLanguages = Object.keys(translations); + if (!supportedLanguages.includes(languageCode)) { + throw new Error(`Language '${languageCode}' is not supported. Allowed values: ${supportedLanguages.join(', ')}`); + } + } + + _validateResultingTextExists(text, key) { + if (isUndefined(text)) { + throw new Error(this._keyNotFoundMessage(key)); + } + } + + _validateNumberOfArgumentsMatch(expectedParams, givenParams) { + if (expectedParams.length !== givenParams.length) { + throw new Error(this._wrongNumberOfArgumentsErrorMessage(expectedParams, givenParams)); + } + } + + // private + + _evaluateWithPotentialArguments(translatedText, params) { + const translatedTextArguments = translatedText.match(/%s/g) || []; + if (translatedTextArguments.length > 0) { + this._validateNumberOfArgumentsMatch(translatedTextArguments, params); + // Node 15 will include the replaceAll() method + return translatedText.replace(/%s/g, () => params.shift()); + } else { + return translatedText; } - return translatedText; } - translationsForCurrentLanguage() { + _translationsForCurrentLanguage() { return this._translations[this._languageCode] || {}; } - defaultTranslationFor(key) { + _defaultTranslationFor(key) { return this._translations[I18n.defaultLanguage()][key]; } - keyNotFoundMessage(key) { + // error messages + + _keyNotFoundMessage(key) { return `${key} not found in translations (language: ${this._languageCode})!`; } + + _wrongNumberOfArgumentsErrorMessage(expectedParams, givenParams) { + return `Expected ${expectedParams.length} argument(s) on the translation string, got ${givenParams.length} instead`; + } } module.exports = I18n; diff --git a/tests/core/i18n_test.js b/tests/core/i18n_test.js index 9449a668..4341e848 100644 --- a/tests/core/i18n_test.js +++ b/tests/core/i18n_test.js @@ -28,13 +28,47 @@ suite('i18n', () => { assert .that(() => i18n.translate('other_key')) - .raises('other_key not found in translations (language: en)!'); + .raises(/other_key not found in translations \(language: en\)!/); }); test('falls back to default language if the key is not found in the given language', () => { - const translations = { en: { aKey: 'a text' } }; + const translations = { en: { aKey: 'a text' }, es: {} }; const i18n = new I18n('es', translations); assert.areEqual(i18n.translate('aKey'), 'a text'); }); + + test('format a translation key with a parameter', () => { + const translations = { en: { key: 'hello %s string' } }; + const i18n = new I18n('en', translations); + + assert.areEqual(i18n.translate('key', 'my friend'), 'hello my friend string'); + }); + + test('formatting a translation key with less arguments than needed fails', () => { + const translations = { en: { key: 'hello %s string' } }; + const i18n = new I18n('en', translations); + + assert.that(() => i18n.translate('key')).raises(/Expected 1 argument\(s\) on the translation string, got 0 instead/); + }); + + test('formatting a translation key with more arguments than needed fails', () => { + const translations = { en: { key: 'hello %s string' } }; + const i18n = new I18n('en', translations); + + assert.that(() => i18n.translate('key', 'one', 'another')).raises(/Expected 1 argument\(s\) on the translation string, got 2 instead/); + }); + + test('format a translation key with a parameter', () => { + const translations = { en: { key: 'the answer to %s is %s' } }; + const i18n = new I18n('en', translations); + + assert.areEqual(i18n.translate('key', 'everything', '42'), 'the answer to everything is 42'); + }); + + test('it is not valid to create a translator for a language we do not have texts for', () => { + const translations = { en: { key: 'something' }, es: { key: 'algo' } }; + + assert.that(() => new I18n('xxxx', translations)).raises(/Language 'xxxx' is not supported. Allowed values: en, es/); + }); }); From 7c959d8547bbbea50310b1b9299f5f29576a1e6f Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Sun, 27 Dec 2020 23:45:36 -0300 Subject: [PATCH 50/64] :sparkles: add global error handler (fixes #177) --- .eslintrc.json | 1 + lib/script_action.js | 33 +++++++++++++++++++++++++++------ package.json | 3 +++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index fa0a1b82..bb25bc05 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ "eqeqeq": "error", "eol-last": "error", "func-call-spacing": "error", + "id-length": "error", "indent": ["error", 2], "keyword-spacing": "error", "new-cap": "error", diff --git a/lib/script_action.js b/lib/script_action.js index 5b002316..f8d6ad59 100644 --- a/lib/script_action.js +++ b/lib/script_action.js @@ -15,6 +15,18 @@ const ScriptAction = { firstParamMatches(params, ...expressions) { return expressions.includes(params[0]); }, + + _packageData() { + return require('../package.json'); + }, + + _exitSuccessfully() { + process.exit(0); + }, + + _exitWithFailure() { + process.exit(1); + }, }; const ShowVersion = { @@ -25,9 +37,9 @@ const ShowVersion = { }, execute(_params) { - const { version, description, homepage } = require('../package.json'); + const { version, description, homepage } = this._packageData(); console.log(`Testy version: ${version}\n${description}\n${homepage}`); - process.exit(0); + this._exitSuccessfully(); }, }; @@ -39,14 +51,14 @@ const ShowHelp = { }, execute(_params) { - const { description } = require('../package.json'); + const { description } = this._packageData(); console.log(`Testy: ${description}\n`); console.log('Usage: \n'); console.log(' testy [-h --help] [-v --version] ...test files or folders...\n'); console.log('Options: \n'); console.log(' -h, --help Displays this text'); console.log(' -v, --version Displays the current version'); - process.exit(0); + this._exitSuccessfully(); }, }; @@ -58,8 +70,17 @@ const RunTests = { }, execute(params) { - const testyInstance = Testy.configuredWith(Configuration.current()); - testyInstance.run(params); + try { + const testyInstance = Testy.configuredWith(Configuration.current()); + testyInstance.run(params); + this._exitSuccessfully(); + } catch (error) { + const { bugs } = this._packageData(); + console.log('Sorry, an unexpected error happened while loading Testy, you did nothing wrong. Details below:'); + console.log(error.stack); + console.log(`Please report this issue including the full error message and stacktrace at ${bugs.url}.`); + this._exitWithFailure(); + } }, }; diff --git a/package.json b/package.json index e0785a49..2774c45b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "type": "git", "url": "https://github.com/ngarbezza/testy" }, + "bugs": { + "url": "https://github.com/ngarbezza/testy/issues" + }, "engines": { "node": ">= 8.*" }, From df5ffc9c049b4abc0e0f84c8e4b681aa0e190fad Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 28 Dec 2020 00:36:50 -0300 Subject: [PATCH 51/64] :construction: start making the internationalized messages to decouple from the i18n translation process itself --- lib/asserter.js | 5 ++- lib/assertion.js | 38 +++++++++++++++---- lib/formatter.js | 8 +++- lib/internationalized_message.js | 14 +++++++ lib/translations.json | 24 ++++++------ .../assertions/boolean_assertions_test.js | 10 +++-- tests/core/assertions/null_assertions_test.js | 6 ++- ...porting_failures_and_pending_tests_test.js | 7 ++-- .../assertions/undefined_assertions_test.js | 6 ++- tests/ui/formatter_test.js | 24 +++++++++++- 10 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 lib/internationalized_message.js diff --git a/lib/asserter.js b/lib/asserter.js index 3be744aa..f5578ec3 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -3,12 +3,13 @@ const TestResult = require('./test_result'); const TestResultReporter = require('./test_result_reporter'); const Assertion = require('./assertion'); +const InternationalizedMessage = require('./internationalized_message'); const { isStringWithContent } = require('./utils'); class FailureGenerator extends TestResultReporter { with(description) { - this.report(TestResult.failure(description || this.translated('explicitly_failed'))); + this.report(TestResult.failure(description || new InternationalizedMessage('explicitly_failed'))); } } @@ -22,7 +23,7 @@ class PendingMarker extends TestResultReporter { } invalidReasonErrorMessage() { - return this.translated('invalid_pending_reason'); + return new InternationalizedMessage('invalid_pending_reason'); } } diff --git a/lib/assertion.js b/lib/assertion.js index 7710f00b..d2b2d471 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -3,6 +3,7 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); +const InternationalizedMessage = require('./internationalized_message'); const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); @@ -15,31 +16,49 @@ class Assertion extends TestResultReporter { // Boolean assertions isTrue() { - this._reportAssertionResult(this._actual === true, this.translated('be_true')); + this._reportAssertionResult( + this._actual === true, + new InternationalizedMessage('expectation_be_true', this._actualResultAsString()), + ); } isFalse() { - this._reportAssertionResult(this._actual === false, this.translated('be_false')); + this._reportAssertionResult( + this._actual === false, + new InternationalizedMessage('expectation_be_false', this._actualResultAsString()), + ); } // Undefined value assertions isUndefined() { - this._reportAssertionResult(isUndefined(this._actual), this.translated('be_undefined')); + this._reportAssertionResult( + isUndefined(this._actual), + new InternationalizedMessage('expectation_be_undefined', this._actualResultAsString()), + ); } isNotUndefined() { - this._reportAssertionResult(!isUndefined(this._actual), this.translated('be_defined')); + this._reportAssertionResult( + !isUndefined(this._actual), + new InternationalizedMessage('expectation_be_defined', this._actualResultAsString()), + ); } // Null value assertions isNull() { - this._reportAssertionResult(this._actual === null, this.translated('be_null')); + this._reportAssertionResult( + this._actual === null, + new InternationalizedMessage('expectation_be_null', this._actualResultAsString()), + ); } isNotNull() { - this._reportAssertionResult(this._actual !== null, this.translated('be_not_null')); + this._reportAssertionResult( + this._actual !== null, + new InternationalizedMessage('expectation_be_not_null', this._actualResultAsString()), + ); } // Equality assertions @@ -197,7 +216,12 @@ class Assertion extends TestResultReporter { if (wasSuccess) { this.report(TestResult.success()); } else { - const defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; + let defaultFailureMessage; + if (matcherFailureMessage instanceof InternationalizedMessage) { + defaultFailureMessage = matcherFailureMessage; + } else { + defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; + } this.report(TestResult.failure(overrideFailureMessage ? matcherFailureMessage : defaultFailureMessage)); } } diff --git a/lib/formatter.js b/lib/formatter.js index 6bdca7e7..f09cacdb 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -1,5 +1,7 @@ 'use strict'; +const { isString } = require('./utils'); + // Colors and emphasis const off = '\x1b[0m'; const bold = '\x1b[1m'; @@ -100,7 +102,11 @@ class Formatter { } _displayResultDetail(detail) { - this._console.log(` => ${detail}`); + this._console.log(` => ${this._potentiallyInternationalized(detail)}`); + } + + _potentiallyInternationalized(text) { + return isString(text) ? text : text.expressedInLocale(this._i18n); } _displayErrorsAndFailuresSummary(runner) { diff --git a/lib/internationalized_message.js b/lib/internationalized_message.js new file mode 100644 index 00000000..afc395d1 --- /dev/null +++ b/lib/internationalized_message.js @@ -0,0 +1,14 @@ +'use strict'; + +class InternationalizedMessage { + constructor(key, ...params) { + this._key = key; + this._params = params; + } + + expressedInLocale(locale) { + return locale.translate(this._key, ...this._params); + } +} + +module.exports = InternationalizedMessage; diff --git a/lib/translations.json b/lib/translations.json index b1ede735..07355af5 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -25,12 +25,12 @@ "but_got": "but got", "instead": "instead", "expecting_error": "error", - "be_true": "be true", - "be_false": "be false", - "be_undefined": "be undefined", - "be_defined": "be defined", - "be_null": "be null", - "be_not_null": "be not null", + "expectation_be_true": "Expected %s to be true", + "expectation_be_false": "Expected %s to be false", + "expectation_be_undefined": "Expected %s to be undefined", + "expectation_be_defined": "Expected %s to be defined", + "expectation_be_null": "Expected %s to be null", + "expectation_be_not_null": "Expected %s to be not null", "include": "include", "be_included_in": "be included in", "be_not_included_in": "be not included in", @@ -75,12 +75,12 @@ "but_got": "pero se obtuvo", "instead": "en su lugar", "expecting_error": "el error", - "be_true": "sea true", - "be_false": "sea false", - "be_undefined": "sea undefined", - "be_defined": "esté definido", - "be_null": "sea null", - "be_not_null": "no sea null", + "expectation_be_true": "Se esperaba que %s sea true", + "expectation_be_false": "Se esperaba que %s sea false", + "expectation_be_undefined": "Se esperaba que %s sea undefined", + "expectation_be_defined": "Se esperaba que %s esté definido", + "expectation_be_defined": "Se esperaba que %s sea null", + "expectation_be_defined": "Se esperaba que %s no sea null", "include": "incluya a", "not_include": "no incluya a", "include_exactly": "incluya exactamente a", diff --git a/tests/core/assertions/boolean_assertions_test.js b/tests/core/assertions/boolean_assertions_test.js index bd0a980f..c6213cb7 100644 --- a/tests/core/assertions/boolean_assertions_test.js +++ b/tests/core/assertions/boolean_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('boolean assertions', () => { test('isTrue passes with true', () => { const result = resultOfATestWith(assert => assert.that(true).isTrue()); @@ -14,13 +16,13 @@ suite('boolean assertions', () => { test('isTrue does not pass with false', () => { const result = resultOfATestWith(assert => assert.that(false).isTrue()); - expectFailureOn(result, 'Expected false to be true'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_true', 'false')); }); test('isTrue does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isTrue()); - expectFailureOn(result, 'Expected null to be true'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_true', 'null')); }); test('isFalse passes with false', () => { @@ -32,12 +34,12 @@ suite('boolean assertions', () => { test('isFalse does not pass with true', () => { const result = resultOfATestWith(assert => assert.that(true).isFalse()); - expectFailureOn(result, 'Expected true to be false'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_false', 'true')); }); test('isFalse does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isFalse()); - expectFailureOn(result, 'Expected null to be false'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_false', 'null')); }); }); diff --git a/tests/core/assertions/null_assertions_test.js b/tests/core/assertions/null_assertions_test.js index 015b1f0c..a75873e7 100644 --- a/tests/core/assertions/null_assertions_test.js +++ b/tests/core/assertions/null_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('assertions about null', () => { test('isNull passes with a null value', () => { const result = resultOfATestWith(assert => assert.isNull(null)); @@ -14,7 +16,7 @@ suite('assertions about null', () => { test('isNull does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isNull(undefined)); - expectFailureOn(result, 'Expected undefined to be null'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_null', 'undefined')); }); test('isNotNull passes with a non-null value', () => { @@ -26,6 +28,6 @@ suite('assertions about null', () => { test('isNotNull does not pass when the value is null', () => { const result = resultOfATestWith(assert => assert.isNotNull(null)); - expectFailureOn(result, 'Expected null to be not null'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_not_null', 'null')); }); }); diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js index ccb8bba0..6a142c7e 100644 --- a/tests/core/assertions/reporting_failures_and_pending_tests_test.js +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -1,5 +1,6 @@ 'use strict'; +const InternationalizedMessage = require('../../../lib/internationalized_message'); const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectErrorOn, expectFailureOn, expectPendingResultOn } = require('../../support/assertion_helpers'); @@ -8,10 +9,10 @@ suite('reporting failures and pending tests', () => { test('marking a test as explicitly failed with no message', () => { const result = resultOfATestWith((_assert, fail, _pending) => fail.with()); - expectFailureOn(result, 'Explicitly failed'); + expectFailureOn(result, new InternationalizedMessage('explicitly_failed')); }); - test('marking a test as explicitly failed with no message', () => { + test('marking a test as explicitly failed with a custom message', () => { const result = resultOfATestWith((_assert, fail, _pending) => fail.with('I should not be here!')); expectFailureOn(result, 'I should not be here!'); @@ -20,7 +21,7 @@ suite('reporting failures and pending tests', () => { test('marking a test as pending with no message', () => { const result = resultOfATestWith((_assert, _fail, pending) => pending.dueTo()); - expectErrorOn(result, 'In order to mark a test as pending, you need to specify a reason.'); + expectErrorOn(result, new InternationalizedMessage('invalid_pending_reason')); }); test('marking a test as pending with a custom message', () => { diff --git a/tests/core/assertions/undefined_assertions_test.js b/tests/core/assertions/undefined_assertions_test.js index fb6ff5cd..20e0eb90 100644 --- a/tests/core/assertions/undefined_assertions_test.js +++ b/tests/core/assertions/undefined_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('undefined assertions', () => { test('isUndefined passes with an undefined value', () => { const result = resultOfATestWith(assert => assert.isUndefined(undefined)); @@ -14,7 +16,7 @@ suite('undefined assertions', () => { test('isUndefined does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isUndefined(null)); - expectFailureOn(result, 'Expected null to be undefined'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_undefined', 'null')); }); test('isNotUndefined passes with a not-undefined value', () => { @@ -26,6 +28,6 @@ suite('undefined assertions', () => { test('isNotUndefined does not pass when the value is undefined', () => { const result = resultOfATestWith(assert => assert.isNotUndefined(undefined)); - expectFailureOn(result, 'Expected undefined to be defined'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_defined', 'undefined')); }); }); diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js index 23f0b361..65b5d5cb 100644 --- a/tests/ui/formatter_test.js +++ b/tests/ui/formatter_test.js @@ -22,7 +22,7 @@ suite('formatter', () => { }, }; - i18n = new I18n(); + i18n = new I18n(I18n.defaultLanguage()); formatter = new Formatter(dummyConsole, i18n); }); @@ -43,6 +43,17 @@ suite('formatter', () => { }); }); + test('display error status in red when not specifying a reason', () => { + withRunner((runner, _assert, _fail, pending) => { + const test = aTestWithBody(() => pending.dueTo()); + runSingleTest(runner, test); + formatter.displayErrorResult(test); + const testResultMessage = '[\u001b[31m\u001b[1mERROR\u001b[0m] \u001b[31mjust a test\u001b[0m'; + const pendingReasonMessage = ' => In order to mark a test as pending, you need to specify a reason.'; + assert.that(dummyConsole.messages()).isEqualTo([testResultMessage, pendingReasonMessage]); + }); + }); + test('display failure status in red including reason', () => { withRunner((runner, _assert, fail) => { const test = aTestWithBody(() => fail.with('I wanted to fail')); @@ -54,6 +65,17 @@ suite('formatter', () => { }); }); + test('display failure status in red including default explicit failure reason', () => { + withRunner((runner, _assert, fail) => { + const test = aTestWithBody(() => fail.with()); + runSingleTest(runner, test); + formatter.displayFailureResult(test); + const testResultMessage = '[\u001b[31m\u001b[1mFAIL\u001b[0m] \u001b[31mjust a test\u001b[0m'; + const failureDetailMessage = ' => Explicitly failed'; + assert.that(dummyConsole.messages()).isEqualTo([testResultMessage, failureDetailMessage]); + }); + }); + test('display pending status in yellow and no reason if the test is empty', () => { withRunner(runner => { const test = aPendingTest(); From b56144b9eab0f6cd6d9f217df4535b2dc50fce2a Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 28 Dec 2020 01:41:21 -0300 Subject: [PATCH 52/64] :construction: continue making the refactoring to decouple i18n from assertions --- lib/assertion.js | 25 +++++++------ lib/translations.json | 36 ++++++++++--------- .../assertions/collection_assertions_test.js | 34 +++++++++--------- .../assertions/equality_assertions_test.js | 26 ++++++++++---- .../assertions/numeric_assertions_test.js | 6 ++-- .../string_match_assertions_test.js | 4 ++- tests/core/test_test.js | 6 ++-- tests/examples/basic_assertions_test.js | 6 ++-- tests/examples/basic_features_test.js | 14 +++++--- tests/utils_test.js | 6 ++-- 10 files changed, 96 insertions(+), 67 deletions(-) diff --git a/lib/assertion.js b/lib/assertion.js index d2b2d471..17046e42 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -76,48 +76,47 @@ class Assertion extends TestResultReporter { includes(expectedObject, equalityCriteria) { const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('include')} ${prettyPrint(expectedObject)}`; + const failureMessage = new InternationalizedMessage('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_included_in')} ${prettyPrint(expectedCollection)}`; + const failureMessage = new InternationalizedMessage('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = `${this.translated('not_include')} ${prettyPrint(expectedObject)}`; + const failureMessage = new InternationalizedMessage('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = !expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = `${this.translated('be_not_included_in')} ${prettyPrint(expectedCollection)}`; + const failureMessage = new InternationalizedMessage('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = `${this.translated('include_exactly')} ${prettyPrint(objects)}`; + const failureMessage = new InternationalizedMessage('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isEmpty() { const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); - const failureMessage = this.translated('be_empty'); + const failureMessage = new InternationalizedMessage('expectation_be_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotEmpty() { const setValueWhenUndefined = this._actual || {}; - const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; - const failureMessage = this.translated('be_not_empty'); + const failureMessage = new InternationalizedMessage('expectation_be_not_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -149,7 +148,7 @@ class Assertion extends TestResultReporter { isNearTo(number, precisionDigits = 4) { const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; - const failureMessage = `be near to ${number} (using ${precisionDigits} precision digits)`; + const failureMessage = new InternationalizedMessage('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); this._reportAssertionResult(result, failureMessage, false); } @@ -157,7 +156,7 @@ class Assertion extends TestResultReporter { matches(regex) { const result = this._actual.match(regex); - const failureMessage = `${this.translated('match')} ${regex}`; + const failureMessage = new InternationalizedMessage('expectation_match_regex', this._actualResultAsString(), regex); this._reportAssertionResult(result, failureMessage, false); } @@ -236,11 +235,11 @@ class Assertion extends TestResultReporter { if (collectionOneArray.length !== collectionTwoArray.length) { return false; } - for (let i = 0; i < collectionOne.length; i++) { + for (let index = 0; index < collectionOne.length; index++) { const includedInOne = collectionOne.find(element => - this._areConsideredEqual(element, collectionTwoArray[i])); + this._areConsideredEqual(element, collectionTwoArray[index])); const includedInTwo = collectionTwo.find(element => - this._areConsideredEqual(element, collectionOneArray[i])); + this._areConsideredEqual(element, collectionOneArray[index])); if (!includedInOne || !includedInTwo) { return false; } diff --git a/lib/translations.json b/lib/translations.json index 07355af5..9c26111c 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -31,14 +31,15 @@ "expectation_be_defined": "Expected %s to be defined", "expectation_be_null": "Expected %s to be null", "expectation_be_not_null": "Expected %s to be not null", - "include": "include", - "be_included_in": "be included in", - "be_not_included_in": "be not included in", - "not_include": "not include", - "include_exactly": "include exactly", - "be_empty": "be empty", - "be_not_empty": "be not empty", - "match": "match", + "expectation_be_near_to": "Expected %s to be near to %s (using %s precision digits)", + "expectation_match_regex": "Expected %s to match %s", + "expectation_include": "Expected %s to include %s", + "expectation_not_include": "Expected %s to not include %s", + "expectation_be_included_in": "Expected %s to be included in %s", + "expectation_be_not_included_in": "Expected %s to be not included in %s", + "expectation_include_exactly": "Expected %s to include exactly %s", + "expectation_be_empty": "Expected %s to be empty", + "expectation_be_not_empty": "Expected %s to be not empty", "expected_no_errors": "Expected no errors to happen", "but": "but", "was_raised": "was raised", @@ -79,14 +80,17 @@ "expectation_be_false": "Se esperaba que %s sea false", "expectation_be_undefined": "Se esperaba que %s sea undefined", "expectation_be_defined": "Se esperaba que %s esté definido", - "expectation_be_defined": "Se esperaba que %s sea null", - "expectation_be_defined": "Se esperaba que %s no sea null", - "include": "incluya a", - "not_include": "no incluya a", - "include_exactly": "incluya exactamente a", - "be_empty": "sea vacío", - "be_not_empty": "no sea vacío", - "match": "sea cubierto por la expresión regular", + "expectation_be_null": "Se esperaba que %s sea null", + "expectation_be_not_null": "Se esperaba que %s no sea null", + "expectation_be_near_to": "Se esperaba que %s esté cercano a %s (usando %s dígitos de precisión)", + "expectation_match_regex": "Se esperaba que %s sea cubierto por la expresión regular %s", + "expectation_include": "Se esperaba que %s incluya a %s", + "expectation_not_include": "Se esperaba que %s incluya a %s", + "expectation_be_included_in": "Se esperaba que %s esté incluido en %s", + "expectation_be_not_included_in": "Se esperaba que %s no esté incluido en %s", + "expectation_include_exactly": "Se esperaba que %s incluya exactamente a %s", + "expectation_be_empty": "Se esperaba que %s sea vacío", + "expectation_be_not_empty": "Se esperaba que %s no sea vacío", "expected_no_errors": "No se esperaban errores", "but": "pero", "was_raised": "fue lanzado", diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index 6136ec43..a621ccd0 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -1,10 +1,12 @@ 'use strict'; -const Utils = require('../../../lib/utils'); const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const Utils = require('../../../lib/utils'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('collection assertions', () => { const nonEmptySet = new Set([1]); const emptySet = new Set([]); @@ -18,11 +20,11 @@ suite('collection assertions', () => { test('includes does not pass if the actual object is not an array', () => { const result = resultOfATestWith(assert => assert.that([]).includes('hey')); - expectFailureOn(result, "Expected [] to include 'hey'"); + expectFailureOn(result, new InternationalizedMessage('expectation_include', '[]', "'hey'")); }); test('includes works with non-primitives', () => { - const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).includes({ a: '1' })); + const result = resultOfATestWith(assert => assert.that([{ asd: '1' }]).includes({ asd: '1' })); expectSuccess(result); }); @@ -30,7 +32,7 @@ suite('collection assertions', () => { test('doesNotInclude fails if the object is in the array', () => { const result = resultOfATestWith(assert => assert.that(['hey']).doesNotInclude('hey')); - expectFailureOn(result, "Expected [ 'hey' ] to not include 'hey'"); + expectFailureOn(result, new InternationalizedMessage('expectation_not_include', "[ 'hey' ]", "'hey'")); }); test('doesNotInclude passes if the object is not an array', () => { @@ -40,9 +42,9 @@ suite('collection assertions', () => { }); test('doesNotInclude fails properly with non-primitives', () => { - const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).doesNotInclude({ a: '1' })); + const result = resultOfATestWith(assert => assert.that([{ asd: '1' }]).doesNotInclude({ asd: '1' })); - expectFailureOn(result, "Expected [ { a: '1' } ] to not include { a: '1' }"); + expectFailureOn(result, new InternationalizedMessage('expectation_not_include', "[ { asd: '1' } ]", "{ asd: '1' }")); }); test('includesExactly passes with a single object included', () => { @@ -52,7 +54,7 @@ suite('collection assertions', () => { }); test('includesExactly passes using a non-primitive single object', () => { - const result = resultOfATestWith(assert => assert.that([{ a: '1' }]).includesExactly({ a: '1' })); + const result = resultOfATestWith(assert => assert.that([{ asd: '1' }]).includesExactly({ asd: '1' })); expectSuccess(result); }); @@ -60,19 +62,19 @@ suite('collection assertions', () => { test('includesExactly fails if the collection has more objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey', 'ho']).includesExactly('hey')); - expectFailureOn(result, "Expected [ 'hey', 'ho' ] to include exactly [ 'hey' ]"); + expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey', 'ho' ]", "[ 'hey' ]")); }); test('includesExactly fails if the collection has less objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('hey', 'ho')); - expectFailureOn(result, "Expected [ 'hey' ] to include exactly [ 'hey', 'ho' ]"); + expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey' ]", "[ 'hey', 'ho' ]")); }); test('includesExactly fails if none of the objects are included at all', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('ho')); - expectFailureOn(result, "Expected [ 'hey' ] to include exactly [ 'ho' ]"); + expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey' ]", "[ 'ho' ]")); }); test('includesExactly passes with many items no matter the order', () => { @@ -96,7 +98,7 @@ suite('collection assertions', () => { test('isEmpty does not pass if the array has elements', () => { const result = resultOfATestWith(assert => assert.that(['hey']).isEmpty()); - expectFailureOn(result, "Expected [ 'hey' ] to be empty"); + expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', "[ 'hey' ]")); }); test('isEmpty passes with an empty string', () => { @@ -120,7 +122,7 @@ suite('collection assertions', () => { test('isNotEmpty does not pass if the array is empty', () => { const result = resultOfATestWith(assert => assert.that([]).isNotEmpty()); - expectFailureOn(result, 'Expected [] to be not empty'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', '[]')); }); test('isNotEmpty passes with a string with content', () => { @@ -144,13 +146,13 @@ suite('collection assertions', () => { test('isEmpty does not pass on a set with elements', () => { const result = resultOfATestWith(assert => assert.that(nonEmptySet).isEmpty()); - expectFailureOn(result, `Expected ${Utils.prettyPrint(nonEmptySet)} to be empty`); + expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', Utils.prettyPrint(nonEmptySet))); }); test('isNotEmpty does not pass on an empty set', () => { const result = resultOfATestWith(assert => assert.that(emptySet).isNotEmpty()); - expectFailureOn(result, `Expected ${Utils.prettyPrint(emptySet)} to be not empty`); + expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', Utils.prettyPrint(emptySet))); }); test('isNotEmpty passes on a set with elements', () => { @@ -162,13 +164,13 @@ suite('collection assertions', () => { test('isEmpty throwing error instead of failure', () => { const result = resultOfATestWith(assert => assert.isEmpty(undefined)); - expectFailureOn(result, 'Expected undefined to be empty'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', 'undefined')); }); test('isNotEmpty throwing error instead of failure', () => { const result = resultOfATestWith(assert => assert.isNotEmpty(undefined)); - expectFailureOn(result, 'Expected undefined to be not empty'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', 'undefined')); }); test('includes works with Sets', () => { diff --git a/tests/core/assertions/equality_assertions_test.js b/tests/core/assertions/equality_assertions_test.js index c9c4b3d4..35b2f80d 100644 --- a/tests/core/assertions/equality_assertions_test.js +++ b/tests/core/assertions/equality_assertions_test.js @@ -36,48 +36,60 @@ suite('equality assertions', () => { }); test('isEqualTo passes with objects having the same property values', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; const objectTwo = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); expectSuccess(result); }); test('isEqualTo fails with objects having different property values', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: { b1: 'b1', b2: 'b2' } }; const objectTwo = { a: 'a', b: { b1: 'b1', b2: '' } }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); expectFailureOn(result, "Expected { a: 'a', b: { b1: 'b1', b2: 'b2' } } to be equal to { a: 'a', b: { b1: 'b1', b2: '' } }"); }); test('isEqualTo fails if one object has less properties than the other', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b' }; const objectTwo = { a: 'a', b: 'b', c: 'c' }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); expectFailureOn(result, "Expected { a: 'a', b: 'b' } to be equal to { a: 'a', b: 'b', c: 'c' }"); }); test('isEqualTo fails if one object has more properties than the other', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b', c: 'c' }; const objectTwo = { a: 'a', b: 'b' }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); expectFailureOn(result, "Expected { a: 'a', b: 'b', c: 'c' } to be equal to { a: 'a', b: 'b' }"); }); test('isEqualTo with custom criteria fails if objects do not have that property', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b' }; const objectTwo = { a: 'a', b: 'b' }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'notFound')); expectFailureOn(result, 'Expected { a: \'a\', b: \'b\' } to be equal to { a: \'a\', b: \'b\' } Equality check failed. Objects do not have \'notFound\' property'); }); test('isEqualTo with custom criteria passes if the criteria evaluates to true', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b1', myEqualMessage: () => true }; const objectTwo = { a: 'a', b: 'b2', myEqualMessage: () => true }; + /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'myEqualMessage')); expectSuccess(result); @@ -85,11 +97,11 @@ suite('equality assertions', () => { test('isEqualTo with custom criteria passes if the criteria evaluates to true, and we are comparing instances of the same class', () => { class AClass { - constructor(a) { - this.a = a; + constructor(asd) { + this.asd = asd; } myEqualMessage() { - return true; + return true; } } const objectOne = new AClass(); @@ -101,11 +113,11 @@ suite('equality assertions', () => { test('isEqualTo with equals() default criteria passes if it evaluates to true, and we are comparing instances of the same class', () => { class AClass { - constructor(a) { - this.a = a; + constructor(asd) { + this.asd = asd; } equals() { - return true; + return true; } } const objectOne = new AClass(1); @@ -138,7 +150,7 @@ suite('equality assertions', () => { test('isEqualTo fails with object with circular references', () => { const objectOne = { toString() { - return 'circular!'; + return 'circular!'; } }; objectOne.self = objectOne; const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectOne)); diff --git a/tests/core/assertions/numeric_assertions_test.js b/tests/core/assertions/numeric_assertions_test.js index 9f5329b7..62986c28 100644 --- a/tests/core/assertions/numeric_assertions_test.js +++ b/tests/core/assertions/numeric_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('numeric assertions', () => { test('isNearTo passes if an exact integer matches', () => { const result = resultOfATestWith(assert => assert.that(42).isNearTo(42)); @@ -14,7 +16,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the integer part of the number is not equal', () => { const result = resultOfATestWith(assert => assert.that(42).isNearTo(43)); - expectFailureOn(result, 'Expected 42 to be near to 43 (using 4 precision digits)'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_near_to', '42', '43', '4')); }); test('isNearTo passes if the actual number rounded using the specified decimals matches the expected number', () => { @@ -26,7 +28,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the actual number rounded using the specified decimals does not match the expected number', () => { const result = resultOfATestWith(assert => assert.that(42.001).isNearTo(42, 3)); - expectFailureOn(result, 'Expected 42.001 to be near to 42 (using 3 precision digits)'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_near_to', '42.001', '42', '3')); }); test('isNearTo passes with a default precision of 4', () => { diff --git a/tests/core/assertions/string_match_assertions_test.js b/tests/core/assertions/string_match_assertions_test.js index ac0a5cea..623c96b7 100644 --- a/tests/core/assertions/string_match_assertions_test.js +++ b/tests/core/assertions/string_match_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const InternationalizedMessage = require('../../../lib/internationalized_message'); + suite('assertions about strings match', () => { test('matches() passes when the regex match the actual string', () => { const result = resultOfATestWith(assert => assert.that('hello').matches(/ll/)); @@ -14,7 +16,7 @@ suite('assertions about strings match', () => { test('matches() fails when the regex does the actual string', () => { const result = resultOfATestWith(assert => assert.that('goodbye').matches(/ll/)); - expectFailureOn(result, "Expected 'goodbye' to match /ll/"); + expectFailureOn(result, new InternationalizedMessage('expectation_match_regex', "'goodbye'", /ll/)); }); test('isMatching() shortcut works', () => { diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 369d9a47..df4f410c 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -1,11 +1,13 @@ 'use strict'; const { suite, test, assert } = require('../../testy'); -const Test = require('../../lib/test'); const { aTestWithNoAssertions, aTestWithBody } = require('../support/tests_factory'); const { withRunner, runSingleTest } = require('../support/runner_helpers'); const { expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); +const Test = require('../../lib/test'); +const InternationalizedMessage = require('../../lib/internationalized_message'); + suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { withRunner(runner => { @@ -49,7 +51,7 @@ suite('tests behavior', () => { const result = runSingleTest(runner, test); - expectFailureOn(result, 'Expected [] to be not empty'); + expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', '[]')); }); }); }); diff --git a/tests/examples/basic_assertions_test.js b/tests/examples/basic_assertions_test.js index 6c518579..9b1d8ab2 100644 --- a/tests/examples/basic_assertions_test.js +++ b/tests/examples/basic_assertions_test.js @@ -38,7 +38,7 @@ suite('testing testy - basic assertions', () => { }); test('error checking', () => assert.that(() => { - throw 'hey!'; + throw 'hey!'; }).raises('hey!')); // commented so CI can pass - uncomment to see the failure @@ -50,7 +50,7 @@ suite('testing testy - basic assertions', () => { test('testing that no specific error happened - even if other error occurs', () => assert.that(() => { - throw 'ho!'; + throw 'ho!'; }).doesNotRaise('hey!'), ); @@ -59,7 +59,7 @@ suite('testing testy - basic assertions', () => { ); test('object comparison', () => - assert.areEqual({ a: 2, b: [1, 2, 3] }, { a: 2, b: [1, 2, 3] }), + assert.areEqual({ asd: 2, bnm: [1, 2, 3] }, { asd: 2, bnm: [1, 2, 3] }), ); // commented so CI can pass - uncomment to see the failure diff --git a/tests/examples/basic_features_test.js b/tests/examples/basic_features_test.js index 7f4c9da6..c36568e0 100644 --- a/tests/examples/basic_features_test.js +++ b/tests/examples/basic_features_test.js @@ -28,29 +28,35 @@ suite('testing testy - basic features', () => { test('custom equality check', () => { const criteria = (o1, o2) => o1.a === o2.a; + /* eslint-disable id-length */ assert.areEqual({ a: 'a', b: 'b1' }, { a: 'a', b: 'b2' }, criteria); assert.that({ a: 'a', b: 'b1' }).isEqualTo({ a: 'a', b: 'b2' }, criteria); assert.that({ a: 'a', b: 'b1' }).isNotEqualTo({ a: 'a2', b: 'b1' }, criteria); + /* eslint-enable id-length */ }); test('equality check when objects understand equals()', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b1', equals: function(another) { - return this.a === another.a; + return this.a === another.a; } }; const objectTwo = { a: 'a', b: 'b2', equals: function(another) { - return this.b === another.b; + return this.b === another.b; } }; + /* eslint-enable id-length */ assert.that(objectOne).isEqualTo(objectTwo); assert.that(objectTwo).isNotEqualTo(objectOne); }); test('equality check using custom message name', () => { + /* eslint-disable id-length */ const objectOne = { a: 'a', b: 'b1', sameAs: function(another) { - return this.a === another.a; + return this.a === another.a; } }; const objectTwo = { a: 'a', b: 'b2', sameAs: function(another) { - return this.b === another.b; + return this.b === another.b; } }; + /* eslint-enable id-length */ assert.that(objectOne).isEqualTo(objectTwo, 'sameAs'); assert.that(objectTwo).isNotEqualTo(objectOne, 'sameAs'); }); diff --git a/tests/utils_test.js b/tests/utils_test.js index 4e40cfb7..381239e7 100644 --- a/tests/utils_test.js +++ b/tests/utils_test.js @@ -5,13 +5,13 @@ const Utils = require('../lib/utils'); suite('utility functions', () => { test('isCyclic is true if the object has a cyclic reference', () => { - const obj = { a: 2 }; + const obj = { asd: 2 }; obj.yourself = obj; assert.isTrue(Utils.isCyclic(obj)); }); test('isCyclic is false if the object does not have a cyclic reference', () => { - const obj = { a: 2 }; + const obj = { asd: 2 }; assert.isFalse(Utils.isCyclic(obj)); }); @@ -40,7 +40,7 @@ suite('utility functions', () => { test('respondsTo() is true when the property exists as a function in the object', () => { const thingThatKnowsHowToDance = { dance() { - return 'I am dancing!'; + return 'I am dancing!'; } }; assert.isTrue(Utils.respondsTo(thingThatKnowsHowToDance, 'dance')); }); From 88326d5afd038280e85c040a67e22b9a021325b5 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 28 Dec 2020 10:48:54 -0300 Subject: [PATCH 53/64] :recycle: throw error objects instead of strings --- lib/test.js | 4 ++-- lib/test_suite.js | 8 ++++---- .../core/assertions/collection_assertions_test.js | 4 ++-- tests/core/assertions/exception_assertions_test.js | 2 +- tests/core/test_suite_test.js | 14 +++++++------- tests/core/test_test.js | 12 ++++++------ 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/test.js b/lib/test.js index 5a05ee9d..0500d7a3 100644 --- a/lib/test.js +++ b/lib/test.js @@ -119,13 +119,13 @@ class Test { _ensureNameIsValid(name) { if (!isStringWithContent(name)) { - throw 'Test does not have a valid name'; + throw new Error('Test does not have a valid name'); } } _ensureBodyIsValid(body) { if (!isUndefined(body) && !isFunction(body)) { - throw 'Test does not have a valid body'; + throw new Error('Test does not have a valid body'); } } } diff --git a/lib/test_suite.js b/lib/test_suite.js index 78cc4af8..6671af23 100644 --- a/lib/test_suite.js +++ b/lib/test_suite.js @@ -22,7 +22,7 @@ class TestSuite { if (isUndefined(this._before)) { this._before = initializationBlock; } else { - throw 'There is already a before() block. Please leave just one before() block and run again the tests.'; + throw new Error('There is already a before() block. Please leave just one before() block and run again the tests.'); } } @@ -30,7 +30,7 @@ class TestSuite { if (isUndefined(this._after)) { this._after = releasingBlock; } else { - throw 'There is already an after() block. Please leave just one after() block and run again the tests.'; + throw new Error('There is already an after() block. Please leave just one after() block and run again the tests.'); } } @@ -101,13 +101,13 @@ class TestSuite { _ensureNameIsValid(name) { if (!isStringWithContent(name)) { - throw 'Suite does not have a valid name'; + throw new Error('Suite does not have a valid name'); } } _ensureBodyIsValid(body) { if (!isFunction(body)) { - throw 'Suite does not have a valid body'; + throw new Error('Suite does not have a valid body'); } } diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index a621ccd0..cb27efde 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -161,13 +161,13 @@ suite('collection assertions', () => { expectSuccess(result); }); - test('isEmpty throwing error instead of failure', () => { + test('isEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isEmpty(undefined)); expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', 'undefined')); }); - test('isNotEmpty throwing error instead of failure', () => { + test('isNotEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isNotEmpty(undefined)); expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', 'undefined')); diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index 2227b621..59f060b5 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -38,7 +38,7 @@ suite('exception assertions', () => { test('raises() can receive a regex and it passes when it matches the thrown error with message', () => { const result = resultOfATestWith(assert => assert.that(() => { - throw new TypeError('things happened'); + throw new Error('things happened'); }).raises(/happened/), ); diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index ffb1c3aa..1b4b6c2d 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -33,7 +33,7 @@ suite('test suite behavior', () => { assert .that(() => mySuite.before(() => 5 + 6)) - .raises('There is already a before() block. Please leave just one before() block and run again the tests.'); + .raises(/There is already a before\(\) block. Please leave just one before\(\) block and run again the tests./); }); test('more than one after block is not allowed', () => { @@ -41,7 +41,7 @@ suite('test suite behavior', () => { assert .that(() => mySuite.after(() => 5 + 6)) - .raises('There is already an after() block. Please leave just one after() block and run again the tests.'); + .raises(/There is already an after\(\) block. Please leave just one after\(\) block and run again the tests./); }); test('after hook can be used', () => { @@ -82,23 +82,23 @@ suite('test suite behavior', () => { }); test('a suite cannot be created without a name', () => { - assert.that(() => new TestSuite()).raises('Suite does not have a valid name'); + assert.that(() => new TestSuite()).raises(/Suite does not have a valid name/); }); test('a suite cannot be created with an empty name', () => { - assert.that(() => new TestSuite(' ')).raises('Suite does not have a valid name'); + assert.that(() => new TestSuite(' ')).raises(/Suite does not have a valid name/); }); test('a suite cannot be created with a name that is not a string', () => { - assert.that(() => new TestSuite(new Date())).raises('Suite does not have a valid name'); + assert.that(() => new TestSuite(new Date())).raises(/Suite does not have a valid name/); }); test('a suite cannot be created without a body', () => { - assert.that(() => new TestSuite('hey')).raises('Suite does not have a valid body'); + assert.that(() => new TestSuite('hey')).raises(/Suite does not have a valid body/); }); test('a suite cannot be created with a body that is not a function', () => { - assert.that(() => new TestSuite('hey', 'ho')).raises('Suite does not have a valid body'); + assert.that(() => new TestSuite('hey', 'ho')).raises(/Suite does not have a valid body/); }); test('running with fail fast enabled stops at the first failure', () => { diff --git a/tests/core/test_test.js b/tests/core/test_test.js index df4f410c..b0c2f5c9 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -19,27 +19,27 @@ suite('tests behavior', () => { }); test('a test cannot be created without a name', () => { - assert.that(() => new Test()).raises('Test does not have a valid name'); + assert.that(() => new Test()).raises(/Test does not have a valid name/); }); test('a test cannot be created with a name that is not a string', () => { - assert.that(() => new Test(new Date())).raises('Test does not have a valid name'); + assert.that(() => new Test(new Date())).raises(/Test does not have a valid name/); }); test('a test can be created with undefined body', () => { - assert.that(() => new Test('hey', undefined)).doesNotRaise('Test does not have a valid body'); + assert.that(() => new Test('hey', undefined)).doesNotRaise(/Test does not have a valid body/); }); test('a test cannot be created without a body', () => { - assert.that(() => new Test('hey', null)).raises('Test does not have a valid body'); + assert.that(() => new Test('hey', null)).raises(/Test does not have a valid body/); }); test('a test cannot be created with a body that is not a function', () => { - assert.that(() => new Test('hey', 'ho')).raises('Test does not have a valid body'); + assert.that(() => new Test('hey', 'ho')).raises(/Test does not have a valid body/); }); test('a test cannot be created with an empty name', () => { - assert.that(() => new Test(' ', undefined)).raises('Test does not have a valid name'); + assert.that(() => new Test(' ', undefined)).raises(/Test does not have a valid name/); }); test('a test fails on the first assertion failed', () => { From 2dd0da481b25a456d1b7db07299e550c5a614f9e Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 28 Dec 2020 15:19:22 -0300 Subject: [PATCH 54/64] :construction: continue i18n refactoring --- lib/asserter.js | 6 +- lib/assertion.js | 71 +++++++++---------- lib/i18n.js | 16 +++++ lib/internationalized_message.js | 14 ---- lib/translations.json | 24 +++---- .../assertions/boolean_assertions_test.js | 10 +-- .../assertions/collection_assertions_test.js | 26 +++---- .../assertions/exception_assertions_test.js | 34 ++++++++- tests/core/assertions/null_assertions_test.js | 6 +- .../assertions/numeric_assertions_test.js | 6 +- ...porting_failures_and_pending_tests_test.js | 7 +- .../string_match_assertions_test.js | 4 +- .../assertions/undefined_assertions_test.js | 6 +- tests/core/test_test.js | 4 +- 14 files changed, 127 insertions(+), 107 deletions(-) delete mode 100644 lib/internationalized_message.js diff --git a/lib/asserter.js b/lib/asserter.js index f5578ec3..fdd8345e 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -3,13 +3,13 @@ const TestResult = require('./test_result'); const TestResultReporter = require('./test_result_reporter'); const Assertion = require('./assertion'); -const InternationalizedMessage = require('./internationalized_message'); +const I18n = require('./i18n'); const { isStringWithContent } = require('./utils'); class FailureGenerator extends TestResultReporter { with(description) { - this.report(TestResult.failure(description || new InternationalizedMessage('explicitly_failed'))); + this.report(TestResult.failure(description || I18n.message('explicitly_failed'))); } } @@ -23,7 +23,7 @@ class PendingMarker extends TestResultReporter { } invalidReasonErrorMessage() { - return new InternationalizedMessage('invalid_pending_reason'); + return I18n.message('invalid_pending_reason'); } } diff --git a/lib/assertion.js b/lib/assertion.js index 17046e42..60af45ba 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -3,9 +3,9 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); -const InternationalizedMessage = require('./internationalized_message'); +const I18n = require('./i18n'); -const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); +const { prettyPrint, isUndefined, isRegex, isString, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); class Assertion extends TestResultReporter { constructor(runner, actual) { @@ -18,14 +18,14 @@ class Assertion extends TestResultReporter { isTrue() { this._reportAssertionResult( this._actual === true, - new InternationalizedMessage('expectation_be_true', this._actualResultAsString()), + I18n.message('expectation_be_true', this._actualResultAsString()), ); } isFalse() { this._reportAssertionResult( this._actual === false, - new InternationalizedMessage('expectation_be_false', this._actualResultAsString()), + I18n.message('expectation_be_false', this._actualResultAsString()), ); } @@ -34,14 +34,14 @@ class Assertion extends TestResultReporter { isUndefined() { this._reportAssertionResult( isUndefined(this._actual), - new InternationalizedMessage('expectation_be_undefined', this._actualResultAsString()), + I18n.message('expectation_be_undefined', this._actualResultAsString()), ); } isNotUndefined() { this._reportAssertionResult( !isUndefined(this._actual), - new InternationalizedMessage('expectation_be_defined', this._actualResultAsString()), + I18n.message('expectation_be_defined', this._actualResultAsString()), ); } @@ -50,14 +50,14 @@ class Assertion extends TestResultReporter { isNull() { this._reportAssertionResult( this._actual === null, - new InternationalizedMessage('expectation_be_null', this._actualResultAsString()), + I18n.message('expectation_be_null', this._actualResultAsString()), ); } isNotNull() { this._reportAssertionResult( this._actual !== null, - new InternationalizedMessage('expectation_be_not_null', this._actualResultAsString()), + I18n.message('expectation_be_not_null', this._actualResultAsString()), ); } @@ -76,47 +76,47 @@ class Assertion extends TestResultReporter { includes(expectedObject, equalityCriteria) { const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = new InternationalizedMessage('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = I18n.message('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = new InternationalizedMessage('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = I18n.message('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = new InternationalizedMessage('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = I18n.message('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = !expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = new InternationalizedMessage('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = I18n.message('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = new InternationalizedMessage('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); + const failureMessage = I18n.message('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isEmpty() { const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); - const failureMessage = new InternationalizedMessage('expectation_be_empty', this._actualResultAsString()); + const failureMessage = I18n.message('expectation_be_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotEmpty() { const setValueWhenUndefined = this._actual || {}; const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; - const failureMessage = new InternationalizedMessage('expectation_be_not_empty', this._actualResultAsString()); + const failureMessage = I18n.message('expectation_be_not_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -131,16 +131,11 @@ class Assertion extends TestResultReporter { } doesNotRaiseAnyErrors() { - let noErrorsOccurred = false; - let failureMessage = ''; try { this._actual.call(); - noErrorsOccurred = true; + this._reportAssertionResult(true); } catch (error) { - noErrorsOccurred = false; - failureMessage = `${this.translated('expected_no_errors')}, ${this.translated('but')} ${prettyPrint(error)} ${this.translated('was_raised')}`; - } finally { - this._reportAssertionResult(noErrorsOccurred, failureMessage, true); + this._reportAssertionResult(false, I18n.message('expectation_no_errors', prettyPrint(error))); } } @@ -148,16 +143,16 @@ class Assertion extends TestResultReporter { isNearTo(number, precisionDigits = 4) { const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; - const failureMessage = new InternationalizedMessage('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); - this._reportAssertionResult(result, failureMessage, false); + const failureMessage = I18n.message('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); + this._reportAssertionResult(result, failureMessage); } // String assertions matches(regex) { const result = this._actual.match(regex); - const failureMessage = new InternationalizedMessage('expectation_match_regex', this._actualResultAsString(), regex); - this._reportAssertionResult(result, failureMessage, false); + const failureMessage = I18n.message('expectation_match_regex', this._actualResultAsString(), regex); + this._reportAssertionResult(result, failureMessage); } // Private @@ -181,25 +176,27 @@ class Assertion extends TestResultReporter { } _exceptionAssertion(errorExpectation, shouldFail) { - let hasFailed = false; + let assertionHasFailed = false; let actualError; try { this._actual.call(); - hasFailed = !shouldFail; + assertionHasFailed = !shouldFail; } catch (error) { actualError = error; const errorCheck = this._checkIfErrorMatchesExpectation(errorExpectation, actualError); - hasFailed = shouldFail ? errorCheck : !errorCheck; + assertionHasFailed = shouldFail ? errorCheck : !errorCheck; } finally { - const toHappenOrNot = shouldFail ? this.translated('to_happen') : this.translated('not_to_happen'); - const expectedErrorIntroduction = `${this.translated('expected')} ${this.translated('expecting_error')} ${prettyPrint(errorExpectation)} ${toHappenOrNot}`; let failureMessage; if (isUndefined(actualError)) { - failureMessage = expectedErrorIntroduction; + failureMessage = I18n.message('expectation_error', prettyPrint(errorExpectation)); } else { - failureMessage = `${expectedErrorIntroduction}, ${this.translated('but_got')} ${prettyPrint(actualError)} ${this.translated('instead')}`; + if (shouldFail) { + failureMessage = I18n.message('expectation_different_error', prettyPrint(errorExpectation), prettyPrint(actualError)); + } else { + failureMessage = I18n.message('expectation_no_error', prettyPrint(actualError)); + } } - this._reportAssertionResult(hasFailed, failureMessage, true); + this._reportAssertionResult(assertionHasFailed, failureMessage, true); } } @@ -216,10 +213,10 @@ class Assertion extends TestResultReporter { this.report(TestResult.success()); } else { let defaultFailureMessage; - if (matcherFailureMessage instanceof InternationalizedMessage) { - defaultFailureMessage = matcherFailureMessage; - } else { + if (isString(matcherFailureMessage)) { defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; + } else { + defaultFailureMessage = matcherFailureMessage; } this.report(TestResult.failure(overrideFailureMessage ? matcherFailureMessage : defaultFailureMessage)); } diff --git a/lib/i18n.js b/lib/i18n.js index 73458ad3..7dd5c6e2 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -3,7 +3,23 @@ const TRANSLATIONS = require('./translations'); const { isUndefined } = require('./utils'); +class InternationalizedMessage { + constructor(key, ...params) { + this._key = key; + this._params = params; + Object.freeze(this); + } + + expressedInLocale(locale) { + return locale.translate(this._key, ...this._params); + } +} + class I18n { + static message(key, ...params) { + return new InternationalizedMessage(key, ...params); + } + static defaultLanguage() { return 'en'; } diff --git a/lib/internationalized_message.js b/lib/internationalized_message.js deleted file mode 100644 index afc395d1..00000000 --- a/lib/internationalized_message.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -class InternationalizedMessage { - constructor(key, ...params) { - this._key = key; - this._params = params; - } - - expressedInLocale(locale) { - return locale.translate(this._key, ...this._params); - } -} - -module.exports = InternationalizedMessage; diff --git a/lib/translations.json b/lib/translations.json index 9c26111c..f9743b9e 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -20,11 +20,6 @@ "to": "to ", "be_equal_to": "be equal to", "be_not_equal_to": "be not equal to", - "to_happen": "to happen", - "not_to_happen": "not to happen", - "but_got": "but got", - "instead": "instead", - "expecting_error": "error", "expectation_be_true": "Expected %s to be true", "expectation_be_false": "Expected %s to be false", "expectation_be_undefined": "Expected %s to be undefined", @@ -40,9 +35,10 @@ "expectation_include_exactly": "Expected %s to include exactly %s", "expectation_be_empty": "Expected %s to be empty", "expectation_be_not_empty": "Expected %s to be not empty", - "expected_no_errors": "Expected no errors to happen", - "but": "but", - "was_raised": "was raised", + "expectation_error": "Expected error %s to happen", + "expectation_no_error": "Expected error %s not to happen", + "expectation_no_errors": "Expected no errors to happen, but %s was raised", + "expectation_different_error": "Expected error %s to happen, but got %s instead", "explicitly_failed": "Explicitly failed", "running_tests_in": "Running tests in", "fail_fast": "Fail fast", @@ -71,11 +67,6 @@ "to": "", "be_equal_to": "sea igual a", "be_not_equal_to": "no sea igual a", - "to_happen": "ocurra", - "not_to_happen": "no ocurra", - "but_got": "pero se obtuvo", - "instead": "en su lugar", - "expecting_error": "el error", "expectation_be_true": "Se esperaba que %s sea true", "expectation_be_false": "Se esperaba que %s sea false", "expectation_be_undefined": "Se esperaba que %s sea undefined", @@ -91,9 +82,10 @@ "expectation_include_exactly": "Se esperaba que %s incluya exactamente a %s", "expectation_be_empty": "Se esperaba que %s sea vacío", "expectation_be_not_empty": "Se esperaba que %s no sea vacío", - "expected_no_errors": "No se esperaban errores", - "but": "pero", - "was_raised": "fue lanzado", + "expectation_error": "Se esperaba que ocurra el error %s", + "expectation_no_error": "No se esperaba que ocurra el error %s", + "expectation_no_errors": "No se esperaban errores, pero %s fue lanzado", + "expectation_different_error": "Se esperaba que ocurra el error %s, pero en su lugar ocurrió el error %s", "explicitly_failed": "Explícitamente marcado como fallido", "running_tests_in": "Ejecutando tests en", "fail_fast": "Fallo rápido", diff --git a/tests/core/assertions/boolean_assertions_test.js b/tests/core/assertions/boolean_assertions_test.js index c6213cb7..ca0447b2 100644 --- a/tests/core/assertions/boolean_assertions_test.js +++ b/tests/core/assertions/boolean_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('boolean assertions', () => { test('isTrue passes with true', () => { @@ -16,13 +16,13 @@ suite('boolean assertions', () => { test('isTrue does not pass with false', () => { const result = resultOfATestWith(assert => assert.that(false).isTrue()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_true', 'false')); + expectFailureOn(result, I18n.message('expectation_be_true', 'false')); }); test('isTrue does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isTrue()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_true', 'null')); + expectFailureOn(result, I18n.message('expectation_be_true', 'null')); }); test('isFalse passes with false', () => { @@ -34,12 +34,12 @@ suite('boolean assertions', () => { test('isFalse does not pass with true', () => { const result = resultOfATestWith(assert => assert.that(true).isFalse()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_false', 'true')); + expectFailureOn(result, I18n.message('expectation_be_false', 'true')); }); test('isFalse does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isFalse()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_false', 'null')); + expectFailureOn(result, I18n.message('expectation_be_false', 'null')); }); }); diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index cb27efde..54e9edca 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -5,7 +5,7 @@ const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); const Utils = require('../../../lib/utils'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('collection assertions', () => { const nonEmptySet = new Set([1]); @@ -20,7 +20,7 @@ suite('collection assertions', () => { test('includes does not pass if the actual object is not an array', () => { const result = resultOfATestWith(assert => assert.that([]).includes('hey')); - expectFailureOn(result, new InternationalizedMessage('expectation_include', '[]', "'hey'")); + expectFailureOn(result, I18n.message('expectation_include', '[]', "'hey'")); }); test('includes works with non-primitives', () => { @@ -32,7 +32,7 @@ suite('collection assertions', () => { test('doesNotInclude fails if the object is in the array', () => { const result = resultOfATestWith(assert => assert.that(['hey']).doesNotInclude('hey')); - expectFailureOn(result, new InternationalizedMessage('expectation_not_include', "[ 'hey' ]", "'hey'")); + expectFailureOn(result, I18n.message('expectation_not_include', "[ 'hey' ]", "'hey'")); }); test('doesNotInclude passes if the object is not an array', () => { @@ -44,7 +44,7 @@ suite('collection assertions', () => { test('doesNotInclude fails properly with non-primitives', () => { const result = resultOfATestWith(assert => assert.that([{ asd: '1' }]).doesNotInclude({ asd: '1' })); - expectFailureOn(result, new InternationalizedMessage('expectation_not_include', "[ { asd: '1' } ]", "{ asd: '1' }")); + expectFailureOn(result, I18n.message('expectation_not_include', "[ { asd: '1' } ]", "{ asd: '1' }")); }); test('includesExactly passes with a single object included', () => { @@ -62,19 +62,19 @@ suite('collection assertions', () => { test('includesExactly fails if the collection has more objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey', 'ho']).includesExactly('hey')); - expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey', 'ho' ]", "[ 'hey' ]")); + expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey', 'ho' ]", "[ 'hey' ]")); }); test('includesExactly fails if the collection has less objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('hey', 'ho')); - expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey' ]", "[ 'hey', 'ho' ]")); + expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey' ]", "[ 'hey', 'ho' ]")); }); test('includesExactly fails if none of the objects are included at all', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('ho')); - expectFailureOn(result, new InternationalizedMessage('expectation_include_exactly', "[ 'hey' ]", "[ 'ho' ]")); + expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey' ]", "[ 'ho' ]")); }); test('includesExactly passes with many items no matter the order', () => { @@ -98,7 +98,7 @@ suite('collection assertions', () => { test('isEmpty does not pass if the array has elements', () => { const result = resultOfATestWith(assert => assert.that(['hey']).isEmpty()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', "[ 'hey' ]")); + expectFailureOn(result, I18n.message('expectation_be_empty', "[ 'hey' ]")); }); test('isEmpty passes with an empty string', () => { @@ -122,7 +122,7 @@ suite('collection assertions', () => { test('isNotEmpty does not pass if the array is empty', () => { const result = resultOfATestWith(assert => assert.that([]).isNotEmpty()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', '[]')); + expectFailureOn(result, I18n.message('expectation_be_not_empty', '[]')); }); test('isNotEmpty passes with a string with content', () => { @@ -146,13 +146,13 @@ suite('collection assertions', () => { test('isEmpty does not pass on a set with elements', () => { const result = resultOfATestWith(assert => assert.that(nonEmptySet).isEmpty()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', Utils.prettyPrint(nonEmptySet))); + expectFailureOn(result, I18n.message('expectation_be_empty', Utils.prettyPrint(nonEmptySet))); }); test('isNotEmpty does not pass on an empty set', () => { const result = resultOfATestWith(assert => assert.that(emptySet).isNotEmpty()); - expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', Utils.prettyPrint(emptySet))); + expectFailureOn(result, I18n.message('expectation_be_not_empty', Utils.prettyPrint(emptySet))); }); test('isNotEmpty passes on a set with elements', () => { @@ -164,13 +164,13 @@ suite('collection assertions', () => { test('isEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isEmpty(undefined)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_empty', 'undefined')); + expectFailureOn(result, I18n.message('expectation_be_empty', 'undefined')); }); test('isNotEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isNotEmpty(undefined)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', 'undefined')); + expectFailureOn(result, I18n.message('expectation_be_not_empty', 'undefined')); }); test('includes works with Sets', () => { diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index 59f060b5..d869c970 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const I18n = require('../../../lib/i18n'); + suite('exception assertions', () => { test('raises() can receive a string and it passes when the exact string is expected', () => { const result = resultOfATestWith(assert => @@ -52,13 +54,39 @@ suite('exception assertions', () => { }).raises(/happiness/), ); - expectFailureOn(result, 'Expected error /happiness/ to happen, but got \'a terrible error\' instead'); + expectFailureOn(result, I18n.message('expectation_different_error', '/happiness/', "'a terrible error'")); }); test('raises() fails when no errors occur in the given function', () => { const result = resultOfATestWith(assert => assert.that(() => 1 + 2).raises('a weird error')); - expectFailureOn(result, 'Expected error \'a weird error\' to happen'); + expectFailureOn(result, I18n.message('expectation_error', "'a weird error'")); + }); + + test('doesNotRaise() passes when no errors happen at all', () => { + const result = resultOfATestWith(assert => assert.that(() => 1 + 2).doesNotRaise('a problem')); + + expectSuccess(result); + }); + + test('doesNotRaise() passes when another error happens that do not match the expected one', () => { + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'another problem'; + }).doesNotRaise('a problem'), + ); + + expectSuccess(result); + }); + + test('doesNotRaise() fails when the expected error happens during the execution of the test', () => { + const result = resultOfATestWith(assert => + assert.that(() => { + throw 'this problem'; + }).doesNotRaise('this problem'), + ); + + expectFailureOn(result, I18n.message('expectation_no_error', "'this problem'")); }); test('doesNoRaiseAnyErrors() passes when no errors occur in the given function', () => { @@ -74,6 +102,6 @@ suite('exception assertions', () => { }).doesNotRaiseAnyErrors(), ); - expectFailureOn(result, 'Expected no errors to happen, but \'an unexpected error\' was raised'); + expectFailureOn(result, I18n.message('expectation_no_errors', "'an unexpected error'")); }); }); diff --git a/tests/core/assertions/null_assertions_test.js b/tests/core/assertions/null_assertions_test.js index a75873e7..42856280 100644 --- a/tests/core/assertions/null_assertions_test.js +++ b/tests/core/assertions/null_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('assertions about null', () => { test('isNull passes with a null value', () => { @@ -16,7 +16,7 @@ suite('assertions about null', () => { test('isNull does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isNull(undefined)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_null', 'undefined')); + expectFailureOn(result, I18n.message('expectation_be_null', 'undefined')); }); test('isNotNull passes with a non-null value', () => { @@ -28,6 +28,6 @@ suite('assertions about null', () => { test('isNotNull does not pass when the value is null', () => { const result = resultOfATestWith(assert => assert.isNotNull(null)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_not_null', 'null')); + expectFailureOn(result, I18n.message('expectation_be_not_null', 'null')); }); }); diff --git a/tests/core/assertions/numeric_assertions_test.js b/tests/core/assertions/numeric_assertions_test.js index 62986c28..540ce78b 100644 --- a/tests/core/assertions/numeric_assertions_test.js +++ b/tests/core/assertions/numeric_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('numeric assertions', () => { test('isNearTo passes if an exact integer matches', () => { @@ -16,7 +16,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the integer part of the number is not equal', () => { const result = resultOfATestWith(assert => assert.that(42).isNearTo(43)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_near_to', '42', '43', '4')); + expectFailureOn(result, I18n.message('expectation_be_near_to', '42', '43', '4')); }); test('isNearTo passes if the actual number rounded using the specified decimals matches the expected number', () => { @@ -28,7 +28,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the actual number rounded using the specified decimals does not match the expected number', () => { const result = resultOfATestWith(assert => assert.that(42.001).isNearTo(42, 3)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_near_to', '42.001', '42', '3')); + expectFailureOn(result, I18n.message('expectation_be_near_to', '42.001', '42', '3')); }); test('isNearTo passes with a default precision of 4', () => { diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js index 6a142c7e..8cfc0fe7 100644 --- a/tests/core/assertions/reporting_failures_and_pending_tests_test.js +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -1,15 +1,16 @@ 'use strict'; -const InternationalizedMessage = require('../../../lib/internationalized_message'); const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectErrorOn, expectFailureOn, expectPendingResultOn } = require('../../support/assertion_helpers'); +const I18n = require('../../../lib/i18n'); + suite('reporting failures and pending tests', () => { test('marking a test as explicitly failed with no message', () => { const result = resultOfATestWith((_assert, fail, _pending) => fail.with()); - expectFailureOn(result, new InternationalizedMessage('explicitly_failed')); + expectFailureOn(result, I18n.message('explicitly_failed')); }); test('marking a test as explicitly failed with a custom message', () => { @@ -21,7 +22,7 @@ suite('reporting failures and pending tests', () => { test('marking a test as pending with no message', () => { const result = resultOfATestWith((_assert, _fail, pending) => pending.dueTo()); - expectErrorOn(result, new InternationalizedMessage('invalid_pending_reason')); + expectErrorOn(result, I18n.message('invalid_pending_reason')); }); test('marking a test as pending with a custom message', () => { diff --git a/tests/core/assertions/string_match_assertions_test.js b/tests/core/assertions/string_match_assertions_test.js index 623c96b7..873f19b8 100644 --- a/tests/core/assertions/string_match_assertions_test.js +++ b/tests/core/assertions/string_match_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('assertions about strings match', () => { test('matches() passes when the regex match the actual string', () => { @@ -16,7 +16,7 @@ suite('assertions about strings match', () => { test('matches() fails when the regex does the actual string', () => { const result = resultOfATestWith(assert => assert.that('goodbye').matches(/ll/)); - expectFailureOn(result, new InternationalizedMessage('expectation_match_regex', "'goodbye'", /ll/)); + expectFailureOn(result, I18n.message('expectation_match_regex', "'goodbye'", /ll/)); }); test('isMatching() shortcut works', () => { diff --git a/tests/core/assertions/undefined_assertions_test.js b/tests/core/assertions/undefined_assertions_test.js index 20e0eb90..b74bbdc9 100644 --- a/tests/core/assertions/undefined_assertions_test.js +++ b/tests/core/assertions/undefined_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const InternationalizedMessage = require('../../../lib/internationalized_message'); +const I18n = require('../../../lib/i18n'); suite('undefined assertions', () => { test('isUndefined passes with an undefined value', () => { @@ -16,7 +16,7 @@ suite('undefined assertions', () => { test('isUndefined does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isUndefined(null)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_undefined', 'null')); + expectFailureOn(result, I18n.message('expectation_be_undefined', 'null')); }); test('isNotUndefined passes with a not-undefined value', () => { @@ -28,6 +28,6 @@ suite('undefined assertions', () => { test('isNotUndefined does not pass when the value is undefined', () => { const result = resultOfATestWith(assert => assert.isNotUndefined(undefined)); - expectFailureOn(result, new InternationalizedMessage('expectation_be_defined', 'undefined')); + expectFailureOn(result, I18n.message('expectation_be_defined', 'undefined')); }); }); diff --git a/tests/core/test_test.js b/tests/core/test_test.js index b0c2f5c9..3be951ae 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -6,7 +6,7 @@ const { withRunner, runSingleTest } = require('../support/runner_helpers'); const { expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); const Test = require('../../lib/test'); -const InternationalizedMessage = require('../../lib/internationalized_message'); +const I18n = require('../../lib/i18n'); suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { @@ -51,7 +51,7 @@ suite('tests behavior', () => { const result = runSingleTest(runner, test); - expectFailureOn(result, new InternationalizedMessage('expectation_be_not_empty', '[]')); + expectFailureOn(result, I18n.message('expectation_be_not_empty', '[]')); }); }); }); From 3b84f789a7aacc54af83d630686a6e0b0d7c3ef9 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 29 Dec 2020 01:02:32 -0300 Subject: [PATCH 55/64] :recycle: finish i18n refactoring (fixes #71) --- lib/asserter.js | 6 +- lib/assertion.js | 65 ++++++++-------- lib/console_ui.js | 2 +- lib/equality_assertion_strategy.js | 15 ++-- lib/formatter.js | 2 +- lib/i18n.js | 76 +++++++++++++++++-- lib/test_result_reporter.js | 4 - lib/test_runner.js | 2 +- lib/translations.json | 18 +++-- lib/utils.js | 13 +++- .../assertions/boolean_assertions_test.js | 10 +-- .../assertions/collection_assertions_test.js | 26 +++---- .../assertions/equality_assertions_test.js | 34 +++++---- .../assertions/exception_assertions_test.js | 10 +-- tests/core/assertions/null_assertions_test.js | 6 +- .../assertions/numeric_assertions_test.js | 6 +- ...porting_failures_and_pending_tests_test.js | 6 +- .../string_match_assertions_test.js | 4 +- .../assertions/undefined_assertions_test.js | 6 +- tests/core/i18n_test.js | 2 +- tests/core/test_test.js | 4 +- tests/ui/formatter_test.js | 5 +- 22 files changed, 194 insertions(+), 128 deletions(-) diff --git a/lib/asserter.js b/lib/asserter.js index fdd8345e..1bd2a80e 100644 --- a/lib/asserter.js +++ b/lib/asserter.js @@ -3,13 +3,13 @@ const TestResult = require('./test_result'); const TestResultReporter = require('./test_result_reporter'); const Assertion = require('./assertion'); -const I18n = require('./i18n'); +const { I18nMessage } = require('./i18n'); const { isStringWithContent } = require('./utils'); class FailureGenerator extends TestResultReporter { with(description) { - this.report(TestResult.failure(description || I18n.message('explicitly_failed'))); + this.report(TestResult.failure(description || I18nMessage.of('explicitly_failed'))); } } @@ -23,7 +23,7 @@ class PendingMarker extends TestResultReporter { } invalidReasonErrorMessage() { - return I18n.message('invalid_pending_reason'); + return I18nMessage.of('invalid_pending_reason'); } } diff --git a/lib/assertion.js b/lib/assertion.js index 60af45ba..6a80626a 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -3,9 +3,9 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); -const I18n = require('./i18n'); +const { I18n, I18nMessage } = require('./i18n'); -const { prettyPrint, isUndefined, isRegex, isString, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); +const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); class Assertion extends TestResultReporter { constructor(runner, actual) { @@ -18,14 +18,14 @@ class Assertion extends TestResultReporter { isTrue() { this._reportAssertionResult( this._actual === true, - I18n.message('expectation_be_true', this._actualResultAsString()), + I18nMessage.of('expectation_be_true', this._actualResultAsString()), ); } isFalse() { this._reportAssertionResult( this._actual === false, - I18n.message('expectation_be_false', this._actualResultAsString()), + I18nMessage.of('expectation_be_false', this._actualResultAsString()), ); } @@ -34,14 +34,14 @@ class Assertion extends TestResultReporter { isUndefined() { this._reportAssertionResult( isUndefined(this._actual), - I18n.message('expectation_be_undefined', this._actualResultAsString()), + I18nMessage.of('expectation_be_undefined', this._actualResultAsString()), ); } isNotUndefined() { this._reportAssertionResult( !isUndefined(this._actual), - I18n.message('expectation_be_defined', this._actualResultAsString()), + I18nMessage.of('expectation_be_defined', this._actualResultAsString()), ); } @@ -50,14 +50,14 @@ class Assertion extends TestResultReporter { isNull() { this._reportAssertionResult( this._actual === null, - I18n.message('expectation_be_null', this._actualResultAsString()), + I18nMessage.of('expectation_be_null', this._actualResultAsString()), ); } isNotNull() { this._reportAssertionResult( this._actual !== null, - I18n.message('expectation_be_not_null', this._actualResultAsString()), + I18nMessage.of('expectation_be_not_null', this._actualResultAsString()), ); } @@ -76,47 +76,47 @@ class Assertion extends TestResultReporter { includes(expectedObject, equalityCriteria) { const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = I18n.message('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = I18nMessage.of('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = I18n.message('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = I18nMessage.of('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = I18n.message('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = I18nMessage.of('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = !expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = I18n.message('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = I18nMessage.of('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = I18n.message('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); + const failureMessage = I18nMessage.of('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isEmpty() { const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); - const failureMessage = I18n.message('expectation_be_empty', this._actualResultAsString()); + const failureMessage = I18nMessage.of('expectation_be_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotEmpty() { const setValueWhenUndefined = this._actual || {}; const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; - const failureMessage = I18n.message('expectation_be_not_empty', this._actualResultAsString()); + const failureMessage = I18nMessage.of('expectation_be_not_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } @@ -135,7 +135,7 @@ class Assertion extends TestResultReporter { this._actual.call(); this._reportAssertionResult(true); } catch (error) { - this._reportAssertionResult(false, I18n.message('expectation_no_errors', prettyPrint(error))); + this._reportAssertionResult(false, I18nMessage.of('expectation_no_errors', prettyPrint(error))); } } @@ -143,7 +143,7 @@ class Assertion extends TestResultReporter { isNearTo(number, precisionDigits = 4) { const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; - const failureMessage = I18n.message('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); + const failureMessage = I18nMessage.of('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); this._reportAssertionResult(result, failureMessage); } @@ -151,7 +151,7 @@ class Assertion extends TestResultReporter { matches(regex) { const result = this._actual.match(regex); - const failureMessage = I18n.message('expectation_match_regex', this._actualResultAsString(), regex); + const failureMessage = I18nMessage.of('expectation_match_regex', this._actualResultAsString(), regex); this._reportAssertionResult(result, failureMessage); } @@ -160,14 +160,15 @@ class Assertion extends TestResultReporter { _equalityAssertion(expected, criteria, shouldBeEqual) { const { comparisonResult, additionalFailureMessage, overrideFailureMessage } = EqualityAssertionStrategy.evaluate(this._actual, expected, criteria); - const resultIsSuccessful = shouldBeEqual ? comparisonResult : !comparisonResult; + if (overrideFailureMessage) { - this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage, true); + this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage); } else { - const expectationMessage = shouldBeEqual ? this.translated('be_equal_to') : this.translated('be_not_equal_to'); - const failureMessage = `${expectationMessage} ${prettyPrint(expected)}${additionalFailureMessage}`; - this._reportAssertionResult(resultIsSuccessful, failureMessage, false); + const expectationMessageKey = shouldBeEqual ? 'equality_assertion_be_equal_to' : 'equality_assertion_be_not_equal_to'; + const expectationMessage = I18nMessage.of(expectationMessageKey, this._actualResultAsString(), prettyPrint(expected)); + const finalMessage = I18nMessage.joined([expectationMessage, additionalFailureMessage], ' '); + this._reportAssertionResult(resultIsSuccessful, finalMessage); } } @@ -188,15 +189,15 @@ class Assertion extends TestResultReporter { } finally { let failureMessage; if (isUndefined(actualError)) { - failureMessage = I18n.message('expectation_error', prettyPrint(errorExpectation)); + failureMessage = I18nMessage.of('expectation_error', prettyPrint(errorExpectation)); } else { if (shouldFail) { - failureMessage = I18n.message('expectation_different_error', prettyPrint(errorExpectation), prettyPrint(actualError)); + failureMessage = I18nMessage.of('expectation_different_error', prettyPrint(errorExpectation), prettyPrint(actualError)); } else { - failureMessage = I18n.message('expectation_no_error', prettyPrint(actualError)); + failureMessage = I18nMessage.of('expectation_no_error', prettyPrint(actualError)); } } - this._reportAssertionResult(assertionHasFailed, failureMessage, true); + this._reportAssertionResult(assertionHasFailed, failureMessage); } } @@ -208,17 +209,11 @@ class Assertion extends TestResultReporter { } } - _reportAssertionResult(wasSuccess, matcherFailureMessage, overrideFailureMessage) { + _reportAssertionResult(wasSuccess, matcherFailureMessage) { if (wasSuccess) { this.report(TestResult.success()); } else { - let defaultFailureMessage; - if (isString(matcherFailureMessage)) { - defaultFailureMessage = `${this.translated('expected')} ${this._actualResultAsString()} ${this.translated('to')}${matcherFailureMessage}`; - } else { - defaultFailureMessage = matcherFailureMessage; - } - this.report(TestResult.failure(overrideFailureMessage ? matcherFailureMessage : defaultFailureMessage)); + this.report(TestResult.failure(matcherFailureMessage)); } } diff --git a/lib/console_ui.js b/lib/console_ui.js index 54e4f180..489058ad 100644 --- a/lib/console_ui.js +++ b/lib/console_ui.js @@ -1,6 +1,6 @@ 'use strict'; -const I18n = require('./i18n'); +const { I18n } = require('./i18n'); const Formatter = require('./formatter'); class ConsoleUI { diff --git a/lib/equality_assertion_strategy.js b/lib/equality_assertion_strategy.js index 8a618a58..10818e0b 100644 --- a/lib/equality_assertion_strategy.js +++ b/lib/equality_assertion_strategy.js @@ -1,5 +1,6 @@ 'use strict'; +const { I18nMessage } = require('./i18n'); const { isString, isFunction, isUndefined, respondsTo, isCyclic, deepStrictEqual } = require('./utils'); const EqualityAssertionStrategy = { @@ -31,7 +32,7 @@ const BothPartsUndefined = { evaluate() { return { comparisonResult: false, - overrideFailureMessage: 'Equality cannot be determined. Both parts are undefined', + overrideFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_undetermination'), }; }, }; @@ -46,7 +47,7 @@ const CustomFunction = { evaluate(actual, expected, criteria) { return { comparisonResult: criteria(actual, expected), - additionalFailureMessage: '', + additionalFailureMessage: I18nMessage.empty(), }; }, }; @@ -73,14 +74,14 @@ const CustomPropertyName = { _compareUsingCustomCriteria(actual, expected, criteria) { return { comparisonResult: actual[criteria](expected), - additionalFailureMessage: '', + additionalFailureMessage: I18nMessage.empty(), }; }, _failWithCriteriaNotFoundMessage(criteria) { return { comparisonResult: false, - additionalFailureMessage: ` Equality check failed. Objects do not have '${criteria}' property`, + additionalFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_missing_property', criteria), }; }, }; @@ -95,7 +96,7 @@ const ObjectWithEqualsProperty = { evaluate(actual, expected) { return { comparisonResult: actual.equals(expected), - additionalFailureMessage: '', + additionalFailureMessage: I18nMessage.empty(), }; }, }; @@ -110,7 +111,7 @@ const ObjectWithCyclicReference = { evaluate(_actual, _expected) { return { comparisonResult: false, - additionalFailureMessage: ' (circular references found, equality check cannot be done. Please compare objects\' properties individually)', + additionalFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_circular_references'), }; }, }; @@ -125,7 +126,7 @@ const DefaultEquality = { evaluate(actual, expected) { return { comparisonResult: deepStrictEqual(actual, expected), - additionalFailureMessage: '', + additionalFailureMessage: I18nMessage.empty(), }; }, }; diff --git a/lib/formatter.js b/lib/formatter.js index f09cacdb..dfa28d7a 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -106,7 +106,7 @@ class Formatter { } _potentiallyInternationalized(text) { - return isString(text) ? text : text.expressedInLocale(this._i18n); + return isString(text) ? text : text.expressedIn(this._i18n); } _displayErrorsAndFailuresSummary(runner) { diff --git a/lib/i18n.js b/lib/i18n.js index 7dd5c6e2..dcc71361 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -1,25 +1,85 @@ 'use strict'; const TRANSLATIONS = require('./translations'); -const { isUndefined } = require('./utils'); +const { subclassResponsibility, isUndefined } = require('./utils'); -class InternationalizedMessage { +class I18nMessage { + static of(key, ...params) { + return new InternationalizedMessage(key, ...params); + } + + static empty() { + return new EmptyMessage(); + } + + static joined(messages, joinedBy) { + const messagesWithContent = messages.filter(message => message.hasContent()); + + if (messagesWithContent.length === 0) { + throw new Error('No messages with content have been found to be composed'); + } else if (messagesWithContent.length === 1) { + return messagesWithContent[0]; + } else { + return new ComposedInternationalizedMessage(messagesWithContent, joinedBy); + } + } + + expressedIn(_locale) { + subclassResponsibility(); + } + + hasContent() { + subclassResponsibility(); + } +} + +class EmptyMessage extends I18nMessage { + expressedIn(_locale) { + return ''; + } + + hasContent() { + return false; + } +} + +class ComposedInternationalizedMessage extends I18nMessage { + constructor(messages, joinString) { + super(); + this._messages = messages; + this._joinString = joinString; + Object.freeze(this); + } + + expressedIn(locale) { + return this._messages + .map(message => message.expressedIn(locale)) + .join(this._joinString); + } + + hasContent() { + return true; + } +} + +class InternationalizedMessage extends I18nMessage { constructor(key, ...params) { + super(); this._key = key; this._params = params; Object.freeze(this); } - expressedInLocale(locale) { + expressedIn(locale) { return locale.translate(this._key, ...this._params); } + + hasContent() { + return true; + } } class I18n { - static message(key, ...params) { - return new InternationalizedMessage(key, ...params); - } - static defaultLanguage() { return 'en'; } @@ -90,4 +150,4 @@ class I18n { } } -module.exports = I18n; +module.exports = { I18n, I18nMessage }; diff --git a/lib/test_result_reporter.js b/lib/test_result_reporter.js index cdad89c8..efb62574 100644 --- a/lib/test_result_reporter.js +++ b/lib/test_result_reporter.js @@ -8,10 +8,6 @@ class TestResultReporter { report(result) { this._runner.setResultForCurrentTest(result); } - - translated(key) { - return this._runner._i18n.translate(key); - } } module.exports = TestResultReporter; diff --git a/lib/test_runner.js b/lib/test_runner.js index 029d5153..061d40e4 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -3,7 +3,7 @@ const Test = require('./test'); const TestSuite = require('./test_suite'); const FailFast = require('./fail_fast'); -const I18n = require('./i18n'); +const { I18n } = require('./i18n'); const { shuffle } = require('./utils'); class TestRunner { diff --git a/lib/translations.json b/lib/translations.json index f9743b9e..9b55661e 100644 --- a/lib/translations.json +++ b/lib/translations.json @@ -16,10 +16,6 @@ "skipped": "skipped", "errors": "error(s)", "failures_summary": "Failures summary:", - "expected": "Expected", - "to": "to ", - "be_equal_to": "be equal to", - "be_not_equal_to": "be not equal to", "expectation_be_true": "Expected %s to be true", "expectation_be_false": "Expected %s to be false", "expectation_be_undefined": "Expected %s to be undefined", @@ -39,6 +35,11 @@ "expectation_no_error": "Expected error %s not to happen", "expectation_no_errors": "Expected no errors to happen, but %s was raised", "expectation_different_error": "Expected error %s to happen, but got %s instead", + "equality_assertion_be_equal_to": "Expected %s to be equal to %s", + "equality_assertion_be_not_equal_to": "Expected %s to be not equal to %s", + "equality_assertion_failed_due_to_undetermination": "Equality cannot be determined. Both parts are undefined", + "equality_assertion_failed_due_to_missing_property": "Equality check failed. Objects do not have %s property", + "equality_assertion_failed_due_to_circular_references": "(circular references found, equality check cannot be done. Please compare objects' properties individually)", "explicitly_failed": "Explicitly failed", "running_tests_in": "Running tests in", "fail_fast": "Fail fast", @@ -63,10 +64,6 @@ "skipped": "no ejecutado(s)", "errors": "con error(es)", "failures_summary": "Resumen de errores/fallidos:", - "expected": "Se esperaba que", - "to": "", - "be_equal_to": "sea igual a", - "be_not_equal_to": "no sea igual a", "expectation_be_true": "Se esperaba que %s sea true", "expectation_be_false": "Se esperaba que %s sea false", "expectation_be_undefined": "Se esperaba que %s sea undefined", @@ -86,6 +83,11 @@ "expectation_no_error": "No se esperaba que ocurra el error %s", "expectation_no_errors": "No se esperaban errores, pero %s fue lanzado", "expectation_different_error": "Se esperaba que ocurra el error %s, pero en su lugar ocurrió el error %s", + "equality_assertion_be_equal_to": "Se esperaba que %s sea igual a %s", + "equality_assertion_be_not_equal_to": "Se esperaba que %s no sea igual a %s", + "equality_undetermined": "La igualdad no puede ser determinada. Ambas partes son undefined", + "equality_assertion_failed_due_to_missing_property": "Igualdad entre objetos fallida. Los objetos no tienen la propiedad %s", + "equality_assertion_failed_due_to_circular_references": "(se encontraron referencias circulares, no es posible verificar la igualdad. Por favor, comparar propiedades de los objetos de manera individual)", "explicitly_failed": "Explícitamente marcado como fallido", "running_tests_in": "Ejecutando tests en", "fail_fast": "Fallo rápido", diff --git a/lib/utils.js b/lib/utils.js index cfa031ee..bbf2b84d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,14 +7,14 @@ const assert = require('assert'); const isCyclic = object => { if (object === null) { - return false; + return false; } const seenObjects = []; function detect(obj) { if (typeof obj === 'object') { if (seenObjects.includes(obj)) { - return true; + return true; } seenObjects.push(obj); return !!Object.keys(obj).find(key => detect(obj[key])); @@ -109,6 +109,10 @@ const convertToArray = object => { return conversionFunction(object); }; +const subclassResponsibility = () => { + throw new Error('subclass responsibility'); +}; + module.exports = { // comparison on objects isCyclic, @@ -126,9 +130,10 @@ module.exports = { // collections shuffle, numberOfElements, + convertToArray, // files resolvePathFor, allFilesMatching, - // converts - convertToArray, + // object orientation + subclassResponsibility, }; diff --git a/tests/core/assertions/boolean_assertions_test.js b/tests/core/assertions/boolean_assertions_test.js index ca0447b2..827ba3ad 100644 --- a/tests/core/assertions/boolean_assertions_test.js +++ b/tests/core/assertions/boolean_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('boolean assertions', () => { test('isTrue passes with true', () => { @@ -16,13 +16,13 @@ suite('boolean assertions', () => { test('isTrue does not pass with false', () => { const result = resultOfATestWith(assert => assert.that(false).isTrue()); - expectFailureOn(result, I18n.message('expectation_be_true', 'false')); + expectFailureOn(result, I18nMessage.of('expectation_be_true', 'false')); }); test('isTrue does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isTrue()); - expectFailureOn(result, I18n.message('expectation_be_true', 'null')); + expectFailureOn(result, I18nMessage.of('expectation_be_true', 'null')); }); test('isFalse passes with false', () => { @@ -34,12 +34,12 @@ suite('boolean assertions', () => { test('isFalse does not pass with true', () => { const result = resultOfATestWith(assert => assert.that(true).isFalse()); - expectFailureOn(result, I18n.message('expectation_be_false', 'true')); + expectFailureOn(result, I18nMessage.of('expectation_be_false', 'true')); }); test('isFalse does not pass with another value', () => { const result = resultOfATestWith(assert => assert.that(null).isFalse()); - expectFailureOn(result, I18n.message('expectation_be_false', 'null')); + expectFailureOn(result, I18nMessage.of('expectation_be_false', 'null')); }); }); diff --git a/tests/core/assertions/collection_assertions_test.js b/tests/core/assertions/collection_assertions_test.js index 54e9edca..43723af8 100644 --- a/tests/core/assertions/collection_assertions_test.js +++ b/tests/core/assertions/collection_assertions_test.js @@ -5,7 +5,7 @@ const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); const Utils = require('../../../lib/utils'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('collection assertions', () => { const nonEmptySet = new Set([1]); @@ -20,7 +20,7 @@ suite('collection assertions', () => { test('includes does not pass if the actual object is not an array', () => { const result = resultOfATestWith(assert => assert.that([]).includes('hey')); - expectFailureOn(result, I18n.message('expectation_include', '[]', "'hey'")); + expectFailureOn(result, I18nMessage.of('expectation_include', '[]', "'hey'")); }); test('includes works with non-primitives', () => { @@ -32,7 +32,7 @@ suite('collection assertions', () => { test('doesNotInclude fails if the object is in the array', () => { const result = resultOfATestWith(assert => assert.that(['hey']).doesNotInclude('hey')); - expectFailureOn(result, I18n.message('expectation_not_include', "[ 'hey' ]", "'hey'")); + expectFailureOn(result, I18nMessage.of('expectation_not_include', "[ 'hey' ]", "'hey'")); }); test('doesNotInclude passes if the object is not an array', () => { @@ -44,7 +44,7 @@ suite('collection assertions', () => { test('doesNotInclude fails properly with non-primitives', () => { const result = resultOfATestWith(assert => assert.that([{ asd: '1' }]).doesNotInclude({ asd: '1' })); - expectFailureOn(result, I18n.message('expectation_not_include', "[ { asd: '1' } ]", "{ asd: '1' }")); + expectFailureOn(result, I18nMessage.of('expectation_not_include', "[ { asd: '1' } ]", "{ asd: '1' }")); }); test('includesExactly passes with a single object included', () => { @@ -62,19 +62,19 @@ suite('collection assertions', () => { test('includesExactly fails if the collection has more objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey', 'ho']).includesExactly('hey')); - expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey', 'ho' ]", "[ 'hey' ]")); + expectFailureOn(result, I18nMessage.of('expectation_include_exactly', "[ 'hey', 'ho' ]", "[ 'hey' ]")); }); test('includesExactly fails if the collection has less objects than expected', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('hey', 'ho')); - expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey' ]", "[ 'hey', 'ho' ]")); + expectFailureOn(result, I18nMessage.of('expectation_include_exactly', "[ 'hey' ]", "[ 'hey', 'ho' ]")); }); test('includesExactly fails if none of the objects are included at all', () => { const result = resultOfATestWith(assert => assert.that(['hey']).includesExactly('ho')); - expectFailureOn(result, I18n.message('expectation_include_exactly', "[ 'hey' ]", "[ 'ho' ]")); + expectFailureOn(result, I18nMessage.of('expectation_include_exactly', "[ 'hey' ]", "[ 'ho' ]")); }); test('includesExactly passes with many items no matter the order', () => { @@ -98,7 +98,7 @@ suite('collection assertions', () => { test('isEmpty does not pass if the array has elements', () => { const result = resultOfATestWith(assert => assert.that(['hey']).isEmpty()); - expectFailureOn(result, I18n.message('expectation_be_empty', "[ 'hey' ]")); + expectFailureOn(result, I18nMessage.of('expectation_be_empty', "[ 'hey' ]")); }); test('isEmpty passes with an empty string', () => { @@ -122,7 +122,7 @@ suite('collection assertions', () => { test('isNotEmpty does not pass if the array is empty', () => { const result = resultOfATestWith(assert => assert.that([]).isNotEmpty()); - expectFailureOn(result, I18n.message('expectation_be_not_empty', '[]')); + expectFailureOn(result, I18nMessage.of('expectation_be_not_empty', '[]')); }); test('isNotEmpty passes with a string with content', () => { @@ -146,13 +146,13 @@ suite('collection assertions', () => { test('isEmpty does not pass on a set with elements', () => { const result = resultOfATestWith(assert => assert.that(nonEmptySet).isEmpty()); - expectFailureOn(result, I18n.message('expectation_be_empty', Utils.prettyPrint(nonEmptySet))); + expectFailureOn(result, I18nMessage.of('expectation_be_empty', Utils.prettyPrint(nonEmptySet))); }); test('isNotEmpty does not pass on an empty set', () => { const result = resultOfATestWith(assert => assert.that(emptySet).isNotEmpty()); - expectFailureOn(result, I18n.message('expectation_be_not_empty', Utils.prettyPrint(emptySet))); + expectFailureOn(result, I18nMessage.of('expectation_be_not_empty', Utils.prettyPrint(emptySet))); }); test('isNotEmpty passes on a set with elements', () => { @@ -164,13 +164,13 @@ suite('collection assertions', () => { test('isEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isEmpty(undefined)); - expectFailureOn(result, I18n.message('expectation_be_empty', 'undefined')); + expectFailureOn(result, I18nMessage.of('expectation_be_empty', 'undefined')); }); test('isNotEmpty fails when the object is undefined', () => { const result = resultOfATestWith(assert => assert.isNotEmpty(undefined)); - expectFailureOn(result, I18n.message('expectation_be_not_empty', 'undefined')); + expectFailureOn(result, I18nMessage.of('expectation_be_not_empty', 'undefined')); }); test('includes works with Sets', () => { diff --git a/tests/core/assertions/equality_assertions_test.js b/tests/core/assertions/equality_assertions_test.js index 35b2f80d..8d24104c 100644 --- a/tests/core/assertions/equality_assertions_test.js +++ b/tests/core/assertions/equality_assertions_test.js @@ -4,6 +4,8 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); +const { I18nMessage } = require('../../../lib/i18n'); + suite('equality assertions', () => { test('isEqualTo pass with equal primitive objects', () => { const result = resultOfATestWith(assert => assert.that(42).isEqualTo(42)); @@ -14,7 +16,7 @@ suite('equality assertions', () => { test('isEqualTo fails with different primitive objects', () => { const result = resultOfATestWith(assert => assert.that(42).isEqualTo(21)); - expectFailureOn(result, 'Expected 42 to be equal to 21'); + expectFailureOn(result, I18nMessage.of('equality_assertion_be_equal_to', '42', '21')); }); test('isEqualTo passes with boxed and unboxed numbers', () => { @@ -32,7 +34,7 @@ suite('equality assertions', () => { test('isEqualTo fails with arrays in different order', () => { const result = resultOfATestWith(assert => assert.that([1, 2, 3]).isEqualTo([1, 3, 2])); - expectFailureOn(result, 'Expected [ 1, 2, 3 ] to be equal to [ 1, 3, 2 ]'); + expectFailureOn(result, I18nMessage.of('equality_assertion_be_equal_to', '[ 1, 2, 3 ]', '[ 1, 3, 2 ]')); }); test('isEqualTo passes with objects having the same property values', () => { @@ -52,7 +54,7 @@ suite('equality assertions', () => { /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureOn(result, "Expected { a: 'a', b: { b1: 'b1', b2: 'b2' } } to be equal to { a: 'a', b: { b1: 'b1', b2: '' } }"); + expectFailureOn(result, I18nMessage.of('equality_assertion_be_equal_to', "{ a: 'a', b: { b1: 'b1', b2: 'b2' } }", "{ a: 'a', b: { b1: 'b1', b2: '' } }")); }); test('isEqualTo fails if one object has less properties than the other', () => { @@ -62,7 +64,7 @@ suite('equality assertions', () => { /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureOn(result, "Expected { a: 'a', b: 'b' } to be equal to { a: 'a', b: 'b', c: 'c' }"); + expectFailureOn(result, I18nMessage.of('equality_assertion_be_equal_to', "{ a: 'a', b: 'b' }", "{ a: 'a', b: 'b', c: 'c' }")); }); test('isEqualTo fails if one object has more properties than the other', () => { @@ -72,7 +74,7 @@ suite('equality assertions', () => { /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.that(objectOne).isEqualTo(objectTwo)); - expectFailureOn(result, "Expected { a: 'a', b: 'b', c: 'c' } to be equal to { a: 'a', b: 'b' }"); + expectFailureOn(result, I18nMessage.of('equality_assertion_be_equal_to', "{ a: 'a', b: 'b', c: 'c' }", "{ a: 'a', b: 'b' }")); }); test('isEqualTo with custom criteria fails if objects do not have that property', () => { @@ -81,8 +83,10 @@ suite('equality assertions', () => { const objectTwo = { a: 'a', b: 'b' }; /* eslint-enable id-length */ const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectTwo, 'notFound')); - - expectFailureOn(result, 'Expected { a: \'a\', b: \'b\' } to be equal to { a: \'a\', b: \'b\' } Equality check failed. Objects do not have \'notFound\' property'); + + const assertionMessage = I18nMessage.of('equality_assertion_be_equal_to', "{ a: 'a', b: 'b' }", "{ a: 'a', b: 'b' }"); + const additionalMessage = I18nMessage.of('equality_assertion_failed_due_to_missing_property', 'notFound'); + expectFailureOn(result, I18nMessage.joined([assertionMessage, additionalMessage], ' ')); }); test('isEqualTo with custom criteria passes if the criteria evaluates to true', () => { @@ -131,21 +135,21 @@ suite('equality assertions', () => { const resultOne = resultOfATestWith(assert => assert.areEqual(undefined, {})); const resultTwo = resultOfATestWith(assert => assert.areEqual({}, undefined)); - expectFailureOn(resultOne, 'Expected undefined to be equal to {}'); - expectFailureOn(resultTwo, 'Expected {} to be equal to undefined'); + expectFailureOn(resultOne, I18nMessage.of('equality_assertion_be_equal_to', 'undefined', '{}')); + expectFailureOn(resultTwo, I18nMessage.of('equality_assertion_be_equal_to', '{}', 'undefined')); }); test('isEqualTo fails when comparing null with an object', () => { const resultOne = resultOfATestWith(assert => assert.areEqual(null, {})); const resultTwo = resultOfATestWith(assert => assert.areEqual({}, null)); - expectFailureOn(resultOne, 'Expected null to be equal to {}'); - expectFailureOn(resultTwo, 'Expected {} to be equal to null'); + expectFailureOn(resultOne, I18nMessage.of('equality_assertion_be_equal_to', 'null', '{}')); + expectFailureOn(resultTwo, I18nMessage.of('equality_assertion_be_equal_to', '{}', 'null')); }); test('isEqualTo fails if both parts are undefined', () => { const result = resultOfATestWith(assert => assert.areEqual(undefined, undefined)); - expectFailureOn(result, 'Equality cannot be determined. Both parts are undefined'); + expectFailureOn(result, I18nMessage.of('equality_assertion_failed_due_to_undetermination')); }); test('isEqualTo fails with object with circular references', () => { @@ -154,7 +158,9 @@ suite('equality assertions', () => { } }; objectOne.self = objectOne; const result = resultOfATestWith(assert => assert.areEqual(objectOne, objectOne)); - - expectFailureOn(result, "Expected circular! to be equal to circular! (circular references found, equality check cannot be done. Please compare objects' properties individually)"); + + const assertionMessage = I18nMessage.of('equality_assertion_be_equal_to', 'circular!', 'circular!'); + const additionalMessage = I18nMessage.of('equality_assertion_failed_due_to_circular_references'); + expectFailureOn(result, I18nMessage.joined([assertionMessage, additionalMessage], ' ')); }); }); diff --git a/tests/core/assertions/exception_assertions_test.js b/tests/core/assertions/exception_assertions_test.js index d869c970..e4d39aa8 100644 --- a/tests/core/assertions/exception_assertions_test.js +++ b/tests/core/assertions/exception_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('exception assertions', () => { test('raises() can receive a string and it passes when the exact string is expected', () => { @@ -54,13 +54,13 @@ suite('exception assertions', () => { }).raises(/happiness/), ); - expectFailureOn(result, I18n.message('expectation_different_error', '/happiness/', "'a terrible error'")); + expectFailureOn(result, I18nMessage.of('expectation_different_error', '/happiness/', "'a terrible error'")); }); test('raises() fails when no errors occur in the given function', () => { const result = resultOfATestWith(assert => assert.that(() => 1 + 2).raises('a weird error')); - expectFailureOn(result, I18n.message('expectation_error', "'a weird error'")); + expectFailureOn(result, I18nMessage.of('expectation_error', "'a weird error'")); }); test('doesNotRaise() passes when no errors happen at all', () => { @@ -86,7 +86,7 @@ suite('exception assertions', () => { }).doesNotRaise('this problem'), ); - expectFailureOn(result, I18n.message('expectation_no_error', "'this problem'")); + expectFailureOn(result, I18nMessage.of('expectation_no_error', "'this problem'")); }); test('doesNoRaiseAnyErrors() passes when no errors occur in the given function', () => { @@ -102,6 +102,6 @@ suite('exception assertions', () => { }).doesNotRaiseAnyErrors(), ); - expectFailureOn(result, I18n.message('expectation_no_errors', "'an unexpected error'")); + expectFailureOn(result, I18nMessage.of('expectation_no_errors', "'an unexpected error'")); }); }); diff --git a/tests/core/assertions/null_assertions_test.js b/tests/core/assertions/null_assertions_test.js index 42856280..ba839b07 100644 --- a/tests/core/assertions/null_assertions_test.js +++ b/tests/core/assertions/null_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('assertions about null', () => { test('isNull passes with a null value', () => { @@ -16,7 +16,7 @@ suite('assertions about null', () => { test('isNull does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isNull(undefined)); - expectFailureOn(result, I18n.message('expectation_be_null', 'undefined')); + expectFailureOn(result, I18nMessage.of('expectation_be_null', 'undefined')); }); test('isNotNull passes with a non-null value', () => { @@ -28,6 +28,6 @@ suite('assertions about null', () => { test('isNotNull does not pass when the value is null', () => { const result = resultOfATestWith(assert => assert.isNotNull(null)); - expectFailureOn(result, I18n.message('expectation_be_not_null', 'null')); + expectFailureOn(result, I18nMessage.of('expectation_be_not_null', 'null')); }); }); diff --git a/tests/core/assertions/numeric_assertions_test.js b/tests/core/assertions/numeric_assertions_test.js index 540ce78b..bae8bb81 100644 --- a/tests/core/assertions/numeric_assertions_test.js +++ b/tests/core/assertions/numeric_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('numeric assertions', () => { test('isNearTo passes if an exact integer matches', () => { @@ -16,7 +16,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the integer part of the number is not equal', () => { const result = resultOfATestWith(assert => assert.that(42).isNearTo(43)); - expectFailureOn(result, I18n.message('expectation_be_near_to', '42', '43', '4')); + expectFailureOn(result, I18nMessage.of('expectation_be_near_to', '42', '43', '4')); }); test('isNearTo passes if the actual number rounded using the specified decimals matches the expected number', () => { @@ -28,7 +28,7 @@ suite('numeric assertions', () => { test('isNearTo fails if the actual number rounded using the specified decimals does not match the expected number', () => { const result = resultOfATestWith(assert => assert.that(42.001).isNearTo(42, 3)); - expectFailureOn(result, I18n.message('expectation_be_near_to', '42.001', '42', '3')); + expectFailureOn(result, I18nMessage.of('expectation_be_near_to', '42.001', '42', '3')); }); test('isNearTo passes with a default precision of 4', () => { diff --git a/tests/core/assertions/reporting_failures_and_pending_tests_test.js b/tests/core/assertions/reporting_failures_and_pending_tests_test.js index 8cfc0fe7..d6bf05f9 100644 --- a/tests/core/assertions/reporting_failures_and_pending_tests_test.js +++ b/tests/core/assertions/reporting_failures_and_pending_tests_test.js @@ -4,13 +4,13 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectErrorOn, expectFailureOn, expectPendingResultOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('reporting failures and pending tests', () => { test('marking a test as explicitly failed with no message', () => { const result = resultOfATestWith((_assert, fail, _pending) => fail.with()); - expectFailureOn(result, I18n.message('explicitly_failed')); + expectFailureOn(result, I18nMessage.of('explicitly_failed')); }); test('marking a test as explicitly failed with a custom message', () => { @@ -22,7 +22,7 @@ suite('reporting failures and pending tests', () => { test('marking a test as pending with no message', () => { const result = resultOfATestWith((_assert, _fail, pending) => pending.dueTo()); - expectErrorOn(result, I18n.message('invalid_pending_reason')); + expectErrorOn(result, I18nMessage.of('invalid_pending_reason')); }); test('marking a test as pending with a custom message', () => { diff --git a/tests/core/assertions/string_match_assertions_test.js b/tests/core/assertions/string_match_assertions_test.js index 873f19b8..4de3fe89 100644 --- a/tests/core/assertions/string_match_assertions_test.js +++ b/tests/core/assertions/string_match_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('assertions about strings match', () => { test('matches() passes when the regex match the actual string', () => { @@ -16,7 +16,7 @@ suite('assertions about strings match', () => { test('matches() fails when the regex does the actual string', () => { const result = resultOfATestWith(assert => assert.that('goodbye').matches(/ll/)); - expectFailureOn(result, I18n.message('expectation_match_regex', "'goodbye'", /ll/)); + expectFailureOn(result, I18nMessage.of('expectation_match_regex', "'goodbye'", /ll/)); }); test('isMatching() shortcut works', () => { diff --git a/tests/core/assertions/undefined_assertions_test.js b/tests/core/assertions/undefined_assertions_test.js index b74bbdc9..82444d8d 100644 --- a/tests/core/assertions/undefined_assertions_test.js +++ b/tests/core/assertions/undefined_assertions_test.js @@ -4,7 +4,7 @@ const { suite, test } = require('../../../testy'); const { resultOfATestWith } = require('../../support/runner_helpers'); const { expectSuccess, expectFailureOn } = require('../../support/assertion_helpers'); -const I18n = require('../../../lib/i18n'); +const { I18nMessage } = require('../../../lib/i18n'); suite('undefined assertions', () => { test('isUndefined passes with an undefined value', () => { @@ -16,7 +16,7 @@ suite('undefined assertions', () => { test('isUndefined does not pass with a another value', () => { const result = resultOfATestWith(assert => assert.isUndefined(null)); - expectFailureOn(result, I18n.message('expectation_be_undefined', 'null')); + expectFailureOn(result, I18nMessage.of('expectation_be_undefined', 'null')); }); test('isNotUndefined passes with a not-undefined value', () => { @@ -28,6 +28,6 @@ suite('undefined assertions', () => { test('isNotUndefined does not pass when the value is undefined', () => { const result = resultOfATestWith(assert => assert.isNotUndefined(undefined)); - expectFailureOn(result, I18n.message('expectation_be_defined', 'undefined')); + expectFailureOn(result, I18nMessage.of('expectation_be_defined', 'undefined')); }); }); diff --git a/tests/core/i18n_test.js b/tests/core/i18n_test.js index 4341e848..515f99d1 100644 --- a/tests/core/i18n_test.js +++ b/tests/core/i18n_test.js @@ -1,7 +1,7 @@ 'use strict'; const { suite, test, assert } = require('../../testy'); -const I18n = require('../../lib/i18n'); +const { I18n } = require('../../lib/i18n'); suite('i18n', () => { test('default language is en', () => { diff --git a/tests/core/test_test.js b/tests/core/test_test.js index 3be951ae..c2ee372b 100644 --- a/tests/core/test_test.js +++ b/tests/core/test_test.js @@ -6,7 +6,7 @@ const { withRunner, runSingleTest } = require('../support/runner_helpers'); const { expectErrorOn, expectFailureOn } = require('../support/assertion_helpers'); const Test = require('../../lib/test'); -const I18n = require('../../lib/i18n'); +const { I18nMessage } = require('../../lib/i18n'); suite('tests behavior', () => { test('running a test that does not have any assertion generates an error with a descriptive message', () => { @@ -51,7 +51,7 @@ suite('tests behavior', () => { const result = runSingleTest(runner, test); - expectFailureOn(result, I18n.message('expectation_be_not_empty', '[]')); + expectFailureOn(result, I18nMessage.of('expectation_be_not_empty', '[]')); }); }); }); diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js index 65b5d5cb..e22f355d 100644 --- a/tests/ui/formatter_test.js +++ b/tests/ui/formatter_test.js @@ -1,11 +1,12 @@ 'use strict'; const { suite, test, before, assert } = require('../../testy'); -const Formatter = require('../../lib/formatter'); -const I18n = require('../../lib/i18n'); const { withRunner, runSingleTest } = require('../support/runner_helpers'); const { aTestWithBody, aPendingTest } = require('../support/tests_factory'); +const Formatter = require('../../lib/formatter'); +const { I18n } = require('../../lib/i18n'); + suite('formatter', () => { let formatter, dummyConsole, i18n; From aaa02914237e01c2fca6436a1c249e6e4e69d521 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 29 Dec 2020 01:06:48 -0300 Subject: [PATCH 56/64] :rotating_light: remove unused import --- lib/assertion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/assertion.js b/lib/assertion.js index 6a80626a..10edf9d7 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -3,7 +3,7 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); const TestResult = require('./test_result'); -const { I18n, I18nMessage } = require('./i18n'); +const { I18nMessage } = require('./i18n'); const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); From 531d1d6360c93a3aae2f11bd0c957c45e93cd35c Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 29 Dec 2020 01:34:41 -0300 Subject: [PATCH 57/64] :art: refactor error messages to only be built when it is necessary, speeding up tests :tada: --- lib/assertion.js | 88 +++++++++++++----------------- lib/equality_assertion_strategy.js | 14 ++--- lib/test_result_reporter.js | 12 +++- 3 files changed, 56 insertions(+), 58 deletions(-) diff --git a/lib/assertion.js b/lib/assertion.js index 10edf9d7..79c6b3f0 100644 --- a/lib/assertion.js +++ b/lib/assertion.js @@ -2,7 +2,6 @@ const TestResultReporter = require('./test_result_reporter'); const EqualityAssertionStrategy = require('./equality_assertion_strategy'); -const TestResult = require('./test_result'); const { I18nMessage } = require('./i18n'); const { prettyPrint, isUndefined, isRegex, notNullOrUndefined, numberOfElements, convertToArray } = require('./utils'); @@ -18,14 +17,14 @@ class Assertion extends TestResultReporter { isTrue() { this._reportAssertionResult( this._actual === true, - I18nMessage.of('expectation_be_true', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_true', this._actualResultAsString()), ); } isFalse() { this._reportAssertionResult( this._actual === false, - I18nMessage.of('expectation_be_false', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_false', this._actualResultAsString()), ); } @@ -34,14 +33,14 @@ class Assertion extends TestResultReporter { isUndefined() { this._reportAssertionResult( isUndefined(this._actual), - I18nMessage.of('expectation_be_undefined', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_undefined', this._actualResultAsString()), ); } isNotUndefined() { this._reportAssertionResult( !isUndefined(this._actual), - I18nMessage.of('expectation_be_defined', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_defined', this._actualResultAsString()), ); } @@ -50,14 +49,14 @@ class Assertion extends TestResultReporter { isNull() { this._reportAssertionResult( this._actual === null, - I18nMessage.of('expectation_be_null', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_null', this._actualResultAsString()), ); } isNotNull() { this._reportAssertionResult( this._actual !== null, - I18nMessage.of('expectation_be_not_null', this._actualResultAsString()), + () => I18nMessage.of('expectation_be_not_null', this._actualResultAsString()), ); } @@ -76,66 +75,80 @@ class Assertion extends TestResultReporter { includes(expectedObject, equalityCriteria) { const resultIsSuccessful = convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = I18nMessage.of('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = () => I18nMessage.of('expectation_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = I18nMessage.of('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = () => I18nMessage.of('expectation_be_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } doesNotInclude(expectedObject, equalityCriteria) { const resultIsSuccessful = !convertToArray(this._actual).find(element => this._areConsideredEqual(element, expectedObject, equalityCriteria)); - const failureMessage = I18nMessage.of('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); + const failureMessage = () => I18nMessage.of('expectation_not_include', this._actualResultAsString(), prettyPrint(expectedObject)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotIncludedIn(expectedCollection, equalityCriteria) { const resultIsSuccessful = !expectedCollection.find(element => this._areConsideredEqual(element, this._actual, equalityCriteria)); - const failureMessage = I18nMessage.of('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); + const failureMessage = () => I18nMessage.of('expectation_be_not_included_in', this._actualResultAsString(), prettyPrint(expectedCollection)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } includesExactly(...objects) { const resultIsSuccessful = this._haveElementsConsideredEqual(this._actual, objects); - const failureMessage = I18nMessage.of('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); + const failureMessage = () => I18nMessage.of('expectation_include_exactly', this._actualResultAsString(), prettyPrint(objects)); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isEmpty() { const resultIsSuccessful = numberOfElements(this._actual || {}) === 0 && notNullOrUndefined(this._actual); - const failureMessage = I18nMessage.of('expectation_be_empty', this._actualResultAsString()); + const failureMessage = () => I18nMessage.of('expectation_be_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } isNotEmpty() { const setValueWhenUndefined = this._actual || {}; const resultIsSuccessful = numberOfElements(setValueWhenUndefined) > 0; - const failureMessage = I18nMessage.of('expectation_be_not_empty', this._actualResultAsString()); + const failureMessage = () => I18nMessage.of('expectation_be_not_empty', this._actualResultAsString()); this._reportAssertionResult(resultIsSuccessful, failureMessage); } // Exception assertions raises(errorExpectation) { - this._exceptionAssertion(errorExpectation, true); + try { + this._actual.call(); + this.reportFailure(I18nMessage.of('expectation_error', prettyPrint(errorExpectation))); + } catch (actualError) { + const assertionPassed = this._checkIfErrorMatchesExpectation(errorExpectation, actualError); + const errorMessage = () => I18nMessage.of('expectation_different_error', prettyPrint(errorExpectation), prettyPrint(actualError)); + this._reportAssertionResult(assertionPassed, errorMessage); + } } doesNotRaise(notExpectedError) { - this._exceptionAssertion(notExpectedError, false); + try { + this._actual.call(); + this.reportSuccess(); + } catch (actualError) { + const errorCheck = this._checkIfErrorMatchesExpectation(notExpectedError, actualError); + const failureMessage = () => I18nMessage.of('expectation_no_error', prettyPrint(actualError)); + this._reportAssertionResult(!errorCheck, failureMessage); + } } doesNotRaiseAnyErrors() { try { this._actual.call(); - this._reportAssertionResult(true); + this.reportSuccess(true); } catch (error) { - this._reportAssertionResult(false, I18nMessage.of('expectation_no_errors', prettyPrint(error))); + this.reportFailure(I18nMessage.of('expectation_no_errors', prettyPrint(error))); } } @@ -143,7 +156,7 @@ class Assertion extends TestResultReporter { isNearTo(number, precisionDigits = 4) { const result = Number.parseFloat((this._actual).toFixed(precisionDigits)) === number; - const failureMessage = I18nMessage.of('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); + const failureMessage = () => I18nMessage.of('expectation_be_near_to', this._actualResultAsString(), number.toString(), precisionDigits.toString()); this._reportAssertionResult(result, failureMessage); } @@ -151,7 +164,7 @@ class Assertion extends TestResultReporter { matches(regex) { const result = this._actual.match(regex); - const failureMessage = I18nMessage.of('expectation_match_regex', this._actualResultAsString(), regex); + const failureMessage = () => I18nMessage.of('expectation_match_regex', this._actualResultAsString(), regex); this._reportAssertionResult(result, failureMessage); } @@ -166,8 +179,8 @@ class Assertion extends TestResultReporter { this._reportAssertionResult(resultIsSuccessful, overrideFailureMessage); } else { const expectationMessageKey = shouldBeEqual ? 'equality_assertion_be_equal_to' : 'equality_assertion_be_not_equal_to'; - const expectationMessage = I18nMessage.of(expectationMessageKey, this._actualResultAsString(), prettyPrint(expected)); - const finalMessage = I18nMessage.joined([expectationMessage, additionalFailureMessage], ' '); + const expectationMessage = () => I18nMessage.of(expectationMessageKey, this._actualResultAsString(), prettyPrint(expected)); + const finalMessage = () => I18nMessage.joined([expectationMessage.call(), additionalFailureMessage.call()], ' '); this._reportAssertionResult(resultIsSuccessful, finalMessage); } } @@ -176,31 +189,6 @@ class Assertion extends TestResultReporter { return EqualityAssertionStrategy.evaluate(objectOne, objectTwo, equalityCriteria).comparisonResult; } - _exceptionAssertion(errorExpectation, shouldFail) { - let assertionHasFailed = false; - let actualError; - try { - this._actual.call(); - assertionHasFailed = !shouldFail; - } catch (error) { - actualError = error; - const errorCheck = this._checkIfErrorMatchesExpectation(errorExpectation, actualError); - assertionHasFailed = shouldFail ? errorCheck : !errorCheck; - } finally { - let failureMessage; - if (isUndefined(actualError)) { - failureMessage = I18nMessage.of('expectation_error', prettyPrint(errorExpectation)); - } else { - if (shouldFail) { - failureMessage = I18nMessage.of('expectation_different_error', prettyPrint(errorExpectation), prettyPrint(actualError)); - } else { - failureMessage = I18nMessage.of('expectation_no_error', prettyPrint(actualError)); - } - } - this._reportAssertionResult(assertionHasFailed, failureMessage); - } - } - _checkIfErrorMatchesExpectation(errorExpectation, actualError) { if (isRegex(errorExpectation)) { return errorExpectation.test(actualError); @@ -209,11 +197,11 @@ class Assertion extends TestResultReporter { } } - _reportAssertionResult(wasSuccess, matcherFailureMessage) { + _reportAssertionResult(wasSuccess, failureMessage) { if (wasSuccess) { - this.report(TestResult.success()); + this.reportSuccess(); } else { - this.report(TestResult.failure(matcherFailureMessage)); + this.reportFailure(failureMessage.call()); } } diff --git a/lib/equality_assertion_strategy.js b/lib/equality_assertion_strategy.js index 10818e0b..8e6dc62f 100644 --- a/lib/equality_assertion_strategy.js +++ b/lib/equality_assertion_strategy.js @@ -32,7 +32,7 @@ const BothPartsUndefined = { evaluate() { return { comparisonResult: false, - overrideFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_undetermination'), + overrideFailureMessage: () => I18nMessage.of('equality_assertion_failed_due_to_undetermination'), }; }, }; @@ -47,7 +47,7 @@ const CustomFunction = { evaluate(actual, expected, criteria) { return { comparisonResult: criteria(actual, expected), - additionalFailureMessage: I18nMessage.empty(), + additionalFailureMessage: () => I18nMessage.empty(), }; }, }; @@ -74,14 +74,14 @@ const CustomPropertyName = { _compareUsingCustomCriteria(actual, expected, criteria) { return { comparisonResult: actual[criteria](expected), - additionalFailureMessage: I18nMessage.empty(), + additionalFailureMessage: () => I18nMessage.empty(), }; }, _failWithCriteriaNotFoundMessage(criteria) { return { comparisonResult: false, - additionalFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_missing_property', criteria), + additionalFailureMessage: () => I18nMessage.of('equality_assertion_failed_due_to_missing_property', criteria), }; }, }; @@ -96,7 +96,7 @@ const ObjectWithEqualsProperty = { evaluate(actual, expected) { return { comparisonResult: actual.equals(expected), - additionalFailureMessage: I18nMessage.empty(), + additionalFailureMessage: () => I18nMessage.empty(), }; }, }; @@ -111,7 +111,7 @@ const ObjectWithCyclicReference = { evaluate(_actual, _expected) { return { comparisonResult: false, - additionalFailureMessage: I18nMessage.of('equality_assertion_failed_due_to_circular_references'), + additionalFailureMessage: () => I18nMessage.of('equality_assertion_failed_due_to_circular_references'), }; }, }; @@ -126,7 +126,7 @@ const DefaultEquality = { evaluate(actual, expected) { return { comparisonResult: deepStrictEqual(actual, expected), - additionalFailureMessage: I18nMessage.empty(), + additionalFailureMessage: () => I18nMessage.empty(), }; }, }; diff --git a/lib/test_result_reporter.js b/lib/test_result_reporter.js index efb62574..a13ae09d 100644 --- a/lib/test_result_reporter.js +++ b/lib/test_result_reporter.js @@ -1,13 +1,23 @@ 'use strict'; +const TestResult = require('./test_result'); + class TestResultReporter { constructor(runner) { this._runner = runner; } - + report(result) { this._runner.setResultForCurrentTest(result); } + + reportSuccess() { + return this.report(TestResult.success()); + } + + reportFailure(message) { + return this.report(TestResult.failure(message)); + } } module.exports = TestResultReporter; From 92884438b6d7f90c814f7da36839ca435888260e Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Wed, 30 Dec 2020 11:04:41 -0300 Subject: [PATCH 58/64] :recycle: increasing i18n test coverage, paying tech debt (fixes #179) --- lib/i18n.js | 6 +++- tests/core/i18n_messages_test.js | 57 ++++++++++++++++++++++++++++++++ tests/ui/formatter_test.js | 2 +- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/core/i18n_messages_test.js diff --git a/lib/i18n.js b/lib/i18n.js index dcc71361..c21c35ea 100644 --- a/lib/i18n.js +++ b/lib/i18n.js @@ -80,6 +80,10 @@ class InternationalizedMessage extends I18nMessage { } class I18n { + static default(translations = TRANSLATIONS) { + return new this(this.defaultLanguage(), translations); + } + static defaultLanguage() { return 'en'; } @@ -132,7 +136,7 @@ class I18n { } _translationsForCurrentLanguage() { - return this._translations[this._languageCode] || {}; + return this._translations[this._languageCode]; } _defaultTranslationFor(key) { diff --git a/tests/core/i18n_messages_test.js b/tests/core/i18n_messages_test.js new file mode 100644 index 00000000..20d47249 --- /dev/null +++ b/tests/core/i18n_messages_test.js @@ -0,0 +1,57 @@ +'use strict'; + +const { suite, test, assert } = require('../../testy'); +const { I18n, I18nMessage } = require('../../lib/i18n'); + +suite('i18n messages', () => { + const translations = { en: { key1: 'value 1', key2: 'value 2', key3: 'value 3' } }; + const locale = I18n.default(translations); + + test('empty messages return an empty string', () => { + const message = I18nMessage.empty(); + + assert.isEmpty(message.expressedIn(locale)); + assert.isFalse(message.hasContent()); + }); + + test('joined messages fail on an empty collection of messages', () => { + assert + .that(() => I18nMessage.joined([], ', ')) + .raises(/No messages with content have been found to be composed/); + }); + + test('joined messages fail on a collection containing just empty messages', () => { + const emptyMessageOne = I18nMessage.empty(); + const emptyMessageTwo = I18nMessage.empty(); + + assert + .that(() => I18nMessage.joined([emptyMessageOne, emptyMessageTwo], ', ')) + .raises(/No messages with content have been found to be composed/); + }); + + test('a joined message with a single message is equivalent to the single message', () => { + const messageWithContent = I18nMessage.of('key1'); + const joinedMessage = I18nMessage.joined([messageWithContent], ', '); + + assert.isTrue(joinedMessage.hasContent()); + assert.areEqual(joinedMessage, messageWithContent); + assert.areEqual(joinedMessage.expressedIn(locale), 'value 1'); + }); + + test('a joined message with more than one single message joins the result with the given string', () => { + const messageOne = I18nMessage.of('key1'); + const messageTwo = I18nMessage.of('key2'); + const messageThree = I18nMessage.of('key3'); + const joinedMessage = I18nMessage.joined([messageOne, messageTwo, messageThree], ', '); + + assert.isTrue(joinedMessage.hasContent()); + assert.areEqual(joinedMessage.expressedIn(locale), 'value 1, value 2, value 3'); + }); + + test('I18nMessage is abstract and requires a protocol to be implemented', () => { + const message = new I18nMessage(); + + assert.that(() => message.hasContent()).raises(/subclass responsibility/); + assert.that(() => message.expressedIn(locale)).raises(/subclass responsibility/); + }); +}); diff --git a/tests/ui/formatter_test.js b/tests/ui/formatter_test.js index e22f355d..120673a0 100644 --- a/tests/ui/formatter_test.js +++ b/tests/ui/formatter_test.js @@ -23,7 +23,7 @@ suite('formatter', () => { }, }; - i18n = new I18n(I18n.defaultLanguage()); + i18n = I18n.default(); formatter = new Formatter(dummyConsole, i18n); }); From 4d377d2d93da3d1102e0257b190c52953758d46d Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Wed, 6 Jan 2021 23:43:29 -0300 Subject: [PATCH 59/64] :pencil: upgrade license copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 624325b0..ae8b92a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Nahuel Garbezza +Copyright (c) (2018-2021) Nahuel Garbezza Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From be41db9872ea4490b5dae238d6c553b214667326 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Mon, 11 Jan 2021 23:38:26 -0300 Subject: [PATCH 60/64] :white_check_mark: add tests for failures count on the test runner --- tests/core/test_runner_test.js | 31 ++++++++++++++++++++++++++++++- tests/core/test_suite_test.js | 9 ++------- tests/support/suites_factory.js | 20 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/support/suites_factory.js diff --git a/tests/core/test_runner_test.js b/tests/core/test_runner_test.js index d9ca4251..dcc66f01 100644 --- a/tests/core/test_runner_test.js +++ b/tests/core/test_runner_test.js @@ -2,8 +2,17 @@ const { suite, test, assert } = require('../../testy'); const TestRunner = require('../../lib/test_runner'); +const { suiteNamed } = require('../support/suites_factory'); +const { withRunner } = require('../support/runner_helpers'); +const { aFailingTest } = require('../support/tests_factory'); suite('test runner', () => { + const emptyRunnerCallbacks = { + onFinish: () => {}, + onSuccess: () => {}, + onFailure: () => {}, + }; + test('with no tests, it finishes with success', () => { let result = 'not called'; let finished = false; @@ -12,7 +21,7 @@ suite('test runner', () => { finished = true; }, onSuccess: () => { - result = 'success'; + result = 'success'; }, onFailure: () => { result = 'failure'; @@ -26,4 +35,24 @@ suite('test runner', () => { assert.isTrue(finished); assert.that(result).isEqualTo('success'); }); + + test('failures count is zero with no tests', () => { + withRunner(runner => { + runner.run(); + + assert.that(runner.failuresCount()).isEqualTo(0); + }); + }); + + test('failures count is one with one failed test', () => { + withRunner((runner, asserter) => { + const suite = suiteNamed('with one failure'); + const failingTest = aFailingTest(asserter); + suite.addTest(failingTest); + runner.addSuite(suite); + runner.run(); + + assert.that(runner.failuresCount()).isEqualTo(1); + }); + }); }); diff --git a/tests/core/test_suite_test.js b/tests/core/test_suite_test.js index 1b4b6c2d..bb114a31 100644 --- a/tests/core/test_suite_test.js +++ b/tests/core/test_suite_test.js @@ -2,16 +2,11 @@ const { suite, test, before, assert } = require('../../testy'); const TestSuite = require('../../lib/test_suite'); -const { withRunner } = require('../support/runner_helpers'); const FailFast = require('../../lib/fail_fast'); +const { withRunner } = require('../support/runner_helpers'); +const { newEmptySuite } = require('../support/suites_factory'); const { aPassingTest, aFailingTest, anErroredTest, aPendingTest } = require('../support/tests_factory'); -const noop = () => {}; -const emptySuiteCallbacks = { onStart: noop, onFinish: noop }; - -const newEmptySuite = () => suiteNamed('myTestSuite'); -const suiteNamed = suiteName => new TestSuite(suiteName, () => {}, emptySuiteCallbacks); - suite('test suite behavior', () => { let runner, mySuite; let passingTest, failingTest, erroredTest, pendingTest; diff --git a/tests/support/suites_factory.js b/tests/support/suites_factory.js new file mode 100644 index 00000000..5939d96a --- /dev/null +++ b/tests/support/suites_factory.js @@ -0,0 +1,20 @@ +'use strict'; + +const TestSuite = require('../../lib/test_suite'); + +const noop = () => {}; +const emptySuiteCallbacks = { + onStart: noop, + onFinish: noop, +}; + +const newEmptySuite = () => + suiteNamed('myTestSuite'); + +const suiteNamed = suiteName => + new TestSuite(suiteName, () => {}, emptySuiteCallbacks); + +module.exports = { + newEmptySuite, + suiteNamed, +}; From 224d950f4ddc16c0b0883d156dc359979b11c958 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Tue, 12 Jan 2021 07:00:13 -0300 Subject: [PATCH 61/64] :white_check_mark: add tests for errors and failures accessing in the test runner --- lib/test_runner.js | 28 +++++---------- tests/core/test_runner_test.js | 63 +++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/lib/test_runner.js b/lib/test_runner.js index 061d40e4..42f508ad 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -23,19 +23,19 @@ class TestRunner { registerTest(name, testBody, callbacks) { const testToAdd = new Test(name, testBody, callbacks); - this.currentSuite().addTest(testToAdd); + this._currentSuite.addTest(testToAdd); } registerBefore(beforeBlock) { - this.currentSuite().before(beforeBlock); + this._currentSuite.before(beforeBlock); } registerAfter(afterBlock) { - this.currentSuite().after(afterBlock); + this._currentSuite.after(afterBlock); } addSuite(suiteToAdd) { - this.suites().push(suiteToAdd); + this._suites.push(suiteToAdd); this._setCurrentSuite(suiteToAdd); return suiteToAdd; } @@ -56,7 +56,7 @@ class TestRunner { run() { this._randomizeSuites(); - this.suites().forEach(suite => { + this._suites.forEach(suite => { this._setCurrentSuite(suite); const context = { failFastMode: this._failFastMode, randomOrderMode: this._randomOrder }; suite.run(context); @@ -65,7 +65,7 @@ class TestRunner { } setResultForCurrentTest(result) { - this.currentSuite().currentTest().setResult(result); + this._currentSuite.currentTest().setResult(result); } finish() { @@ -102,18 +102,8 @@ class TestRunner { return this._countEach('totalCount'); } - // Accessing - - currentSuite() { - return this._currentSuite; - } - - suites() { - return this._suites; - } - allFailuresAndErrors() { - return this.suites().reduce((failures, suite) => + return this._suites.reduce((failures, suite) => failures.concat(suite.allFailuresAndErrors()), [], ); } @@ -129,14 +119,14 @@ class TestRunner { } _countEach(property) { - return this.suites().reduce((count, suite) => + return this._suites.reduce((count, suite) => count + suite[property](), 0, ); } _randomizeSuites() { if (this._randomOrder) { - shuffle(this.suites()); + shuffle(this._suites); } } } diff --git a/tests/core/test_runner_test.js b/tests/core/test_runner_test.js index dcc66f01..4300cd12 100644 --- a/tests/core/test_runner_test.js +++ b/tests/core/test_runner_test.js @@ -2,17 +2,11 @@ const { suite, test, assert } = require('../../testy'); const TestRunner = require('../../lib/test_runner'); -const { suiteNamed } = require('../support/suites_factory'); const { withRunner } = require('../support/runner_helpers'); -const { aFailingTest } = require('../support/tests_factory'); +const { suiteNamed } = require('../support/suites_factory'); +const { aFailingTest, anErroredTest } = require('../support/tests_factory'); suite('test runner', () => { - const emptyRunnerCallbacks = { - onFinish: () => {}, - onSuccess: () => {}, - onFailure: () => {}, - }; - test('with no tests, it finishes with success', () => { let result = 'not called'; let finished = false; @@ -39,7 +33,9 @@ suite('test runner', () => { test('failures count is zero with no tests', () => { withRunner(runner => { runner.run(); - + + assert.isFalse(runner.hasErrorsOrFailures()); + assert.isEmpty(runner.allFailuresAndErrors()); assert.that(runner.failuresCount()).isEqualTo(0); }); }); @@ -52,7 +48,56 @@ suite('test runner', () => { runner.addSuite(suite); runner.run(); + assert.isTrue(runner.hasErrorsOrFailures()); assert.that(runner.failuresCount()).isEqualTo(1); + assert.that(runner.allFailuresAndErrors()).includesExactly(failingTest); + }); + }); + + test('errors count is zero with no tests', () => { + withRunner(runner => { + runner.run(); + + assert.isFalse(runner.hasErrorsOrFailures()); + assert.isEmpty(runner.allFailuresAndErrors()); + assert.that(runner.errorsCount()).isEqualTo(0); + }); + }); + + test('errors count is one with an errored test', () => { + withRunner((runner, asserter) => { + const suite = suiteNamed('with one error'); + const erroredTest = anErroredTest(asserter); + suite.addTest(erroredTest); + runner.addSuite(suite); + runner.run(); + + assert.isTrue(runner.hasErrorsOrFailures()); + assert.that(runner.errorsCount()).isEqualTo(1); + assert.that(runner.allFailuresAndErrors()).includesExactly(erroredTest); + }); + }); + + test('counting several errors and failures', () => { + withRunner((runner, asserter) => { + const suite = suiteNamed('with errors and failures'); + const errorOne = anErroredTest(asserter); + const errorTwo = anErroredTest(asserter); + const errorThree = anErroredTest(asserter); + const failureOne = aFailingTest(asserter); + const failureTwo = aFailingTest(asserter); + suite.addTest(errorOne); + suite.addTest(failureOne); + suite.addTest(errorTwo); + suite.addTest(errorThree); + suite.addTest(failureTwo); + runner.addSuite(suite); + runner.run(); + + assert.that(runner.errorsCount()).isEqualTo(3); + assert.that(runner.failuresCount()).isEqualTo(2); + assert.isTrue(runner.hasErrorsOrFailures()); + assert.that(runner.allFailuresAndErrors()).includesExactly(errorOne, errorTwo, errorThree, failureOne, failureTwo); }); }); }); From 9f25ee7ced11993a733f33d74d7f8633c3e22620 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Thu, 14 Jan 2021 20:38:40 -0300 Subject: [PATCH 62/64] :pencil: add ADR to track the decision about offensive vocabulary --- .../0006-avoid-offensive-vocabulary.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 doc/decisions/0006-avoid-offensive-vocabulary.md diff --git a/doc/decisions/0006-avoid-offensive-vocabulary.md b/doc/decisions/0006-avoid-offensive-vocabulary.md new file mode 100644 index 00000000..f2b20a2b --- /dev/null +++ b/doc/decisions/0006-avoid-offensive-vocabulary.md @@ -0,0 +1,20 @@ +# 6. Avoid offensive vocabulary + +Date: 2021-01-13 + +## Status + +Accepted + +## Context + +There is an initiative about questioning the technical vocabulary we use, and avoiding some words that are widely used and may offend people. This is a project that adheres to this initiative, therefore... + +## Decision + +Remove all existing technical vocabulary that might be offensive, and prevent those terms to be added in the future. For instance, the use of "master/slave" replaced by "main/replica" (or similar), or "whitelist/blacklist" by "safelist/blocklist" (or similar). + +## Consequences + +* Supporting causes like Black Lives Matter and let all people know that their voices are heard. +* Having a friendly place for all contributors around the world. From 1f700d370a3f0d2c1ee4259ea80546e23daaa6a7 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Thu, 14 Jan 2021 20:39:05 -0300 Subject: [PATCH 63/64] :recycle: extract method in test runner for clarity --- lib/test_runner.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/test_runner.js b/lib/test_runner.js index 42f508ad..d046ddf9 100644 --- a/lib/test_runner.js +++ b/lib/test_runner.js @@ -58,12 +58,18 @@ class TestRunner { this._randomizeSuites(); this._suites.forEach(suite => { this._setCurrentSuite(suite); - const context = { failFastMode: this._failFastMode, randomOrderMode: this._randomOrder }; - suite.run(context); + suite.run(this._executionContext()); }); this._callbacks.onFinish(this); } + _executionContext() { + return { + failFastMode: this._failFastMode, + randomOrderMode: this._randomOrder, + }; + } + setResultForCurrentTest(result) { this._currentSuite.currentTest().setResult(result); } From 52d0813c515cb8ec58a772bfd466d3e8f5536218 Mon Sep 17 00:00:00 2001 From: Nahuel Garbezza Date: Thu, 14 Jan 2021 20:39:26 -0300 Subject: [PATCH 64/64] :bookmark: release v5.1.0 --- CHANGELOG.md | 37 ++++++++++++++++++++++++++++++++++++- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5187620..aa220845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Everything is released. Yay! :tada: +## [5.1.0] - 2021-01-13 + +This release includes a lot of contributions (4 new contributors!), and several refactorings to continue improving the quality of the tool. + +### Added + +* [[feature] added after() hook to run code after each test](https://github.com/ngarbezza/testy/issues/135), thank you @adico1! +* [[feature] isIncludedIn() and isNotIncludedIn() assertions](https://github.com/ngarbezza/testy/issues/75), thank you @ask-imran for your first contribution! +* [[feature] warning message when no tests found](https://github.com/ngarbezza/testy/issues/157), thank you @niyonx for your first contribution! +* [[feature] show error when a requested file does not exist](https://github.com/ngarbezza/testy/issues/158), thank you @chelsieng for your first contribution! +* [[feature] global error handler](https://github.com/ngarbezza/testy/issues/177) + +### Fixed + +* [[bug] suite and test names cannot be empty](https://github.com/ngarbezza/testy/issues/136), thank you @ask-imran! +* [[bug] includes() and doesNotInclude() matchers only work on Arrays](https://github.com/ngarbezza/testy/issues/130), thank you @trochepablo for your first contribution! +* [[bug] color for pending summary was not painted](https://github.com/ngarbezza/testy/issues/173) +* [[bug] it was possible to mark tests as pending without specifying reason](https://github.com/ngarbezza/testy/issues/172) + +### Refactored + +* [rename "master" branch to "main"](https://github.com/ngarbezza/testy/issues/133); also, an ADR was added to track the decision that we want a better vocabulary +* [parametrizable i18n messages](https://github.com/ngarbezza/testy/issues/71) +* [write more tests for the i18n module](https://github.com/ngarbezza/testy/issues/179) +* [throw error objects instead of strings](https://github.com/ngarbezza/testy/issues/176) +* [speed up tests by not creating error messages on successful assertions](https://github.com/ngarbezza/testy/commit/531d1d6360c93a3aae2f11bd0c957c45e93cd35c) +* [added some npm scripts for test coverage and dependencies graph](https://github.com/ngarbezza/testy/commit/d4ca1fa7804b2353458eb214d1f302fefc9fed9d) +* [changes in modularization: extract assertion and test result reporter](https://github.com/ngarbezza/testy/commit/4913b5a187bc0700b3de4b5b1a9adc0e2a8dc57e) +* add more tests and increased the current coverage ([example 1](https://github.com/ngarbezza/testy/commit/be41db9872ea4490b5dae238d6c553b214667326), [example 2](https://github.com/ngarbezza/testy/commit/28b2ee51078300382c7398cb40203d6e40ca26d1)) +* formatter object decoupled from console ui (ADR 0004 added [here](https://github.com/ngarbezza/testy/commit/9ab5c55fd4738054effef1e1aab15824a62c6750)) +* avoid test doubles at all (ADR 0005 added [here](https://github.com/ngarbezza/testy/commit/5a65fbc6e6e58b1f03f996c381240d4a1b8c3875)), removed test runner double + +... and more minor cleanups. + ## [5.0.2] - 2020-10-13 A hacktoberfest release! 5 bugs fixed and two new contributors! :muscle: @@ -205,7 +239,8 @@ readable and extensible. It also includes a huge internal refactor to make the t ### Changed - Fix passed count at test runner level (no reported issue) -[Unreleased]: https://github.com/ngarbezza/testy/compare/v5.0.2...HEAD +[Unreleased]: https://github.com/ngarbezza/testy/compare/v5.1.0...HEAD +[5.1.0]: https://github.com/ngarbezza/testy/compare/v5.0.2...v5.1.0 [5.0.2]: https://github.com/ngarbezza/testy/compare/v5.0.1...v5.0.2 [5.0.1]: https://github.com/ngarbezza/testy/compare/v5.0.0...v5.0.1 [5.0.0]: https://github.com/ngarbezza/testy/compare/v4.4.0...v5.0.0 diff --git a/package-lock.json b/package-lock.json index 56a4341f..bba9175f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "@pmoo/testy", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 1 } diff --git a/package.json b/package.json index 2774c45b..60a74b07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pmoo/testy", - "version": "5.0.2", + "version": "5.1.0", "description": "A minimal testing framework, for educational purposes.", "homepage": "https://ngarbezza.github.io/testy/", "repository": {