chaijs / deep-eql Goto Github PK
View Code? Open in Web Editor NEWImproved deep equality testing for Node.js and the browser.
License: MIT License
Improved deep equality testing for Node.js and the browser.
License: MIT License
The last line of entriesEqual
throws the following error in my test cases:
TypeError: Cannot convert object to primitive value
at Array.toString (native)
at Array.sort (native)
[...]
Here is how I can reproduce it with chai:
function getAnimals() {
const duck = Object.create(null);
duck.name = "duck";
const cat = Object.create(null);
cat.name = "cat";
return new Set([duck, cat]);
}
const actual = getAnimals();
const expected = new Set([{name: "duck"}, {name: "cat"}]);
assert.deepEqual(actual, expected);
The error is caused by the fact that the objects with a null prototype cannot be converted to strings implicitly, and the array sort function performs a lexicographical sort on strings so it first tries to convert them to strings.
(Tested on Node 9.4)
Since version 4.0.1 (67d704c / #81, CC @snewcomer and @keithamus), deep-eql compares non-enumerable symbols. I find this surprising and unintuitive, and it’s different from string keys, where only enumerable keys are compared:
> key = 'x';
> deepEql(Object.defineProperty({}, key, { value: 'x', enumerable: false }), Object.defineProperty({}, key, { value: 'y', enumerable: false }))
true
> key = Symbol.for('x');
> deepEql(Object.defineProperty({}, key, { value: 'x', enumerable: false }), Object.defineProperty({}, key, { value: 'y', enumerable: false }))
false
@meeber in chaijs/chai#1054 (comment) also only suggested that enumerable symbols should be compared, and I suggest changing deep-eql to only compare these.
When running browserify to bundle test files using chai, I am getting the following error:
Cannot find module './lib-cov/eql'
This is because index.js
has a conditional require that is not supported by browserify. There was a similar problem in chai itself that has been fixed, is it possible to apply the same fix to deep-eql too? See chaijs/chai#28.
The readme should note in the "Rules" section that referential equality is used to compare Error
objects.
So that we can be sure that the project doesn't break on those supported browsers in PRs.
This package (deep-eql) is used in chai latest version as a dependency.
deep-eql package has a dependency from "karma": "^0.13.22"
Using even the latest(v3.0.1) version of the deep-eql package I am getting the following error:
lerna ERR! execute Error: Command failed: npm install
lerna ERR! execute npm WARN EBADENGINE Unsupported engine {
lerna ERR! execute npm WARN EBADENGINE package: '[email protected]',
lerna ERR! execute npm WARN EBADENGINE required: { node: '0.10 || 0.12 || 4 || 5' },
lerna ERR! execute npm WARN EBADENGINE current: { node: 'v16.13.1', npm: '8.1.2' }
lerna ERR! execute npm WARN EBADENGINE }
Please see also the screenshot:
My suggestion is to update karma version to a newer one that supports a higher version of the nodejs.
I have an immutable data structure defined as follow:
let DataStructure = function (name, description, imageUrl, linkUrl) {
let _name = name;
let _description = description;
let _imageUrl = imageUrl;
let _linkUrl = linkUrl;
Object.defineProperty(this, 'name', {
get: () => _name,
});
Object.defineProperty(this, 'description', {
get: () => _description,
});
Object.defineProperty(this, 'imageUrl', {
get: () => _imageUrl,
});
Object.defineProperty(this, 'linkUrl', {
get: () => _linkUrl,
});
};
Comparing to object instances with different values always returns true when it should return false.
It seems like comparing anything that's a builtin that should have internal slots, that is only pretending to be that builtin, throws instead of returning false.
var maplikeSet = new Set();
Object.defineProperty(maplikeSet, 'constructor', { enumerable: false, value: Map });
deepEqual(maplikeSet, new Map());
this should return false, but instead, it throws:
TypeError: Method get Map.prototype.size called on incompatible receiver #<Map>
This bug is affecting v4 as well, so once fixed in v5, a backport would be appreciated.
I'd be happy to make a PR for both, if desired.
In some cases, it would make sense to use the weak equality ==
instead of the strong one ===
.
Take this example, where two URL parsers give different results :
{
pathvars: {
id: 42
}
}
vs
{
pathvars: {
id: "42"
}
}
I have no idea how complicated it would be to achieve internally. From the API point of view, I would add another function or a flag in the current one's arguments to enable the weak comparison.
Until recently, my team was using Node.js v18.13.0. Our CI system picked up Node.js v18.17.0 and started failing. Specifically, we started failing when using Chai to perform expect(…).to.deep.equal
on URLs.
After reading the changelog for v18.17.0, I see that Ada-based URLs were backported to Node.js 18 (see here).
Narrowing down some more, it seems there is a behavior change in Node.js 18.17.0 where Symbol(query)
is lazily set on URLs whenever searchParams
is accessed (see here). So, if you've never accessed that property of a URL, Symbol(query)
does not exist; however, once you have accessed that property, Symbol(query)
does exist. This leads to the following breakage (I'm demonstrating with Mocha):
const { expect } = require('chai')
describe('URL', () => {
it('succeeds', () => {
const url = new URL('foo://bar')
expect(url).to.deep.equal(new URL('foo://bar'))
})
// The following succeeded in Node.js < v18.17.0.
it('fails', () => {
const url = new URL('foo://bar')
void url.searchParams
expect(url).to.deep.equal(new URL('foo://bar'))
})
})
Maybe this is working as expected, but it was an unfortunate bug we hit. We have worked around it by changing our tests to no longer expect(…).to.deep.equal
on URLs.
Dear deep-eql maintainers,
Thank you for your contribution to the open-source community.
We've noticed that chai, a new maintainer, just published version 4.0.1 of deep-eql to npm.
As part of our efforts to fight software supply chain attacks, we would like to verify this release is known and intended, and not a result of an unauthorized activity.
Tagging @chaijs (publisher of the previous version).
This issue was automatically created by ChainAlert.
If you find this behavior legitimate, kindly close and ignore this issue. Read more
I stumbled on a very un-intuitive issue today trying to compare typescript classes (compiled to es5) with a POJO in a chai test. It turns out that deep-eql does not behave very well when comparing objects that defines properties using Object.defineProperty()
. Here is some sample typescript code to better explain what is going on:
class Foo {
public a = 1;
public get b() { return 2; }
}
const foo = new Foo();
// Notice how the property is not mentioned in the output, making
// both json exactly the same while still failing the comparison.
expect(foo).to.deep.equal({ a: 1 }); // fails => expect { a: 1 } to deep equal { a: 1 }
// This is a little counter intuitive based on the error but could be
// a perfectly valid scenario.
expect(foo).to.deep.equal({ a: 1, b: 2 }) // success
// Now, this is really not what I was expecting.
expect(foo).to.deep.equal({ a: 1, b: undefined }) // success
It seems that, as long as the key is somehow present in both objects, the value does not matter when comparing defined properties. I would expect either property values to be matching, or properties to be completely ignored in the comparison process, but not the current behavior.
Hello there, since component not maintained as 639. Did you want update readme.md?
we take github tarballs for debian packages and without tags manually finding a commit is hard
In work I am doing to solve #33, I noticed that this test will fail:
it('returns true if Comparator says so even on primitives (switch arg order)', function () {
var valueA = { a: 1 };
var valueB = {
a: new Matcher(function (value) {
return typeof value === 'number';
}),
};
assert(eql(valueA, valueB, { comparator: matcherComparator }) === true,
'eql({a:1}, {a:value => typeof value === "number"}, <comparator>) === true');
});
This only changes the argument order from the existing test. This is because only the left-hand side is checked as a primitive, when really both sides should be to ensure identical behavior.
I will fix this as part of a PR, but I believe we'll end up breaking null
comparator behavior.
Hello! This library looks like a great alternative for deep-equal
but there are some cases where I can't make it work:
console.log(deepEql(
new Set([null, '', 1, 5, 2, false]),
new Set([undefined, 0, '5', true, '2', '-000']),
{ comparator: loosely }
))
function isPrimitive (value) {
return value === null || typeof value !== 'object'
}
function loosely (a, b) {
// console.log('loosely', [typeof a, typeof b], [a, b])
if (!isPrimitive(a)) return null
if (!isPrimitive(b)) return null
const equal = a == b
// console.log('loosely primitives', [a, b], 'equal?', equal)
return equal
}
Currently I'm trying to test against several deep-equal
tests to ensure that is mostly compatible to not break anything in the lib switching. Probably it's a good idea as you could find bugs this way
Btw I took the loosely comaprator from here: #47 (comment)
Thanks!
I'm currently struggling to determine why two objects that look the same to me are being compared as being different.
Is there anyway for me to get information as to why the lib is returning false? either as part of the response or just logging would help.
thanks
Travis-CI is giving the following warning:
github remote: 40 of 60 requests remaining, resetting at Tue Jun 07 2016 04:29:01 GMT+0000 (UTC)
github remote: see https://github.com/component/guide/blob/master/changelogs/1.0.0.md#required-authentication for more information.
See https://github.com/component/guide/blob/master/changelogs/1.0.0.md#required-authentication
// @ts-ignore
import _equals from 'deep-eql';
let u1 = new URL(`https://bafybeicm5clh7fl4up4prnbfqksou6vsp5voth54rcsxhsjysimm3o77fq.on.fleek.co/`);
let u2 = new URL(`https://bafybeicm5clh7fl4up4prnbfqksou6vsp5voth54rcsxhsjysimm3o77fq.on.fleek.co/`);
console.dir(_equals(u1, u2))
I'm trying to write unit tests for my redux-saga functionality. I found out that there is an issue when trying to compare 2 identical objects containing a function. Basically it never equals:
const obj1 = {
test: function () {},
prop: 'prop',
};
const obj2 = {
test: function () {},
prop: 'prop',
};
I've seen example created using Tape framework where that comparison worked.
Hello, I've recently encounted a problem with chai's deep equal assertion when an object contains a property of type Temporal.PlainDate. For example the following assertion does not result in error while it's not the same date.
expect({ name: 'a', date: Temporal.PlainDate.from('2022-05-30') }).to.deep.equal({
name: 'a',
date: Temporal.PlainDate.from('2022-05-31'),
});
The reason is that in deep-eql
https://github.com/chaijs/deep-eql/blob/main/index.js#L197 the Temporal.PlainDate type is not managed since it's still in stage 3.
What's your opinion on making a PR to add the Temporal type comparison?
Otherwise do you have alternative solutions to make the comparison work?
I was curious how well chai would handle big sets and maps and checked a couple of things and also stumbled across this PR and I am sorry to say that these benchmarks are somewhat flawed.
The Node.js assert
module was never used for comparison because the benchmark required a in-existing function that would be replaced with deep-eql
in that case. The result is that the node and deep-eql results are actually both from the same library so they should be pretty much identical. However, that is not always the case and they partially differ by a big margin e.g.
buffer x 1,166,637 ops/sec ±6.09% (73 runs sampled)
buffer (node) x 611,182 ops/sec ±2.47% (65 runs sampled)
object literal x 160,121 ops/sec ±2.89% (81 runs sampled)
object literal (node) x 392,734 ops/sec ±2.38% (85 runs sampled)
My main concern with these benchmarks is though, that only very simply objects are used. Having nice numbers for those is awesome but they are normally not an issue. If a complex object is used though, this can be really bad.
// Set with 10000 numbers
const actual = new Set(Array(1e4).fill(1).map((_, i) => len - i - 1))
const expected = new Set(Array(1e4).fill(1).map((_, i) => i))
This time compared to Node.js 8.4 (this time for real)
set x 3.89 ops/sec ±1.26% (58 runs sampled)
set (node) x 1,387 ops/sec ±0.33% (138 runs sampled)
So Node.js is more then 350 times as fast in this case.
While checking Sets and Maps further I also noticed the following:
When using Sets twice the work is done that is needed in general because the key
and the value
are both the value when returned in a forEach
.
Worse though: comparing Sets or Maps with Objects does not even work properly!
const deepEql = require('./')
const a = new Set([{}, {a:5}])
const b = new Set([{a:5}, {}])
deepEql(a,b) // false
Sorting an array with objects is not possible and that is why this bug exists.
I suggest to have a look at a PR from me that would improve the situation and fix the bug as well nodejs/node#14258.
As chai currently does not have a loose equal comparison the algorithm should be pretty straight forward (otherwise there is quite some extra handling for that).
chai.expect(new Map([['a','a']])).to.deep.equal(new Map([['a','b']]))
// AssertionError: expected {} to deeply equal {}
chai.expect([['a','a']]).to.deep.equal([['a','b']])
// AssertionError: expected [ [ 'a', 'a' ] ] to deeply equal [ [ 'a', 'b' ] ]
As you can se it is not clear why it fails from the error message
I'm trying to test if my URLSearchParams
are correct with the to.deep.equal
option but it returns true
(is equal) when it shouldn't.
See the running code at https://stackblitz.com/edit/node-qx7qra?file=index.test.js
Let's assume that we're testing this function:
function getIncorrectParams() {
return new URLSearchParams({
from: '2020',
to: '2021',
ids: ['abc-1'],
});
}
If I write:
test('via string', () => {
expect(getIncorrectParams().toString()).not.to.equal(
new URLSearchParams({
from: '2020',
to: '2021',
ids: ['abc-1', 'abc-2'],
}).toString()
);
});
It correctly notices that the strings are not the same, good. But as soon as I remove the toString
and test with:
test('via deep equal', () => {
expect(getIncorrectParams()).not.to.deep.equal(
new URLSearchParams({
from: '2020',
to: '2021',
ids: ['abc-1', 'abc-2'],
})
);
});
It claims that the assertion failed because the two objects are "equal". Note if you actually go ahead and run the example, the debug output clearly shows that there are differences!
And the deep.equal
property works if I use "normal" object. See this example where I convert the URLSearchParams
to a "plain object" first:
test('deep equal plain objects', () => {
expect(Object.fromEntries(getIncorrectParams().entries())).not.to.deep.equal({
from: '2020',
to: '2021',
ids: ['abc-1', 'abc-2'],
});
});
Commits aren't triggering new releases. This is from the latest commit:
deep-eql@ semantic-release /home/travis/build/chaijs/deep-eql
semantic-release pre && npm publish && semantic-release postsemantic-release WARN invalid config loglevel="notice"
semantic-release ERR! pre Failed to determine new version.
semantic-release ERR! pre ENOCHANGE There are no relevant changes, so no new version is released.
Any ideas?
Is it possible to limit the deep equality comparison to a certain depth?
For example:
const a = {
foo: {
bar: {
baz: "qux"
}
}
}
const b = {
foo: { // compared deeply
bar: { // compared by identity (===)
baz: "qux"
}
}
}
// this passes, since 'a' and 'b' are structurally equal
expect(a).to.deep.equal(b);
// this would fail, since the 'bar' object would be compared by identity
expect(a).to.deep.depth(1).equal(b);
In my real-world scenario the bar
object would of course be a lot more complicated. Comparing these by identity is more than sufficient, and deep-equality even runs into issues due to circular references.
The dataview comparison in ExtensiveDeepEqualByType
compares the underlying buffers instead of comparing the dataviews themselves.
This means that the following dataviews are treated as different even if they represent the same data:
const shortBuffer = new Uint8Array([0x01, 0x01]);
const longBuffer = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
const view1 = new DataView(shortBuffer, 0, 1);
const view2 = new DataView(longBuffer, 1, 1);
Both views represent the slice [0x01]
but the equality test returns false because the underlying buffers are different.
Note that this behavior is not even fully consistent. If you want to treat buffer
as a public property that should match in both cases, then the offset should also matter. At the moment new DataView(longBuffer, 0, 2)
is equal to new DataView(2, 2)
because the buffers are the same (even if the data is different due to the different offsets).
Performing a deep equal on an uninitialized array doesn't behave as expected.
const myTestArray1 = [undefined, 'test']
const myTestArray2 = []
// myTestArray2[0] should be equal to undefined by default
myTestArray2[1] = 'test'
expect(myTestArray1).to.deep.equal(myTestArray2)
I would expect these two arrays to pass a deep equality check since javascript seems to default uninitialized values to undefined
I encountered the necessity to compare some generated data with test data, where those data sets include dynamically generated id values nested in different levels. So the goal was to test the object for equality by ignoring/ommiting those id_ keys.
E.G.
// test for equality but ignore the values of id
{
id: 1234,
a: 'foo',
b: {
id: 45678
c: 'bar'
}
}
I cerated my own - much simpler deepEql_ function
const deepEql = (a: unknown, b: unknown, omitKeys: string[] = []): boolean => {
if (a === b) return true;
omitKeys.forEach(key => delete a[key]);
omitKeys.forEach(key => delete b[key]);
if (typeof a !== 'object' || typeof b !== 'object' || a == null || b == null) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
// eslint-disable-next-line no-restricted-syntax
for (const key of keysA) {
if (!keysB.includes(key)) return false;
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
if (a[key].toString() !== b[key].toString()) return false;
} else if (!this.deepEql(a[key], b[key], omitKeys)) return false;
}
return true;
}
which works in my case, but I would love to see the possibility to specify which keys to exclude in the deep-eql api which seems obviously much more reliable than my solution
As @hildjj and @rocketraman point out in chaijs/chai#394, deep equality for Maps and Sets doesn't work (because their keys are not exposed like a normal object, which is what we check for).
x = new Map
x.set('foo', 'bar');
y = new Map
y.set('bar', 'baz');
chai.expect(y).to.deep.equal(x) // this passes, but should fail.
There's also not any particularly convenient ways to get all of the values out in a succinct way, except maybe the spread operator, but this is only available if transpiling ES6.
chai.expect([...y]).to.deep.equal([...x])
With Maps and Sets now a part of Node v4.0, deep-eql should definitely support Maps and Sets.
To implement this, we'd just need to add some extra conditions within around L56 to detect Maps and Sets (and WeakMaps and WeakSets), and call out to functions such as mapEqual
and setEqual
.
When done, we should PR upstream to chai proper.
Looks like #57 broke the build for IE 9 & 11 as well as Safari 10.
Apparently IE 9 & 11 add an enumerable description
property to Error
objects but not to TypeError
objects, causing one of the comparison tests to fail. The description
property appears to have the same value as message
. (If message
and description
is empty, then IE 9 & 11 also add an enumerable number
property, but that doesn't come into play with any of the tests.)
Apparently Safari 10 adds enumerable line
, column
, and sourceURL
properties to all Error
objects. This causes all of the tests to fail that expected two Error
objects to be equal, since the line
and/or column
properties will always be different between Error
objects.
What a pain. I'm not sure what the right solution is here.
Today, when reserving the objectEqual
function in this library while evaluating deep.equal
call stacks in a chai test harness, I observed that when comparing objectEqual()
from the default
branch in extensiveDeepEqualByType)
it sorts the keys.
Line 418 in 04d6da6
Line 419 in 04d6da6
Is it not more performant for large objects to just iterate over unsorted properties with something like hasOwnProperty
on the rightHandProperty
properties? If so, I can make a PR.
@keithamus Similar to the issue with Chai core recently, merging the latest PR failed to auto-release a new version (v4.0).
Simple reproduction:
$ node
> const eql = require('deep-eql')
undefined
> eql({}, null)
TypeError: Invalid value used as weak map key
at WeakMap.set (native)
at memoizeSet (/Users/samuelreed/git/forks/ajv/node_modules/deep-eql/index.js:92:17)
at extensiveDeepEqual (/Users/samuelreed/git/forks/ajv/node_modules/deep-eql/index.js:175:5)
at deepEqual (/Users/samuelreed/git/forks/ajv/node_modules/deep-eql/index.js:141:10)
at repl:1:1
at sigintHandlersWrap (vm.js:22:35)
at sigintHandlersWrap (vm.js:96:12)
at ContextifyScript.Script.runInThisContext (vm.js:21:12)
at REPLServer.defaultEval (repl.js:313:29)
at bound (domain.js:280:14)
$ node --version
v6.9.1
Hi, I came here because I am investigating double dependencies in my project.
├─┬ [email protected]
│ ├── [email protected]
│ ├─┬ [email protected]
│ │ └── [email protected]
│ └── [email protected]
As you can see type-detect
is included twice because deep-eql
still depends on 0.x. I see you already upgraded it to 1.0.0, but it hasn't been published to NPM. Could you do that please?
Due to the way npm hoisting works, two deeply equal instances of the same ES6 class loaded from different locations (node_modules) return false.
Maybe instead of referential equality for functions, Function.toString
will be a better way to go?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.