Skip to content

Commit f7d716c

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

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
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

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
const isPrimitive = val => val === null || typeof val !== '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

+10-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',

0 commit comments

Comments
 (0)