Skip to content

Commit

Permalink
fix: async act detection (#407)
Browse files Browse the repository at this point in the history
The previous version of async act detection left an open hanging act scope, which broke tests and expectations. This PR delays the detection until it's been called at least once.
  • Loading branch information
Sunil Pai authored and Kent C. Dodds committed Jul 23, 2019
1 parent 4aa0c56 commit ffe2b79
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 59 deletions.
76 changes: 76 additions & 0 deletions src/__tests__/new-act.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
let asyncAct

jest.mock('react-dom/test-utils', () => ({
act: cb => {
return cb()
},
}))

beforeEach(() => {
jest.resetModules()
asyncAct = require('../act-compat').asyncAct
jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterEach(() => {
console.error.mockRestore()
})

test('async act works when it does not exist (older versions of react)', async () => {
const callback = jest.fn()
await asyncAct(async () => {
await Promise.resolve()
await callback()
})
expect(console.error).toHaveBeenCalledTimes(0)
expect(callback).toHaveBeenCalledTimes(1)

callback.mockClear()
console.error.mockClear()

await asyncAct(async () => {
await Promise.resolve()
await callback()
})
expect(console.error).toHaveBeenCalledTimes(0)
expect(callback).toHaveBeenCalledTimes(1)
})

test('async act recovers from errors', async () => {
try {
await asyncAct(async () => {
await null
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"call console.error",
],
]
`)
})

test('async act recovers from sync errors', async () => {
try {
await asyncAct(() => {
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"call console.error",
],
]
`)
})

/* eslint no-console:0 */
73 changes: 72 additions & 1 deletion src/__tests__/no-act.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,80 @@
import {act} from '..'
let act, asyncAct

beforeEach(() => {
jest.resetModules()
act = require('..').act
asyncAct = require('../act-compat').asyncAct
jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterEach(() => {
console.error.mockRestore()
})

jest.mock('react-dom/test-utils', () => ({}))

test('act works even when there is no act from test utils', () => {
const callback = jest.fn()
act(callback)
expect(callback).toHaveBeenCalledTimes(1)
expect(console.error).toHaveBeenCalledTimes(0)
})

test('async act works when it does not exist (older versions of react)', async () => {
const callback = jest.fn()
await asyncAct(async () => {
await Promise.resolve()
await callback()
})
expect(console.error).toHaveBeenCalledTimes(0)
expect(callback).toHaveBeenCalledTimes(1)

callback.mockClear()
console.error.mockClear()

await asyncAct(async () => {
await Promise.resolve()
await callback()
})
expect(console.error).toHaveBeenCalledTimes(0)
expect(callback).toHaveBeenCalledTimes(1)
})

test('async act recovers from errors', async () => {
try {
await asyncAct(async () => {
await null
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"call console.error",
],
]
`)
})

test('async act recovers from sync errors', async () => {
try {
await asyncAct(() => {
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"call console.error",
],
]
`)
})

/* eslint no-console:0 */
82 changes: 70 additions & 12 deletions src/__tests__/old-act.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
import {asyncAct} from '../act-compat'
let asyncAct

beforeEach(() => {
jest.resetModules()
asyncAct = require('../act-compat').asyncAct
jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterEach(() => {
console.error.mockRestore()
})

jest.mock('../react-dom-16.9.0-is-released', () => ({
reactDomSixteenPointNineIsReleased: true,
}))

jest.mock('react-dom/test-utils', () => ({
act: cb => {
const promise = cb()
cb()
return {
then() {
console.error('blah, do not do this')
return promise
console.error(
'Warning: Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
)
},
}
},
}))

test('async act works even when the act is an old one', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {})
const callback = jest.fn()
await asyncAct(async () => {
console.error('sigil')
await Promise.resolve()
await callback()
console.error('sigil')
})
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
],
]
`)
Array [
Array [
Array [
"sigil",
],
],
Array [
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
],
Array [
"sigil",
],
]
`)
expect(callback).toHaveBeenCalledTimes(1)

// and it doesn't warn you twice
Expand All @@ -42,8 +62,46 @@ Array [
})
expect(console.error).toHaveBeenCalledTimes(0)
expect(callback).toHaveBeenCalledTimes(1)
})

console.error.mockRestore()
test('async act recovers from async errors', async () => {
try {
await asyncAct(async () => {
await null
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(2)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least react-dom@16.9.0 to remove this warning.",
],
Array [
"call console.error",
],
]
`)
})

test('async act recovers from sync errors', async () => {
try {
await asyncAct(() => {
throw new Error('test error')
})
} catch (err) {
console.error('call console.error')
}
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"call console.error",
],
]
`)
})

/* eslint no-console:0 */
Loading

0 comments on commit ffe2b79

Please sign in to comment.