implement mergeExpects

This commit is contained in:
Simon Knott 2024-08-30 15:35:57 +02:00
parent 9a160c6f67
commit 22f9d71019
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
2 changed files with 28 additions and 50 deletions

View file

@ -105,11 +105,13 @@ export const printReceivedStringContainExpectedResult = (
type ExpectMessage = string | { message?: string };
function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[]): any {
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, prefix));
function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[], parentPrefixes: string[][]): any {
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, [prefix, ...parentPrefixes]));
}
function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
const getPrefixSymbol = Symbol('get prefix');
function createExpect(info: ExpectMetaInfo, prefix: string[] = [], parentPrefixes: string[][] = []) {
const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
const [actual, messageOrOptions] = argumentsList;
@ -120,10 +122,10 @@ function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
throw new Error('`expect.poll()` accepts only function as a first argument');
newInfo.generator = actual as any;
}
return createMatchers(actual, newInfo, prefix);
return createMatchers(actual, newInfo, prefix, parentPrefixes);
},
get: function(target: any, property: string) {
get: function(target: any, property: string | typeof getPrefixSymbol) {
if (property === 'configure')
return configure;
@ -149,7 +151,7 @@ function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
}
expectLibrary.extend(wrappedMatchers);
return createExpect(info, qualifier);
return createExpect(info, qualifier, parentPrefixes);
};
}
@ -159,6 +161,9 @@ function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
};
}
if (property === getPrefixSymbol)
return { prefix, parentPrefixes };
if (property === 'poll') {
return (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => {
const poll = isString(messageOrOptions) ? {} : messageOrOptions || {};
@ -247,25 +252,27 @@ type ExpectMetaInfo = {
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
private _info: ExpectMetaInfo;
private _prefix: string[];
private _prefixes: string[][];
constructor(info: ExpectMetaInfo, prefix: string[]) {
constructor(info: ExpectMetaInfo, prefixes: string[][]) {
this._info = { ...info };
this._prefix = prefix;
this._prefixes = prefixes;
}
get(target: Object, matcherName: string | symbol, receiver: any): any {
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}`;
for (const prefix of this._prefixes) {
for (let i = prefix.length; i > 0; i--) {
const qualifiedName = `${prefix.slice(0, i).join(':')}$${matcherName}`;
if (Reflect.has(target, qualifiedName)) {
matcher = Reflect.get(target, qualifiedName, receiver);
break;
}
}
}
}
if (typeof matcherName !== 'string')
return matcher;
@ -397,5 +404,11 @@ function computeArgsSuffix(matcherName: string, args: any[]) {
export const expect: Expect<{}> = createExpect({}).extend(customMatchers);
export function mergeExpects(...expects: any[]) {
return expect;
const parentPrefixes = expects.flatMap(e => {
const internals = e[getPrefixSymbol];
if (!internals) // non-playwright expects mutate the global expect, so we don't need to do anything special
return [];
return [internals.prefix, ...internals.parentPrefixes];
});
return createExpect({}, undefined, parentPrefixes);
}

View file

@ -18,41 +18,6 @@ import path from 'path';
import { test, expect, parseTestRunnerOutput, stripAnsi } from './playwright-test-fixtures';
const { spawnAsync } = require('../../packages/playwright-core/lib/utils');
test('should be able to call expect.extend in config', async ({ runInlineTest }) => {
const result = await runInlineTest({
'helper.ts': `
import { test as base, expect } from '@playwright/test';
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
if (pass) {
return {
message: () =>
'passed',
pass: true,
};
} else {
return {
message: () => 'failed',
pass: false,
};
}
},
});
export const test = base;
`,
'expect-test.spec.ts': `
import { test } from './helper';
test('numeric ranges', () => {
test.expect(100).toBeWithinRange(90, 110);
test.expect(101).not.toBeWithinRange(0, 100);
});
`
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should not expand huge arrays', async ({ runInlineTest }) => {
const result = await runInlineTest({
'expect-test.spec.ts': `