diff --git a/src/dom/__tests__/asyncHook.test.ts b/src/dom/__tests__/asyncHook.test.ts deleted file mode 100644 index d460d35f..00000000 --- a/src/dom/__tests__/asyncHook.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { useState, useRef, useEffect } from 'react' -import { renderHook } from '..' - -describe('async hook tests', () => { - const useSequence = (values: string[], intervalMs = 50) => { - const [first, ...otherValues] = values - const [value, setValue] = useState(() => first) - const index = useRef(0) - - useEffect(() => { - const interval = setInterval(() => { - setValue(otherValues[index.current++]) - if (index.current >= otherValues.length) { - clearInterval(interval) - } - }, intervalMs) - return () => { - clearInterval(interval) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, otherValues) - - return value - } - - test('should wait for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - }) - - test('should wait for multiple updates', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - - await waitForNextUpdate() - - expect(result.current).toBe('third') - }) - - test('should reject if timeout exceeded when waiting for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( - Error('Timed out in waitForNextUpdate after 10ms.') - ) - }) - - test('should not reject when waiting for next update if timeout has been disabled', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) - - expect(result.current).toBe('first') - - await waitForNextUpdate({ timeout: false }) - - expect(result.current).toBe('second') - }) - - test('should wait for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('third') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for arbitrary expectation to pass', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - expect(complete).toBe(true) - }) - - test('should not hang if expectation is already passing', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for truthy value', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await waitFor(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary truthy value', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitFor(() => actual === 1) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await expect( - waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: 75 } - ) - ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) - }) - - test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) - - expect(result.current).toBe('first') - - await waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: false } - ) - - expect(result.current).toBe('third') - }) - - test('should check on interval when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - let checks = 0 - - try { - await waitFor( - () => { - checks++ - return result.current === 'third' - }, - { interval: 100 } - ) - } catch {} - - expect(checks).toBe(3) - }) - - test('should wait for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary value to change', async () => { - const { waitForValueToChange } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitForValueToChange(() => actual) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => result.current === 'third', { - timeout: 75 - }) - ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) - }) - - test('should not reject when waiting for value to change if timeout is disabled', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third', { - timeout: false - }) - - expect(result.current).toBe('third') - }) - - test('should reject if selector throws error', async () => { - const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current - }) - ).rejects.toThrow(Error('Something Unexpected')) - }) -}) diff --git a/src/dom/__tests__/autoCleanup.disabled.test.ts b/src/dom/__tests__/autoCleanup.disabled.test.ts deleted file mode 100644 index 2c574b83..00000000 --- a/src/dom/__tests__/autoCleanup.disabled.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/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: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/dom/__tests__/autoCleanup.noAfterEach.test.ts b/src/dom/__tests__/autoCleanup.noAfterEach.test.ts deleted file mode 100644 index 40b33f16..00000000 --- a/src/dom/__tests__/autoCleanup.noAfterEach.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (no afterEach) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/dom/__tests__/autoCleanup.pure.test.ts b/src/dom/__tests__/autoCleanup.pure.test.ts deleted file mode 100644 index 1f84b87c..00000000 --- a/src/dom/__tests__/autoCleanup.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (pure) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - renderHook = (require('../pure') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/dom/__tests__/autoCleanup.test.ts b/src/dom/__tests__/autoCleanup.test.ts deleted file mode 100644 index f783f1c2..00000000 --- a/src/dom/__tests__/autoCleanup.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect } from 'react' -import { renderHook } from '..' - -// 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/src/dom/__tests__/cleanup.test.ts b/src/dom/__tests__/cleanup.test.ts deleted file mode 100644 index 20a0f267..00000000 --- a/src/dom/__tests__/cleanup.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect } from 'react' -import { renderHook, cleanup, addCleanup, removeCleanup } from '../pure' - -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 () => { - const cleanupCalled: boolean[] = [] - const hookWithCleanup = (id: number) => { - useEffect(() => { - return () => { - cleanupCalled[id] = true - } - }) - } - - renderHook(() => hookWithCleanup(1)) - renderHook(() => hookWithCleanup(2)) - - await cleanup() - - expect(cleanupCalled[1]).toBe(true) - expect(cleanupCalled[2]).toBe(true) - }) - - test('should call cleanups in reverse order', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - addCleanup(() => { - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup']) - }) - - test('should wait for async cleanup', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - addCleanup(async () => { - await new Promise((resolve) => setTimeout(resolve, 10)) - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup']) - }) - - test('should remove cleanup using removeCleanup', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - const anotherCleanup = () => { - callSequence.push('another cleanup') - } - addCleanup(anotherCleanup) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - removeCleanup(anotherCleanup) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'cleanup']) - }) - - test('should remove cleanup using returned handler', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - const remove = addCleanup(() => { - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - remove() - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'cleanup']) - }) -}) diff --git a/src/dom/__tests__/customHook.test.ts b/src/dom/__tests__/customHook.test.ts deleted file mode 100644 index 5a1e83ab..00000000 --- a/src/dom/__tests__/customHook.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useState, useCallback } from 'react' -import { renderHook, act } from '..' - -describe('custom hook tests', () => { - function useCounter() { - const [count, setCount] = useState(0) - - const increment = useCallback(() => setCount(count + 1), [count]) - const decrement = useCallback(() => setCount(count - 1), [count]) - - return { count, increment, decrement } - } - - test('should increment counter', () => { - const { result } = renderHook(() => useCounter()) - - act(() => result.current.increment()) - - expect(result.current.count).toBe(1) - }) - - test('should decrement counter', () => { - const { result } = renderHook(() => useCounter()) - - act(() => result.current.decrement()) - - expect(result.current.count).toBe(-1) - }) -}) diff --git a/src/dom/__tests__/errorHook.test.ts b/src/dom/__tests__/errorHook.test.ts deleted file mode 100644 index 8b3760b9..00000000 --- a/src/dom/__tests__/errorHook.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { useState, useEffect } from 'react' -import { renderHook, act } from '..' - -describe('error hook tests', () => { - function useError(throwError?: boolean) { - if (throwError) { - throw new Error('expected') - } - return true - } - - function useAsyncError(throwError: boolean) { - const [value, setValue] = useState() - useEffect(() => { - const timeout = setTimeout(() => setValue(throwError), 100) - return () => clearTimeout(timeout) - }, [throwError]) - return useError(value) - } - - function useEffectError(throwError: boolean) { - useEffect(() => { - useError(throwError) - }, [throwError]) - return true - } - - describe('synchronous', () => { - test('should raise error', () => { - const { result } = renderHook(() => useError(true)) - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture error', () => { - const { result } = renderHook(() => useError(true)) - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture error', () => { - const { result } = renderHook(() => useError(false)) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset error', () => { - const { result, rerender } = renderHook(({ throwError }) => useError(throwError), { - initialProps: { throwError: true } - }) - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('asynchronous', () => { - test('should raise async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - await waitForNextUpdate() - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - await waitForNextUpdate() - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(false)) - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset async error', async () => { - const { result, waitForNextUpdate, rerender } = renderHook( - ({ throwError }) => useAsyncError(throwError), - { initialProps: { throwError: true } } - ) - - await waitForNextUpdate() - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('effect', () => { - test('this one - should raise effect error', () => { - const { result } = renderHook(() => useEffectError(true)) - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('this one - should capture effect error', () => { - const { result } = renderHook(() => useEffectError(true)) - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture effect error', () => { - const { result } = renderHook(() => useEffectError(false)) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset effect error', () => { - const { result, rerender } = renderHook(({ throwError }) => useEffectError(throwError), { - initialProps: { throwError: true } - }) - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('error output suppression', () => { - test('should allow console.error to be mocked', async () => { - const consoleError = console.error - console.error = jest.fn() - - try { - const { rerender, unmount } = renderHook( - (stage) => { - useEffect(() => { - console.error(`expected in effect`) - return () => { - console.error(`expected in unmount`) - } - }, []) - console.error(`expected in ${stage}`) - }, - { - initialProps: 'render' - } - ) - - act(() => { - console.error('expected in act') - }) - - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) - console.error('expected in async act') - }) - - rerender('rerender') - - unmount() - - expect(console.error).toBeCalledWith('expected in render') - expect(console.error).toBeCalledWith('expected in effect') - expect(console.error).toBeCalledWith('expected in act') - expect(console.error).toBeCalledWith('expected in async act') - expect(console.error).toBeCalledWith('expected in rerender') - expect(console.error).toBeCalledWith('expected in unmount') - expect(console.error).toBeCalledTimes(6) - } finally { - console.error = consoleError - } - }) - }) -}) diff --git a/src/dom/__tests__/errorSuppression.disabled.test.ts b/src/dom/__tests__/errorSuppression.disabled.test.ts deleted file mode 100644 index e1921f09..00000000 --- a/src/dom/__tests__/errorSuppression.disabled.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This verifies that if RHTL_DISABLE_ERROR_FILTERING is set -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (disabled) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - process.env.RHTL_DISABLE_ERROR_FILTERING = 'true' - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/dom/__tests__/errorSuppression.noAfterEach.test.ts b/src/dom/__tests__/errorSuppression.noAfterEach.test.ts deleted file mode 100644 index c736020e..00000000 --- a/src/dom/__tests__/errorSuppression.noAfterEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noAfterEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/dom/__tests__/errorSuppression.noBeforeEach.test.ts b/src/dom/__tests__/errorSuppression.noBeforeEach.test.ts deleted file mode 100644 index c3f2496f..00000000 --- a/src/dom/__tests__/errorSuppression.noBeforeEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noBeforeEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off BeforeEach -- ignore Jest LifeCycle Type - beforeEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/dom/__tests__/errorSuppression.pure.test.ts b/src/dom/__tests__/errorSuppression.pure.test.ts deleted file mode 100644 index e60ec710..00000000 --- a/src/dom/__tests__/errorSuppression.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (pure) tests', () => { - const originalConsoleError = console.error - - let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput'] - - beforeAll(() => { - suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) - - test('should manually patch console.error', () => { - const restore = suppressErrorOutput() - - try { - expect(console.error).not.toBe(originalConsoleError) - } finally { - restore() - } - - expect(console.error).toBe(originalConsoleError) - }) -}) diff --git a/src/dom/__tests__/resultHistory.test.ts b/src/dom/__tests__/resultHistory.test.ts deleted file mode 100644 index 69ce2408..00000000 --- a/src/dom/__tests__/resultHistory.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { renderHook } from '..' - -describe('result history tests', () => { - function useValue(value: number) { - if (value === 2) { - throw Error('expected') - } - return value - } - - test('should capture all renders states of hook', () => { - const { result, rerender } = renderHook((value) => useValue(value), { - initialProps: 0 - }) - - expect(result.current).toEqual(0) - expect(result.all).toEqual([0]) - - rerender(1) - - expect(result.current).toBe(1) - expect(result.all).toEqual([0, 1]) - - rerender(2) - - expect(result.error).toEqual(Error('expected')) - expect(result.all).toEqual([0, 1, Error('expected')]) - - rerender(3) - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 1, Error('expected'), 3]) - - rerender() - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 1, Error('expected'), 3, 3]) - }) -}) diff --git a/src/dom/__tests__/suspenseHook.test.ts b/src/dom/__tests__/suspenseHook.test.ts deleted file mode 100644 index 41e9f99a..00000000 --- a/src/dom/__tests__/suspenseHook.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { renderHook } from '..' - -describe('suspense hook tests', () => { - const cache: { value?: Promise | string | Error } = {} - const fetchName = (isSuccessful: boolean) => { - if (!cache.value) { - cache.value = new Promise((resolve, reject) => { - setTimeout(() => { - if (isSuccessful) { - resolve('Bob') - } else { - reject(new Error('Failed to fetch name')) - } - }, 50) - }) - .then((value) => (cache.value = value)) - .catch((e: Error) => (cache.value = e)) - } - return cache.value - } - - const useFetchName = (isSuccessful = true) => { - const name = fetchName(isSuccessful) - if (name instanceof Promise || name instanceof Error) { - throw name as unknown - } - return name - } - - beforeEach(() => { - delete cache.value - }) - - test('should allow rendering to be suspended', async () => { - const { result, waitForNextUpdate } = renderHook(() => useFetchName(true)) - - await waitForNextUpdate() - - expect(result.current).toBe('Bob') - }) - - test('should set error if suspense promise rejects', async () => { - const { result, waitForNextUpdate } = renderHook(() => useFetchName(false)) - - await waitForNextUpdate() - - expect(result.error).toEqual(new Error('Failed to fetch name')) - }) - - test('should return undefined if current value is requested before suspension has resolved', async () => { - const { result } = renderHook(() => useFetchName(true)) - - expect(result.current).toBe(undefined) - }) - - test('should return undefined if error is requested before suspension has resolved', async () => { - const { result } = renderHook(() => useFetchName(true)) - - expect(result.error).toBe(undefined) - }) -}) diff --git a/src/dom/__tests__/useContext.test.tsx b/src/dom/__tests__/useContext.test.tsx deleted file mode 100644 index 84046e30..00000000 --- a/src/dom/__tests__/useContext.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { createContext, useContext } from 'react' -import { renderHook } from '..' - -describe('useContext tests', () => { - test('should get default value from context', () => { - const TestContext = createContext('foo') - - const { result } = renderHook(() => useContext(TestContext)) - - const value = result.current - - expect(value).toBe('foo') - }) - - test('should get value from context provider', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC = ({ children }) => ( - {children} - ) - - const { result } = renderHook(() => useContext(TestContext), { wrapper }) - - expect(result.current).toBe('bar') - }) - - test('should update mutated value in context', () => { - const TestContext = createContext('foo') - - const value = { current: 'bar' } - - const wrapper: React.FC = ({ children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }) - - value.current = 'baz' - - rerender() - - expect(result.current).toBe('baz') - }) - - test('should update value in context when props are updated', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC<{ current: string }> = ({ current, children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { - wrapper, - initialProps: { - current: 'bar' - } - }) - - rerender({ current: 'baz' }) - - expect(result.current).toBe('baz') - }) -}) diff --git a/src/dom/__tests__/useEffect.test.ts b/src/dom/__tests__/useEffect.test.ts deleted file mode 100644 index 0091b7a8..00000000 --- a/src/dom/__tests__/useEffect.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useLayoutEffect } from 'react' -import { renderHook } from '..' - -describe('useEffect tests', () => { - test('should handle useEffect hook', () => { - const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } - - const { rerender, unmount } = renderHook( - ({ id }) => { - useEffect(() => { - sideEffect[id] = true - return () => { - sideEffect[id] = false - } - }, [id]) - }, - { initialProps: { id: 1 } } - ) - - expect(sideEffect[1]).toBe(true) - expect(sideEffect[2]).toBe(false) - - rerender({ id: 2 }) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(true) - - unmount() - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - }) - - test('should handle useLayoutEffect hook', () => { - const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } - - const { rerender, unmount } = renderHook( - ({ id }) => { - useLayoutEffect(() => { - sideEffect[id] = true - return () => { - sideEffect[id] = false - } - }, [id]) - }, - { initialProps: { id: 1 } } - ) - - expect(sideEffect[1]).toBe(true) - expect(sideEffect[2]).toBe(false) - - rerender({ id: 2 }) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(true) - - unmount() - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - }) -}) diff --git a/src/dom/__tests__/useMemo.test.ts b/src/dom/__tests__/useMemo.test.ts deleted file mode 100644 index dcf0de7d..00000000 --- a/src/dom/__tests__/useMemo.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useMemo, useCallback } from 'react' -import { renderHook } from '..' - -describe('useCallback tests', () => { - test('should handle useMemo hook', () => { - const { result, rerender } = renderHook(({ value }) => useMemo(() => ({ value }), [value]), { - initialProps: { value: 1 } - }) - - const value1 = result.current - - expect(value1).toEqual({ value: 1 }) - - rerender() - - const value2 = result.current - - expect(value2).toEqual({ value: 1 }) - - expect(value2).toBe(value1) - - rerender({ value: 2 }) - - const value3 = result.current - - expect(value3).toEqual({ value: 2 }) - - expect(value3).not.toBe(value1) - }) - - test('should handle useCallback hook', () => { - const { result, rerender } = renderHook( - ({ value }) => { - const callback = () => ({ value }) - return useCallback(callback, [value]) - }, - { initialProps: { value: 1 } } - ) - - const callback1 = result.current - - const callbackValue1 = callback1() - - expect(callbackValue1).toEqual({ value: 1 }) - - const callback2 = result.current - - const callbackValue2 = callback2() - - expect(callbackValue2).toEqual({ value: 1 }) - - expect(callback2).toBe(callback1) - - rerender({ value: 2 }) - - const callback3 = result.current - - const callbackValue3 = callback3() - - expect(callbackValue3).toEqual({ value: 2 }) - - expect(callback3).not.toBe(callback1) - }) -}) diff --git a/src/dom/__tests__/useReducer.test.ts b/src/dom/__tests__/useReducer.test.ts deleted file mode 100644 index fab39201..00000000 --- a/src/dom/__tests__/useReducer.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useReducer } from 'react' -import { renderHook, act } from '..' - -describe('useReducer tests', () => { - test('should handle useReducer hook', () => { - const reducer = (state: number, action: { type: string }) => - action.type === 'inc' ? state + 1 : state - const { result } = renderHook(() => useReducer(reducer, 0)) - - const [initialState, dispatch] = result.current - - expect(initialState).toBe(0) - - act(() => dispatch({ type: 'inc' })) - - const [state] = result.current - - expect(state).toBe(1) - }) -}) diff --git a/src/dom/__tests__/useRef.test.ts b/src/dom/__tests__/useRef.test.ts deleted file mode 100644 index a8663e16..00000000 --- a/src/dom/__tests__/useRef.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useRef, useImperativeHandle } from 'react' -import { renderHook } from '..' - -describe('useHook tests', () => { - test('should handle useRef hook', () => { - const { result } = renderHook(() => useRef()) - - const refContainer = result.current - - expect(Object.keys(refContainer)).toEqual(['current']) - expect(refContainer.current).toBeUndefined() - }) - - test('should handle useImperativeHandle hook', () => { - const { result } = renderHook(() => { - const ref = useRef boolean>>({}) - useImperativeHandle(ref, () => ({ - fakeImperativeMethod: () => true - })) - return ref - }) - - const refContainer = result.current - - expect(refContainer.current.fakeImperativeMethod()).toBe(true) - }) -}) diff --git a/src/dom/__tests__/useState.test.ts b/src/dom/__tests__/useState.test.ts deleted file mode 100644 index 78cbaa6f..00000000 --- a/src/dom/__tests__/useState.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useState } from 'react' -import { renderHook, act } from '..' - -describe('useState tests', () => { - test('should use setState value', () => { - const { result } = renderHook(() => useState('foo')) - - const [value] = result.current - - expect(value).toBe('foo') - }) - - test('should update setState value using setter', () => { - const { result } = renderHook(() => useState('foo')) - - const [ignoredValue, setValue] = result.current - - act(() => setValue('bar')) - - const [value] = result.current - - expect(value).toBe('bar') - }) -}) diff --git a/src/native/__tests__/asyncHook.test.ts b/src/native/__tests__/asyncHook.test.ts deleted file mode 100644 index d460d35f..00000000 --- a/src/native/__tests__/asyncHook.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { useState, useRef, useEffect } from 'react' -import { renderHook } from '..' - -describe('async hook tests', () => { - const useSequence = (values: string[], intervalMs = 50) => { - const [first, ...otherValues] = values - const [value, setValue] = useState(() => first) - const index = useRef(0) - - useEffect(() => { - const interval = setInterval(() => { - setValue(otherValues[index.current++]) - if (index.current >= otherValues.length) { - clearInterval(interval) - } - }, intervalMs) - return () => { - clearInterval(interval) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, otherValues) - - return value - } - - test('should wait for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - }) - - test('should wait for multiple updates', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - - await waitForNextUpdate() - - expect(result.current).toBe('third') - }) - - test('should reject if timeout exceeded when waiting for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( - Error('Timed out in waitForNextUpdate after 10ms.') - ) - }) - - test('should not reject when waiting for next update if timeout has been disabled', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) - - expect(result.current).toBe('first') - - await waitForNextUpdate({ timeout: false }) - - expect(result.current).toBe('second') - }) - - test('should wait for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('third') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for arbitrary expectation to pass', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - expect(complete).toBe(true) - }) - - test('should not hang if expectation is already passing', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for truthy value', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await waitFor(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary truthy value', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitFor(() => actual === 1) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await expect( - waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: 75 } - ) - ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) - }) - - test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) - - expect(result.current).toBe('first') - - await waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: false } - ) - - expect(result.current).toBe('third') - }) - - test('should check on interval when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - let checks = 0 - - try { - await waitFor( - () => { - checks++ - return result.current === 'third' - }, - { interval: 100 } - ) - } catch {} - - expect(checks).toBe(3) - }) - - test('should wait for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary value to change', async () => { - const { waitForValueToChange } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitForValueToChange(() => actual) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => result.current === 'third', { - timeout: 75 - }) - ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) - }) - - test('should not reject when waiting for value to change if timeout is disabled', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third', { - timeout: false - }) - - expect(result.current).toBe('third') - }) - - test('should reject if selector throws error', async () => { - const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current - }) - ).rejects.toThrow(Error('Something Unexpected')) - }) -}) diff --git a/src/native/__tests__/autoCleanup.disabled.test.ts b/src/native/__tests__/autoCleanup.disabled.test.ts deleted file mode 100644 index 2c574b83..00000000 --- a/src/native/__tests__/autoCleanup.disabled.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/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: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/native/__tests__/autoCleanup.noAfterEach.test.ts b/src/native/__tests__/autoCleanup.noAfterEach.test.ts deleted file mode 100644 index 40b33f16..00000000 --- a/src/native/__tests__/autoCleanup.noAfterEach.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (no afterEach) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/native/__tests__/autoCleanup.pure.test.ts b/src/native/__tests__/autoCleanup.pure.test.ts deleted file mode 100644 index 1f84b87c..00000000 --- a/src/native/__tests__/autoCleanup.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (pure) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - renderHook = (require('../pure') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/native/__tests__/autoCleanup.test.ts b/src/native/__tests__/autoCleanup.test.ts deleted file mode 100644 index f783f1c2..00000000 --- a/src/native/__tests__/autoCleanup.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect } from 'react' -import { renderHook } from '..' - -// 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/src/native/__tests__/cleanup.test.ts b/src/native/__tests__/cleanup.test.ts deleted file mode 100644 index 20a0f267..00000000 --- a/src/native/__tests__/cleanup.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect } from 'react' -import { renderHook, cleanup, addCleanup, removeCleanup } from '../pure' - -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 () => { - const cleanupCalled: boolean[] = [] - const hookWithCleanup = (id: number) => { - useEffect(() => { - return () => { - cleanupCalled[id] = true - } - }) - } - - renderHook(() => hookWithCleanup(1)) - renderHook(() => hookWithCleanup(2)) - - await cleanup() - - expect(cleanupCalled[1]).toBe(true) - expect(cleanupCalled[2]).toBe(true) - }) - - test('should call cleanups in reverse order', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - addCleanup(() => { - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup']) - }) - - test('should wait for async cleanup', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - addCleanup(async () => { - await new Promise((resolve) => setTimeout(resolve, 10)) - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup']) - }) - - test('should remove cleanup using removeCleanup', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - const anotherCleanup = () => { - callSequence.push('another cleanup') - } - addCleanup(anotherCleanup) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - removeCleanup(anotherCleanup) - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'cleanup']) - }) - - test('should remove cleanup using returned handler', async () => { - const callSequence: string[] = [] - addCleanup(() => { - callSequence.push('cleanup') - }) - const remove = addCleanup(() => { - callSequence.push('another cleanup') - }) - const hookWithCleanup = () => { - useEffect(() => { - return () => { - callSequence.push('unmount') - } - }) - } - renderHook(() => hookWithCleanup()) - - remove() - - await cleanup() - - expect(callSequence).toEqual(['unmount', 'cleanup']) - }) -}) diff --git a/src/native/__tests__/customHook.test.ts b/src/native/__tests__/customHook.test.ts deleted file mode 100644 index 5a1e83ab..00000000 --- a/src/native/__tests__/customHook.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useState, useCallback } from 'react' -import { renderHook, act } from '..' - -describe('custom hook tests', () => { - function useCounter() { - const [count, setCount] = useState(0) - - const increment = useCallback(() => setCount(count + 1), [count]) - const decrement = useCallback(() => setCount(count - 1), [count]) - - return { count, increment, decrement } - } - - test('should increment counter', () => { - const { result } = renderHook(() => useCounter()) - - act(() => result.current.increment()) - - expect(result.current.count).toBe(1) - }) - - test('should decrement counter', () => { - const { result } = renderHook(() => useCounter()) - - act(() => result.current.decrement()) - - expect(result.current.count).toBe(-1) - }) -}) diff --git a/src/native/__tests__/errorHook.test.ts b/src/native/__tests__/errorHook.test.ts deleted file mode 100644 index 69e54270..00000000 --- a/src/native/__tests__/errorHook.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { useState, useEffect } from 'react' -import { renderHook, act } from '..' - -describe('error hook tests', () => { - function useError(throwError?: boolean) { - if (throwError) { - throw new Error('expected') - } - return true - } - - function useAsyncError(throwError: boolean) { - const [value, setValue] = useState() - useEffect(() => { - const timeout = setTimeout(() => setValue(throwError), 100) - return () => clearTimeout(timeout) - }, [throwError]) - return useError(value) - } - - function useEffectError(throwError: boolean) { - useEffect(() => { - useError(throwError) - }, [throwError]) - return true - } - - describe('synchronous', () => { - test('should raise error', () => { - const { result } = renderHook(() => useError(true)) - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture error', () => { - const { result } = renderHook(() => useError(true)) - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture error', () => { - const { result } = renderHook(() => useError(false)) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset error', () => { - const { result, rerender } = renderHook(({ throwError }) => useError(throwError), { - initialProps: { throwError: true } - }) - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('asynchronous', () => { - test('should raise async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - await waitForNextUpdate() - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - await waitForNextUpdate() - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture async error', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsyncError(false)) - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset async error', async () => { - const { result, waitForNextUpdate, rerender } = renderHook( - ({ throwError }) => useAsyncError(throwError), - { initialProps: { throwError: true } } - ) - - await waitForNextUpdate() - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('effect', () => { - test('should raise effect error', () => { - const { result } = renderHook(() => useEffectError(true)) - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture effect error', () => { - const { result } = renderHook(() => useEffectError(true)) - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture effect error', () => { - const { result } = renderHook(() => useEffectError(false)) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset effect error', () => { - const { result, rerender } = renderHook(({ throwError }) => useEffectError(throwError), { - initialProps: { throwError: true } - }) - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('error output suppression', () => { - test('should allow console.error to be mocked', async () => { - const consoleError = console.error - console.error = jest.fn() - - try { - const { rerender, unmount } = renderHook( - (stage) => { - useEffect(() => { - console.error(`expected in effect`) - return () => { - console.error(`expected in unmount`) - } - }, []) - console.error(`expected in ${stage}`) - }, - { - initialProps: 'render' - } - ) - - act(() => { - console.error('expected in act') - }) - - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) - console.error('expected in async act') - }) - - rerender('rerender') - - unmount() - - expect(console.error).toBeCalledWith('expected in render') - expect(console.error).toBeCalledWith('expected in effect') - expect(console.error).toBeCalledWith('expected in act') - expect(console.error).toBeCalledWith('expected in async act') - expect(console.error).toBeCalledWith('expected in rerender') - expect(console.error).toBeCalledWith('expected in unmount') - expect(console.error).toBeCalledTimes(6) - } finally { - console.error = consoleError - } - }) - }) -}) diff --git a/src/native/__tests__/errorSuppression.disabled.test.ts b/src/native/__tests__/errorSuppression.disabled.test.ts deleted file mode 100644 index e1921f09..00000000 --- a/src/native/__tests__/errorSuppression.disabled.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This verifies that if RHTL_DISABLE_ERROR_FILTERING is set -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (disabled) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - process.env.RHTL_DISABLE_ERROR_FILTERING = 'true' - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/native/__tests__/errorSuppression.noAfterEach.test.ts b/src/native/__tests__/errorSuppression.noAfterEach.test.ts deleted file mode 100644 index c736020e..00000000 --- a/src/native/__tests__/errorSuppression.noAfterEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noAfterEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/native/__tests__/errorSuppression.noBeforeEach.test.ts b/src/native/__tests__/errorSuppression.noBeforeEach.test.ts deleted file mode 100644 index c3f2496f..00000000 --- a/src/native/__tests__/errorSuppression.noBeforeEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noBeforeEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off BeforeEach -- ignore Jest LifeCycle Type - beforeEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/native/__tests__/errorSuppression.pure.test.ts b/src/native/__tests__/errorSuppression.pure.test.ts deleted file mode 100644 index e60ec710..00000000 --- a/src/native/__tests__/errorSuppression.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (pure) tests', () => { - const originalConsoleError = console.error - - let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput'] - - beforeAll(() => { - suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) - - test('should manually patch console.error', () => { - const restore = suppressErrorOutput() - - try { - expect(console.error).not.toBe(originalConsoleError) - } finally { - restore() - } - - expect(console.error).toBe(originalConsoleError) - }) -}) diff --git a/src/native/__tests__/resultHistory.test.ts b/src/native/__tests__/resultHistory.test.ts deleted file mode 100644 index 69ce2408..00000000 --- a/src/native/__tests__/resultHistory.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { renderHook } from '..' - -describe('result history tests', () => { - function useValue(value: number) { - if (value === 2) { - throw Error('expected') - } - return value - } - - test('should capture all renders states of hook', () => { - const { result, rerender } = renderHook((value) => useValue(value), { - initialProps: 0 - }) - - expect(result.current).toEqual(0) - expect(result.all).toEqual([0]) - - rerender(1) - - expect(result.current).toBe(1) - expect(result.all).toEqual([0, 1]) - - rerender(2) - - expect(result.error).toEqual(Error('expected')) - expect(result.all).toEqual([0, 1, Error('expected')]) - - rerender(3) - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 1, Error('expected'), 3]) - - rerender() - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 1, Error('expected'), 3, 3]) - }) -}) diff --git a/src/native/__tests__/suspenseHook.test.ts b/src/native/__tests__/suspenseHook.test.ts deleted file mode 100644 index 41e9f99a..00000000 --- a/src/native/__tests__/suspenseHook.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { renderHook } from '..' - -describe('suspense hook tests', () => { - const cache: { value?: Promise | string | Error } = {} - const fetchName = (isSuccessful: boolean) => { - if (!cache.value) { - cache.value = new Promise((resolve, reject) => { - setTimeout(() => { - if (isSuccessful) { - resolve('Bob') - } else { - reject(new Error('Failed to fetch name')) - } - }, 50) - }) - .then((value) => (cache.value = value)) - .catch((e: Error) => (cache.value = e)) - } - return cache.value - } - - const useFetchName = (isSuccessful = true) => { - const name = fetchName(isSuccessful) - if (name instanceof Promise || name instanceof Error) { - throw name as unknown - } - return name - } - - beforeEach(() => { - delete cache.value - }) - - test('should allow rendering to be suspended', async () => { - const { result, waitForNextUpdate } = renderHook(() => useFetchName(true)) - - await waitForNextUpdate() - - expect(result.current).toBe('Bob') - }) - - test('should set error if suspense promise rejects', async () => { - const { result, waitForNextUpdate } = renderHook(() => useFetchName(false)) - - await waitForNextUpdate() - - expect(result.error).toEqual(new Error('Failed to fetch name')) - }) - - test('should return undefined if current value is requested before suspension has resolved', async () => { - const { result } = renderHook(() => useFetchName(true)) - - expect(result.current).toBe(undefined) - }) - - test('should return undefined if error is requested before suspension has resolved', async () => { - const { result } = renderHook(() => useFetchName(true)) - - expect(result.error).toBe(undefined) - }) -}) diff --git a/src/native/__tests__/useContext.test.tsx b/src/native/__tests__/useContext.test.tsx deleted file mode 100644 index 84046e30..00000000 --- a/src/native/__tests__/useContext.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { createContext, useContext } from 'react' -import { renderHook } from '..' - -describe('useContext tests', () => { - test('should get default value from context', () => { - const TestContext = createContext('foo') - - const { result } = renderHook(() => useContext(TestContext)) - - const value = result.current - - expect(value).toBe('foo') - }) - - test('should get value from context provider', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC = ({ children }) => ( - {children} - ) - - const { result } = renderHook(() => useContext(TestContext), { wrapper }) - - expect(result.current).toBe('bar') - }) - - test('should update mutated value in context', () => { - const TestContext = createContext('foo') - - const value = { current: 'bar' } - - const wrapper: React.FC = ({ children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }) - - value.current = 'baz' - - rerender() - - expect(result.current).toBe('baz') - }) - - test('should update value in context when props are updated', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC<{ current: string }> = ({ current, children }) => ( - {children} - ) - - const { result, rerender } = renderHook(() => useContext(TestContext), { - wrapper, - initialProps: { - current: 'bar' - } - }) - - rerender({ current: 'baz' }) - - expect(result.current).toBe('baz') - }) -}) diff --git a/src/native/__tests__/useEffect.test.ts b/src/native/__tests__/useEffect.test.ts deleted file mode 100644 index 0091b7a8..00000000 --- a/src/native/__tests__/useEffect.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useLayoutEffect } from 'react' -import { renderHook } from '..' - -describe('useEffect tests', () => { - test('should handle useEffect hook', () => { - const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } - - const { rerender, unmount } = renderHook( - ({ id }) => { - useEffect(() => { - sideEffect[id] = true - return () => { - sideEffect[id] = false - } - }, [id]) - }, - { initialProps: { id: 1 } } - ) - - expect(sideEffect[1]).toBe(true) - expect(sideEffect[2]).toBe(false) - - rerender({ id: 2 }) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(true) - - unmount() - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - }) - - test('should handle useLayoutEffect hook', () => { - const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } - - const { rerender, unmount } = renderHook( - ({ id }) => { - useLayoutEffect(() => { - sideEffect[id] = true - return () => { - sideEffect[id] = false - } - }, [id]) - }, - { initialProps: { id: 1 } } - ) - - expect(sideEffect[1]).toBe(true) - expect(sideEffect[2]).toBe(false) - - rerender({ id: 2 }) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(true) - - unmount() - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - }) -}) diff --git a/src/native/__tests__/useMemo.test.ts b/src/native/__tests__/useMemo.test.ts deleted file mode 100644 index dcf0de7d..00000000 --- a/src/native/__tests__/useMemo.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useMemo, useCallback } from 'react' -import { renderHook } from '..' - -describe('useCallback tests', () => { - test('should handle useMemo hook', () => { - const { result, rerender } = renderHook(({ value }) => useMemo(() => ({ value }), [value]), { - initialProps: { value: 1 } - }) - - const value1 = result.current - - expect(value1).toEqual({ value: 1 }) - - rerender() - - const value2 = result.current - - expect(value2).toEqual({ value: 1 }) - - expect(value2).toBe(value1) - - rerender({ value: 2 }) - - const value3 = result.current - - expect(value3).toEqual({ value: 2 }) - - expect(value3).not.toBe(value1) - }) - - test('should handle useCallback hook', () => { - const { result, rerender } = renderHook( - ({ value }) => { - const callback = () => ({ value }) - return useCallback(callback, [value]) - }, - { initialProps: { value: 1 } } - ) - - const callback1 = result.current - - const callbackValue1 = callback1() - - expect(callbackValue1).toEqual({ value: 1 }) - - const callback2 = result.current - - const callbackValue2 = callback2() - - expect(callbackValue2).toEqual({ value: 1 }) - - expect(callback2).toBe(callback1) - - rerender({ value: 2 }) - - const callback3 = result.current - - const callbackValue3 = callback3() - - expect(callbackValue3).toEqual({ value: 2 }) - - expect(callback3).not.toBe(callback1) - }) -}) diff --git a/src/native/__tests__/useReducer.test.ts b/src/native/__tests__/useReducer.test.ts deleted file mode 100644 index fab39201..00000000 --- a/src/native/__tests__/useReducer.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useReducer } from 'react' -import { renderHook, act } from '..' - -describe('useReducer tests', () => { - test('should handle useReducer hook', () => { - const reducer = (state: number, action: { type: string }) => - action.type === 'inc' ? state + 1 : state - const { result } = renderHook(() => useReducer(reducer, 0)) - - const [initialState, dispatch] = result.current - - expect(initialState).toBe(0) - - act(() => dispatch({ type: 'inc' })) - - const [state] = result.current - - expect(state).toBe(1) - }) -}) diff --git a/src/native/__tests__/useRef.test.ts b/src/native/__tests__/useRef.test.ts deleted file mode 100644 index a8663e16..00000000 --- a/src/native/__tests__/useRef.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useRef, useImperativeHandle } from 'react' -import { renderHook } from '..' - -describe('useHook tests', () => { - test('should handle useRef hook', () => { - const { result } = renderHook(() => useRef()) - - const refContainer = result.current - - expect(Object.keys(refContainer)).toEqual(['current']) - expect(refContainer.current).toBeUndefined() - }) - - test('should handle useImperativeHandle hook', () => { - const { result } = renderHook(() => { - const ref = useRef boolean>>({}) - useImperativeHandle(ref, () => ({ - fakeImperativeMethod: () => true - })) - return ref - }) - - const refContainer = result.current - - expect(refContainer.current.fakeImperativeMethod()).toBe(true) - }) -}) diff --git a/src/native/__tests__/useState.test.ts b/src/native/__tests__/useState.test.ts deleted file mode 100644 index 78cbaa6f..00000000 --- a/src/native/__tests__/useState.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useState } from 'react' -import { renderHook, act } from '..' - -describe('useState tests', () => { - test('should use setState value', () => { - const { result } = renderHook(() => useState('foo')) - - const [value] = result.current - - expect(value).toBe('foo') - }) - - test('should update setState value using setter', () => { - const { result } = renderHook(() => useState('foo')) - - const [ignoredValue, setValue] = result.current - - act(() => setValue('bar')) - - const [value] = result.current - - expect(value).toBe('bar') - }) -}) diff --git a/src/server/__tests__/asyncHook.test.ts b/src/server/__tests__/asyncHook.test.ts deleted file mode 100644 index 7d23a981..00000000 --- a/src/server/__tests__/asyncHook.test.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { useState, useRef, useEffect } from 'react' -import { renderHook } from '..' - -describe('async hook tests', () => { - const useSequence = (values: string[], intervalMs = 50) => { - const [first, ...otherValues] = values - const [value, setValue] = useState(() => first) - const index = useRef(0) - - useEffect(() => { - const interval = setInterval(() => { - setValue(otherValues[index.current++]) - if (index.current >= otherValues.length) { - clearInterval(interval) - } - }, intervalMs) - return () => { - clearInterval(interval) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, otherValues) - - return value - } - - test('should wait for next update', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - }) - - test('should wait for multiple updates', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - - await waitForNextUpdate() - - expect(result.current).toBe('third') - }) - - test('should reject if timeout exceeded when waiting for next update', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( - Error('Timed out in waitForNextUpdate after 10ms.') - ) - }) - - test('should not reject when waiting for next update if timeout has been disabled', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second'], 1100) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitForNextUpdate({ timeout: false }) - - expect(result.current).toBe('second') - }) - - test('should wait for expectation to pass', async () => { - const { result, hydrate, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('third') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for arbitrary expectation to pass', async () => { - const { waitFor, hydrate } = renderHook(() => null) - - hydrate() - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - expect(complete).toBe(true) - }) - - test('should not hang if expectation is already passing', async () => { - const { result, hydrate, waitFor } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for truthy value', async () => { - const { result, hydrate, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitFor(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary truthy value', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitFor(() => actual === 1) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for expectation to pass', async () => { - const { result, hydrate, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await expect( - waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: 75 } - ) - ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) - }) - - test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { - const { result, hydrate, waitFor } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: false } - ) - - expect(result.current).toBe('third') - }) - - test('should check on interval when waiting for expectation to pass', async () => { - const { result, waitFor, hydrate } = renderHook(() => useSequence(['first', 'second', 'third'])) - - hydrate() - - let checks = 0 - - try { - await waitFor( - () => { - checks++ - return result.current === 'third' - }, - { interval: 100 } - ) - } catch {} - - expect(checks).toBe(3) - }) - - test('should wait for value to change', async () => { - const { result, hydrate, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary value to change', async () => { - const { waitForValueToChange } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitForValueToChange(() => actual) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for value to change', async () => { - const { result, hydrate, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => result.current === 'third', { - timeout: 75 - }) - ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) - }) - - test('should not reject when waiting for value to change if timeout is disabled', async () => { - const { result, hydrate, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third', { - timeout: false - }) - - expect(result.current).toBe('third') - }) - - test('should reject if selector throws error', async () => { - const { result, hydrate, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second']) - ) - - expect(result.current).toBe('first') - - hydrate() - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current - }) - ).rejects.toThrow(Error('Something Unexpected')) - }) -}) diff --git a/src/server/__tests__/autoCleanup.disabled.test.ts b/src/server/__tests__/autoCleanup.disabled.test.ts deleted file mode 100644 index 2c574b83..00000000 --- a/src/server/__tests__/autoCleanup.disabled.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/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: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/server/__tests__/autoCleanup.noAfterEach.test.ts b/src/server/__tests__/autoCleanup.noAfterEach.test.ts deleted file mode 100644 index 40b33f16..00000000 --- a/src/server/__tests__/autoCleanup.noAfterEach.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (no afterEach) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - renderHook = (require('..') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/server/__tests__/autoCleanup.pure.test.ts b/src/server/__tests__/autoCleanup.pure.test.ts deleted file mode 100644 index 1f84b87c..00000000 --- a/src/server/__tests__/autoCleanup.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect } from 'react' - -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('skip auto cleanup (pure) tests', () => { - let cleanupCalled = false - let renderHook: ReactHooksRenderer['renderHook'] - - beforeAll(() => { - renderHook = (require('../pure') as ReactHooksRenderer).renderHook - }) - - test('first', () => { - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - renderHook(() => hookWithCleanup()) - }) - - test('second', () => { - expect(cleanupCalled).toBe(false) - }) -}) diff --git a/src/server/__tests__/autoCleanup.test.ts b/src/server/__tests__/autoCleanup.test.ts deleted file mode 100644 index 87e473c1..00000000 --- a/src/server/__tests__/autoCleanup.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect } from 'react' -import { renderHook } from '..' - -// 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', () => { - const cleanups: Record = { - ssr: false, - hydrated: false - } - - test('first', () => { - const hookWithCleanup = (name: string) => { - useEffect(() => { - return () => { - cleanups[name] = true - } - }) - } - - renderHook(() => hookWithCleanup('ssr')) - - const { hydrate } = renderHook(() => hookWithCleanup('hydrated')) - hydrate() - }) - - test('second', () => { - expect(cleanups.ssr).toBe(false) - expect(cleanups.hydrated).toBe(true) - }) -}) diff --git a/src/server/__tests__/cleanup.test.ts b/src/server/__tests__/cleanup.test.ts deleted file mode 100644 index c12815ac..00000000 --- a/src/server/__tests__/cleanup.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { useEffect } from 'react' -import { renderHook, cleanup } from '..' - -describe('cleanup tests', () => { - test('should flush effects on cleanup', async () => { - let cleanupCalled = false - - const hookWithCleanup = () => { - useEffect(() => { - return () => { - cleanupCalled = true - } - }) - } - - const { hydrate } = renderHook(() => hookWithCleanup()) - - hydrate() - - await cleanup() - - expect(cleanupCalled).toBe(true) - }) - - test('should cleanup all rendered hooks', async () => { - let cleanupCalled = [false, false] - const hookWithCleanup = (id: number) => { - useEffect(() => { - return () => { - cleanupCalled = cleanupCalled.map((_, i) => (i === id ? true : _)) - } - }) - } - - const { hydrate: hydrate1 } = renderHook(() => hookWithCleanup(0)) - const { hydrate: hydrate2 } = renderHook(() => hookWithCleanup(1)) - - hydrate1() - hydrate2() - - await cleanup() - - expect(cleanupCalled[0]).toBe(true) - expect(cleanupCalled[1]).toBe(true) - }) - - test('should only cleanup hydrated hooks', async () => { - let cleanupCalled = [false, false] - const hookWithCleanup = (id: number) => { - useEffect(() => { - return () => { - cleanupCalled = cleanupCalled.map((_, i) => (i === id ? true : _)) - } - }) - } - - renderHook(() => hookWithCleanup(0)) - const { hydrate } = renderHook(() => hookWithCleanup(1)) - - hydrate() - - await cleanup() - - expect(cleanupCalled[0]).toBe(false) - expect(cleanupCalled[1]).toBe(true) - }) -}) diff --git a/src/server/__tests__/customHook.test.ts b/src/server/__tests__/customHook.test.ts deleted file mode 100644 index cb512682..00000000 --- a/src/server/__tests__/customHook.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useState, useCallback } from 'react' -import { renderHook, act } from '..' - -describe('custom hook tests', () => { - function useCounter() { - const [count, setCount] = useState(0) - - const increment = useCallback(() => setCount(count + 1), [count]) - const decrement = useCallback(() => setCount(count - 1), [count]) - - return { count, increment, decrement } - } - - test('should increment counter', () => { - const { result, hydrate } = renderHook(() => useCounter()) - - hydrate() - - act(() => result.current.increment()) - - expect(result.current.count).toBe(1) - }) - - test('should decrement counter', () => { - const { result, hydrate } = renderHook(() => useCounter()) - - hydrate() - - act(() => result.current.decrement()) - - expect(result.current.count).toBe(-1) - }) -}) diff --git a/src/server/__tests__/errorHook.test.ts b/src/server/__tests__/errorHook.test.ts deleted file mode 100644 index f3ce0442..00000000 --- a/src/server/__tests__/errorHook.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { useState, useEffect } from 'react' - -import { renderHook, act } from '..' - -describe('error hook tests', () => { - function useError(throwError?: boolean) { - if (throwError) { - throw new Error('expected') - } - return true - } - - function useAsyncError(throwError: boolean) { - const [value, setValue] = useState() - useEffect(() => { - const timeout = setTimeout(() => setValue(throwError), 100) - return () => clearTimeout(timeout) - }, [throwError]) - return useError(value) - } - - function useEffectError(throwError: boolean) { - useEffect(() => { - useError(throwError) - }, [throwError]) - return true - } - - describe('synchronous', () => { - test('should raise error', () => { - const { result } = renderHook(() => useError(true)) - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture error', () => { - const { result } = renderHook(() => useError(true)) - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture error', () => { - const { result } = renderHook(() => useError(false)) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset error', () => { - const { result, hydrate, rerender } = renderHook(({ throwError }) => useError(throwError), { - initialProps: { throwError: true } - }) - - expect(result.error).not.toBe(undefined) - - hydrate() - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('asynchronous', () => { - test('should raise async error', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - hydrate() - - await waitForNextUpdate() - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture async error', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(true)) - - hydrate() - - await waitForNextUpdate() - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture async error', async () => { - const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncError(false)) - - hydrate() - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset async error', async () => { - const { result, hydrate, waitForNextUpdate, rerender } = renderHook( - ({ throwError }) => useAsyncError(throwError), - { initialProps: { throwError: true } } - ) - - hydrate() - - await waitForNextUpdate() - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - await waitForNextUpdate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('effect', () => { - test('should raise effect error', () => { - const { result, hydrate } = renderHook(() => useEffectError(true)) - - hydrate() - - expect(() => { - expect(result.current).not.toBe(undefined) - }).toThrow(Error('expected')) - }) - - test('should capture effect error', () => { - const { result, hydrate } = renderHook(() => useEffectError(true)) - - hydrate() - - expect(result.error).toEqual(Error('expected')) - }) - - test('should not capture effect error', () => { - const { result, hydrate } = renderHook(() => useEffectError(false)) - - hydrate() - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - - test('should reset effect error', () => { - const { result, hydrate, rerender } = renderHook( - ({ throwError }) => useEffectError(throwError), - { initialProps: { throwError: true } } - ) - - hydrate() - - expect(result.error).not.toBe(undefined) - - rerender({ throwError: false }) - - expect(result.current).not.toBe(undefined) - expect(result.error).toBe(undefined) - }) - }) - - describe('error output suppression', () => { - test('should allow console.error to be mocked', async () => { - const consoleError = console.error - console.error = jest.fn() - - try { - const { hydrate, rerender, unmount } = renderHook( - (stage) => { - useEffect(() => { - console.error(`expected in effect`) - return () => { - console.error(`expected in unmount`) - } - }, []) - console.error(`expected in ${stage}`) - }, - { - initialProps: 'render' - } - ) - - hydrate() - - act(() => { - console.error('expected in act') - }) - - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)) - console.error('expected in async act') - }) - - rerender('rerender') - - unmount() - - expect(console.error).toBeCalledWith('expected in render') // twice render/hydrate - expect(console.error).toBeCalledWith('expected in effect') - expect(console.error).toBeCalledWith('expected in act') - expect(console.error).toBeCalledWith('expected in async act') - expect(console.error).toBeCalledWith('expected in rerender') - expect(console.error).toBeCalledWith('expected in unmount') - expect(console.error).toBeCalledTimes(7) - } finally { - console.error = consoleError - } - }) - }) -}) diff --git a/src/server/__tests__/errorSuppression.disabled.test.ts b/src/server/__tests__/errorSuppression.disabled.test.ts deleted file mode 100644 index e1921f09..00000000 --- a/src/server/__tests__/errorSuppression.disabled.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This verifies that if RHTL_DISABLE_ERROR_FILTERING is set -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (disabled) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - process.env.RHTL_DISABLE_ERROR_FILTERING = 'true' - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/server/__tests__/errorSuppression.noAfterEach.test.ts b/src/server/__tests__/errorSuppression.noAfterEach.test.ts deleted file mode 100644 index c736020e..00000000 --- a/src/server/__tests__/errorSuppression.noAfterEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noAfterEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type - afterEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/server/__tests__/errorSuppression.noBeforeEach.test.ts b/src/server/__tests__/errorSuppression.noBeforeEach.test.ts deleted file mode 100644 index c3f2496f..00000000 --- a/src/server/__tests__/errorSuppression.noBeforeEach.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This verifies that if afterEach is unavailable -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (noBeforeEach) tests', () => { - const originalConsoleError = console.error - - beforeAll(() => { - // @ts-expect-error Turning off BeforeEach -- ignore Jest LifeCycle Type - beforeEach = false - require('..') - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) -}) - -export {} diff --git a/src/server/__tests__/errorSuppression.pure.test.ts b/src/server/__tests__/errorSuppression.pure.test.ts deleted file mode 100644 index e60ec710..00000000 --- a/src/server/__tests__/errorSuppression.pure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ReactHooksRenderer } from '../../types/react' - -// This verifies that if pure imports are used -// then we DON'T auto-wire up the afterEach for folks -describe('error output suppression (pure) tests', () => { - const originalConsoleError = console.error - - let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput'] - - beforeAll(() => { - suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput - }) - - test('should not patch console.error', () => { - expect(console.error).toBe(originalConsoleError) - }) - - test('should manually patch console.error', () => { - const restore = suppressErrorOutput() - - try { - expect(console.error).not.toBe(originalConsoleError) - } finally { - restore() - } - - expect(console.error).toBe(originalConsoleError) - }) -}) diff --git a/src/server/__tests__/hydrationErrors.test.ts b/src/server/__tests__/hydrationErrors.test.ts deleted file mode 100644 index 56a11aea..00000000 --- a/src/server/__tests__/hydrationErrors.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useState, useCallback } from 'react' -import { renderHook } from '..' - -describe('hydration errors tests', () => { - function useCounter() { - const [count, setCount] = useState(0) - - const increment = useCallback(() => setCount(count + 1), [count]) - const decrement = useCallback(() => setCount(count - 1), [count]) - - return { count, increment, decrement } - } - - test('should throw error if component is rehydrated twice in a row', () => { - const { hydrate } = renderHook(() => useCounter()) - - hydrate() - - expect(() => hydrate()).toThrow(Error('The component can only be hydrated once')) - }) - - test('should throw error if component tries to rerender without hydrating', () => { - const { rerender } = renderHook(() => useCounter()) - - expect(() => rerender()).toThrow( - Error('You must hydrate the component before you can rerender') - ) - }) -}) diff --git a/src/server/__tests__/resultHistory.test.ts b/src/server/__tests__/resultHistory.test.ts deleted file mode 100644 index 5f2f8b9c..00000000 --- a/src/server/__tests__/resultHistory.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { renderHook } from '..' - -describe('result history tests', () => { - function useValue(value: number) { - if (value === 2) { - throw Error('expected') - } - return value - } - - test('should capture all renders states of hook', () => { - const { result, hydrate, rerender } = renderHook((value) => useValue(value), { - initialProps: 0 - }) - - expect(result.current).toEqual(0) - expect(result.all).toEqual([0]) - - hydrate() - - expect(result.current).toEqual(0) - expect(result.all).toEqual([0, 0]) - - rerender(1) - - expect(result.current).toBe(1) - expect(result.all).toEqual([0, 0, 1]) - - rerender(2) - - expect(result.error).toEqual(Error('expected')) - expect(result.all).toEqual([0, 0, 1, Error('expected')]) - - rerender(3) - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 0, 1, Error('expected'), 3]) - - rerender() - - expect(result.current).toBe(3) - expect(result.all).toEqual([0, 0, 1, Error('expected'), 3, 3]) - }) -}) diff --git a/src/server/__tests__/ssr.test.ts b/src/server/__tests__/ssr.test.ts new file mode 100644 index 00000000..57982a61 --- /dev/null +++ b/src/server/__tests__/ssr.test.ts @@ -0,0 +1,22 @@ +/** + * @jest-environment node + */ +import {useState} from 'react' +import {renderHook, act} from '..' + +// This verifies that renderHook can be called in +// a SSR-like environment. +describe('renderHook', () => { + function useLoading() { + if (typeof window !== 'undefined') { + const [loading, setLoading] = useState(false) + return {loading, setLoading} + } + } + + test('should not throw in SSR environment', () => { + expect(() => renderHook(() => useLoading())).not.toThrowError( + 'document is not defined', + ) + }) +}) diff --git a/src/server/__tests__/useContext.test.tsx b/src/server/__tests__/useContext.test.tsx deleted file mode 100644 index cf92aab4..00000000 --- a/src/server/__tests__/useContext.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { createContext, useContext } from 'react' -import { renderHook } from '..' - -describe('useContext tests', () => { - test('should get default value from context', () => { - const TestContext = createContext('foo') - - const { result } = renderHook(() => useContext(TestContext)) - - const value = result.current - - expect(value).toBe('foo') - }) - - test('should get value from context provider', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC = ({ children }) => ( - {children} - ) - - const { result } = renderHook(() => useContext(TestContext), { wrapper }) - - expect(result.current).toBe('bar') - }) - - test('should update value in context when props are updated', () => { - const TestContext = createContext('foo') - - const wrapper: React.FC<{ contextValue: string }> = ({ contextValue, children }) => ( - {children} - ) - - const { result, hydrate, rerender } = renderHook(() => useContext(TestContext), { - wrapper, - initialProps: { contextValue: 'bar' } - }) - - hydrate() - - rerender({ contextValue: 'baz' }) - - expect(result.current).toBe('baz') - }) -}) diff --git a/src/server/__tests__/useEffect.test.ts b/src/server/__tests__/useEffect.test.ts deleted file mode 100644 index 782b7a03..00000000 --- a/src/server/__tests__/useEffect.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect } from 'react' -import { renderHook } from '..' - -describe('useEffect tests', () => { - test('should handle useEffect hook', () => { - const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } - - const { hydrate, rerender, unmount } = renderHook( - ({ id }) => { - useEffect(() => { - sideEffect[id] = true - return () => { - sideEffect[id] = false - } - }, [id]) - }, - { initialProps: { id: 1 } } - ) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - - hydrate() - - expect(sideEffect[1]).toBe(true) - expect(sideEffect[2]).toBe(false) - - rerender({ id: 2 }) - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(true) - - unmount() - - expect(sideEffect[1]).toBe(false) - expect(sideEffect[2]).toBe(false) - }) -}) diff --git a/src/server/__tests__/useMemo.test.ts b/src/server/__tests__/useMemo.test.ts deleted file mode 100644 index d762cf6a..00000000 --- a/src/server/__tests__/useMemo.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { useMemo, useCallback } from 'react' -import { renderHook } from '..' - -describe('useCallback tests', () => { - test('should handle useMemo hook', () => { - const { result, hydrate, rerender } = renderHook( - ({ value }) => useMemo(() => ({ value }), [value]), - { - initialProps: { value: 1 } - } - ) - - const value1 = result.current - - expect(value1).toEqual({ value: 1 }) - - hydrate() - - const value2 = result.current - - expect(value2).toEqual({ value: 1 }) - - expect(value2).not.toBe(value1) - - rerender() - - const value3 = result.current - - expect(value3).toEqual({ value: 1 }) - - expect(value3).toBe(value2) - - rerender({ value: 2 }) - - const value4 = result.current - - expect(value4).toEqual({ value: 2 }) - - expect(value4).not.toBe(value2) - }) - - test('should handle useCallback hook', () => { - const { result, hydrate, rerender } = renderHook( - ({ value }) => { - const callback = () => ({ value }) - return useCallback(callback, [value]) - }, - { initialProps: { value: 1 } } - ) - - const callback1 = result.current - - const calbackValue1 = callback1() - - expect(calbackValue1).toEqual({ value: 1 }) - - hydrate() - - const callback2 = result.current - - const calbackValue2 = callback2() - - expect(calbackValue2).toEqual({ value: 1 }) - - expect(callback2).not.toBe(callback1) - - rerender() - - const callback3 = result.current - - const calbackValue3 = callback3() - - expect(calbackValue3).toEqual({ value: 1 }) - - expect(callback3).toBe(callback2) - - rerender({ value: 2 }) - - const callback4 = result.current - - const calbackValue4 = callback4() - - expect(calbackValue4).toEqual({ value: 2 }) - - expect(callback4).not.toBe(callback2) - }) -}) diff --git a/src/server/__tests__/useReducer.test.ts b/src/server/__tests__/useReducer.test.ts deleted file mode 100644 index 6184094a..00000000 --- a/src/server/__tests__/useReducer.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useReducer } from 'react' -import { renderHook, act } from '..' - -describe('useReducer tests', () => { - test('should handle useReducer hook', () => { - const reducer = (state: number, action: { type: string }) => - action.type === 'inc' ? state + 1 : state - - const { result, hydrate } = renderHook(() => { - const [state, dispatch] = useReducer(reducer, 0) - return { state, dispatch } - }) - - hydrate() - - expect(result.current.state).toBe(0) - - act(() => result.current.dispatch({ type: 'inc' })) - - expect(result.current.state).toBe(1) - }) -}) diff --git a/src/server/__tests__/useRef.test.ts b/src/server/__tests__/useRef.test.ts deleted file mode 100644 index f30d0bd7..00000000 --- a/src/server/__tests__/useRef.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useRef, useImperativeHandle } from 'react' -import { renderHook } from '..' - -describe('useHook tests', () => { - test('should handle useRef hook', () => { - const { result } = renderHook(() => useRef('foo')) - - const refContainer = result.current - - expect(Object.keys(refContainer)).toEqual(['current']) - expect(refContainer.current).toBe('foo') - }) - - test('should handle useImperativeHandle hook', () => { - const { result, hydrate } = renderHook(() => { - const ref = useRef boolean>>({}) - useImperativeHandle(ref, () => ({ - fakeImperativeMethod: () => true - })) - return ref - }) - - expect(result.current.current).toEqual({}) - - hydrate() - - expect(result.current.current.fakeImperativeMethod()).toBe(true) - }) -}) diff --git a/src/server/__tests__/useState.test.ts b/src/server/__tests__/useState.test.ts deleted file mode 100644 index 27925863..00000000 --- a/src/server/__tests__/useState.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useState } from 'react' -import { renderHook, act } from '..' - -describe('useState tests', () => { - test('should use state value', () => { - const { result } = renderHook(() => { - const [value, setValue] = useState('foo') - return { value, setValue } - }) - - expect(result.current.value).toBe('foo') - }) - - test('should retain state value after hydration', () => { - const { result, hydrate } = renderHook(() => { - const [value, setValue] = useState('foo') - return { value, setValue } - }) - - hydrate() - - expect(result.current.value).toBe('foo') - }) - - test('should update state value using setter', () => { - const { result, hydrate } = renderHook(() => { - const [value, setValue] = useState('foo') - return { value, setValue } - }) - - hydrate() - - act(() => { - result.current.setValue('bar') - }) - - expect(result.current.value).toBe('bar') - }) -}) diff --git a/src/server/pure.ts b/src/server/pure.ts index 1bacd008..2e6c91f4 100644 --- a/src/server/pure.ts +++ b/src/server/pure.ts @@ -12,8 +12,8 @@ function createServerRenderer( { wrapper }: RendererOptions ) { let renderProps: TProps | undefined - let hydrated = false - const container = document.createElement('div') + let container: HTMLDivElement | undefined + let serverOutput = '' const testHarness = createTestHarness(rendererProps, wrapper, false) return { @@ -21,33 +21,32 @@ function createServerRenderer( renderProps = props act(() => { try { - const serverOutput = ReactDOMServer.renderToString(testHarness(props)) - container.innerHTML = serverOutput + serverOutput = ReactDOMServer.renderToString(testHarness(props)) } catch (e: unknown) { rendererProps.setError(e as Error) } }) }, hydrate() { - if (hydrated) { + if (container) { throw new Error('The component can only be hydrated once') } else { + container.innerHTML = serverOutput act(() => { - ReactDOM.hydrate(testHarness(renderProps), container) + ReactDOM.hydrate(testHarness(renderProps), container || null) }) - hydrated = true } }, rerender(props?: TProps) { - if (!hydrated) { + if (!container) { throw new Error('You must hydrate the component before you can rerender') } act(() => { - ReactDOM.render(testHarness(props), container) + ReactDOM.render(testHarness(props), container || null) }) }, unmount() { - if (hydrated) { + if (typeof container !== 'undefined') { act(() => { ReactDOM.unmountComponentAtNode(container) })