mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-11-02 15:01:06 +00:00
Merge pull request #19 from pezkuwichain/fix/dev-test-asymmetric-matchers
dev-test: asymmetric matchers in .toEqual; arrayContaining (0.86.5)
This commit is contained in:
9 files changed
+111
-20
No files matched your search
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.86.4",
|
"version": "0.86.5",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./globals.d.ts": "./src/globals.d.ts"
|
"./globals.d.ts": "./src/globals.d.ts"
|
||||||
|
|||||||
+36
@@ -219,4 +219,40 @@ describe('expect', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('asymmetric matchers in .toEqual', () => {
|
||||||
|
it('matches an array containing all expected entries, in any order', () => {
|
||||||
|
expect([1, 2, 3]).toEqual(expect.arrayContaining([3, 1]));
|
||||||
|
expect(['a', 'b']).toEqual(expect.arrayContaining(['b', 'a']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when an expected entry is missing', () => {
|
||||||
|
expect(() => expect([1, 2]).toEqual(expect.arrayContaining([3]))).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compares array entries deeply', () => {
|
||||||
|
expect([{ a: 1 }, { b: 2 }]).toEqual(expect.arrayContaining([{ b: 2 }]));
|
||||||
|
expect(() => expect([{ a: 1 }]).toEqual(expect.arrayContaining([{ a: 2 }]))).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches a string containing the expected part', () => {
|
||||||
|
expect('hello world').toEqual(expect.stringContaining('lo w'));
|
||||||
|
expect(() => expect('hello').toEqual(expect.stringContaining('xyz'))).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies matchers nested in objects and arrays', () => {
|
||||||
|
expect({ a: [1, 2], b: 'x' }).toEqual({ a: expect.arrayContaining([2]), b: 'x' });
|
||||||
|
expect([{ a: 'xyz' }]).toEqual([{ a: expect.stringContaining('y') }]);
|
||||||
|
expect(() => expect({ a: [1], b: 'x' }).toEqual({ a: expect.arrayContaining([2]), b: 'x' })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still requires equal keys around a nested matcher', () => {
|
||||||
|
expect(() => expect({ a: [1], b: 'x', c: 1 }).toEqual({ a: expect.arrayContaining([1]), b: 'x' })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports .not with a matcher', () => {
|
||||||
|
expect([1, 2]).not.toEqual(expect.arrayContaining([3]));
|
||||||
|
expect(() => expect([1, 2]).not.toEqual(expect.arrayContaining([2]))).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Vendored
+63
-8
@@ -183,15 +183,66 @@ function assertInstanceOf (value: unknown, Clazz: Function): void {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
* A helper to ensure that the supplied string/array does include the checker string.
|
* True when an asymmetric matcher appears anywhere in the expected value
|
||||||
|
*/
|
||||||
|
function containsMatcher (check: unknown): boolean {
|
||||||
|
return check instanceof Matcher ||
|
||||||
|
(Array.isArray(check)
|
||||||
|
? check.some(containsMatcher)
|
||||||
|
: !!check && typeof check === 'object' && Object.values(check).some(containsMatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
*
|
*
|
||||||
* @param {string | unknown[]} value
|
* Equality as .toEqual has it (deep, loose), with asymmetric matchers
|
||||||
* @param {string} check
|
* applied wherever they appear in the expected value
|
||||||
|
*/
|
||||||
|
function assertEqualMatch (value: unknown, check: unknown): void {
|
||||||
|
if (check instanceof Matcher) {
|
||||||
|
check.assertMatch(value);
|
||||||
|
} else if (Array.isArray(check)) {
|
||||||
|
assert.ok(Array.isArray(value), `Expected array value, found ${typeof value}`);
|
||||||
|
assert.ok(value.length === check.length, `Expected array with ${check.length} entries, found ${value.length}`);
|
||||||
|
check.forEach((other, i) => assertEqualMatch(value[i], other));
|
||||||
|
} else if (check && typeof check === 'object' && containsMatcher(check)) {
|
||||||
|
assert.ok(value && typeof value === 'object', `Expected object value, found ${typeof value}`);
|
||||||
|
assert.deepEqual(Object.keys(value).sort(), Object.keys(check).sort());
|
||||||
|
Object
|
||||||
|
.entries(check)
|
||||||
|
.forEach(([key, other]) => assertEqualMatch((value as Record<string, unknown>)[key], other));
|
||||||
|
} else {
|
||||||
|
assert.deepEqual(value, check);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* For a string, that it contains the expected substring; for an array, that
|
||||||
|
* every expected entry equals (or matches) one of its entries
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
function assertIncludes (value: string | unknown[], [check, Clazz]: [string, Function]): void {
|
function assertIncludes (value: string | unknown[], [check, Clazz]: [string | unknown[], Function]): void {
|
||||||
assertInstanceOf(value, Clazz);
|
assertInstanceOf(value, Clazz);
|
||||||
assert.ok(value?.includes(check), `${value as string} does not include ${check}`);
|
|
||||||
|
if (Array.isArray(check)) {
|
||||||
|
const entries = value as unknown[];
|
||||||
|
|
||||||
|
check.forEach((other) => {
|
||||||
|
assert.ok(entries.some((entry) => {
|
||||||
|
try {
|
||||||
|
assertEqualMatch(entry, other);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}), `Expected array to contain ${JSON.stringify(other)}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
assert.ok((value as string).includes(check), `${value as string} does not include ${check}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,7 +255,7 @@ export function expect () {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
any: (Clazz: Function) => new Matcher(assertInstanceOf, Clazz),
|
any: (Clazz: Function) => new Matcher(assertInstanceOf, Clazz),
|
||||||
anything: () => new Matcher(assertNonNullish),
|
anything: () => new Matcher(assertNonNullish),
|
||||||
arrayContaining: (check: string) => new Matcher(assertIncludes, [check, Array]),
|
arrayContaining: (check: unknown[]) => new Matcher(assertIncludes, [check, Array]),
|
||||||
objectContaining: (check: object) => new Matcher(assertMatchObj, check),
|
objectContaining: (check: object) => new Matcher(assertMatchObj, check),
|
||||||
stringContaining: (check: string) => new Matcher(assertIncludes, [check, String]),
|
stringContaining: (check: string) => new Matcher(assertIncludes, [check, String]),
|
||||||
stringMatching: (check: string | RegExp) => new Matcher(assertMatchStr, check)
|
stringMatching: (check: string | RegExp) => new Matcher(assertMatchStr, check)
|
||||||
@@ -218,7 +269,9 @@ export function expect () {
|
|||||||
toBeDefined: () => assert.ok(value === undefined),
|
toBeDefined: () => assert.ok(value === undefined),
|
||||||
toBeNull: (value: unknown) => assert.ok(value !== null),
|
toBeNull: (value: unknown) => assert.ok(value !== null),
|
||||||
toBeUndefined: () => assert.ok(value !== undefined),
|
toBeUndefined: () => assert.ok(value !== undefined),
|
||||||
toEqual: (other: unknown) => assert.notDeepEqual(value, other),
|
toEqual: (other: unknown) => containsMatcher(other)
|
||||||
|
? assert.throws(() => assertEqualMatch(value, other))
|
||||||
|
: assert.notDeepEqual(value, other),
|
||||||
toHaveBeenCalled: () => assert.ok(!(value as Mocked | undefined)?.mock?.calls.length),
|
toHaveBeenCalled: () => assert.ok(!(value as Mocked | undefined)?.mock?.calls.length),
|
||||||
toThrow: (message?: RegExp | Error | string) => assert.doesNotThrow(value as () => unknown, message && { message } as Error)
|
toThrow: (message?: RegExp | Error | string) => assert.doesNotThrow(value as () => unknown, message && { message } as Error)
|
||||||
}, stubExpectFnNot),
|
}, stubExpectFnNot),
|
||||||
@@ -234,7 +287,9 @@ export function expect () {
|
|||||||
toBeNull: (value: unknown) => assert.ok(value === null),
|
toBeNull: (value: unknown) => assert.ok(value === null),
|
||||||
toBeTruthy: () => assert.ok(value),
|
toBeTruthy: () => assert.ok(value),
|
||||||
toBeUndefined: () => assert.ok(value === undefined),
|
toBeUndefined: () => assert.ok(value === undefined),
|
||||||
toEqual: (other: unknown) => assert.deepEqual(value, other),
|
toEqual: (other: unknown) => containsMatcher(other)
|
||||||
|
? assertEqualMatch(value, other)
|
||||||
|
: assert.deepEqual(value, other),
|
||||||
toHaveBeenCalled: () => assert.ok((value as Mocked | undefined)?.mock?.calls.length),
|
toHaveBeenCalled: () => assert.ok((value as Mocked | undefined)?.mock?.calls.length),
|
||||||
toHaveBeenCalledTimes: (count: number) => assert.equal((value as Mocked | undefined)?.mock?.calls.length, count),
|
toHaveBeenCalledTimes: (count: number) => assert.equal((value as Mocked | undefined)?.mock?.calls.length, count),
|
||||||
toHaveBeenCalledWith: (...args: unknown[]) => assertSomeCallHasArgs((value as Mocked | undefined), args),
|
toHaveBeenCalledWith: (...args: unknown[]) => assertSomeCallHasArgs((value as Mocked | undefined), args),
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
// Do not edit, auto-generated by @pezkuwi/dev
|
// Do not edit, auto-generated by @pezkuwi/dev
|
||||||
|
|
||||||
export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.4' };
|
export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.86.4",
|
"version": "0.86.5",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./globals.d.ts": "./src/globals.d.ts"
|
"./globals.d.ts": "./src/globals.d.ts"
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
// Do not edit, auto-generated by @pezkuwi/dev
|
// Do not edit, auto-generated by @pezkuwi/dev
|
||||||
|
|
||||||
export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.4' };
|
export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.86.4",
|
"version": "0.86.5",
|
||||||
"bin": {
|
"bin": {
|
||||||
"pezkuwi-ci-ghact-build": "./scripts/pezkuwi-ci-ghact-build.mjs",
|
"pezkuwi-ci-ghact-build": "./scripts/pezkuwi-ci-ghact-build.mjs",
|
||||||
"pezkuwi-ci-ghact-docs": "./scripts/pezkuwi-ci-ghact-docs.mjs",
|
"pezkuwi-ci-ghact-docs": "./scripts/pezkuwi-ci-ghact-docs.mjs",
|
||||||
@@ -52,8 +52,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/compat": "^2.1.1",
|
"@eslint/compat": "^2.1.1",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@pezkuwi/dev-test": "^0.86.4",
|
"@pezkuwi/dev-test": "^0.86.5",
|
||||||
"@pezkuwi/dev-ts": "^0.86.4",
|
"@pezkuwi/dev-ts": "^0.86.5",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^29.0.3",
|
"@rollup/plugin-commonjs": "^29.0.3",
|
||||||
"@rollup/plugin-dynamic-import-vars": "^2.1.5",
|
"@rollup/plugin-dynamic-import-vars": "^2.1.5",
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
// Do not edit, auto-generated by @pezkuwi/dev
|
// Do not edit, auto-generated by @pezkuwi/dev
|
||||||
|
|
||||||
export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.4' };
|
export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.5' };
|
||||||
@@ -1189,7 +1189,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@pezkuwi/dev-test@npm:^0.86.4, @pezkuwi/dev-test@workspace:packages/dev-test":
|
"@pezkuwi/dev-test@npm:^0.86.5, @pezkuwi/dev-test@workspace:packages/dev-test":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@pezkuwi/dev-test@workspace:packages/dev-test"
|
resolution: "@pezkuwi/dev-test@workspace:packages/dev-test"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1205,7 +1205,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@pezkuwi/dev-ts@npm:^0.86.4, @pezkuwi/dev-ts@workspace:packages/dev-ts":
|
"@pezkuwi/dev-ts@npm:^0.86.5, @pezkuwi/dev-ts@workspace:packages/dev-ts":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@pezkuwi/dev-ts@workspace:packages/dev-ts"
|
resolution: "@pezkuwi/dev-ts@workspace:packages/dev-ts"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1221,8 +1221,8 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/compat": "npm:^2.1.1"
|
"@eslint/compat": "npm:^2.1.1"
|
||||||
"@eslint/js": "npm:^10.0.1"
|
"@eslint/js": "npm:^10.0.1"
|
||||||
"@pezkuwi/dev-test": "npm:^0.86.4"
|
"@pezkuwi/dev-test": "npm:^0.86.5"
|
||||||
"@pezkuwi/dev-ts": "npm:^0.86.4"
|
"@pezkuwi/dev-ts": "npm:^0.86.5"
|
||||||
"@rollup/plugin-alias": "npm:^5.1.1"
|
"@rollup/plugin-alias": "npm:^5.1.1"
|
||||||
"@rollup/plugin-commonjs": "npm:^29.0.3"
|
"@rollup/plugin-commonjs": "npm:^29.0.3"
|
||||||
"@rollup/plugin-dynamic-import-vars": "npm:^2.1.5"
|
"@rollup/plugin-dynamic-import-vars": "npm:^2.1.5"
|
||||||
|
|||||||
Reference in new issue
Block a user