tcompare
TypeScript icon, indicating that this package has built-in type declarations

6.4.6 • Public • Published

tcompare

A comprehensive comparison library, for use in test frameworks. Walks an object once, generating both a simple true/false result, as well as a string representation of both the actual and expected values (highlighting just the parts that differ) and a patch-style diff string.

USAGE

// require() is fine too
import {
  match,
  same,
  strict,
  has,
  hasStrict,
  matchStrict,
  matchOnly,
  matchOnlyStrict,
} from 'tcompare'
import type { Result } from 'tcompare'

const result: Result = match(object, pattern)
if (!result.match) {
  console.log(`item did not match pattern`)
  console.log(result.diff)
} else {
  console.log(`it's a match!`)
}

// raw classes exported also
import { MatchOnly } from 'tcompare'
const mo = new MatchOnly({ a: 1, b: 2 }, { expect: { a: Number } })
const diff: string = mo.print()
console.log(mo.match) // false
console.log(diff)
/*
--- expected
+++ actual
@@ -1,2 +1,3 @@
 Object {
+  "b": 2,
 }
*/
  • indent - String to indent each nested level. Defaults to ' '.## METHODS

Each method corresponds to an exported class. Except for format() (which returns a string), they all return a Result object. (That is, {diff:string, match:boolean}.)

  • format(object, [options]) - No comparisons performed. Just print out the object. Returns just the formatted string.
  • same(object, pattern, [options]) - Deep equivalence. Ensure that all items in the pattern are found in the object, and vice versa, matching loosely (so, for example 1 will match with '1').
  • strict(object, pattern, [options]) - Deep equality. Ensure that all items in the pattern are found in the object, and vice versa, matching strictly (so, for example 1 will not match with '1'). Objects must have the same constructors, and all fields will be matched recursively using the same strict test.
  • has(object, pattern, [options]) - Ensure that all items in the pattern are found in the object, but ignore additional items found in the object, matching loosely. Classes only need to match loosely, so a plain JavaScript object can be used to check for fields on a class instance.
  • hasStrict(object, pattern, [options]) - Ensure that all items in the pattern are found in the object, but ignore additional items found in the object, matching strictly. Constructors do not have to match between objects, but if constructor is set as an ownProperty on the pattern object, then it will be checked for strict equality.
  • match(object, pattern, [options]) - Verify that all items in pattern are found in object, and that they match in an extremely loose way. This is the loosest possible algorithm, allowing cases where we just want to verify that an object contains a few important properties. In summary:
    • If the object and pattern are loosely equal, then pass
    • If the object and the pattern are both Regular Expressions, Date objects or Buffers, then pass if they represent equivalent values.
    • If the pattern is a RegExp, cast object to a string, and test against the RegExp.
    • If both are Strings, pass if pattern appears in object. (
    • If pattern is a function, and object is an instance of that function, then pass. (This also applies to Symbol, Number, String, etc.)
    • If pattern and object are collections (object, map, set, array or iterable), then compare their contents. Each type of collection can only match its same type, with the exception of non-Set iterables (including arguments objects), which are cast to Arrays.
  • matchOnly(object, pattern, [options]) - Same comparison testing as match(), but will fail if the object has any properties that are not present in the pattern.
  • matchStrict(object, pattern, [options]) - Same comparison testing as match(), but will fail when two values are equivalent but not strictly equal. (That is, when a == b && !(a === b).)
  • matchOnlyStrict(object, pattern, [options]) - Same comparison testing as matchOnly(), but will fail when two values are equivalent but not strictly equal. (That is, when a == b && !(a === b).)

There are classes exported to correspond to each of these. All of these are instantiated like new Format(object, options). An expect option is required for all classes except Format. Call obj.print() on the resulting object to generate a diff. Once the diff (or format) is generated, it'll have a match boolean member.

Classes

The exported classes should usually not be used directly, and their implementation details are subject to change as needed between versions.

The class heirarchy is:

Format
+-- Same
    +-- Strict
    +-- Has
    |   +-- HasStrict (uses Strict.prototype.test)
    |   +-- Match
    |       +-- MatchStrict (fails if a==b && a!==b)
    +-- MatchOnly (uses Match.prototype.test)
    +-- MatchOnlyStrict (uses MatchStrict.prototype.test)

In order to compare or print an object, instantiate one of the classes, and call then the print() method, which will return the diff or formatted value. The match boolean property will be set after calling print(). If the objects match, then the returned diff will also be an empty string.

OPTIONS

FormatOptions type

Every method and class can take the following options.

  • sort - Set to true to sort object keys. This is important when serializing in a deterministic way.

  • style - Set to pretty for a very human-readable style of object printing. Set to js for a copy-and-paste friendly valid JavaScript output. Set to tight for a minimal white-space js format. Default is pretty. Example:

    // pretty style
    Object {
      "myMap": Map {
        Object {
          "a": 1,
        } => Object {
          "b": 2,
        }
      }
    }
    
    // js style
    {
      "myMap": new Map([
        [{
          "a": 1,
        }, {
          "b": 2,
        }]
      ])
    }
    
    // tight style
    {"myMap":new Map([[{"a":1,},{"b":2,}],]),}
    

    Note that tight is not suitable for comparisons, only formatting.

  • reactString - Represent and compare React elements as JSX strings. Only supported in the pretty formatting style. Enabled by default, set { reactString: false } in the options to disable it.

    When enabled, react elements are first compared as react JSX strings, and if the strings match, treated as equivalent, even if they would not otherwise be treated as a match as plain objects (for example, if children is set to 'hello' vs ['hello'], these are considered identical, because they result in the same JSX).

    If they do not match, then they are still considered a match if their plain object represenatations would be considered a match. So for example, <x a="b" /> would match <x a={/b|c/} /> for functions where strings can match against regular expressions.

  • bufferChunkSize - The number of bytes to show per line when printing long Buffer objects. Defaults to 32.

  • indent - String to indent each nested level. Defaults to ' '.

  • includeEnumerable - Set to true to walk over all enumerable properties of a given object when comparing or formatting, rather than the default of only showing enumerable own-properties. Note that calling getter functions may be hazardous, as they may trigger side-effects.

  • includeGetters - Set to true to walk over all enumerable getters on an object's prototype (but not from further down the prototype chain), in addition to own-properties. This is useful in cases where you want to compare or print an object with enumerable getters that return internal values in a read-only manner. Note that calling getter functions can be hazardous, as they may trigger side-effects.

SameOptions type

Comparison classes also take the following options.

  • expect - required. The pattern object to compare against.
  • diffContext - Optional, default 10. Number of lines of context to show in diff output.

Circular References

Circular references are displayed using YAML-like references, in order to determine which item is circularly referenced.

When doing comparisons, a pattern and object will be considered matching if they contain the same circularity. So, for example, if a pattern refers to itself, then an object should refer to itself as well.

const a = { list: [], b: {} }
a.list.push(a)
a.list.push(a.b)
a.b.a = a
console.log(format(a))

/*
&ref_1 Object {
  "list": Array [
    <*ref_1>,
    Object {
      "a": <*ref_1>,
    },
  ],
  "b": Object {
    "a": <*ref_1>,
  },
}
*/

Note that circular references are never going to be valid JavaScript, even when using the js style.

Caveat: Circularity Between Pattern and Object Gets Weird

It's possible to get strange output when an object and pattern refer to one another.

import { same } from 'tcompare'

const a = {}
a.o = a
const b = { o: a }

console.error(same(a, b).diff)

// produces this confusing output:
/*
--- expected
+++ actual
@@ -1,5 +1,3 @@
 &ref_1 Object {
-  "o": &ref_1 Object {
-    "o": <*ref_1>,
-  },
+  "o": <*ref_1>,
 }
*/

The more correct output would be something like:

--- expected
+++ actual
@@ -1,5 +1,3 @@
 &ref_1 Object {
-  "o": &ref_2 Object {
-    "o": <*ref_2>,
-  },
+  "o": <*ref_1>,
 }

However, this requires tracking IDs in a much more complicated way, being aware of whether the object is being read as an pattern object or test object when determining its reference ID.

Since this is a relatively unusual thing to happen, and only affects the output (but still properly detects whether it should be treated as a match or not), it will likely not be addressed.

Dependents (21)

Package Sidebar

Install

npm i tcompare

Weekly Downloads

101,700

Version

6.4.6

License

BlueOak-1.0.0

Unpacked Size

486 kB

Total Files

101

Last publish

Collaborators

  • isaacs