diff --git a/src/index.js b/src/index.js index f4411f5b..354ea3f9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,8 @@ import React, { Suspense } from 'react' import { act, create } from 'react-test-renderer' +let cleanupCallbacks = [] + function TestHook({ callback, hookProps, onError, children }) { try { children(callback(hookProps)) @@ -73,6 +75,15 @@ function renderHook(callback, { initialProps, wrapper } = {}) { }) const { unmount, update } = testRenderer + function unmountHook() { + act(() => { + cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== unmountHook) + unmount() + }) + } + + cleanupCallbacks.push(unmountHook) + let waitingForNextUpdate = null const resolveOnNextUpdate = (resolve) => { addResolver((...args) => { @@ -93,12 +104,22 @@ function renderHook(callback, { initialProps, wrapper } = {}) { update(toRender()) }) }, - unmount: () => { - act(() => { - unmount() - }) - } + unmount: unmountHook } } -export { renderHook, act } +async function cleanup() { + await act(async () => { + await act(async () => {}) + cleanupCallbacks.forEach((cb) => cb()) + cleanupCallbacks = [] + }) +} + +if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) { + afterEach(async () => { + await cleanup() + }) +} + +export { renderHook, cleanup, act } diff --git a/test/autoCleanup.disabled.test.js b/test/autoCleanup.disabled.test.js new file mode 100644 index 00000000..7da342d5 --- /dev/null +++ b/test/autoCleanup.disabled.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (disabled) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' + renderHook = require('src').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/autoCleanup.noAfterEach.test.js b/test/autoCleanup.noAfterEach.test.js new file mode 100644 index 00000000..c1f51eea --- /dev/null +++ b/test/autoCleanup.noAfterEach.test.js @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set +// then we DON'T auto-wire up the afterEach for folks +describe('skip auto cleanup (no afterEach) tests', () => { + let cleanupCalled = false + let renderHook + + beforeAll(() => { + afterEach = false + renderHook = require('src').renderHook + }) + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(false) + }) +}) diff --git a/test/autoCleanup.test.js b/test/autoCleanup.test.js new file mode 100644 index 00000000..fc70d111 --- /dev/null +++ b/test/autoCleanup.test.js @@ -0,0 +1,24 @@ +import { useEffect } from 'react' +import { renderHook } from 'src' + +// This verifies that by importing RHTL in an +// environment which supports afterEach (like jest) +// we'll get automatic cleanup between tests. +describe('auto cleanup tests', () => { + let cleanupCalled = false + + test('first', () => { + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + renderHook(() => hookWithCleanup()) + }) + + test('second', () => { + expect(cleanupCalled).toBe(true) + }) +}) diff --git a/test/cleanup.test.js b/test/cleanup.test.js new file mode 100644 index 00000000..a8c3bbba --- /dev/null +++ b/test/cleanup.test.js @@ -0,0 +1,41 @@ +import { useEffect } from 'react' +import { renderHook, cleanup } from 'src' + +describe('cleanup tests', () => { + test('should flush effects on cleanup', async () => { + let cleanupCalled = false + + const hookWithCleanup = () => { + useEffect(() => { + return () => { + cleanupCalled = true + } + }) + } + + renderHook(() => hookWithCleanup()) + + await cleanup() + + expect(cleanupCalled).toBe(true) + }) + + test('should cleanup all rendered hooks', async () => { + let cleanupCalled = [] + const hookWithCleanup = (id) => { + useEffect(() => { + return () => { + cleanupCalled[id] = true + } + }) + } + + renderHook(() => hookWithCleanup(1)) + renderHook(() => hookWithCleanup(2)) + + await cleanup() + + expect(cleanupCalled[1]).toBe(true) + expect(cleanupCalled[2]).toBe(true) + }) +})