Skip to content

Commit 4fc75ca

Browse files
start implementing t.unorderedEqual()
1 parent e58f466 commit 4fc75ca

File tree

9 files changed

+241
-4
lines changed

9 files changed

+241
-4
lines changed

docs/03-assertions.md

+4
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ Assert that `actual` is deeply equal to `expected`. See [Concordance](https://gi
137137

138138
Assert that `actual` is not deeply equal to `expected`. The inverse of `.deepEqual()`. Returns a boolean indicating whether the assertion passed.
139139

140+
### `.unorderedEqual(actual, expected, message?)`
141+
142+
Assert that all values in `actual` are in `expected`, returning a boolean indicating whether the assertion passed.
143+
140144
### `.like(actual, selector, message?)`
141145

142146
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.

lib/assert.js

+96
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import isPromise from 'is-promise';
66
import concordanceOptions from './concordance-options.js';
77
import {CIRCULAR_SELECTOR, isLikeSelector, selectComparable} from './like-selector.js';
88
import {SnapshotError, VersionMismatchError} from './snapshot-manager.js';
9+
import {checkValueForUnorderedEqual} from './unordered-equal.js';
910

1011
function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
1112
options = {...options, ...concordanceOptions};
@@ -958,5 +959,100 @@ export class Assertions {
958959
pass();
959960
return true;
960961
});
962+
963+
this.unorderedEqual = withSkip((actual, expected, message) => {
964+
if (!checkMessage('unorderedEqual', message)) {
965+
return false;
966+
}
967+
968+
const actualInfo = checkValueForUnorderedEqual(actual);
969+
970+
if (!actualInfo.isValid) {
971+
fail(new AssertionError({
972+
assertion: 'unorderedEqual',
973+
improperUsage: true,
974+
message: '`t.unorderedEqual` only compares Maps, Sets, and arrays',
975+
values: [formatWithLabel('Called with:', actual)],
976+
}));
977+
return false;
978+
}
979+
980+
const expectedInfo = checkValueForUnorderedEqual(expected);
981+
982+
if (!expectedInfo.isValid) {
983+
fail(new AssertionError({
984+
assertion: 'unorderedEqual',
985+
improperUsage: true,
986+
message: '`t.unorderedEqual` only compares Maps, Sets, and arrays',
987+
values: [formatWithLabel('Called with:', expected)],
988+
}));
989+
return false;
990+
}
991+
992+
if (actualInfo.size !== expectedInfo.size) {
993+
fail(new AssertionError({
994+
assertion: 'unorderedEqual',
995+
message: 'size must be equal',
996+
}));
997+
return false;
998+
}
999+
1000+
if (actualInfo.type === 'map') {
1001+
if (expectedInfo.type !== 'map') {
1002+
fail(new AssertionError({
1003+
assertion: 'unorderedEqual',
1004+
message: 'both must be maps',
1005+
}));
1006+
return false;
1007+
}
1008+
1009+
const comparedKeysResult = concordance.compare(actual.keys, expected.keys, concordanceOptions);
1010+
if (!comparedKeysResult.pass) {
1011+
fail(new AssertionError({
1012+
assertion: 'unorderedEqual',
1013+
message: 'keys must be equal',
1014+
}));
1015+
return false;
1016+
}
1017+
1018+
for (const [key, value] of actual.entries()) {
1019+
const result = concordance.compare(value, expected.get(key), concordanceOptions);
1020+
if (!result.pass) {
1021+
fail(new AssertionError({
1022+
assertion: 'unorderedEqual',
1023+
message: 'all values must be equal - map',
1024+
}));
1025+
return false;
1026+
}
1027+
}
1028+
1029+
pass();
1030+
return true;
1031+
}
1032+
1033+
if (expectedInfo.type === 'map') {
1034+
fail(new AssertionError({
1035+
assertion: 'unorderedEqual',
1036+
message: 'both must be set-likes',
1037+
}));
1038+
return false;
1039+
}
1040+
1041+
const setActual = actualInfo.type === 'set' ? actual : new Set(actual);
1042+
const setExpected = expectedInfo.type === 'set' ? expected : new Set(expected);
1043+
1044+
for (const value of setActual) {
1045+
if (!setExpected.has(value)) {
1046+
fail(new AssertionError({
1047+
assertion: 'unorderedEqual',
1048+
message: 'all values must be equal - set',
1049+
}));
1050+
return false;
1051+
}
1052+
}
1053+
1054+
pass();
1055+
return true;
1056+
});
9611057
}
9621058
}

lib/unordered-equal.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const checkValueForUnorderedEqual = value => {
2+
/* eslint-disable indent, operator-linebreak, unicorn/no-nested-ternary */
3+
const type = (
4+
value instanceof Map ? 'map' :
5+
value instanceof Set ? 'set' :
6+
Array.isArray(value) ? 'array' :
7+
'invalid'
8+
);
9+
/* eslint-enable indent, operator-linebreak, unicorn/no-nested-ternary */
10+
11+
if (type === 'invalid') {
12+
return {isValid: false};
13+
}
14+
15+
return {
16+
isValid: true,
17+
type,
18+
size: type === 'array'
19+
? value.length
20+
: value.size,
21+
};
22+
};

test-tap/assert.js

+95
Original file line numberDiff line numberDiff line change
@@ -1809,3 +1809,98 @@ test('.assert()', t => {
18091809

18101810
t.end();
18111811
});
1812+
1813+
test('.unorderedEqual()', t => {
1814+
passes(t, () => assertions.unorderedEqual([1, 2, 3], [2, 3, 1]));
1815+
1816+
passes(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), new Set([2, 3, 1])));
1817+
1818+
passes(t, () => assertions.unorderedEqual([1, 2, 3], new Set([2, 3, 1])));
1819+
1820+
passes(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), [2, 3, 1]));
1821+
1822+
passes(t, () => assertions.unorderedEqual(
1823+
new Map([['a', 1], ['b', 2], ['c', 3]]),
1824+
new Map([['b', 2], ['c', 3], ['a', 1]]),
1825+
));
1826+
1827+
// Types must match
1828+
1829+
fails(t, () => assertions.unorderedEqual('foo', [1, 2, 3]));
1830+
1831+
fails(t, () => assertions.unorderedEqual([1, 2, 3], 'foo'));
1832+
1833+
// Sizes must match
1834+
1835+
fails(t, () => assertions.unorderedEqual([1, 2, 3], [1, 2, 3, 4]));
1836+
1837+
fails(t, () => assertions.unorderedEqual([1, 2, 3, 4], [1, 2, 3]));
1838+
1839+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), new Set([1, 2, 3, 4])));
1840+
1841+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3, 4]), new Set([1, 2, 3])));
1842+
1843+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), [1, 2, 3, 4]));
1844+
1845+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3, 4]), [1, 2, 3]));
1846+
1847+
fails(t, () => assertions.unorderedEqual([1, 2, 3], new Set([1, 2, 3, 4])));
1848+
1849+
fails(t, () => assertions.unorderedEqual([1, 2, 3, 4], new Set([1, 2, 3])));
1850+
1851+
fails(t, () => assertions.unorderedEqual(
1852+
new Map([['a', 1], ['b', 2], ['c', 3]]),
1853+
new Map([['a', 1], ['b', 2]])),
1854+
);
1855+
1856+
fails(t, () => assertions.unorderedEqual(
1857+
new Map([['a', 1], ['b', 2]]),
1858+
new Map([['a', 1], ['b', 2], ['c', 3]])),
1859+
);
1860+
1861+
// Keys must match - maps
1862+
1863+
fails(t, () => assertions.unorderedEqual(
1864+
new Map([['a', 1], ['b', 2], ['c', 3]]),
1865+
new Map([['a', 1], ['d', 2], ['c', 3]])),
1866+
);
1867+
1868+
fails(t, () => assertions.unorderedEqual(
1869+
new Map([['a', 1], ['d', 2], ['c', 3]]),
1870+
new Map([['a', 1], ['b', 2], ['c', 3]])),
1871+
);
1872+
1873+
// Values must match - maps
1874+
1875+
fails(t, () => assertions.unorderedEqual(
1876+
new Map([['a', 1], ['b', 2], ['c', 3]]),
1877+
new Map([['a', 1], ['b', 4], ['c', 3]])),
1878+
);
1879+
1880+
fails(t, () => assertions.unorderedEqual(
1881+
new Map([['a', 1], ['b', 4], ['c', 3]]),
1882+
new Map([['a', 1], ['b', 2], ['c', 3]])),
1883+
);
1884+
1885+
// Values must match - sets
1886+
1887+
fails(t, () => assertions.unorderedEqual([1, 2, 3], [1, 2, 4]));
1888+
1889+
fails(t, () => assertions.unorderedEqual([1, 2, 4], [1, 2, 3]));
1890+
1891+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), new Set([1, 2, 4])));
1892+
1893+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 4]), new Set([1, 2, 3])));
1894+
1895+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 3]), [1, 2, 4]));
1896+
1897+
fails(t, () => assertions.unorderedEqual(new Set([1, 2, 4]), [1, 2, 3]));
1898+
1899+
fails(t, () => assertions.unorderedEqual([1, 2, 3], new Set([1, 2, 4])));
1900+
1901+
fails(t, () => assertions.unorderedEqual([1, 2, 4], new Set([1, 2, 3])));
1902+
1903+
// TODO: check error messages
1904+
1905+
t.end();
1906+
});

test-tap/test.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,12 @@ test('skipped assertions count towards the plan', t => {
270270
a.false.skip(false);
271271
a.regex.skip('foo', /foo/);
272272
a.notRegex.skip('bar', /foo/);
273+
a.unorderedEqual.skip([1, 2, 3], [2, 3, 1]);
273274
});
274275
return instance.run().then(result => {
275276
t.equal(result.passed, true);
276-
t.equal(instance.planCount, 16);
277-
t.equal(instance.assertCount, 16);
277+
t.equal(instance.planCount, 17);
278+
t.equal(instance.assertCount, 17);
278279
});
279280
});
280281

@@ -299,11 +300,12 @@ test('assertion.skip() is bound', t => {
299300
(a.false.skip)(false);
300301
(a.regex.skip)('foo', /foo/);
301302
(a.notRegex.skip)('bar', /foo/);
303+
(a.unorderedEqual.skip)([1, 2, 3], [2, 3, 1]);
302304
});
303305
return instance.run().then(result => {
304306
t.equal(result.passed, true);
305-
t.equal(instance.planCount, 16);
306-
t.equal(instance.assertCount, 16);
307+
t.equal(instance.planCount, 17);
308+
t.equal(instance.assertCount, 17);
307309
});
308310
});
309311

@@ -488,6 +490,7 @@ test('assertions are bound', t =>
488490
(a.false)(false);
489491
(a.regex)('foo', /foo/);
490492
(a.notRegex)('bar', /foo/);
493+
(a.unorderedEquals)([1, 2, 3], [2, 3, 1]);
491494
}).run().then(result => {
492495
t.ok(result.passed);
493496
}),

test/assertions/fixtures/happy-path.js

+1
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ test(passes, 'false', false);
4343
test(passes, 'regex', 'foo', /foo/);
4444
test(passes, 'notRegex', 'bar', /foo/);
4545
test(passes, 'assert', 1);
46+
test(passes, 'unorderedEqual', [1, 2, 3], [2, 3, 1]);

test/assertions/snapshots/test.js.md

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Generated by [AVA](https://avajs.dev).
2727
't.throwsAsync() passes',
2828
't.true(true) passes',
2929
't.truthy(1) passes',
30+
't.unorderedEqual([1,2,3], [2,3,1]) passes',
3031
]
3132

3233
## throws requires native errors
34 Bytes
Binary file not shown.

types/assertions.d.cts

+15
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,18 @@ export type TruthyAssertion = {
342342
/** Skip this assertion. */
343343
skip(actual: any, message?: string): void;
344344
};
345+
346+
// TODO: limit to Map | Set | Array
347+
export type UnorderedEqualAssertion = {
348+
/** Assert that all values in `actual` are in `expected`, returning a boolean indicating whether the assertion passed. */
349+
<Actual, Expected extends Actual>(actual: Actual, expected: Expected, message?: string): actual is Expected;
350+
351+
/** Assert that all values in `actual` are in `expected`, returning a boolean indicating whether the assertion passed. */
352+
<Actual extends Expected, Expected>(actual: Actual, expected: Expected, message?: string): expected is Actual;
353+
354+
/** Assert that all values in `actual` are in `expected`, returning a boolean indicating whether the assertion passed. */
355+
<Actual, Expected>(actual: Actual, expected: Expected, message?: string): boolean;
356+
357+
/** Skip this assertion. */
358+
skip(actual: any, expected: any, message?: string): void;
359+
};

0 commit comments

Comments
 (0)