Skip to content

Commit b2a34e7

Browse files
committed
Update t.like to support Symbol keys and ignore non-enumerable properties
Fixes avajs#3208
1 parent 13a34fb commit b2a34e7

File tree

3 files changed

+25
-13
lines changed

3 files changed

+25
-13
lines changed

docs/03-assertions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Assert that `actual` is not deeply equal to `expected`. The inverse of `.deepEqu
141141

142142
Assert that `actual` is like `selector`. This is a variant of `.deepEqual()`, however `selector` does not need to have the same enumerable properties as `actual` does.
143143

144-
Instead AVA derives a *comparable* value from `actual`, recursively based on the shape of `selector`. This value is then compared to `selector` using `.deepEqual()`.
144+
Instead AVA derives a *comparable* value from `actual`, recursively based on the enumerable shape of `selector`. This value is then compared to `selector` using `.deepEqual()`.
145145

146146
Any values in `selector` that are not arrays or regular objects should be deeply equal to the corresponding values in `actual`.
147147

lib/like-selector.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
const isPrimitive = val => val === null || typeof val !== 'object';
1+
const isPrimitive = value => value === null || typeof value !== 'object';
22

33
export function isLikeSelector(selector) {
4-
if (isPrimitive(selector)) {
4+
// Require selector to be an array or plain object.
5+
if (
6+
isPrimitive(selector)
7+
|| (!Array.isArray(selector) && Reflect.getPrototypeOf(selector) !== Object.prototype)
8+
) {
59
return false;
610
}
711

8-
const keyCount = Reflect.ownKeys(selector).length;
9-
if (Array.isArray(selector)) {
10-
// In addition to `length`, require at at least one element.
11-
return keyCount > 1;
12-
} else {
13-
// Require a plain object with at least one property.
14-
return Reflect.getPrototypeOf(selector) === Object.prototype && keyCount > 0;
15-
}
12+
// Also require at least one enumerable property.
13+
const descriptors = Object.getOwnPropertyDescriptors(selector);
14+
return Reflect.ownKeys(descriptors).some(key => descriptors[key].enumerable === true);
1615
}
1716

1817
export const CIRCULAR_SELECTOR = new Error('Encountered a circular selector');
@@ -29,7 +28,9 @@ export function selectComparable(actual, selector, circular = new Set()) {
2928
}
3029

3130
const comparable = Array.isArray(selector) ? [] : {};
32-
for (const [key, subselector] of Object.entries(selector)) {
31+
const enumerableSubselectors = {...selector};
32+
for (const key of Reflect.ownKeys(enumerableSubselectors)) {
33+
const subselector = enumerableSubselectors[key];
3334
comparable[key] = isLikeSelector(subselector)
3435
? selectComparable(Reflect.get(actual, key), subselector, circular)
3536
: Reflect.get(actual, key);

test-tap/assert.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ test('.like()', t => {
720720
return assertions.like({xc: [circular, 'c']}, {xc: [circular, 'd']});
721721
});
722722

723-
failsWith(t, () => assertions.like({a: 'a'}, {}), {
723+
failsWith(t, () => assertions.like({a: 'a'}, Object.defineProperties({}, {ignored: {}})), {
724724
assertion: 'like',
725725
message: '`t.like()` selector must be a non-empty object',
726726
values: [{label: 'Called with:', formatted: '{}'}],
@@ -732,6 +732,15 @@ test('.like()', t => {
732732
values: [{label: 'Called with:', formatted: '\'bar\''}],
733733
});
734734

735+
passes(t, () => {
736+
const specimen = {[Symbol.toStringTag]: 'Custom', extra: true};
737+
const selector = Object.defineProperties(
738+
{[Symbol.toStringTag]: 'Custom'},
739+
{ignored: {value: true}},
740+
);
741+
return assertions.like(specimen, selector);
742+
});
743+
735744
failsWith(t, () => {
736745
const likePattern = {
737746
a: 'a',
@@ -767,9 +776,11 @@ test('.like()', t => {
767776

768777
passes(t, () => assertions.like([1, 2, 3], [1, 2, 3]));
769778
passes(t, () => assertions.like([1, 2, 3], [1, 2]));
779+
// eslint-disable-next-line no-sparse-arrays
770780
passes(t, () => assertions.like([1, 2, 3], [1, , 3]));
771781

772782
fails(t, () => assertions.like([1, 2, 3], [3, 2, 1]));
783+
// eslint-disable-next-line no-sparse-arrays
773784
fails(t, () => assertions.like([1, 2, 3], [1, , 4]));
774785
fails(t, () => assertions.like([1, 2], [1, 2, 3]));
775786

0 commit comments

Comments
 (0)