From 8bef5d2b9903180297c7eece39a155cf1f35f103 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 7 Oct 2024 10:25:36 +0200 Subject: [PATCH] fix(browser): not.toBeInTheDocument works with locators API (#6634) --- packages/browser/matchers.d.ts | 2 +- packages/browser/src/client/tester/expect-element.ts | 12 +++++++++--- packages/expect/src/jest-expect.ts | 4 ++-- packages/expect/src/jest-extend.ts | 4 ++-- packages/expect/src/utils.ts | 5 ++++- packages/vitest/src/integrations/chai/poll.ts | 2 ++ test/browser/test/dom.test.ts | 4 ++++ 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/browser/matchers.d.ts b/packages/browser/matchers.d.ts index 32032072547e..bef6ff300771 100644 --- a/packages/browser/matchers.d.ts +++ b/packages/browser/matchers.d.ts @@ -16,7 +16,7 @@ declare module 'vitest' { type PromisifyDomAssertion = Promisify> interface ExpectStatic { - element: (element: T, options?: ExpectPollOptions) => PromisifyDomAssertion> + element: (element: T, options?: ExpectPollOptions) => PromisifyDomAssertion> } } diff --git a/packages/browser/src/client/tester/expect-element.ts b/packages/browser/src/client/tester/expect-element.ts index 56c73ae26c58..f26b1f0d6eae 100644 --- a/packages/browser/src/client/tester/expect-element.ts +++ b/packages/browser/src/client/tester/expect-element.ts @@ -1,7 +1,7 @@ import * as matchers from '@testing-library/jest-dom/matchers' import type { Locator } from '@vitest/browser/context' import type { ExpectPollOptions } from 'vitest' -import { expect } from 'vitest' +import { chai, expect } from 'vitest' export async function setupExpectDom() { expect.extend(matchers) @@ -10,10 +10,16 @@ export async function setupExpectDom() { throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of Element or Locator, received ${typeof elementOrLocator}`) } - return expect.poll(() => { - if (elementOrLocator instanceof Element) { + return expect.poll(function element(this: object) { + if (elementOrLocator instanceof Element || elementOrLocator == null) { return elementOrLocator } + const isNot = chai.util.flag(this, 'negate') + const name = chai.util.flag(this, '_name') + // special case for `toBeInTheDocument` matcher + if (isNot && name === 'toBeInTheDocument') { + return elementOrLocator.query() + } return elementOrLocator.element() }, options) } diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index e925e3c3316e..a038ceada951 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -22,7 +22,7 @@ import { stringify, } from './jest-matcher-utils' import { JEST_MATCHERS_OBJECT } from './constants' -import { recordAsyncExpect, wrapSoft } from './utils' +import { recordAsyncExpect, wrapAssertion } from './utils' // polyfill globals because expect can be used in node environment declare class Node { @@ -43,7 +43,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => any, ) { const addMethod = (n: keyof Assertion) => { - const softWrapper = wrapSoft(utils, fn) + const softWrapper = wrapAssertion(utils, n, fn) utils.addMethod(chai.Assertion.prototype, n, softWrapper) utils.addMethod( (globalThis as any)[JEST_MATCHERS_OBJECT].matchers, diff --git a/packages/expect/src/jest-extend.ts b/packages/expect/src/jest-extend.ts index 5397429ff18a..117187cb5860 100644 --- a/packages/expect/src/jest-extend.ts +++ b/packages/expect/src/jest-extend.ts @@ -18,7 +18,7 @@ import { } from './jest-matcher-utils' import { equals, iterableEquality, subsetEquality } from './jest-utils' -import { wrapSoft } from './utils' +import { wrapAssertion } from './utils' function getMatcherState( assertion: Chai.AssertionStatic & Chai.Assertion, @@ -96,7 +96,7 @@ function JestExtendPlugin( } } - const softWrapper = wrapSoft(utils, expectWrapper) + const softWrapper = wrapAssertion(utils, expectAssertionName, expectWrapper) utils.addMethod( (globalThis as any)[JEST_MATCHERS_OBJECT].matchers, expectAssertionName, diff --git a/packages/expect/src/utils.ts b/packages/expect/src/utils.ts index 085980c71317..9a214b521eac 100644 --- a/packages/expect/src/utils.ts +++ b/packages/expect/src/utils.ts @@ -26,11 +26,14 @@ export function recordAsyncExpect( return promise } -export function wrapSoft( +export function wrapAssertion( utils: Chai.ChaiUtils, + name: string, fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => void, ) { return function (this: Chai.AssertionStatic & Assertion, ...args: any[]) { + utils.flag(this, '_name', name) + if (!utils.flag(this, 'soft')) { return fn.apply(this, args) } diff --git a/packages/vitest/src/integrations/chai/poll.ts b/packages/vitest/src/integrations/chai/poll.ts index b290fa048ef4..f734ebd3d0b2 100644 --- a/packages/vitest/src/integrations/chai/poll.ts +++ b/packages/vitest/src/integrations/chai/poll.ts @@ -38,6 +38,7 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] { const assertion = expect(null, message).withContext({ poll: true, }) as Assertion + fn = fn.bind(assertion) const proxy: any = new Proxy(assertion, { get(target, key, receiver) { const assertionFunction = Reflect.get(target, key, receiver) @@ -75,6 +76,7 @@ export function createExpectPoll(expect: ExpectStatic): ExpectStatic['poll'] { }, timeout) const check = async () => { try { + chai.util.flag(assertion, '_name', key) const obj = await fn() chai.util.flag(assertion, 'object', obj) resolve(await assertionFunction.call(assertion, ...args)) diff --git a/test/browser/test/dom.test.ts b/test/browser/test/dom.test.ts index eb7182c4a010..f3a42867d602 100644 --- a/test/browser/test/dom.test.ts +++ b/test/browser/test/dom.test.ts @@ -15,6 +15,10 @@ describe('dom related activity', () => { expect(window.innerHeight).toBe(600) }) + test('element doesn\'t exist', async () => { + await expect.element(page.getByText('empty')).not.toBeInTheDocument() + }) + test('renders div', async () => { const wrapper = createWrapper() const div = createNode()