feat(test runner): expect.extendImmutable
This commit is contained in:
parent
6763d5ab6b
commit
2ca4a01af2
|
|
@ -61,6 +61,7 @@ import {
|
||||||
import { zones } from 'playwright-core/lib/utils';
|
import { zones } from 'playwright-core/lib/utils';
|
||||||
import { TestInfoImpl } from '../worker/testInfo';
|
import { TestInfoImpl } from '../worker/testInfo';
|
||||||
import { ExpectError, isExpectError } from './matcherHint';
|
import { ExpectError, isExpectError } from './matcherHint';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
// #region
|
// #region
|
||||||
// Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts
|
// Mirrored from https://github.com/facebook/jest/blob/f13abff8df9a0e1148baf3584bcde6d1b479edc7/packages/expect/src/print.ts
|
||||||
|
|
@ -104,33 +105,16 @@ export const printReceivedStringContainExpectedResult = (
|
||||||
|
|
||||||
type ExpectMessage = string | { message?: string };
|
type ExpectMessage = string | { message?: string };
|
||||||
|
|
||||||
function createMatchers(actual: unknown, info: ExpectMetaInfo): any {
|
function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[]): any {
|
||||||
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info));
|
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, prefix));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExpect(info: ExpectMetaInfo) {
|
function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
|
||||||
const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
|
|
||||||
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
|
|
||||||
const [actual, messageOrOptions] = argumentsList;
|
|
||||||
const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
|
|
||||||
const newInfo = { ...info, message };
|
|
||||||
if (newInfo.isPoll) {
|
|
||||||
if (typeof actual !== 'function')
|
|
||||||
throw new Error('`expect.poll()` accepts only function as a first argument');
|
|
||||||
newInfo.generator = actual as any;
|
|
||||||
}
|
|
||||||
return createMatchers(actual, newInfo);
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function(target: any, property: string) {
|
function extend(matchers: any, qualifier: string) {
|
||||||
if (property === 'configure')
|
|
||||||
return configure;
|
|
||||||
|
|
||||||
if (property === 'extend') {
|
|
||||||
return (matchers: any) => {
|
|
||||||
const wrappedMatchers: any = {};
|
const wrappedMatchers: any = {};
|
||||||
for (const [name, matcher] of Object.entries(matchers)) {
|
for (const [name, matcher] of Object.entries(matchers)) {
|
||||||
wrappedMatchers[name] = function(...args: any[]) {
|
wrappedMatchers[qualifier + name] = function(...args: any[]) {
|
||||||
const { isNot, promise, utils } = this;
|
const { isNot, promise, utils } = this;
|
||||||
const newThis: ExpectMatcherState = {
|
const newThis: ExpectMatcherState = {
|
||||||
isNot,
|
isNot,
|
||||||
|
|
@ -143,10 +127,41 @@ function createExpect(info: ExpectMetaInfo) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
expectLibrary.extend(wrappedMatchers);
|
expectLibrary.extend(wrappedMatchers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
|
||||||
|
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
|
||||||
|
const [actual, messageOrOptions] = argumentsList;
|
||||||
|
const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
|
||||||
|
const newInfo = { ...info, message };
|
||||||
|
if (newInfo.isPoll) {
|
||||||
|
if (typeof actual !== 'function')
|
||||||
|
throw new Error('`expect.poll()` accepts only function as a first argument');
|
||||||
|
newInfo.generator = actual as any;
|
||||||
|
}
|
||||||
|
return createMatchers(actual, newInfo, prefix);
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(target: any, property: string) {
|
||||||
|
if (property === 'configure')
|
||||||
|
return configure;
|
||||||
|
|
||||||
|
if (property === 'extend') {
|
||||||
|
return (matchers: any) => {
|
||||||
|
extend(matchers, '');
|
||||||
return expectInstance;
|
return expectInstance;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (property === 'extendImmutable') {
|
||||||
|
return (matchers: any) => {
|
||||||
|
const key = randomUUID();
|
||||||
|
const qualifier = [...prefix, key];
|
||||||
|
extend(matchers, `${qualifier.join(':')}$`);
|
||||||
|
return createExpect(info, qualifier);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (property === 'soft') {
|
if (property === 'soft') {
|
||||||
return (actual: unknown, messageOrOptions?: ExpectMessage) => {
|
return (actual: unknown, messageOrOptions?: ExpectMessage) => {
|
||||||
return configure({ soft: true })(actual, messageOrOptions) as any;
|
return configure({ soft: true })(actual, messageOrOptions) as any;
|
||||||
|
|
@ -241,13 +256,26 @@ type ExpectMetaInfo = {
|
||||||
|
|
||||||
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
||||||
private _info: ExpectMetaInfo;
|
private _info: ExpectMetaInfo;
|
||||||
|
private _prefix: string[];
|
||||||
|
|
||||||
constructor(info: ExpectMetaInfo) {
|
constructor(info: ExpectMetaInfo, prefix: string[]) {
|
||||||
this._info = { ...info };
|
this._info = { ...info };
|
||||||
|
this._prefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(target: Object, matcherName: string | symbol, receiver: any): any {
|
get(target: Object, matcherName: string | symbol, receiver: any): any {
|
||||||
let matcher = Reflect.get(target, matcherName, receiver);
|
let matcher = Reflect.get(target, matcherName, receiver);
|
||||||
|
|
||||||
|
if (typeof matcherName === 'string') {
|
||||||
|
for (let i = this._prefix.length; i > 0; i--) {
|
||||||
|
const qualifiedName = `${this._prefix.slice(0, i).join(':')}$${matcherName}`;
|
||||||
|
if (Reflect.has(target, qualifiedName)) {
|
||||||
|
matcher = Reflect.get(target, qualifiedName, receiver);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof matcherName !== 'string')
|
if (typeof matcherName !== 'string')
|
||||||
return matcher;
|
return matcher;
|
||||||
if (matcher === undefined)
|
if (matcher === undefined)
|
||||||
|
|
|
||||||
|
|
@ -1063,3 +1063,44 @@ test('should throw error when using .equals()', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('expect.extendImmutable should work', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'expect-test.spec.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
const expectFoo = expect.extendImmutable({
|
||||||
|
toFoo() {
|
||||||
|
console.log('%%foo');
|
||||||
|
return { pass: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const expectFoo2 = expect.extendImmutable({
|
||||||
|
toFoo() {
|
||||||
|
console.log('%%foo2');
|
||||||
|
return { pass: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const expectBar = expectFoo.extendImmutable({
|
||||||
|
toBar() {
|
||||||
|
console.log('%%bar');
|
||||||
|
return { pass: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test('logs', () => {
|
||||||
|
expect(expectFoo).not.toBe(expectFoo2);
|
||||||
|
expect(expectFoo).not.toBe(expectBar);
|
||||||
|
|
||||||
|
expectFoo().toFoo();
|
||||||
|
expectFoo2().toFoo();
|
||||||
|
expectBar().toFoo();
|
||||||
|
expectBar().toBar();
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.outputLines).toEqual([
|
||||||
|
'foo',
|
||||||
|
'foo2',
|
||||||
|
'foo',
|
||||||
|
'bar',
|
||||||
|
]);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue