Skip to content

Commit

Permalink
Use dedicated entrypoint instead
Browse files Browse the repository at this point in the history
Less renaming to-do
  • Loading branch information
eps1lon committed Nov 27, 2024
1 parent 571fbc8 commit 1bb540d
Show file tree
Hide file tree
Showing 13 changed files with 768 additions and 309 deletions.
1 change: 1 addition & 0 deletions async.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types/pure-async'
2 changes: 2 additions & 0 deletions async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// makes it so people can import from '@testing-library/react/async'
module.exports = require('./dist/async')
1 change: 1 addition & 0 deletions pure-async.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types/pure-async'
2 changes: 2 additions & 0 deletions pure-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// makes it so people can import from '@testing-library/react/pure-async'
module.exports = require('./dist/pure-async')
71 changes: 71 additions & 0 deletions src/__tests__/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// TODO: Upstream that the rule should check import source
/* eslint-disable testing-library/no-await-sync-events */
/* eslint-disable jest/no-conditional-in-test */
/* eslint-disable jest/no-if */
import * as React from 'react'
import {act, render, fireEvent} from '../async'

test('async data requires async APIs', async () => {
let resolve
const promise = new Promise(_resolve => {
resolve = _resolve
})

function Component() {
const value = React.use(promise)
return <div>{value}</div>
}

const {container} = await render(
<React.Suspense fallback="loading...">
<Component />
</React.Suspense>,
)

expect(container).toHaveTextContent('loading...')

await act(async () => {
resolve('Hello, Dave!')
})

expect(container).toHaveTextContent('Hello, Dave!')
})

test('async fireEvent', async () => {
let resolve
function Component() {
const [promise, setPromise] = React.useState('initial')
const value = typeof promise === 'string' ? promise : React.use(promise)
return (
<button
onClick={() =>
setPromise(
new Promise(_resolve => {
resolve = _resolve
}),
)
}
>
Value: {value}
</button>
)
}

const {container} = await render(
<React.Suspense fallback="loading...">
<Component />
</React.Suspense>,
)

expect(container).toHaveTextContent('Value: initial')

await fireEvent.click(container.querySelector('button'))

expect(container).toHaveTextContent('loading...')

await act(() => {
resolve('Hello, Dave!')
})

expect(container).toHaveTextContent('Hello, Dave!')
})
25 changes: 0 additions & 25 deletions src/__tests__/renderAsync.js

This file was deleted.

14 changes: 0 additions & 14 deletions src/act-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,8 @@ function withGlobalActEnvironment(actImplementation) {

const act = withGlobalActEnvironment(reactAct)

async function actAsync(scope) {
const previousActEnvironment = getIsReactActEnvironment()
setIsReactActEnvironment(true)
try {
// React.act isn't async yet so we need to force it.
return await reactAct(async () => {
scope()
})
} finally {
setIsReactActEnvironment(previousActEnvironment)
}
}

export default act
export {
actAsync,
setIsReactActEnvironment as setReactActEnvironment,
getIsReactActEnvironment,
}
Expand Down
42 changes: 42 additions & 0 deletions src/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* istanbul ignore file */
import {getIsReactActEnvironment, setReactActEnvironment} from './act-compat'
import {cleanup} from './pure-async'

// if we're running in a test runner that supports afterEach
// or teardown then we'll automatically run cleanup afterEach test
// this ensures that tests run in isolation from each other
// if you don't like this then either import the `pure` module
// or set the RTL_SKIP_AUTO_CLEANUP env variable to 'true'.
if (typeof process === 'undefined' || !process.env?.RTL_SKIP_AUTO_CLEANUP) {
// ignore teardown() in code coverage because Jest does not support it
/* istanbul ignore else */
if (typeof afterEach === 'function') {
afterEach(async () => {
await cleanup()
})
} else if (typeof teardown === 'function') {
// Block is guarded by `typeof` check.
// eslint does not support `typeof` guards.
// eslint-disable-next-line no-undef
teardown(async () => {
await cleanup()
})
}

// No test setup with other test runners available
/* istanbul ignore else */
if (typeof beforeAll === 'function' && typeof afterAll === 'function') {
// This matches the behavior of React < 18.
let previousIsReactActEnvironment = getIsReactActEnvironment()
beforeAll(() => {
previousIsReactActEnvironment = getIsReactActEnvironment()
setReactActEnvironment(true)
})

afterAll(() => {
setReactActEnvironment(previousIsReactActEnvironment)
})
}
}

export * from './pure-async'
70 changes: 70 additions & 0 deletions src/fire-event-async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* istanbul ignore file */
import {fireEvent as dtlFireEvent} from '@testing-library/dom'

// react-testing-library's version of fireEvent will call
// dom-testing-library's version of fireEvent. The reason
// we make this distinction however is because we have
// a few extra events that work a bit differently
const fireEvent = (...args) => dtlFireEvent(...args)

Object.keys(dtlFireEvent).forEach(key => {
fireEvent[key] = (...args) => dtlFireEvent[key](...args)
})

// React event system tracks native mouseOver/mouseOut events for
// running onMouseEnter/onMouseLeave handlers
// @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/EnterLeaveEventPlugin.js#L24-L31
const mouseEnter = fireEvent.mouseEnter
const mouseLeave = fireEvent.mouseLeave
fireEvent.mouseEnter = async (...args) => {
await mouseEnter(...args)
return fireEvent.mouseOver(...args)
}
fireEvent.mouseLeave = async (...args) => {
await mouseLeave(...args)
return fireEvent.mouseOut(...args)
}

const pointerEnter = fireEvent.pointerEnter
const pointerLeave = fireEvent.pointerLeave
fireEvent.pointerEnter = async (...args) => {
await pointerEnter(...args)
return fireEvent.pointerOver(...args)
}
fireEvent.pointerLeave = async (...args) => {
await pointerLeave(...args)
return fireEvent.pointerOut(...args)
}

const select = fireEvent.select
fireEvent.select = async (node, init) => {
await select(node, init)
// React tracks this event only on focused inputs
node.focus()

// React creates this event when one of the following native events happens
// - contextMenu
// - mouseUp
// - dragEnd
// - keyUp
// - keyDown
// so we can use any here
// @link https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/events/SelectEventPlugin.js#L203-L224
await fireEvent.keyUp(node, init)
}

// React event system tracks native focusout/focusin events for
// running blur/focus handlers
// @link https://github.com/facebook/react/pull/19186
const blur = fireEvent.blur
const focus = fireEvent.focus
fireEvent.blur = async (...args) => {
await fireEvent.focusOut(...args)
return blur(...args)
}
fireEvent.focus = async (...args) => {
await fireEvent.focusIn(...args)
return focus(...args)
}

export {fireEvent}
Loading

0 comments on commit 1bb540d

Please sign in to comment.