From 7d4d4ecac1059163d1e6bbf807cca1219016a7a0 Mon Sep 17 00:00:00 2001 From: Valeria Viana Gusmao Date: Thu, 28 Oct 2021 20:08:53 +0200 Subject: [PATCH] fix: expect toMatchObject should properly handle arrays --- dist/Test.d.ts | 2 +- dist/Test.js | 2 +- dist/bundle.js | 2 +- dist/expect.js | 6 +- dist/mod.ts | 155 +++++++++++++++++++++++-------------------- package.json | 2 +- src/Test.ts | 4 +- src/expect.ts | 10 ++- tests/expect.spec.js | 27 ++++++++ 9 files changed, 129 insertions(+), 81 deletions(-) diff --git a/dist/Test.d.ts b/dist/Test.d.ts index d850d50..09f31a1 100644 --- a/dist/Test.d.ts +++ b/dist/Test.d.ts @@ -6,7 +6,7 @@ export declare type TestResult = { }; export declare type FixtureFn = () => Promise | void; export default class Test { - title?: string; + title: string; suite: { title: string; fn?: Function; diff --git a/dist/Test.js b/dist/Test.js index dc9921d..ad066e6 100644 --- a/dist/Test.js +++ b/dist/Test.js @@ -57,7 +57,7 @@ class Test { this.after = (fn) => { this._after.push(fn); }; - this.title = title; + this.title = title !== null && title !== void 0 ? title : ""; } } exports.default = Test; diff --git a/dist/bundle.js b/dist/bundle.js index ec2c2fc..0c2b3a1 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1 +1 @@ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).tinyJest=t()}}((function(){var t={},e=this&&this.__awaiter||function(t,e,i,r){return new(i||(i=Promise))((function(o,n){function s(t){try{u(r.next(t))}catch(e){n(e)}}function f(t){try{u(r.throw(t))}catch(e){n(e)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(s,f)}u((r=r.apply(t,e||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.default=class{constructor(t){this.suite=[],this.results=[],this._before=[],this._after=[],this.it=(t,e)=>{this.suite.push({title:t,fn:e})},this.xit=(t,e)=>{this.suite.push({title:t})},this.run=()=>e(this,void 0,void 0,(function*(){this.results=[];try{yield Promise.all(this._before.map(t=>t()))}catch(t){return[{title:this.title,error:t,passed:!1}]}for(let e of this.suite)if(e.fn)try{yield e.fn(),this.results.push({title:e.title,passed:!0})}catch(t){this.results.push({title:e.title,error:t,passed:!1})}else this.results.push({title:e.title,skipped:!0});try{yield Promise.all(this._after.map(t=>t()))}catch(t){console.error(t)}return this.results})),this.before=t=>{this._before.push(t)},this.after=t=>{this._after.push(t)},this.title=t}};var i={};Object.defineProperty(i,"__esModule",{value:!0});class r extends Error{constructor(t,e,i,r){super(r),this.extensions={matcher:t,expected:e,actual:i}}}const o={toBe:(t,e)=>e!==t&&`Expected ${JSON.stringify(t)} to be ${JSON.stringify(e)}`,toEqual:(t,e)=>e!=t&&`Expected ${JSON.stringify(t)} to equal ${JSON.stringify(e)}`,toBeTruthy:t=>!t&&`Expected ${JSON.stringify(t)} to be truthy`,toBeFalsy:t=>!!t&&`Expected ${JSON.stringify(t)} to be falsy`,toMatchObject:(t,e)=>{for(let i in e){if(typeof t[i]!=typeof e[i])return`Types mismatch for ${i}: ${typeof t[i]} != ${typeof e[i]}`;if("object"!=typeof e[i]){if(o.toBe(t[i],e[i]))return`Mismatched "${i}": ${JSON.stringify(t[i])} != ${JSON.stringify(e[i])}`;continue}const r=o.toMatchObject(t[i],e[i]);if(r)return r}return!1},toThrow:(t,e)=>{try{return t(),`Expected ${t.toString()} to throw error ${e&&" matching "+e.toString()}`}catch(i){return!(!e||e.test(i.toString()))&&`Expected ${t.toString()} to throw error ${e&&` matching ${e.toString()}, but got ${i.toString()} instead`}`}}};i.default=function(t){const e={not:{}};return Object.keys(o).forEach(i=>{e[i]=e=>{const n=o[i](t,e);if(n)throw new r(i,e,t,n)},e.not[i]=e=>{if(!1===o[i](t,e))throw new r(i,e,t,`Expected ${JSON.stringify(t)} not ${i.replace(/[A-Z]/g,t=>" "+t.toLowerCase())}${void 0!==e?" "+JSON.stringify(e):""}`)}}),e};var n={};Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(t){t.forEach(({title:t,passed:e,skipped:i,error:r})=>e?console.info("\x1b[32m","\u2713 "+t):i?console.info("\x1b[33m","\u25a1 "+t):e?void 0:console.error("\x1b[31m","\ud800\udd02 "+t,"\n Failed:",r.message)),console.log("\x1b[0m")};var s={};return Object.defineProperty(s,"__esModule",{value:!0}),Object.defineProperty(s,"Test",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(s,"expect",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(s,"prettify",{enumerable:!0,get:function(){return n.default}}),s})); \ No newline at end of file +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).tinyJest=t()}}((function(){var t={},e=this&&this.__awaiter||function(t,e,r,i){return new(r||(r=Promise))((function(o,n){function s(t){try{u(i.next(t))}catch(e){n(e)}}function f(t){try{u(i.throw(t))}catch(e){n(e)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,f)}u((i=i.apply(t,e||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.default=class{constructor(t){this.suite=[],this.results=[],this._before=[],this._after=[],this.it=(t,e)=>{this.suite.push({title:t,fn:e})},this.xit=(t,e)=>{this.suite.push({title:t})},this.run=()=>e(this,void 0,void 0,(function*(){this.results=[];try{yield Promise.all(this._before.map(t=>t()))}catch(t){return[{title:this.title,error:t,passed:!1}]}for(let e of this.suite)if(e.fn)try{yield e.fn(),this.results.push({title:e.title,passed:!0})}catch(t){this.results.push({title:e.title,error:t,passed:!1})}else this.results.push({title:e.title,skipped:!0});try{yield Promise.all(this._after.map(t=>t()))}catch(t){console.error(t)}return this.results})),this.before=t=>{this._before.push(t)},this.after=t=>{this._after.push(t)},this.title=null!=t?t:""}};var r={};Object.defineProperty(r,"__esModule",{value:!0});class i extends Error{constructor(t,e,r,i){super(i),this.extensions={matcher:t,expected:e,actual:r}}}const o={toBe:(t,e)=>e!==t&&`Expected ${JSON.stringify(t)} to be ${JSON.stringify(e)}`,toEqual:(t,e)=>e!=t&&`Expected ${JSON.stringify(t)} to equal ${JSON.stringify(e)}`,toBeTruthy:t=>!t&&`Expected ${JSON.stringify(t)} to be truthy`,toBeFalsy:t=>!!t&&`Expected ${JSON.stringify(t)} to be falsy`,toMatchObject:(t,e)=>{const r=`${JSON.stringify(t)} does not match ${JSON.stringify(e)}`;if(typeof t!=typeof e||Array.isArray(t)!==Array.isArray(e))return r;for(let i in e){if(typeof t[i]!=typeof e[i])return`${r}:\nTypes mismatch for ${i}: ${typeof t[i]} != ${typeof e[i]}`;if("object"!=typeof e[i]){if(o.toBe(t[i],e[i]))return`Mismatched "${i}": ${JSON.stringify(t[i])} != ${JSON.stringify(e[i])}`;continue}const n=o.toMatchObject(t[i],e[i]);if(n)return n}return!1},toThrow:(t,e)=>{try{return t(),`Expected ${t.toString()} to throw error ${e&&" matching "+e.toString()}`}catch(r){return!(!e||e.test(r.toString()))&&`Expected ${t.toString()} to throw error ${e&&` matching ${e.toString()}, but got ${r.toString()} instead`}`}}};r.default=function(t){const e={not:{}};return Object.keys(o).forEach(r=>{e[r]=e=>{const n=o[r](t,e);if(n)throw new i(r,e,t,n)},e.not[r]=e=>{if(!1===o[r](t,e))throw new i(r,e,t,`Expected ${JSON.stringify(t)} not ${r.replace(/[A-Z]/g,t=>" "+t.toLowerCase())}${void 0!==e?" "+JSON.stringify(e):""}`)}}),e};var n={};Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(t){t.forEach(({title:t,passed:e,skipped:r,error:i})=>e?console.info("\x1b[32m","\u2713 "+t):r?console.info("\x1b[33m","\u25a1 "+t):e?void 0:console.error("\x1b[31m","\ud800\udd02 "+t,"\n Failed:",i.message)),console.log("\x1b[0m")};var s={};return Object.defineProperty(s,"__esModule",{value:!0}),Object.defineProperty(s,"Test",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(s,"expect",{enumerable:!0,get:function(){return r.default}}),Object.defineProperty(s,"prettify",{enumerable:!0,get:function(){return n.default}}),s})); \ No newline at end of file diff --git a/dist/expect.js b/dist/expect.js index 8f3532d..d08d029 100644 --- a/dist/expect.js +++ b/dist/expect.js @@ -26,9 +26,13 @@ const matchers = { return actual ? `Expected ${JSON.stringify(actual)} to be falsy` : false; }, toMatchObject: (actual, expected) => { + const error = `${JSON.stringify(actual)} does not match ${JSON.stringify(expected)}`; + if (typeof actual !== typeof expected || + Array.isArray(actual) !== Array.isArray(expected)) + return error; for (let key in expected) { if (typeof actual[key] !== typeof expected[key]) - return `Types mismatch for ${key}: ${typeof actual[key]} != ${typeof expected[key]}`; + return `${error}:\nTypes mismatch for ${key}: ${typeof actual[key]} != ${typeof expected[key]}`; if (typeof expected[key] !== "object") { const res = matchers.toBe(actual[key], expected[key]); if (res) diff --git a/dist/mod.ts b/dist/mod.ts index 62ef307..06233d6 100644 --- a/dist/mod.ts +++ b/dist/mod.ts @@ -1,3 +1,76 @@ +export type TestResult = { + title: string; + skipped?: boolean; + passed?: boolean; + error?: Error; +}; + +export type FixtureFn = () => Promise | void; +export class Test { + title: string; + suite: { title: string; fn?: Function }[] = []; + // Stores last results + results: TestResult[] = []; + private _before: FixtureFn[] = []; + private _after: FixtureFn[] = []; + constructor(title?: string) { + this.title = title ?? ""; + } + it = (title: string, fn?: Function) => { + this.suite.push({ title, fn }); + }; + xit = (title: string, _fn?: Function) => { + this.suite.push({ title }); + }; + run = async () => { + this.results = []; + try { + await Promise.all(this._before.map((fn) => fn())); + } catch (error) { + return [{ title: this.title, error, passed: false }]; + } + for (let test of this.suite) { + if (!test.fn) { + this.results.push({ title: test.title, skipped: true }); + continue; + } + try { + await test.fn(); + this.results.push({ title: test.title, passed: true }); + } catch (error) { + this.results.push({ title: test.title, error, passed: false }); + } + } + try { + await Promise.all(this._after.map((fn) => fn())); + } catch (error) { + console.error(error); + } + return this.results; + }; + before = (fn: FixtureFn) => { + this._before.push(fn); + }; + after = (fn: FixtureFn) => { + this._after.push(fn); + }; +} + +export function prettify(testResults: TestResult[]) { + testResults.forEach(({ title, passed, skipped, error }) => { + if (passed) return console.info("\x1b[32m", `✓ ${title}`); + if (skipped) return console.info("\x1b[33m", `□ ${title}`); + if (!passed) + return console.error( + "\x1b[31m", + `𐄂 ${title}`, + "\n Failed:", + error!.message + ); + }); + console.log("\x1b[0m"); +} + export class ExpectationError extends Error { extensions: { matcher: string; expected: any; actual: any }; constructor(matcher: string, expected: any, actual: any, diff: string) { @@ -39,9 +112,17 @@ const matchers: Record = { return actual ? `Expected ${JSON.stringify(actual)} to be falsy` : false; }, toMatchObject: (actual: any, expected: any): string | false => { + const error = `${JSON.stringify(actual)} does not match ${JSON.stringify( + expected + )}`; + if ( + typeof actual !== typeof expected || + Array.isArray(actual) !== Array.isArray(expected) + ) + return error; for (let key in expected) { if (typeof actual[key] !== typeof expected[key]) - return `Types mismatch for ${key}: ${typeof actual[ + return `${error}:\nTypes mismatch for ${key}: ${typeof actual[ key ]} != ${typeof expected[key]}`; if (typeof expected[key] !== "object") { @@ -102,75 +183,3 @@ export function expect(actual: any): Expectations & { not: Expectations } { }); return expectation; } - -export function prettify(testResults: TestResult[]) { - testResults.forEach(({ title, passed, skipped, error }) => { - if (passed) return console.info("\x1b[32m", `✓ ${title}`); - if (skipped) return console.info("\x1b[33m", `□ ${title}`); - if (!passed) - return console.error( - "\x1b[31m", - `𐄂 ${title}`, - "\n Failed:", - error!.message - ); - }); - console.log("\x1b[0m"); -} - -export type TestResult = { - title: string; - skipped?: boolean; - passed?: boolean; - error?: Error; -}; -export type FixtureFn = () => Promise | void; -export class Test { - title: string; - suite: { title: string; fn?: Function }[] = []; - // Stores last results - results: TestResult[] = []; - private _before: FixtureFn[] = []; - private _after: FixtureFn[] = []; - constructor(title?: string) { - this.title = title ?? ""; - } - it = (title: string, fn?: Function) => { - this.suite.push({ title, fn }); - }; - xit = (title: string, _fn?: Function) => { - this.suite.push({ title }); - }; - run = async () => { - this.results = []; - try { - await Promise.all(this._before.map((fn) => fn())); - } catch (error) { - return [{ title: this.title, error, passed: false }]; - } - for (let test of this.suite) { - if (!test.fn) { - this.results.push({ title: test.title, skipped: true }); - continue; - } - try { - await test.fn(); - this.results.push({ title: test.title, passed: true }); - } catch (error) { - this.results.push({ title: test.title, error, passed: false }); - } - } - try { - await Promise.all(this._after.map((fn) => fn())); - } catch (error) { - console.error(error); - } - return this.results; - }; - before = (fn: FixtureFn) => { - this._before.push(fn); - }; - after = (fn: FixtureFn) => { - this._after.push(fn); - }; -} diff --git a/package.json b/package.json index 1be9d09..3193308 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tiny-jest", - "version": "1.1.0", + "version": "1.1.1", "description": "Minimalistic zero dependency Jest-like test library to run tests in browser, nodejs or deno.", "keywords": [ "jest", diff --git a/src/Test.ts b/src/Test.ts index 6508607..b6f4eab 100644 --- a/src/Test.ts +++ b/src/Test.ts @@ -7,14 +7,14 @@ export type TestResult = { export type FixtureFn = () => Promise | void; export default class Test { - title?: string; + title: string; suite: { title: string; fn?: Function }[] = []; // Stores last results results: TestResult[] = []; private _before: FixtureFn[] = []; private _after: FixtureFn[] = []; constructor(title?: string) { - this.title = title; + this.title = title ?? ""; } it = (title: string, fn?: Function) => { this.suite.push({ title, fn }); diff --git a/src/expect.ts b/src/expect.ts index afe0825..d7d304a 100644 --- a/src/expect.ts +++ b/src/expect.ts @@ -39,9 +39,17 @@ const matchers: Record = { return actual ? `Expected ${JSON.stringify(actual)} to be falsy` : false; }, toMatchObject: (actual: any, expected: any): string | false => { + const error = `${JSON.stringify(actual)} does not match ${JSON.stringify( + expected + )}`; + if ( + typeof actual !== typeof expected || + Array.isArray(actual) !== Array.isArray(expected) + ) + return error; for (let key in expected) { if (typeof actual[key] !== typeof expected[key]) - return `Types mismatch for ${key}: ${typeof actual[ + return `${error}:\nTypes mismatch for ${key}: ${typeof actual[ key ]} != ${typeof expected[key]}`; if (typeof expected[key] !== "object") { diff --git a/tests/expect.spec.js b/tests/expect.spec.js index 821efa4..0c82f9b 100644 --- a/tests/expect.spec.js +++ b/tests/expect.spec.js @@ -50,4 +50,31 @@ it("expect.not", async () => { expect(error.message).toBe("Expected 2 not to be truthy"); } }); + +it("toMatchObject", () => { + const more = { a: 1, b: 2, c: 3 }; + const less = { a: 1, b: 2 }; + expect(more).toMatchObject(more); + expect(less).toMatchObject(less); + + expect(more).toMatchObject(less); + expect(less).not.toMatchObject(more); +}); + +it("toMatchObject/arrays", () => { + expect([]).toMatchObject([]); + expect([]).not.toMatchObject({}); + expect([1, 2, 3]).toMatchObject([1, 2]); + expect([2, 3]).not.toMatchObject([1, 2, 3]); + expect([{ id: 1, name: "1" }, { id: 2, name: "2" }, { id: 3 }]).toMatchObject( + [{ id: 1 }, { id: 2 }, { id: 3 }] + ); + expect([{ id: 1 }, { id: 2 }, { id: 3 }]).not.toMatchObject([ + { id: 1, name: "1" }, + { id: 2, name: "2" }, + { id: 3 }, + ]); + expect({ a: [1, 2, 3] }).toMatchObject({ a: [1, 2, 3] }); + expect({ a: [1, 2] }).not.toMatchObject({ a: [1, 2, 3] }); +}); module.exports = test;