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 }; type ExpectMessage = string | { message?: string };
function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[]): any { function createMatchers(actual: unknown, info: ExpectMetaInfo, prefix: string[], parentPrefixes: string[][]): any {
return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info, prefix)); 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, { const expectInstance: Expect<{}> = new Proxy(expectLibrary, {
apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) { apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) {
const [actual, messageOrOptions] = argumentsList; 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'); throw new Error('`expect.poll()` accepts only function as a first argument');
newInfo.generator = actual as any; 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') if (property === 'configure')
return configure; return configure;
@ -149,7 +151,7 @@ function createExpect(info: ExpectMetaInfo, prefix: string[] = []) {
} }
expectLibrary.extend(wrappedMatchers); 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') { if (property === 'poll') {
return (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => { return (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => {
const poll = isString(messageOrOptions) ? {} : messageOrOptions || {}; const poll = isString(messageOrOptions) ? {} : messageOrOptions || {};
@ -247,22 +252,24 @@ type ExpectMetaInfo = {
class ExpectMetaInfoProxyHandler implements ProxyHandler<any> { class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
private _info: ExpectMetaInfo; private _info: ExpectMetaInfo;
private _prefix: string[]; private _prefixes: string[][];
constructor(info: ExpectMetaInfo, prefix: string[]) { constructor(info: ExpectMetaInfo, prefixes: string[][]) {
this._info = { ...info }; this._info = { ...info };
this._prefix = prefix; this._prefixes = prefixes;
} }
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') { if (typeof matcherName === 'string') {
for (let i = this._prefix.length; i > 0; i--) { for (const prefix of this._prefixes) {
const qualifiedName = `${this._prefix.slice(0, i).join(':')}$${matcherName}`; for (let i = prefix.length; i > 0; i--) {
if (Reflect.has(target, qualifiedName)) { const qualifiedName = `${prefix.slice(0, i).join(':')}$${matcherName}`;
matcher = Reflect.get(target, qualifiedName, receiver); if (Reflect.has(target, qualifiedName)) {
break; matcher = Reflect.get(target, qualifiedName, receiver);
break;
}
} }
} }
} }
@ -397,5 +404,11 @@ function computeArgsSuffix(matcherName: string, args: any[]) {
export const expect: Expect<{}> = createExpect({}).extend(customMatchers); export const expect: Expect<{}> = createExpect({}).extend(customMatchers);
export function mergeExpects(...expects: any[]) { 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'; import { test, expect, parseTestRunnerOutput, stripAnsi } from './playwright-test-fixtures';
const { spawnAsync } = require('../../packages/playwright-core/lib/utils'); 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 }) => { test('should not expand huge arrays', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'expect-test.spec.ts': ` 'expect-test.spec.ts': `