Skip to content

Commit

Permalink
fix(browser): not.toBeInTheDocument works with locators API (#6634)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va authored Oct 7, 2024
1 parent 9ece395 commit 8bef5d2
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 9 deletions.
2 changes: 1 addition & 1 deletion packages/browser/matchers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ declare module 'vitest' {
type PromisifyDomAssertion<T> = Promisify<Assertion<T>>

interface ExpectStatic {
element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element>>
element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element | null>>
}
}

Expand Down
12 changes: 9 additions & 3 deletions packages/browser/src/client/tester/expect-element.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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<Element>(() => {
if (elementOrLocator instanceof Element) {
return expect.poll<Element | null>(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)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/expect/src/jest-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/expect/src/jest-extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion packages/expect/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/integrations/chai/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
4 changes: 4 additions & 0 deletions test/browser/test/dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 8bef5d2

Please sign in to comment.