diff --git a/.gitignore b/.gitignore index ca2991c..cc166bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -node_modules -coverage -dist -.idea +node_modules/ +coverage/ +dist/ +.idea/ .DS_Store yarn-error.log diff --git a/examples/__tests__/input-event.js b/examples/__tests__/input-event.js index 4df8861..1dea5d9 100644 --- a/examples/__tests__/input-event.js +++ b/examples/__tests__/input-event.js @@ -1,6 +1,6 @@ import React from 'react'; -import { Text, TextInput, View } from 'react-native'; -import { render, fireEvent } from 'native-testing-library'; +import { TextInput } from 'react-native'; +import { render, fireEvent } from '../../src'; class CostInput extends React.Component { state = { diff --git a/examples/__tests__/react-context.js b/examples/__tests__/react-context.js index 3a8ab13..72ab617 100644 --- a/examples/__tests__/react-context.js +++ b/examples/__tests__/react-context.js @@ -1,7 +1,7 @@ import 'jest-native/extend-expect'; import React from 'react'; import { Text } from 'react-native'; -import { render } from 'native-testing-library'; +import { render } from '../../src'; import { NameContext, NameProvider, NameConsumer } from '../react-context'; diff --git a/examples/__tests__/react-intl.js b/examples/__tests__/react-intl.js index 7536d2f..bdd16b4 100644 --- a/examples/__tests__/react-intl.js +++ b/examples/__tests__/react-intl.js @@ -5,7 +5,7 @@ import { FormattedDate } from 'react-intl-native'; import IntlPolyfill from 'intl'; import 'intl/locale-data/jsonp/pt'; -import { getByText, render } from 'native-testing-library'; +import { getByText, render } from '../../src'; const setupTests = () => { if (global.Intl) { diff --git a/examples/__tests__/react-navigation.js b/examples/__tests__/react-navigation.js index 36f6436..929673b 100644 --- a/examples/__tests__/react-navigation.js +++ b/examples/__tests__/react-navigation.js @@ -3,7 +3,7 @@ import React from 'react'; import { Button, Text, View } from 'react-native'; import { createStackNavigator, createAppContainer, withNavigation } from 'react-navigation'; -import { render, fireEvent } from 'native-testing-library'; +import { render, fireEvent } from '../../src'; jest.mock('NativeAnimatedHelper').mock('react-native-gesture-handler', () => { const View = require('react-native').View; @@ -15,25 +15,6 @@ jest.mock('NativeAnimatedHelper').mock('react-native-gesture-handler', () => { }; }); -const originalConsoleWarn = console.warn; -console.warn = arg => { - const warnings = [ - 'Calling .measureInWindow()', - 'Calling .measureLayout()', - 'Calling .setNativeProps()', - 'Calling .focus()', - 'Calling .blur()', - ]; - - const finalArgs = warnings.reduce((acc, curr) => (arg.includes(curr) ? [...acc, arg] : acc), []); - - if (finalArgs.length) { - return; - } - - originalConsoleWarn(message); -}; - const Home = ({ navigation }) => ( Home page @@ -70,14 +51,14 @@ function renderWithNavigation({ screens = {}, navigatorConfig = {} } = {}) { const App = createAppContainer(AppNavigator); - return { ...render(), navigationContainer: App }; + return { ...render(), navigationContainer: App }; } test('full app rendering/navigating', async () => { - const { findByText, getByTestId, getByText } = renderWithNavigation(); + const { findByText, getByTestId, getByTitle } = renderWithNavigation(); expect(getByTestId('title')).toHaveTextContent('Home page'); - fireEvent.press(getByText(/Go to about/i)); + fireEvent.press(getByTitle(/Go to about/i)); const result = await findByText('About page'); expect(result).toHaveTextContent('About page'); diff --git a/examples/__tests__/react-redux.js b/examples/__tests__/react-redux.js index 4bb157a..738f25d 100644 --- a/examples/__tests__/react-redux.js +++ b/examples/__tests__/react-redux.js @@ -3,7 +3,7 @@ import React from 'react'; import { createStore } from 'redux'; import { Provider, connect } from 'react-redux'; import { Button, Text, View } from 'react-native'; -import { render, fireEvent } from 'native-testing-library'; +import { render, fireEvent } from '../../src'; class Counter extends React.Component { increment = () => { @@ -53,26 +53,26 @@ function renderWithRedux(ui, { initialState, store = createStore(reducer, initia } test('can render with redux with defaults', () => { - const { getByTestId, getByText } = renderWithRedux(); - fireEvent.press(getByText('+')); + const { getByTestId, getByTitle } = renderWithRedux(); + fireEvent.press(getByTitle('+')); expect(getByTestId('count-value')).toHaveTextContent(1); }); test('can render with redux with custom initial state', () => { - const { getByTestId, getByText } = renderWithRedux(, { + const { getByTestId, getByTitle } = renderWithRedux(, { initialState: { count: 3 }, }); - fireEvent.press(getByText('-')); + fireEvent.press(getByTitle('-')); expect(getByTestId('count-value')).toHaveTextContent(2); }); test('can render with redux with custom store', () => { const store = createStore(() => ({ count: 1000 })); - const { getByTestId, getByText } = renderWithRedux(, { + const { getByTestId, getByTitle } = renderWithRedux(, { store, }); - fireEvent.press(getByText('+')); + fireEvent.press(getByTitle('+')); expect(getByTestId('count-value')).toHaveTextContent(1000); - fireEvent.press(getByText('-')); + fireEvent.press(getByTitle('-')); expect(getByTestId('count-value')).toHaveTextContent(1000); }); diff --git a/examples/__tests__/update-props.js b/examples/__tests__/update-props.js index 6f95b51..3f00468 100644 --- a/examples/__tests__/update-props.js +++ b/examples/__tests__/update-props.js @@ -1,7 +1,7 @@ import React from 'react'; import 'jest-native/extend-expect'; import { Text, View } from 'react-native'; -import { render } from 'native-testing-library'; +import { render } from '../../src'; let idCounter = 1; diff --git a/examples/__tests__/use-hooks.js b/examples/__tests__/use-hooks.js new file mode 100644 index 0000000..1611176 --- /dev/null +++ b/examples/__tests__/use-hooks.js @@ -0,0 +1,21 @@ +import { useState } from 'react'; +import { renderHook, act } from 'react-hooks-testing-library'; + +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 [_, setValue] = result.current; + + act(() => setValue('bar')); + + const [value] = result.current; + expect(value).toBe('bar'); + }); +}); diff --git a/examples/jest.config.js b/examples/jest.config.js deleted file mode 100644 index d919bf3..0000000 --- a/examples/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - displayName: 'example', - preset: 'react-native', - roots: [__dirname], - rootDir: __dirname, - moduleNameMapper: { - 'native-testing-library': 'src', - }, -}; diff --git a/jest-preset.js b/jest-preset.js new file mode 100644 index 0000000..9b714d1 --- /dev/null +++ b/jest-preset.js @@ -0,0 +1,6 @@ +const jestPreset = require('react-native/jest-preset'); + +module.exports = Object.assign(jestPreset, { + transformIgnorePatterns: ['node_modules/(?!(react-native.*|@?react-navigation.*)/)'], + setupFiles: [...jestPreset.setupFiles, require.resolve('./src/preset/setup.js')], +}); diff --git a/jest.config.js b/jest.config.js index a1110e1..d1f1f13 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,9 @@ +const jestPreset = require('./jest-preset'); + const ignores = ['/node_modules/', '/__tests__/helpers/', '__mocks__']; -module.exports = { - preset: 'react-native', - transformIgnorePatterns: ['node_modules/(?!(react-native.*|@?react-navigation.*)/)'], - collectCoverageFrom: ['src/**/*.+(js|jsx|ts|tsx)'], - testMatch: ['**/__tests__/**/*.+(js|jsx|ts|tsx)'], +module.exports = Object.assign(jestPreset, { + collectCoverageFrom: ['src/lib/**/*.+(js|jsx|ts|tsx)'], testPathIgnorePatterns: [...ignores], coverageThreshold: { global: { @@ -14,4 +13,4 @@ module.exports = { statements: 100, }, }, -}; +}); diff --git a/package.json b/package.json index 71bfe78..3a707f1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ - "dist/**" + "dist/**", + "typings", + "jest-preset.js" ], "engines": { "node": ">=8" @@ -16,8 +18,8 @@ "commit:all": "npm run commit:add && npm run commit", "readme:toc": "doctoc README.md --maxlevel 3 --title '## Table of Contents'", "test": "jest", - "pretty-quick": "pretty-quick --staged --pattern '**/*.(js|jsx|ts|tsx)'", - "prepublish": "rm -rf dist; babel src --out-dir dist --ignore 'src/__tests__/*' && cp src/index.d.ts dist/index.d.ts", + "pretty-quick": "pretty-quick --staged", + "prepublishOnly": "rm -rf dist; babel src --out-dir dist --ignore 'src/**/__tests__/*'", "semantic-release": "semantic-release", "test:coverage": "jest --coverage", "test:watch": "jest --watch --coverage" @@ -35,6 +37,7 @@ "author": "Brandon Carroll (https://github.com/bcarroll22)", "license": "MIT", "dependencies": { + "jest-native": "2.0.0-alpha.5", "pretty-format": "^24.5.0", "react-test-renderer": "^16.8.5", "wait-for-expect": "^1.1.1" @@ -50,18 +53,17 @@ "jest": "24.5.0", "jest-fetch-mock": "^2.1.1", "jest-in-case": "^1.0.2", - "jest-native": "^1.2.0", "metro-react-native-babel-preset": "^0.52.0", "prettier": "^1.16.4", "pretty-quick": "^1.10.0", "react": "^16.8.5", + "react-hooks-testing-library": "^0.5.0", "react-intl": "^2.8.0", "react-intl-native": "^2.1.2", "react-native": "^0.59.0", "react-native-gesture-handler": "^1.1.0", "react-navigation": "^3.5.1", "react-redux": "6.0.1", - "react-test-renderer-tree-to-json": "^1.0.1", "redux": "^4.0.0", "semantic-release": "^15.13.3" }, diff --git a/setup.js b/setup.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/__tests__/__snapshots__/fetch.js.snap b/src/__tests__/__snapshots__/fetch.js.snap deleted file mode 100644 index 762e687..0000000 --- a/src/__tests__/__snapshots__/fetch.js.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Fetch makes an API call and displays the greeting when load-greeting is clicked 1`] = ` - - - - Fetch - - - - hello there - - -`; diff --git a/src/__tests__/filter.js b/src/__tests__/filter.js deleted file mode 100644 index 9255f16..0000000 --- a/src/__tests__/filter.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; - -import { render } from '../'; - -test('it calls a custom filter', () => { - const { queryByTestId } = render(hi); - const filterSpy = jest.fn(); - - queryByTestId('test', { filter: filterSpy }); - expect(filterSpy).toHaveBeenCalled(); -}); - -test('custom filters modify results', () => { - const filter = node => { - return node.type === 'TextInput'; - }; - - const { queryAllByTestId } = render(hi); - expect(queryAllByTestId('test', { filter }).length).toEqual(0); -}); diff --git a/src/__tests__/hooks/async-hook.js b/src/__tests__/hooks/async-hook.js deleted file mode 100644 index 88a70f4..0000000 --- a/src/__tests__/hooks/async-hook.js +++ /dev/null @@ -1,59 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { act, renderHook } from '../../'; - -const getSomeName = () => Promise.resolve('Betty'); - -const useName = prefix => { - const [name, setName] = useState('nobody'); - - useEffect(() => { - getSomeName().then(theName => { - act(() => { - setName(prefix ? `${prefix} ${theName}` : theName); - }); - }); - }, [prefix]); - - return name; -}; - -test('should wait for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useName()); - - expect(result.current).toBe('nobody'); - - await waitForNextUpdate(); - - expect(result.current).toBe('Betty'); -}); - -test('should wait for multiple updates', async () => { - const { result, waitForNextUpdate, rerender } = renderHook(({ prefix }) => useName(prefix), { - initialProps: { prefix: 'Mrs.' }, - }); - - expect(result.current).toBe('nobody'); - - await waitForNextUpdate(); - - expect(result.current).toBe('Mrs. Betty'); - - rerender({ prefix: 'Ms.' }); - - await waitForNextUpdate(); - - expect(result.current).toBe('Ms. Betty'); -}); - -test('should resolve all when updating', async () => { - const { result, waitForNextUpdate } = renderHook(({ prefix }) => useName(prefix), { - initialProps: { prefix: 'Mrs.' }, - }); - - expect(result.current).toBe('nobody'); - - await Promise.all([waitForNextUpdate(), waitForNextUpdate(), waitForNextUpdate()]); - - expect(result.current).toBe('Mrs. Betty'); -}); diff --git a/src/__tests__/hooks/custom-hook.js b/src/__tests__/hooks/custom-hook.js deleted file mode 100644 index 41d0059..0000000 --- a/src/__tests__/hooks/custom-hook.js +++ /dev/null @@ -1,42 +0,0 @@ -import { useState, useCallback } from 'react'; - -import { renderHook, act } from '../../'; - -function useCounter(initialCount = 0) { - const [count, setCount] = useState(initialCount); - - const incrementBy = useCallback(n => setCount(count + n), [count]); - const decrementBy = useCallback(n => setCount(count - n), [count]); - - return { count, incrementBy, decrementBy }; -} - -test('should create counter', () => { - const { result } = renderHook(() => useCounter()); - - expect(result.current.count).toBe(0); -}); - -test('should increment counter', () => { - const { result } = renderHook(() => useCounter()); - - act(() => result.current.incrementBy(1)); - - expect(result.current.count).toBe(1); - - act(() => result.current.incrementBy(2)); - - expect(result.current.count).toBe(3); -}); - -test('should decrement counter', () => { - const { result } = renderHook(() => useCounter()); - - act(() => result.current.decrementBy(1)); - - expect(result.current.count).toBe(-1); - - act(() => result.current.decrementBy(2)); - - expect(result.current.count).toBe(-3); -}); diff --git a/src/__tests__/hooks/error-hook.js b/src/__tests__/hooks/error-hook.js deleted file mode 100644 index 145ef21..0000000 --- a/src/__tests__/hooks/error-hook.js +++ /dev/null @@ -1,105 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { act, renderHook } from '../../'; - -function useError(throwError) { - if (throwError) { - throw new Error('expected'); - } - return true; -} - -const somePromise = () => Promise.resolve(); - -function useAsyncError(throwError) { - const [value, setValue] = useState(); - useEffect(() => { - somePromise().then(() => { - act(() => { - setValue(throwError); - }); - }); - }, [throwError]); - return useError(value); -} - -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: true, - }); - - expect(result.error).not.toBe(undefined); - - rerender(false); - - expect(result.current).not.toBe(undefined); - expect(result.error).toBe(undefined); -}); - -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: true, - }, - ); - - await waitForNextUpdate(); - - expect(result.error).not.toBe(undefined); - - rerender(false); - - await waitForNextUpdate(); - - expect(result.current).not.toBe(undefined); - expect(result.error).toBe(undefined); -}); diff --git a/src/__tests__/hooks/test-hook.js b/src/__tests__/hooks/test-hook.js deleted file mode 100644 index dfec1e8..0000000 --- a/src/__tests__/hooks/test-hook.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -import { act, renderHook } from '../../'; - -test('renderHook calls the callback', () => { - const spy = jest.fn(); - renderHook(spy); - expect(spy).toHaveBeenCalledTimes(1); -}); - -test('confirm we can safely call a React Hook from within the callback', () => { - renderHook(() => useState()); -}); - -test('returns a function to unmount component', () => { - let isMounted; - const { unmount } = renderHook(() => { - useEffect(() => { - isMounted = true; - return () => { - isMounted = false; - }; - }); - }); - expect(isMounted).toBe(true); - unmount(); - expect(isMounted).toBe(false); -}); - -test('returns a function to rerender component', () => { - let renderCount = 0; - const { rerender } = renderHook(() => { - useEffect(() => { - renderCount++; - }); - }); - - expect(renderCount).toBe(1); - rerender(); - expect(renderCount).toBe(2); -}); - -test('accepts wrapper option to wrap rendered hook with', () => { - const ctxA = React.createContext(); - const ctxB = React.createContext(); - const useHook = () => { - return React.useContext(ctxA) * React.useContext(ctxB); - }; - let actual; - renderHook( - () => { - actual = useHook(); - }, - { - // eslint-disable-next-line react/display-name - wrapper: props => ( - - - - ), - }, - ); - expect(actual).toBe(12); -}); - -test('returns result ref with latest result from hook execution', () => { - function useCounter({ initialCount = 0, step = 1 } = {}) { - const [count, setCount] = React.useState(initialCount); - const increment = () => setCount(c => c + step); - const decrement = () => setCount(c => c - step); - return { count, increment, decrement }; - } - - const { result } = renderHook(useCounter); - expect(result.current.count).toBe(0); - act(() => { - result.current.increment(); - }); - expect(result.current.count).toBe(1); -}); diff --git a/src/__tests__/hooks/use-context.js b/src/__tests__/hooks/use-context.js deleted file mode 100644 index 7e6afe3..0000000 --- a/src/__tests__/hooks/use-context.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { createContext, useContext } from 'react'; - -import { renderHook } from '../../'; - -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 = ({ children }) => ( - {children} - ); - - const { result } = renderHook(() => useContext(TestContext), { wrapper }); - - expect(result.current).toBe('bar'); -}); - -test('should update value in context', () => { - const TestContext = createContext('foo'); - - const value = { current: 'bar' }; - - const wrapper = ({ children }) => ( - {children} - ); - - const { result, rerender } = renderHook(() => useContext(TestContext), { wrapper }); - - value.current = 'baz'; - - rerender(); - - expect(result.current).toBe('baz'); -}); diff --git a/src/__tests__/hooks/use-effect.js b/src/__tests__/hooks/use-effect.js deleted file mode 100644 index 5eacd80..0000000 --- a/src/__tests__/hooks/use-effect.js +++ /dev/null @@ -1,61 +0,0 @@ -import { useEffect, useLayoutEffect } from 'react'; - -import { renderHook } from '../../'; - -test('should handle useEffect hook', () => { - const sideEffect = { [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 = { [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/__tests__/hooks/use-memo.js b/src/__tests__/hooks/use-memo.js deleted file mode 100644 index b2c5e06..0000000 --- a/src/__tests__/hooks/use-memo.js +++ /dev/null @@ -1,63 +0,0 @@ -import { useMemo, useCallback } from 'react'; - -import { renderHook } from '../../'; - -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 calbackValue1 = callback1(); - - expect(calbackValue1).toEqual({ value: 1 }); - - const callback2 = result.current; - - const calbackValue2 = callback2(); - - expect(calbackValue2).toEqual({ value: 1 }); - - expect(callback2).toBe(callback1); - - rerender({ value: 2 }); - - const callback3 = result.current; - - const calbackValue3 = callback3(); - - expect(calbackValue3).toEqual({ value: 2 }); - - expect(callback3).not.toBe(callback1); -}); diff --git a/src/__tests__/hooks/use-reducer.js b/src/__tests__/hooks/use-reducer.js deleted file mode 100644 index 51f6170..0000000 --- a/src/__tests__/hooks/use-reducer.js +++ /dev/null @@ -1,18 +0,0 @@ -import { useReducer } from 'react'; - -import { renderHook, act } from '../../'; - -test('should handle useReducer hook', () => { - const reducer = (state, action) => (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/__tests__/hooks/use-ref.js b/src/__tests__/hooks/use-ref.js deleted file mode 100644 index e7d8896..0000000 --- a/src/__tests__/hooks/use-ref.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useRef, useImperativeHandle } from 'react'; - -import { renderHook } from '../../'; - -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(); - useImperativeHandle(ref, () => ({ - fakeImperativeMethod: () => true, - })); - return ref; - }); - - const refContainer = result.current; - - expect(refContainer.current.fakeImperativeMethod()).toBe(true); -}); diff --git a/src/__tests__/hooks/use-state.js b/src/__tests__/hooks/use-state.js deleted file mode 100644 index 8cbafc6..0000000 --- a/src/__tests__/hooks/use-state.js +++ /dev/null @@ -1,23 +0,0 @@ -import { useState } from 'react'; - -import { renderHook, act } from '../../'; - -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 [_, setValue] = result.current; - - act(() => setValue('bar')); - - const [value] = result.current; - - expect(value).toBe('bar'); -}); diff --git a/src/__tests__/misc.js b/src/__tests__/misc.js deleted file mode 100644 index a326316..0000000 --- a/src/__tests__/misc.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { View } from 'react-native'; - -import { render } from '../'; -import { queryByProp, queryByTestId } from '../'; - -// we used to use queryByProp internally, but we don't anymore. Some people -// use it as an undocumented part of the API, so we'll keep it around. -test('queryByProp', () => { - const { container } = render( - - - - - , - ); - - expect(queryByTestId(container, 'foo')).not.toBeNull(); - expect(queryByProp('importantForAccessibility', container, 'auto')).toBeNull(); - expect(() => queryByProp('importantForAccessibility', container, /no/)).toThrow( - /multiple elements/, - ); -}); diff --git a/src/__tests__/query-helpers.js b/src/__tests__/query-helpers.js deleted file mode 100644 index ef7127b..0000000 --- a/src/__tests__/query-helpers.js +++ /dev/null @@ -1,15 +0,0 @@ -import { defaultFilter, filterNodeByType } from '../query-helpers'; - -test('filterNodeByType returns `true` when node.type matches the provided type', () => { - expect(filterNodeByType({ type: 'Text' }, 'Text')).toEqual(true); -}); -test('filterNodeByType returns `false` when node.type does not match the provided type', () => { - expect(filterNodeByType({ type: 'Text' }, 'Test')).toEqual(false); -}); - -test('defaultFilter returns `true` when node.type is in the mocked type list', () => { - expect(defaultFilter({ type: 'Text' })).toEqual(true); -}); -test('defaultFilter returns `false` when node.type is not in the mocked type list', () => { - expect(defaultFilter({ type: 'Test' })).toEqual(false); -}); diff --git a/src/hooks.js b/src/hooks.js deleted file mode 100644 index fc7758d..0000000 --- a/src/hooks.js +++ /dev/null @@ -1,41 +0,0 @@ -function TestHook({ callback, hookProps, children }) { - try { - children(callback(hookProps)); - } catch (e) { - children(undefined, e); - } - - return null; -} - -function resultContainer() { - let value = null; - let error = null; - const resolvers = []; - - const result = { - get current() { - if (error) { - throw error; - } - return value; - }, - get error() { - return error; - }, - }; - - return { - result, - addResolver: resolver => { - resolvers.push(resolver); - }, - updateResult: (val, err) => { - value = val; - error = err; - resolvers.splice(0, resolvers.length).forEach(resolve => resolve()); - }, - }; -} - -export { resultContainer, TestHook }; diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 354b3c7..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { ReactElement, ComponentType } from 'react' -import { ReactTestInstance, ReactTestRenderer, act } from 'react-test-renderer' -import { OptionsReceived } from 'pretty-format' -import { - NativeSyntheticEvent, - TextInputFocusEventData, - TextInputChangeEventData, - TextInputContentSizeChangeEventData, - TextInputEndEditingEventData, - TextInputKeyPressEventData, - TextInputSubmitEditingEventData, - LayoutChangeEvent, - TextInputSelectionChangeEventData, - GestureResponderEvent, - ScrollResponderEvent, - ImageLoadEventData, - ImageErrorEventData, - ImageProgressEventDataIOS, -} from 'react-native' - -// EVENTS -// ------ - -type EventInit = Partial> & { validTargets?: string[] } - -export declare class NativeEvent { - constructor(type: 'focus', init?: EventInit) - constructor(type: 'blur', init?: EventInit) - constructor(type: 'change', init?: EventInit) - constructor(type: 'changeText', value: string) - constructor(type: 'contentSizeChange', init?: EventInit) - constructor(type: 'endEditing', init?: EventInit) - constructor(type: 'keyPress', init?: EventInit) - constructor(type: 'submitEditing', init?: EventInit) - constructor(type: 'layout', init?: EventInit) - constructor(type: 'selectionChange', init?: EventInit) - constructor(type: 'longPress', init?: EventInit) - constructor(type: 'press', init?: EventInit) - constructor(type: 'pressIn', init?: EventInit) - constructor(type: 'pressOut', init?: EventInit) - constructor(type: 'momentumScrollBegin', init?: EventInit) - constructor(type: 'momentumScrollEnd', init?: EventInit) - constructor(type: 'scroll', init?: EventInit) - constructor(type: 'scrollBeginDrag', init?: EventInit) - constructor(type: 'scrollEndDrag', init?: EventInit) - constructor(type: 'load', init?: EventInit) - constructor(type: 'error', init?: EventInit) - constructor(type: 'progress', init?: EventInit) -} - -export declare function getEventHandlerName(key: string): string - -export interface FireEventFn { - (element: NativeTestInstance, event: NativeEvent): any - focus(element: NativeTestInstance, init?: EventInit): any - blur(element: NativeTestInstance, init?: EventInit): any - change(element: NativeTestInstance, init?: EventInit): any - changeText(element: NativeTestInstance, value: string): any - contentSizeChange(element: NativeTestInstance, init?: EventInit): any - endEditing(element: NativeTestInstance, init?: EventInit): any - keyPress(element: NativeTestInstance, init?: EventInit): any - submitEditing(element: NativeTestInstance, init?: EventInit): any - layout(element: NativeTestInstance, init?: EventInit): any - selectionChange(element: NativeTestInstance, init?: EventInit): any - longPress(element: NativeTestInstance, init?: EventInit): any - press(element: NativeTestInstance, init?: EventInit): any - pressIn(element: NativeTestInstance, init?: EventInit): any - pressOut(element: NativeTestInstance, init?: EventInit): any - momentumScrollBegin(element: NativeTestInstance, init?: EventInit): any - momentumScrollEnd(element: NativeTestInstance, init?: EventInit): any - scroll(element: NativeTestInstance, init?: EventInit): any - scrollBeginDrag(element: NativeTestInstance, init?: EventInit): any - scrollEndDrag(element: NativeTestInstance, init?: EventInit): any - load(element: NativeTestInstance, init?: EventInit): any - error(element: NativeTestInstance, init?: EventInit): any - progress(element: NativeTestInstance, init?: EventInit): any -} - -export declare const fireEvent: FireEventFn - -// GET NODE TEXT -// ------------- - -export declare function getNodeText(node: NativeTestInstance): string - -// GET QUERIES FOR ELEMENT -// ----------------------- - -export declare function getQueriesForElement(element: ReactElement, queries?: T): BoundQueries -export declare function within(element: ReactElement, queries?: T): BoundQueries - -// PREETY PRINT -// ------------ - -export declare function prettyPrint(element: ReactTestRenderer | NativeTestInstance | string, maxLength?: number, options?: OptionsReceived): string - -// QUERIES -// ------- - -type Omit = Pick> -type Bound = T extends (arg: any, ...rest: infer U) => infer V ? (...args: U) => V : never -type BoundQueries = { [P in keyof T]: Bound } - -export type NativeTestInstance = Omit - -export type TextMatch = string | RegExp | ((value: string) => boolean) -export type FilterFn = (value: string, index: number) => boolean -export type NormalizerFn = (input: string) => string - -export interface NormalizerOptions { - exact?: boolean, - trim?: boolean, - collapseWhitespace?: boolean, - filter?: FilterFn, - normalizer?: NormalizerFn, -} - -export interface TextNormalizerOptions extends NormalizerOptions { - types?: string[] -} - -export declare function getByA11yHint(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance -export declare function getByA11yLabel(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance -export declare function getByA11yRole(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance -export declare function getByA11yStates(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance -export declare function getByA11yTraits(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance -export declare function getByPlaceholder(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance -export declare function getByText(container: NativeTestInstance, match: TextMatch, options?: TextNormalizerOptions): NativeTestInstance -export declare function getByValue(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance -export declare function getByTestId(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance - -export declare function getAllByA11yHint(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByA11yLabel(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByA11yRole(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByA11yStates(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByA11yTraits(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByPlaceholder(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByText(container: NativeTestInstance, match: TextMatch, options?: TextNormalizerOptions): NativeTestInstance[] -export declare function getAllByValue(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] -export declare function getAllByTestId(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance[] - -export declare function queryByA11yHint(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByA11yLabel(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByA11yRole(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByA11yStates(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByA11yTraits(container: NativeTestInstance, match: string[], options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByPlaceholder(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByText(container: NativeTestInstance, match: TextMatch, options?: TextNormalizerOptions): NativeTestInstance | null -export declare function queryByValue(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null -export declare function queryByTestId(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): NativeTestInstance | null - -export declare function findByA11yHint(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findByA11yLabel(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findByA11yRole(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findByA11yStates(container: NativeTestInstance, match: string[], options?: NormalizerOptions): Promise -export declare function findByA11yTraits(container: NativeTestInstance, match: string[], options?: NormalizerOptions): Promise -export declare function findByPlaceholder(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findByText(container: NativeTestInstance, match: TextMatch, options?: TextNormalizerOptions): Promise -export declare function findByValue(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findByTestId(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise - -export declare function findAllByA11yHint(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findAllByA11yLabel(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findAllByA11yRole(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findAllByA11yStates(container: NativeTestInstance, match: string[], options?: NormalizerOptions): Promise -export declare function findAllByA11yTraits(container: NativeTestInstance, match: string[], options?: NormalizerOptions): Promise -export declare function findAllByPlaceholder(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findAllByText(container: NativeTestInstance, match: TextMatch, options?: TextNormalizerOptions): Promise -export declare function findAllByValue(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise -export declare function findAllByTestId(container: NativeTestInstance, match: TextMatch, options?: NormalizerOptions): Promise - -export interface Queries { - getByA11yHint: typeof getByA11yHint - getByA11yLabel: typeof getByA11yLabel - getByA11yRole: typeof getByA11yRole - getByA11yStates: typeof getByA11yStates - getByA11yTraits: typeof getByA11yTraits - getByPlaceholder: typeof getByPlaceholder - getByText: typeof getByText - getByValue: typeof getByA11yHint - getByTestId: typeof getByTestId - - getAllByA11yHint: typeof getAllByA11yHint - getAllByA11yLabel: typeof getAllByA11yLabel - getAllByA11yRole: typeof getAllByA11yRole - getAllByA11yStates: typeof getAllByA11yStates - getAllByA11yTraits: typeof getAllByA11yTraits - getAllByPlaceholder: typeof getAllByPlaceholder - getAllByText: typeof getAllByText - getAllByValue: typeof getAllByA11yHint - getAllByTestId: typeof getAllByTestId - - queryByA11yHint: typeof queryByA11yHint - queryByA11yLabel: typeof queryByA11yLabel - queryByA11yRole: typeof queryByA11yRole - queryByA11yStates: typeof queryByA11yStates - queryByA11yTraits: typeof queryByA11yTraits - queryByPlaceholder: typeof queryByPlaceholder - queryByText: typeof queryByText - queryByValue: typeof queryByA11yHint - queryByTestId: typeof queryByTestId - - findByA11yHint: typeof findByA11yHint - findByA11yLabel: typeof findByA11yLabel - findByA11yRole: typeof findByA11yRole - findByA11yStates: typeof findByA11yStates - findByA11yTraits: typeof findByA11yTraits - findByPlaceholder: typeof findByPlaceholder - findByText: typeof findByText - findByValue: typeof findByA11yHint - findByTestId: typeof findByTestId - - findAllByA11yHint: typeof findAllByA11yHint - findAllByA11yLabel: typeof findAllByA11yLabel - findAllByA11yRole: typeof findAllByA11yRole - findAllByA11yStates: typeof findAllByA11yStates - findAllByA11yTraits: typeof findAllByA11yTraits - findAllByPlaceholder: typeof findAllByPlaceholder - findAllByText: typeof findAllByText - findAllByValue: typeof findAllByA11yHint - findAllByTestId: typeof findAllByTestId -} - -// QUERY HELPERS -// ------------- - -export declare function defaultFilter(node: NativeTestInstance): boolean -export declare function getBaseElement(container: ReactTestRenderer | ReactTestInstance): ReactTestInstance -export declare function getElementError(message: string, container: ReactTestRenderer): Error -export declare function filterNodeByType(node: NativeTestInstance, type: string): boolean -export declare function queryAllByProp( - attribute: string, - container: ReactTestRenderer, - match: TextMatch, - options?: NormalizerOptions, -): NativeTestInstance[] -export declare function queryByProp( - attribute: string, - container: ReactTestRenderer, - match: TextMatch, - options?: NormalizerOptions, -): NativeTestInstance | null -export function removeBadProperties(node: ReactTestInstance): NativeTestInstance - -// WAIT -// ---- - -export interface WaitOptions { - timeout?: number - interval?: number -} -export declare function wait(callback?: () => void, options?: WaitOptions): Promise - -// WAIT FOR ELEMENT -// ---------------- - -export interface WaitOptions { - timeout?: number - interval?: number -} -export declare function waitForElement(callback: () => T, options?: WaitOptions): Promise - -// WAIT FOR ELEMENT TO BE REMOVED -// ------------------------------ - -export interface WaitOptions { - timeout?: number - interval?: number -} -export declare function waitForElementToBeRemoved(callback: () => any, options?: WaitOptions): Promise - -// MATCHES -// ------- - -export interface DefaultNormalizerOptions { - trim?: boolean, - collapseWhitespace?: boolean, -} -export declare function getDefaultNormalizer(options: DefaultNormalizerOptions): NormalizerFn - -// INDEX -// ----- - -export interface RenderOptions { - wrapper?: ComponentType<{ children: ReactElement }> -} -export interface RenderOptionsWithQueries extends RenderOptions { - queries?: T -} - -export declare function render(ui: ReactElement, options?: RenderOptions): RenderResult & BoundQueries -export declare function render(ui: ReactElement, options: RenderOptionsWithQueries): RenderResult & BoundQueries - -export interface RenderResult { - container: ReactTestRenderer - baseElement: NativeTestInstance - debug: (el?: NativeTestInstance) => void - rerender: (ui: ReactElement) => void - unmount: () => void -} - -export interface RenderHookOptions extends RenderOptions { - initialProps?: T -} - -export declare function renderHook(callback: (props: T) => U, options?: RenderHookOptions): RenderHookResult - -export interface RenderHookResult { - result: { - current: U - } - error?: Error - waitForNextUpdate: () => Promise - rerender: (newProps?: T) => void - unmount: () => void -} - -export { act } diff --git a/src/index.js b/src/index.js index 68304e4..f41a696 100644 --- a/src/index.js +++ b/src/index.js @@ -1,90 +1 @@ -import React from 'react'; -import TR from 'react-test-renderer'; - -import act from './act-compat'; -import * as queries from './queries'; -import { prettyPrint } from './pretty-print'; -import * as queryHelpers from './query-helpers'; -import { resultContainer, TestHook } from './hooks'; -import { getQueriesForElement } from './get-queries-for-element'; -import { fireEvent as rntlFireEvent, NativeEvent } from './events'; - -function render(ui, { options = {}, wrapper: WrapperComponent } = {}) { - const wrapUiIfNeeded = innerElement => - WrapperComponent ? {innerElement} : innerElement; - - let container = {}; - - act(() => { - container = TR.create(wrapUiIfNeeded(ui), options); - }); - - const baseElement = queryHelpers.removeBadProperties(container.root); - - return { - container, - baseElement, - debug: (el = baseElement) => console.log(prettyPrint(el)), - unmount: () => container.unmount(), - rerender: rerenderUi => { - act(() => { - container.update(wrapUiIfNeeded(rerenderUi)); - }); - }, - ...getQueriesForElement(container), - }; -} - -function renderHook(callback, { initialProps, ...options } = {}) { - const { result, updateResult, addResolver } = resultContainer(); - const hookProps = { current: initialProps }; - - const toRender = () => ( - - {updateResult} - - ); - - const { unmount, rerender: rerenderComponent } = render(toRender(), options); - - return { - result, - waitForNextUpdate: () => new Promise(resolve => addResolver(resolve)), - rerender: (newProps = hookProps.current) => { - hookProps.current = newProps; - rerenderComponent(toRender()); - }, - unmount, - }; -} - -function fireEvent(...args) { - let returnValue; - act(() => { - returnValue = rntlFireEvent(...args); - }); - return returnValue; -} - -Object.keys(rntlFireEvent).forEach(key => { - fireEvent[key] = (...args) => { - let returnValue; - act(() => { - returnValue = rntlFireEvent[key](...args); - }); - return returnValue; - }; -}); - -export * from './events'; -export * from './get-node-text'; -export * from './get-queries-for-element'; -export * from './pretty-print'; -export * from './queries'; -export * from './query-helpers'; -export * from './wait'; -export * from './wait-for-element'; -export * from './wait-for-element-to-be-removed'; -export { getDefaultNormalizer } from './matches'; - -export { act, fireEvent, queries, queryHelpers, render, renderHook, NativeEvent }; +export * from './lib'; diff --git a/src/lib/__tests__/__snapshots__/fetch.js.snap b/src/lib/__tests__/__snapshots__/fetch.js.snap new file mode 100644 index 0000000..a9e9abd --- /dev/null +++ b/src/lib/__tests__/__snapshots__/fetch.js.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fetch makes an API call and displays the greeting when load-greeting is clicked 1`] = ` + + + + Fetch + + + + hello there + + +`; diff --git a/src/__tests__/act.js b/src/lib/__tests__/act.js similarity index 89% rename from src/__tests__/act.js rename to src/lib/__tests__/act.js index ec5daf4..84791c7 100644 --- a/src/__tests__/act.js +++ b/src/lib/__tests__/act.js @@ -22,11 +22,11 @@ test('fireEvent triggers useEffect calls', () => { const [count, setCount] = React.useState(0); return