From 822a68207fabd6e1c1b9b631bc2d9ef0d9d2638b Mon Sep 17 00:00:00 2001 From: Scott J Date: Sun, 8 Mar 2020 23:07:25 +0000 Subject: [PATCH] fixed `assert` methods: - `assert.truthy()` - checks if something is truthy - `assert.falsey()` - checks if something is falsey - `assert.eq()` - uses the `==` comparison (alias of `assert.equals()`) - `assert.strictEquals()` - uses the `===` comparison - `assert.deepEquals()` - uses the `react-fast-compare` deep equals comparison - `assert.isType()` - uses a `typeof` comparison - `assert.isImmutable()` - checks if something is a Boolean, Number, String, Null - `assert.isMutable()` - checks if something is an Object, Array, Function, Class, Map, or Set - `assert.isReactElement()` - checks object contains `$$typeof = REACT_ELEMENT_TYPE` --- README.md | 62 ++++++----- examples/end-to-end-tests.js | 4 +- examples/simple-tests.js | 15 ++- package-lock.json | 4 +- package.json | 2 +- src/tea.js | 196 +++++++++++++++++------------------ 6 files changed, 139 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index acd26ce..7eb8a91 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ - end to end tests - supports the following CLI options: - `--fail-fast`: exit after the first failing test - - `--quiet`: only show failing tests - - `--verbose`: show expected/actual for all tests (including passing tests) + - `--quiet`: only show failing tests + - `--verbose`: show expected/actual for all tests (including passing tests) ## Installation @@ -52,7 +52,7 @@ Create a file to contain your tests: touch ./tests/mytests.js ``` -## Usage +## Usage Initialise `tea` in `tests/mytests.js`: @@ -76,7 +76,7 @@ tea = require('tea'); tea() /* initialise tea by running it */ -/* Optionally define some test hooks.. +/* Optionally define some test hooks.. test.beforeEach = () => console.log("before each") test.beforeAll = () => console.log("before all") test.afterEach = () => console.log("after each") @@ -136,6 +136,18 @@ Usage: assert.foo(msg, actual, expected) ``` +Where `foo` can be any of these methods: + +- `assert.truthy()` - checks if something is truthy +- `assert.falsey()` - checks if something is falsey +- `assert.eq()` - uses the `==` comparison (alias of `assert.equals()`) +- `assert.strictEquals()` - uses the `===` comparison +- `assert.deepEquals()` - uses the `react-fast-compare` deep equals comparison +- `assert.isType()` - uses a `typeof` comparison +- `assert.isImmutable()` - checks if something is a Boolean, Number, String, Null +- `assert.isMutable()` - checks if something is an Object, Array, Function, Class, Map, or Set +- `assert.isReactElement()` - checks object contains `$$typeof = REACT_ELEMENT_TYPE` + Examples: ```js @@ -143,18 +155,17 @@ Examples: test("test using assert, description here", () => { assert.isType("app.add should be a function", app.add, "function") - assert.isString("foo should be a String", "foo", true) - assert.isFunction("app.add should be a function", app.add, true) - assert.eq("1 should equal 1", 1, 1) - assert.eq("1 + 1 should equal 2", app.add(1, 1), 2) - assert.equals( + assert.isType("[1,2,3] should be an array", [1,2,3], "array") + assert.equals("1 should equal 1", 1, 1) + assert.strictEquals("1 + 1 should equal 2", app.add(1, 1), 2) + assert.deepEquals( "simple obj1 should equal simple obj1", { foo: "bar" }, { foo: "bar" } ) var obj1 = { name: "dan", age: 22, stats: { s: 10, b: 20, c: 31 } } var obj2 = { name: "bob", age: 21, stats: { s: 10, b: 20, c: 30 } } - assert.isEqual("obj1 should equal obj2", obj1, obj2) + assert.deepEquals("obj1 should equal obj2", obj1, obj2) }) // run the tests @@ -175,7 +186,7 @@ assert({ }) ``` -Examples: +Examples: ```js /* tests using 'assert' with object syntax */ @@ -227,7 +238,7 @@ Options: --quiet Only list failing tests --verbose List the actual/expected results of passing tests too --help Show this help screen - + ``` ### BDD style tests @@ -239,10 +250,10 @@ Tests can be grouped arbitrarily, using the `group` function. Example: group("First group of tests", () => { test("some message", () => { - assert.eq("one should equal one", 1, 1) + assert.strictEquals("one should equal one", 1, 1) }) test("some message", () => { - assert.eq("one should equal one", 1, 1) + assert.strictEquals("one should equal one", 1, 1) }) }) ``` @@ -279,19 +290,19 @@ feature("Calculator", () => { scenario("Addition and subtraction", () => { test("Additions", () => { - expect("1 + 1 equals 2", app.add(1, 1), 2) + expect("1 + 1 equals 2", app.add(1, 1), 2) }) test("Subtractions", () => { - expect("1 - 1 equals 2", app.sub(1, 1), 0) + expect("1 - 1 equals 2", app.sub(1, 1), 0) }) }) scenario("Dividing numbers", () => { test("Dividing numbers", () => { - expect("10 / 2 equals 5", app.div(10, 2), 5) + expect("10 / 2 equals 5", app.div(10, 2), 5) }) test("Dividing nonsense", () => { - expect("1 - 'foo' equals NaN", app.sub(1, 'foo'), NaN) + expect("1 - 'foo' equals NaN", app.sub(1, 'foo'), NaN) }) }) @@ -319,7 +330,7 @@ If setting up a headles browser is too much work, you can use `tea` to test stuf test("check text in page", function(){ var el = document.getElementById('#myElem') - assert.eq("elem contains correct text", el.innerText, "expected text") + assert.strictEquals("elem contains correct text", el.innerText, "expected text") }) run() // run tests @@ -329,7 +340,7 @@ If setting up a headles browser is too much work, you can use `tea` to test stuf You can even copy and paste `dist/tea.umd.js` into the DevTools console directly! ...Then just write/paste some tests in the console and call `run()`. -**NOTE**: +**NOTE**: In the browser, passing command-line options like `--quiet` won't work, but you can set `tea.quiet = true`, `tea.args.verbose = true`, and `tea.failFast = true` in the DevTools directly, and then call `run()`. @@ -381,11 +392,11 @@ test("check the page loaded OK", async () => { // Get page title await ghost.open('http://google.com') let pageTitle = await ghost.pageTitle() - assert.eq(pageTitle, 'Google') + assert.strictEquals(pageTitle, 'Google') // Get the content of the body let body = await ghost.findElement('body') - assert.eq(await body.isVisible(), true) + assert.strictEquals(await body.isVisible(), true) }) ... @@ -409,13 +420,8 @@ Rebuild the bundles in `dist/` using this command: `npm run build` ## To do - Add TAP format results reporter -- Add `skip.test(...)` to easily skip tests +- Add `skip.test(...)` to easily skip tests - Add more assertions: - - `assert.truthy` - - `assert.falsey` - - `assert.isReactElement` - - `assert.isImmutable` - - `assert.isNotImmutable` - `assert.throwsError` - `assert.matchesRegex` - ensure `foo` matches a regex `assert.regex(msg, regex, expected)` - etc diff --git a/examples/end-to-end-tests.js b/examples/end-to-end-tests.js index a5fe880..3f2356d 100644 --- a/examples/end-to-end-tests.js +++ b/examples/end-to-end-tests.js @@ -18,11 +18,11 @@ test("check the page loaded OK", async () => { // check page title await ghost.open("http://google.com") let pageTitle = await ghost.pageTitle() - assert.eq(pageTitle, "Google") + assert.strictEquals(pageTitle, "Google") // check the content of the body let body = await ghost.findElement("body") - assert.eq(await body.isVisible(), true) + assert.strictEquals(await body.isVisible(), true) // ... }) diff --git a/examples/simple-tests.js b/examples/simple-tests.js index 1896d85..52cdc33 100644 --- a/examples/simple-tests.js +++ b/examples/simple-tests.js @@ -37,20 +37,17 @@ test("test using expect, description here", () => { // // tests using the 'assert' assertion method -test("test using assert, description here", () => { - assert.isType("demo.app is should be a function", demoApp.add, "function") - assert.isString("foo should be a String", "foo", true) - assert.isFunction("demo.app is should be a function", demoApp.add, true) - assert.eq("1 should equal 1", 1, 1) - assert.eq("1 + 1 should equal 2", demoApp.add(1, 1), 2) - assert.equals( - "simple obj1 should equal simple obj1", +test("test using ASSERT, description here", () => { + assert.isType("isType: demo.app is should be a function", demoApp.add, "function") + assert.strictEquals("strictEquals: 1 + 1 should equal 2", demoApp.add(1, 1), 2) + assert.deepEquals( + "deepEquals: obj1 should equal simple obj1", { foo: "bar" }, { foo: "bar" } ) var obj1 = { name: "dan", age: 22, stats: { s: 10, b: 20, c: 31 } } var obj2 = { name: "bob", age: 21, stats: { s: 10, b: 20, c: 30 } } - assert.isEqual("obj1 should equal obj2", obj1, obj2) + assert.deepEquals("deepEquals: obj1 should equal obj2", obj1, obj2) }) // diff --git a/package-lock.json b/package-lock.json index 02a3732..f165ad6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "tea", - "version": "1.0.0", + "name": "@scottjarvis/tea", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fe075b2..7887d71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scottjarvis/tea", - "version": "1.0.2", + "version": "1.0.3", "description": "Test Environment Application", "author": "sc0ttj", "license": "MIT", diff --git a/src/tea.js b/src/tea.js index 836f745..3d50be2 100644 --- a/src/tea.js +++ b/src/tea.js @@ -1,34 +1,5 @@ /* global Map:readonly, Set:readonly, ArrayBuffer:readonly */ -// from: react/packages/shared/ReactSymbols.js -// The Symbol used to tag the ReactElement-like types. If there is no native Symbol -// nor polyfill, then a plain number is used for performance. -var hasSymbol = typeof Symbol === "function" && Symbol.for -var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for("react.element") : 0xeac7 -/** - * Verifies the object is a ReactElement. - * See https://reactjs.org/docs/react-api.html#isvalidelement - * @param {?object} object - * @return {boolean} True if `object` is a ReactElement. - * @final - */ -function isReactElement(object) { - return ( - typeof object === "object" && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE - ) -} - -// https://stackoverflow.com/questions/4402272/checking-if-data-is-immutable -var isMutable = function(test) { - return test && (typeof test == "object" || typeof test == "function") -} - -var isImmutable = function(test) { - return !isMutable(test) -} - // main test suite app // // @TODO: @@ -162,6 +133,9 @@ export default (tea = function() { tea.failFast = false // exit after first tests fails or not tea.quiet = false // if true, only show failing tests tea.reportFormat = "console" // choose what format to report test results + // used to format the test results + tea.indent = 2 + tea.indentStr = "" // process command-line options: allow user to override default settings Object.keys(tea.args).forEach(function(key) { @@ -189,18 +163,18 @@ export default (tea = function() { console.log("\nUsage: node path/to/tests.js [options]") console.log("\nOptions:\n") console.log( - " --fail-fast Exit after first failing test" + " --fail-fast Exit after first failing test" ) console.log( - " --format= The style/format of the test results (console|debug|tap)" + " --format= The style/format of the test results (console|debug|tap)" ) console.log( - " --quiet Only list failing tests" + " --quiet Only list failing tests" ) console.log( - " --verbose List the actual/expected results of passing tests too" + " --verbose List the actual/expected results of passing tests too" ) - console.log(" --help Show this help screen") + console.log(" --help Show this help screen\n") } if (tea.isNode && typeof process !== "undefined") { process.exit(0) @@ -215,6 +189,7 @@ export default (tea = function() { // holder var, used to pass the current test between test() and assert() tea.currentTest = {} + // the function we use for grouping and indenting tests tea.scenario = function(msg, fn) { tea.tests.push({ msg: msg, @@ -274,6 +249,39 @@ export default (tea = function() { return this.name + ": " + this.message } + // our various assertion methods call this func to register passes/fails with + // the currentTest object for our reporters to work OK.. + tea.assertHarness = function(assertion, msg, actual, expected, operator) { + tea.assertHarness.result = null + tea.assertHarness.operator = operator || "===" + tea.currentTest.total += 1 + + tea.assertHarness.result = assertion + + if (!tea.assertHarness.result) { + tea.assertHarness.result = new AssertionError( + msg, + actual, + expected, + tea.assertHarness.operator + ) + tea.currentTest.fails += 1 + } else { + tea.currentTest.passes += 1 + } + tea.assertHarness.stack = tea.assertHarness.result.stack + + tea.currentTest.assertions.push({ + actual, + expected, + operator: tea.assertHarness.operator, + result: tea.assertHarness.result, + msg + }) + + return tea.assertHarness.result + } + // runs the given assertion, records the results to current test object tea.expect = function(msg, fn, expected) { tea.expect.result = null @@ -321,14 +329,15 @@ export default (tea = function() { } // react-compatible deep equals function (from react-fast-compare@3.0.1) - var hasElementType = typeof Element !== "undefined" - var hasMap = typeof Map === "function" - var hasSet = typeof Set === "function" - var hasArrayBuffer = typeof ArrayBuffer === "function" - tea.equal = function(a, b) { - // START: fast-deep-equal es6/index.js 3.1.1 + tea.deepEquals = function(a, b) { if (a === b) return true + // START: fast-deep-equal es6/index.js 3.1.1 + var hasElementType = typeof Element !== "undefined" + var hasMap = typeof Map === "function" + var hasSet = typeof Set === "function" + var hasArrayBuffer = typeof ArrayBuffer === "function" + if (a && b && typeof a == "object" && typeof b == "object") { if (a.constructor !== b.constructor) return false @@ -336,7 +345,7 @@ export default (tea = function() { if (Array.isArray(a)) { length = a.length if (length != b.length) return false - for (i = length; i-- !== 0; ) if (!tea.equal(a[i], b[i])) return false + for (i = length; i-- !== 0; ) if (!tea.deepEquals(a[i], b[i])) return false return true } @@ -367,7 +376,7 @@ export default (tea = function() { while (!(i = it.next()).done) if (!b.has(i.value[0])) return false it = a.entries() while (!(i = it.next()).done) - if (!tea.equal(i.value[1], b.get(i.value[0]))) return false + if (!tea.deepEquals(i.value[1], b.get(i.value[0]))) return false return true } @@ -413,7 +422,7 @@ export default (tea = function() { } // all other properties should be traversed as usual - if (!tea.equal(a[keys[i]], b[keys[i]])) return false + if (!tea.deepEquals(a[keys[i]], b[keys[i]])) return false } // END: react-fast-compare return true @@ -423,10 +432,10 @@ export default (tea = function() { } // end fast-deep-equal - // wrapper around tea.equal + // wrapper around tea.deepEquals tea.isEqual = function(a, b) { try { - return tea.equal(a, b) + return tea.deepEquals(a, b) } catch (error) { if ((error.message || "").match(/stack|recursion/i)) { // warn on circular references, don't crash @@ -442,9 +451,10 @@ export default (tea = function() { } } + // function that accepts an object containing 'msg', actual', 'expected' tea.assert = function(options = {}) { return tea.assertHarness( - tea.isEqual(options.actual, options.expected), + (options.actual === options.expected), options.message, options.actual, options.expected || true, @@ -452,13 +462,23 @@ export default (tea = function() { ) } - tea.assert.eq = function(msg, a, b) { - return tea.assertHarness(tea.isEqual(a, b), msg, a, b, "===") + tea.assert.deepEquals = function(msg, a, b) { + return tea.assertHarness(tea.isEqual(a, b), msg, a, b, "deep equals") + } + + tea.assert.equals = function(msg, a, b) { + return tea.assertHarness(a == b, msg, a, b, "==") + } + + tea.assert.eq = tea.assert.equals + + tea.assert.strictEquals = function(msg, a, b) { + return tea.assertHarness(a === b, msg, a, b, "===") } tea.assert.isType = function(msg, a, b) { return tea.assertHarness( - typeof a === b, + (typeof a === b), msg, typeof a, `${b}`, @@ -466,61 +486,35 @@ export default (tea = function() { ) } - tea.assert.isFunction = function(msg, a, b) { - return tea.assertHarness( - typeof a === "function", - msg, - typeof a, - "function", - "typeof Function" - ) + tea.assert.truthy = function(msg, a, b) { + return tea.assertHarness(!!a, msg, a, b, "Truthy") } - tea.assert.isString = function(msg, a, b) { - return tea.assertHarness( - typeof a === "string", - msg, - typeof a, - "string", - "typeof String" - ) + tea.assert.falsey = function(msg, a, b) { + return tea.assertHarness(!a, msg, a, b, "Falsey") } - // aliases for assert - tea.assert.equals = tea.assert.eq - tea.assert.isEqual = tea.assert.eq - - // our various assertion methods call this func to register passes/fails with - // the currentTest object for our reporters to work OK.. - tea.assertHarness = function(assertion, msg, actual, expected, operator) { - tea.assertHarness.result = null - tea.assertHarness.operator = operator || "===" - tea.currentTest.total += 1 - - tea.assertHarness.result = assertion - - if (!tea.assertHarness.result) { - tea.assertHarness.result = new AssertionError( - msg, - actual, - expected, - tea.assertHarness.operator - ) - tea.currentTest.fails += 1 - } else { - tea.currentTest.passes += 1 - } - tea.assertHarness.stack = tea.assertHarness.result.stack + // from: react/packages/shared/ReactSymbols.js + // The Symbol used to tag the ReactElement-like types. If there is no native Symbol + // nor polyfill, then a plain number is used for performance. + var hasSymbol = typeof Symbol === "function" && Symbol.for + tea.REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for("react.element") : 0xeac7 + + tea.assert.isReactElement = function(object) { + return ( + typeof object === "object" && + object !== null && + object.$$typeof === tea.REACT_ELEMENT_TYPE + ) + } - tea.currentTest.assertions.push({ - actual, - expected, - operator: tea.assertHarness.operator, - result: tea.assertHarness.result, - msg - }) + // https://stackoverflow.com/questions/4402272/checking-if-data-is-immutable + tea.assert.isMutable = function(test) { + return test && (typeof test == "object" || typeof test == "function") + } - return tea.assertHarness.result + tea.assert.isImmutable = function(test) { + return !tea.assert.isMutable(test) } // https://vanillajstoolkit.com/helpers/diff/ @@ -702,9 +696,6 @@ export default (tea = function() { tea.reportSummary() } - tea.indent = 2 - tea.indentStr = "" - tea.addIndent = function() { for (var i = 0; i < tea.indent; i++) { tea.indentStr += " " @@ -901,7 +892,6 @@ export default (tea = function() { global.expect = tea.expect global.assert = tea.assert global.run = tea.run - global.tea = tea // syntax like jasmine, mocha, chai global.describe = tea.scenario global.it = tea.test @@ -910,6 +900,8 @@ export default (tea = function() { global.given = tea.scenario global.when = tea.test global.then = tea.expect + // return main + global.tea = tea return tea })