Skip to content

Commit e4b0aa3

Browse files
authored
feat: Remove node specific dependencies and code to better support testing in browser environments
* feat: removed filter-console dependency and fallback if process.env is not available (#624) * fix: protect import helpers for setting env variables and comment why try/catch is being used BREAKING CHANGE: `suppressErrorOutput` will now work when explicitly called, even if the `RHTL_DISABLE_ERROR_FILTERING` env variable has been set Fixes #617
1 parent e11b63a commit e4b0aa3

25 files changed

+559
-188
lines changed

disable-error-filtering.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
process.env.RHTL_DISABLE_ERROR_FILTERING = true
1+
try {
2+
process.env.RHTL_DISABLE_ERROR_FILTERING = true
3+
} catch {
4+
// falling back in the case that process.env.RHTL_DISABLE_ERROR_FILTERING cannot be accessed (e.g. browser environment)
5+
console.warn(
6+
'Could not disable error filtering as process.env.RHTL_DISABLE_ERROR_FILTERING could not be accessed.'
7+
)
8+
}

dont-cleanup-after-each.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
process.env.RHTL_SKIP_AUTO_CLEANUP = true
1+
try {
2+
process.env.RHTL_SKIP_AUTO_CLEANUP = true
3+
} catch {
4+
// falling back in the case that process.env.RHTL_SKIP_AUTO_CLEANUP cannot be accessed (e.g. browser environment)
5+
console.warn(
6+
'Could not skip auto cleanup as process.env.RHTL_SKIP_AUTO_CLEANUP could not be accessed.'
7+
)
8+
}

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
"@types/react": ">=16.9.0",
5151
"@types/react-dom": ">=16.9.0",
5252
"@types/react-test-renderer": ">=16.9.0",
53-
"filter-console": "^0.1.1",
5453
"react-error-boundary": "^3.1.0"
5554
},
5655
"devDependencies": {

src/core/cleanup.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@ function removeCleanup(callback: CleanupCallback) {
1818
cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback)
1919
}
2020

21+
function skipAutoCleanup() {
22+
try {
23+
return !!process.env.RHTL_SKIP_AUTO_CLEANUP
24+
} catch {
25+
// falling back in the case that process.env.RHTL_SKIP_AUTO_CLEANUP cannot be accessed (e.g. browser environment)
26+
return false
27+
}
28+
}
29+
2130
function autoRegisterCleanup() {
2231
// Automatically registers cleanup in supported testing frameworks
23-
if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) {
32+
if (typeof afterEach === 'function' && !skipAutoCleanup()) {
2433
afterEach(async () => {
2534
await cleanup()
2635
})

src/core/console.ts

+30-13
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
1-
import filterConsole from 'filter-console'
1+
const consoleFilters = [
2+
/^The above error occurred in the <.*?> component:/, // error boundary output
3+
/^Error: Uncaught .+/ // jsdom output
4+
]
25

36
function suppressErrorOutput() {
4-
if (process.env.RHTL_DISABLE_ERROR_FILTERING) {
5-
return () => {}
6-
}
7+
const originalError = console.error
78

8-
return filterConsole(
9-
[
10-
/^The above error occurred in the <TestComponent> component:/, // error boundary output
11-
/^Error: Uncaught .+/ // jsdom output
12-
],
13-
{
14-
methods: ['error']
9+
const error = (...args: Parameters<typeof originalError>) => {
10+
const message = typeof args[0] === 'string' ? args[0] : null
11+
if (!message || !consoleFilters.some((filter) => filter.test(message))) {
12+
originalError(...args)
1513
}
16-
)
14+
}
15+
16+
console.error = error
17+
18+
return () => {
19+
console.error = originalError
20+
}
21+
}
22+
23+
function errorFilteringDisabled() {
24+
try {
25+
return !!process.env.RHTL_DISABLE_ERROR_FILTERING
26+
} catch {
27+
// falling back in the case that process.env.RHTL_DISABLE_ERROR_FILTERING cannot be accessed (e.g. browser environment)
28+
return false
29+
}
1730
}
1831

1932
function enableErrorOutputSuppression() {
2033
// Automatically registers console error suppression and restoration in supported testing frameworks
21-
if (typeof beforeEach === 'function' && typeof afterEach === 'function') {
34+
if (
35+
typeof beforeEach === 'function' &&
36+
typeof afterEach === 'function' &&
37+
!errorFilteringDisabled()
38+
) {
2239
let restoreConsole!: () => void
2340

2441
beforeEach(() => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useEffect } from 'react'
2+
3+
import { ReactHooksRenderer } from '../../types/react'
4+
5+
// This verifies that if process.env is unavailable
6+
// then we still auto-wire up the afterEach for folks
7+
describe('skip auto cleanup (no process.env) tests', () => {
8+
const originalEnv = process.env
9+
let cleanupCalled = false
10+
let renderHook: ReactHooksRenderer['renderHook']
11+
12+
beforeAll(() => {
13+
process.env = {
14+
...process.env,
15+
get RHTL_SKIP_AUTO_CLEANUP(): string | undefined {
16+
throw new Error('expected')
17+
}
18+
}
19+
renderHook = (require('..') as ReactHooksRenderer).renderHook
20+
})
21+
22+
afterAll(() => {
23+
process.env = originalEnv
24+
})
25+
26+
test('first', () => {
27+
const hookWithCleanup = () => {
28+
useEffect(() => {
29+
return () => {
30+
cleanupCalled = true
31+
}
32+
})
33+
}
34+
renderHook(() => hookWithCleanup())
35+
})
36+
37+
test('second', () => {
38+
expect(cleanupCalled).toBe(true)
39+
})
40+
})

src/dom/__tests__/errorHook.test.ts

-47
Original file line numberDiff line numberDiff line change
@@ -142,51 +142,4 @@ describe('error hook tests', () => {
142142
expect(result.error).toBe(undefined)
143143
})
144144
})
145-
146-
describe('error output suppression', () => {
147-
test('should allow console.error to be mocked', async () => {
148-
const consoleError = console.error
149-
console.error = jest.fn()
150-
151-
try {
152-
const { rerender, unmount } = renderHook(
153-
(stage) => {
154-
useEffect(() => {
155-
console.error(`expected in effect`)
156-
return () => {
157-
console.error(`expected in unmount`)
158-
}
159-
}, [])
160-
console.error(`expected in ${stage}`)
161-
},
162-
{
163-
initialProps: 'render'
164-
}
165-
)
166-
167-
act(() => {
168-
console.error('expected in act')
169-
})
170-
171-
await act(async () => {
172-
await new Promise((resolve) => setTimeout(resolve, 100))
173-
console.error('expected in async act')
174-
})
175-
176-
rerender('rerender')
177-
178-
unmount()
179-
180-
expect(console.error).toBeCalledWith('expected in render')
181-
expect(console.error).toBeCalledWith('expected in effect')
182-
expect(console.error).toBeCalledWith('expected in act')
183-
expect(console.error).toBeCalledWith('expected in async act')
184-
expect(console.error).toBeCalledWith('expected in rerender')
185-
expect(console.error).toBeCalledWith('expected in unmount')
186-
expect(console.error).toBeCalledTimes(6)
187-
} finally {
188-
console.error = consoleError
189-
}
190-
})
191-
})
192145
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This verifies that if process.env is unavailable
2+
// then we still auto-wire up the afterEach for folks
3+
describe('error output suppression (no process.env) tests', () => {
4+
const originalEnv = process.env
5+
const originalConsoleError = console.error
6+
7+
beforeAll(() => {
8+
process.env = {
9+
...process.env,
10+
get RHTL_DISABLE_ERROR_FILTERING(): string | undefined {
11+
throw new Error('expected')
12+
}
13+
}
14+
require('..')
15+
})
16+
17+
afterAll(() => {
18+
process.env = originalEnv
19+
})
20+
21+
test('should not patch console.error', () => {
22+
expect(console.error).not.toBe(originalConsoleError)
23+
})
24+
})
25+
26+
export {}
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useEffect } from 'react'
2+
3+
import { ReactHooksRenderer } from '../../types/react'
4+
5+
describe('error output suppression tests', () => {
6+
test('should not suppress relevant errors', () => {
7+
const consoleError = console.error
8+
console.error = jest.fn()
9+
10+
const { suppressErrorOutput } = require('..') as ReactHooksRenderer
11+
12+
try {
13+
const restoreConsole = suppressErrorOutput()
14+
15+
console.error('expected')
16+
console.error(new Error('expected'))
17+
console.error('expected with args', new Error('expected'))
18+
19+
restoreConsole()
20+
21+
expect(console.error).toBeCalledWith('expected')
22+
expect(console.error).toBeCalledWith(new Error('expected'))
23+
expect(console.error).toBeCalledWith('expected with args', new Error('expected'))
24+
expect(console.error).toBeCalledTimes(3)
25+
} finally {
26+
console.error = consoleError
27+
}
28+
})
29+
30+
test('should allow console.error to be mocked', async () => {
31+
const { renderHook, act } = require('..') as ReactHooksRenderer
32+
const consoleError = console.error
33+
console.error = jest.fn()
34+
35+
try {
36+
const { rerender, unmount } = renderHook(
37+
(stage) => {
38+
useEffect(() => {
39+
console.error(`expected in effect`)
40+
return () => {
41+
console.error(`expected in unmount`)
42+
}
43+
}, [])
44+
console.error(`expected in ${stage}`)
45+
},
46+
{
47+
initialProps: 'render'
48+
}
49+
)
50+
51+
act(() => {
52+
console.error('expected in act')
53+
})
54+
55+
await act(async () => {
56+
await new Promise((resolve) => setTimeout(resolve, 100))
57+
console.error('expected in async act')
58+
})
59+
60+
rerender('rerender')
61+
62+
unmount()
63+
64+
expect(console.error).toBeCalledWith('expected in render')
65+
expect(console.error).toBeCalledWith('expected in effect')
66+
expect(console.error).toBeCalledWith('expected in act')
67+
expect(console.error).toBeCalledWith('expected in async act')
68+
expect(console.error).toBeCalledWith('expected in rerender')
69+
expect(console.error).toBeCalledWith('expected in unmount')
70+
expect(console.error).toBeCalledTimes(6)
71+
} finally {
72+
console.error = consoleError
73+
}
74+
})
75+
})

src/dom/pure.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ReactDOM from 'react-dom'
1+
import * as ReactDOM from 'react-dom'
22
import { act } from 'react-dom/test-utils'
33

44
import { RendererProps, RendererOptions } from '../types/react'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useEffect } from 'react'
2+
3+
import { ReactHooksRenderer } from '../../types/react'
4+
5+
// This verifies that if process.env is unavailable
6+
// then we still auto-wire up the afterEach for folks
7+
describe('skip auto cleanup (no process.env) tests', () => {
8+
const originalEnv = process.env
9+
let cleanupCalled = false
10+
let renderHook: ReactHooksRenderer['renderHook']
11+
12+
beforeAll(() => {
13+
process.env = {
14+
...process.env,
15+
get RHTL_SKIP_AUTO_CLEANUP(): string | undefined {
16+
throw new Error('expected')
17+
}
18+
}
19+
renderHook = (require('..') as ReactHooksRenderer).renderHook
20+
})
21+
22+
afterAll(() => {
23+
process.env = originalEnv
24+
})
25+
26+
test('first', () => {
27+
const hookWithCleanup = () => {
28+
useEffect(() => {
29+
return () => {
30+
cleanupCalled = true
31+
}
32+
})
33+
}
34+
renderHook(() => hookWithCleanup())
35+
})
36+
37+
test('second', () => {
38+
expect(cleanupCalled).toBe(true)
39+
})
40+
})

src/native/__tests__/errorHook.test.ts

-47
Original file line numberDiff line numberDiff line change
@@ -142,51 +142,4 @@ describe('error hook tests', () => {
142142
expect(result.error).toBe(undefined)
143143
})
144144
})
145-
146-
describe('error output suppression', () => {
147-
test('should allow console.error to be mocked', async () => {
148-
const consoleError = console.error
149-
console.error = jest.fn()
150-
151-
try {
152-
const { rerender, unmount } = renderHook(
153-
(stage) => {
154-
useEffect(() => {
155-
console.error(`expected in effect`)
156-
return () => {
157-
console.error(`expected in unmount`)
158-
}
159-
}, [])
160-
console.error(`expected in ${stage}`)
161-
},
162-
{
163-
initialProps: 'render'
164-
}
165-
)
166-
167-
act(() => {
168-
console.error('expected in act')
169-
})
170-
171-
await act(async () => {
172-
await new Promise((resolve) => setTimeout(resolve, 100))
173-
console.error('expected in async act')
174-
})
175-
176-
rerender('rerender')
177-
178-
unmount()
179-
180-
expect(console.error).toBeCalledWith('expected in render')
181-
expect(console.error).toBeCalledWith('expected in effect')
182-
expect(console.error).toBeCalledWith('expected in act')
183-
expect(console.error).toBeCalledWith('expected in async act')
184-
expect(console.error).toBeCalledWith('expected in rerender')
185-
expect(console.error).toBeCalledWith('expected in unmount')
186-
expect(console.error).toBeCalledTimes(6)
187-
} finally {
188-
console.error = consoleError
189-
}
190-
})
191-
})
192145
})

0 commit comments

Comments
 (0)