diff --git a/.all-contributorsrc b/.all-contributorsrc index a23adf00..a5a750a9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -450,6 +450,17 @@ "contributions": [ "code" ] + }, + { + "login": "danielpmichalski", + "name": "Daniel Michalski", + "avatar_url": "https://avatars2.githubusercontent.com/u/5457711?v=4", + "profile": "https://twitter.com/crazyPolishDan", + "contributions": [ + "code", + "doc", + "test" + ] } ], "repoType": "github" diff --git a/README.md b/README.md index 44b8a5e9..1355078a 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ If you've come here to help contribute - Thanks! Take a look at the [contributin - [Array](#array) - [.toBeArray()](#tobearray) - [.toBeArrayOfSize()](#tobearrayofsize) + - [.toBeArrayWithFloats([members], ?precision)](#tobearraywithfloatsmembers-precision) - [.toIncludeAllMembers([members])](#toincludeallmembersmembers) - [.toIncludeAnyMembers([members])](#toincludeanymembersmembers) - [.toIncludeSameMembers([members])](#toincludesamemembersmembers) @@ -272,6 +273,17 @@ test('passes when value is an array', () => { }); ``` +#### .toBeArrayWithFloats([members], ?precision) + +Use `.toBeArrayWithFloats` when comparing arrays of floating-point numbers with chosen precision in the same order. + +```js +test('passes when arrays have same floats', () => { + expect([1.11112, 2.22221]).toBeArrayWithFloats([1.11114, 2.22223]); // default 4-digit decimal precision + expect([1.112]).not.toBeArrayWithFloats([1.111], 3); // specified 3-digit decimal precision +}); +``` + #### .toIncludeAllMembers([members]) Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. @@ -927,7 +939,7 @@ test('passes when value includes all substrings', () => { | [
Amish Shah](https://hydrabolt.me/)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=hydrabolt "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=hydrabolt "Tests") | [
Dave Cooper](http://davecooper.org)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=grug "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=grug "Tests") | [
Swann Polydor](https://github.com/soueuls)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=soueuls "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=soueuls "Tests") | [
vikneshwar](https://github.com/vikneshwar)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=vikneshwar "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=vikneshwar "Tests") | [
Budi Irawan](http://budiirawan.com)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=deerawan "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=deerawan "Tests") | [
Tejas Bubane](http://foss-geek.blogspot.com/)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=tejasbubane "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=tejasbubane "Tests") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=tejasbubane "Documentation") | [
Subinoy Ghosh](https://github.com/subinoy7)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=subinoy7 "Code") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=subinoy7 "Tests") | | [
Simen Bekkhus](https://github.com/SimenB)
[πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=SimenB "Documentation") | [
Orta](http://orta.io)
[πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=orta "Documentation") | [
Tom](https://jsdevtom.com)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=jsdevtom "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=jsdevtom "Documentation") [πŸ’‘](#example-jsdevtom "Examples") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=jsdevtom "Tests") | [
Lucian Buzzo](https://github.com/LucianBuzzo)
| [
Thiago Delgado Pinto](https://github.com/thiagodp)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=thiagodp "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=thiagodp "Documentation") [πŸ’‘](#example-thiagodp "Examples") [πŸ€”](#ideas-thiagodp "Ideas, Planning, & Feedback") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=thiagodp "Tests") | [
Ragnar Laud](https://github.com/xprn)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=xprn "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=xprn "Documentation") | [
Luiz AmΓ©rico](https://github.com/blikblum)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=blikblum "Code") | | [
Frederick Fogerty](https://github.com/frederickfogerty)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=frederickfogerty "Code") [πŸ€”](#ideas-frederickfogerty "Ideas, Planning, & Feedback") | [
Benjamin Kay](https://github.com/benjaminkay93)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=benjaminkay93 "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=benjaminkay93 "Documentation") | [
Gilles De Mey](https://demey.io)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=gillesdemey "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=gillesdemey "Documentation") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=gillesdemey "Tests") | [
Deniz Dogan](https://github.com/denizdogan)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=denizdogan "Code") | [
Mikey Powers](https://github.com/mvpowers)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=mvpowers "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=mvpowers "Documentation") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=mvpowers "Tests") | [
Tony Trinh](https://github.com/tony19)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=tony19 "Code") | [
Nikita Kurpas](https://github.com/NikitaKurpas)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=NikitaKurpas "Code") | -| [
Alcedo Nathaniel De Guzman Jr](https://twitter.com/natealcedo)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Documentation") [πŸ’‘](#example-natealcedo "Examples") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Tests") | [
Pete Hodgson](http://thepete.net)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=moredip "Code") | +| [
Alcedo Nathaniel De Guzman Jr](https://twitter.com/natealcedo)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Documentation") [πŸ’‘](#example-natealcedo "Examples") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=natealcedo "Tests") | [
Pete Hodgson](http://thepete.net)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=moredip "Code") | [
Daniel Michalski](https://twitter.com/crazyPolishDan)
[πŸ’»](https://github.com/mattphillips/jest-extended/commits?author=danielpmichalski "Code") [πŸ“–](https://github.com/mattphillips/jest-extended/commits?author=danielpmichalski "Documentation") [⚠️](https://github.com/mattphillips/jest-extended/commits?author=danielpmichalski "Tests") | ## LICENSE diff --git a/src/matchers/toBeArrayWithFloats/index.js b/src/matchers/toBeArrayWithFloats/index.js new file mode 100644 index 00000000..00028612 --- /dev/null +++ b/src/matchers/toBeArrayWithFloats/index.js @@ -0,0 +1,18 @@ +import predicate from './predicate'; + +export default { + toBeArrayWithFloats: (received, expected, precision = 4) => { + const pass = predicate(received, expected, precision); + if (pass) { + return { + message: `expected [${received}] not to be equal to [${expected}] with ${precision}-digit precision`, + pass: true + }; + } else { + return { + message: `expected [${received}] to be equal to [${expected}] with ${precision}-digit precision`, + pass: false + }; + } + } +}; diff --git a/src/matchers/toBeArrayWithFloats/index.test.js b/src/matchers/toBeArrayWithFloats/index.test.js new file mode 100644 index 00000000..189beb16 --- /dev/null +++ b/src/matchers/toBeArrayWithFloats/index.test.js @@ -0,0 +1,31 @@ +import matcher from './'; + +expect.extend(matcher); + +describe('.toBeArrayWithFloats', () => { + test('passes for empty arrays', () => { + expect([]).toBeArrayWithFloats([]); + }); + + test('passes for arrays with same floats with default precision', () => { + expect([1.2, 2.45]).toBeArrayWithFloats([1.2, 2.45001]); + }); + + test('passes for arrays with floats rounded to same value with default precision', () => { + expect([0.00011]).toBeArrayWithFloats([0.0001]); + }); + + test('passes for arrays with same floats with specified precision', () => { + expect([1.014, 1.23456]).toBeArrayWithFloats([1.0104, 1.23112], 2); + }); +}); + +describe('.not.toBeArrayWithFloats', () => { + test('passes for arrays with different floats', () => { + expect([1.0]).not.toBeArrayWithFloats([1.0001]); + }); + + test('passes for arrays with different floats due to rounding', () => { + expect([1.0]).not.toBeArrayWithFloats([1.00005]); + }); +}); diff --git a/src/matchers/toBeArrayWithFloats/predicate.js b/src/matchers/toBeArrayWithFloats/predicate.js new file mode 100644 index 00000000..90de8583 --- /dev/null +++ b/src/matchers/toBeArrayWithFloats/predicate.js @@ -0,0 +1,54 @@ +const isUndefined = function(obj) { + return typeof obj === 'undefined'; +}; + +const someUndefined = function(objects) { + let check = false; + objects.forEach(item => (check = check || isUndefined(item))); + return check; +}; + +const areArrays = function(...objects) { + if (someUndefined(objects)) { + return false; + } + + let allArrays = true; + objects.forEach(item => (allArrays = allArrays && Array.isArray(item))); + return allArrays; +}; + +const cutDecimals = function(num, precision) { + return Number.parseFloat(Number.parseFloat(num).toFixed(precision)); +}; + +const floatsEqual = function(float1, float2, precision) { + let a = cutDecimals(float1, precision); + let b = cutDecimals(float2, precision); + + if (a === 0 && b === 0) { + return true; + } else if (a !== 0 && b === 0) { + return false; + } else { + return a / b === 1; + } +}; + +const arrayWithFloatsEquals = function(array1, array2, precision) { + if (array1.length !== array2.length) { + return false; + } + + for (let i = 0; i < array1.length; i++) { + if (!floatsEqual(array1[i], array2[i], precision)) { + return false; + } + } + + return true; +}; + +export default (actual, expected, precision = 4) => { + return areArrays(actual, expected) && arrayWithFloatsEquals(actual, expected, precision); +}; diff --git a/src/matchers/toBeArrayWithFloats/predicate.test.js b/src/matchers/toBeArrayWithFloats/predicate.test.js new file mode 100644 index 00000000..88a742df --- /dev/null +++ b/src/matchers/toBeArrayWithFloats/predicate.test.js @@ -0,0 +1,74 @@ +import predicate from './predicate'; + +describe('toBeArrayWithFloats predicate - positive test cases', () => { + test('returns true for empty arrays', () => { + expect(predicate([], [])).toBe(true); + }); + + test('returns true for arrays with zeroes', () => { + expect(predicate([0.0], [0.0])).toBe(true); + }); + + test('processes equal arrays with zeroes', () => { + expect(predicate([0.0, 1.0], [0.0, 1.0])).toBe(true); + }); + + test('returns true for arrays with same floats with default precision', () => { + expect(predicate([1.0001, 2.123], [1.00011, 2.123])).toBe(true); + }); + + test('returns true for arrays with same floats with specified precision', () => { + expect(predicate([1.231, 2.344], [1.23, 2.34], 2)).toBe(true); + }); + + test('returns true when floats do not differ after rounding', () => { + expect(predicate([1.0001], [1.00014])).toBe(true); + }); +}); + +describe('toBeArrayWithFloats predicate - negative test cases', () => { + test('returns false for undefined objects', () => { + expect(predicate(undefined, undefined)).toBe(false); + }); + + test('returns false for comparing an array to undefined objects', () => { + expect(predicate([], undefined)).toBe(false); + }); + + test('returns false for comparing an array to undefined objects no.2', () => { + expect(predicate(undefined, [])).toBe(false); + }); + + test('returns false when one value is non-array', () => { + expect(predicate([1.0], 'non-array')).toBe(false); + expect(predicate('non-array', [1.0])).toBe(false); + }); + + test('returns false for non-array values', () => { + expect(predicate('string', 5)).toBe(false); + }); + + test('processes non-equal arrays with zeroes', () => { + expect(predicate([0, 1.0], [0, 0])).toBe(false); + }); + + test('processes non-equal arrays with zeroes in 2nd array', () => { + expect(predicate([1.0], [0])).toBe(false); + }); + + test('returns false when comparing arrays of different size', () => { + expect(predicate([1.0], [1.0, 2.0])).toBe(false); + }); + + test('returns false for arrays with different floats with default precision', () => { + expect(predicate([1.1111], [1.1112])).toBe(false); + }); + + test('returns false for arrays with different floats with specified precision', () => { + expect(predicate([1.1334], [1.1234], 2)).toBe(false); + }); + + test('returns false when floats differ by single digit due to rounding', () => { + expect(predicate([1.0001], [1.00015])).toBe(false); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index cd4cc1f4..e81141ca 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -52,6 +52,13 @@ declare namespace jest { */ toBeArrayOfSize(x: number): R; + /** + * Use `.toBeArrayWithFloats` when checking if a value is an `Array` with floating-point numbers. Equality is checked using default or specified precision. + * @param {Array.} expected + * @param {Number} precision, i.e. decimal places taken into account during number comparison (1.0001 is not 1.00016, but 1.0001 is 1.00015 for precision = 4) + */ + toBeArrayWithFloats(expected: number[], precision: number = 4): R; + /** * Use `.toBeAfter` when checking if a date occurs after `date`. * @param {Date} date