diff --git a/.all-contributorsrc b/.all-contributorsrc index 58552c8c..3ef0ecdf 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1194,6 +1194,54 @@ "contributions": [ "code" ] + }, + { + "login": "lidoravitan", + "name": "Lidor Avitan", + "avatar_url": "https://avatars0.githubusercontent.com/u/35113398?v=4", + "profile": "https://github.com/lidoravitan", + "contributions": [ + "doc" + ] + }, + { + "login": "ljharb", + "name": "Jordan Harband", + "avatar_url": "https://avatars1.githubusercontent.com/u/45469?v=4", + "profile": "https://github.com/ljharb", + "contributions": [ + "review", + "ideas" + ] + }, + { + "login": "marcosvega91", + "name": "Marco Moretti", + "avatar_url": "https://avatars2.githubusercontent.com/u/5365582?v=4", + "profile": "https://github.com/marcosvega91", + "contributions": [ + "code" + ] + }, + { + "login": "sanchit121", + "name": "sanchit121", + "avatar_url": "https://avatars2.githubusercontent.com/u/30828115?v=4", + "profile": "https://github.com/sanchit121", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "solufa", + "name": "Solufa", + "avatar_url": "https://avatars.githubusercontent.com/u/9402912?v=4", + "profile": "https://github.com/solufa", + "contributions": [ + "bug", + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 01d98992..496c8563 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -23,7 +23,7 @@ Thanks for your interest in the project. We appreciate bugs filed and PRs submit instead of filing an issue on GitHub. You can follow the instructions in this codesandbox to make a reproduction of your issue: https://kcd.im/rtl-help * Discord - https://discord.gg/c6JN9fM + https://discord.gg/testing-library * Stack Overflow https://stackoverflow.com/questions/tagged/react-testing-library @@ -61,6 +61,7 @@ for the supported version. Relevant code or config ```javascript + ``` What you did: diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md index d9a372d9..e625486b 100644 --- a/.github/ISSUE_TEMPLATE/Question.md +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -16,7 +16,7 @@ For questions related to using the library, please visit a support community instead of filing an issue on GitHub. You can follow the instructions in this codesandbox to make a reproduction of your issue: https://kcd.im/rtl-help -- Discord https://discord.gg/c6JN9fM +- Discord https://discord.gg/testing-library - Stack Overflow https://stackoverflow.com/questions/tagged/react-testing-library - Documentation: https://github.com/testing-library/testing-library-docs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 00000000..e99fa965 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,97 @@ +name: validate +on: + push: + branches: + - '+([0-9])?(.{+([0-9]),x}).x' + - 'master' + - 'next' + - 'next-major' + - 'beta' + - 'alpha' + - '!all-contributors/**' + pull_request: {} +jobs: + main: + continue-on-error: ${{ matrix.react != 'latest' }} + # ignore all-contributors PRs + if: ${{ !contains(github.head_ref, 'all-contributors') }} + strategy: + matrix: + node: [10.13, 12, 14, 15] + react: [latest, next, experimental] + runs-on: ubuntu-latest + steps: + - name: π Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.6.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + - name: β¬οΈ Checkout repo + uses: actions/checkout@v2 + + - name: β Setup node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: π₯ Download deps + uses: bahmutov/npm-install@v1 + with: + useLockFile: false + + # as requested by the React team :) + # https://reactjs.org/blog/2019/10/22/react-release-channels.html#using-the-next-channel-for-integration-testing + - name: βοΈ Setup react + run: npm install react@${{ matrix.react }} react-dom@${{ matrix.react }} + + - name: βΆοΈ Run validate script + run: npm run validate + + - name: β¬οΈ Upload coverage report + uses: codecov/codecov-action@v1 + + release: + needs: main + runs-on: ubuntu-latest + if: + ${{ github.repository == 'testing-library/react-testing-library' && + contains('refs/heads/master,refs/heads/beta,refs/heads/next,refs/heads/alpha', + github.ref) && github.event_name == 'push' }} + steps: + - name: π Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.6.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + - name: β¬οΈ Checkout repo + uses: actions/checkout@v2 + + - name: β Setup node + uses: actions/setup-node@v1 + with: + node-version: 14 + + - name: π₯ Download deps + uses: bahmutov/npm-install@v1 + with: + useLockFile: false + + - name: π Run build script + run: npm run build + + - name: π Release + uses: cycjimmy/semantic-release-action@v2 + with: + semantic_version: 17 + branches: | + [ + '+([0-9])?(.{+([0-9]),x}).x', + 'master', + 'next', + 'next-major', + {name: 'beta', prerelease: true}, + {name: 'alpha', prerelease: true} + ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index eae48ee7..00000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: node_js -cache: npm -notifications: - email: false -node_js: - # technically we support 10.0.0, but some of our tooling doesn't - - 10.14.2 - - 12 - - 14 - - node -env: - - REACT_DIST=latest - - REACT_DIST=next - - REACT_DIST=experimental -install: - - npm install - # as requested by the React team :) - # https://reactjs.org/blog/2019/10/22/react-release-channels.html#using-the-next-channel-for-integration-testing - - npm install react@$REACT_DIST react-dom@$REACT_DIST -script: - - npm run validate - - npx codecov@3 -branches: - only: - - master - - beta - -jobs: - allow_failures: - - REACT_DIST=next - - REACT_DIST=experimental - include: - - stage: release - node_js: 14 - script: kcd-scripts travis-release - if: fork = false diff --git a/README.md b/README.md index 6bccc9ac..c46eac6d 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,12 @@ practices.
[![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] -[![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] +[![version][version-badge]][package] +[![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] - -[](#contributors) -[![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] +[![All Contributors][all-contributors-badge]](#contributors) +[![PRs Welcome][prs-badge]][prs] +[![Code of Conduct][coc-badge]][coc] [![Discord][discord-badge]][discord] [![Watch on GitHub][github-watch-badge]][github-watch] @@ -154,7 +155,7 @@ afterAll(() => { ```jsx // hidden-message.js -import React from 'react' +import * as React from 'react' // NOTE: React Testing Library works well with React Hooks and classes. // Your tests will be the same regardless of how you write your components. @@ -184,7 +185,7 @@ export default HiddenMessage import '@testing-library/jest-dom' // NOTE: jest-dom adds handy assertions to Jest and is recommended, but not required -import React from 'react' +import * as React from 'react' import {render, fireEvent, screen} from '@testing-library/react' import HiddenMessage from '../hidden-message' @@ -209,7 +210,7 @@ test('shows the children when the checkbox is checked', () => { ```jsx // login.js -import React from 'react' +import * as React from 'react' function Login() { const [state, setState] = React.useReducer((s, a) => ({...s, ...a}), { @@ -233,7 +234,7 @@ function Login() { password: passwordInput.value, }), }) - .then(r => r.json()) + .then(r => r.json().then(data => (r.ok ? data : Promise.reject(data)))) .then( user => { setState({loading: false, resolved: true, error: null}) @@ -274,7 +275,7 @@ export default Login // again, these first two imports are something you'd normally handle in // your testing framework configuration rather than importing them in every file. import '@testing-library/jest-dom' -import React from 'react' +import * as React from 'react' // import API mocking utilities from Mock Service Worker. import {rest} from 'msw' import {setupServer} from 'msw/node' @@ -282,9 +283,10 @@ import {setupServer} from 'msw/node' import {render, fireEvent, screen} from '@testing-library/react' import Login from '../login' +const fakeUserResponse = {token: 'fake_user_token'} const server = setupServer( rest.post('/api/login', (req, res, ctx) => { - return res(ctx.json({token: 'fake_user_token'})) + return res(ctx.json(fakeUserResponse)) }), ) @@ -322,7 +324,7 @@ test('allows the user to login successfully', async () => { test('handles server exceptions', async () => { // mock the server error response for this test suite only. server.use( - rest.post('/', (req, res, ctx) => { + rest.post('/api/login', (req, res, ctx) => { return res(ctx.status(500), ctx.json({message: 'Internal server error'})) }), ) @@ -397,8 +399,8 @@ principles: `react-dom`. 3. Utility implementations and APIs should be simple and flexible. -Most importantly, we want React Testing Library to be pretty -light-weight, simple, and easy to understand. +Most importantly, we want React Testing Library to be pretty light-weight, +simple, and easy to understand. ## Docs @@ -407,8 +409,7 @@ light-weight, simple, and easy to understand. ## Issues -Looking to contribute? Look for the [Good First Issue][good-first-issue] -label. +Looking to contribute? Look for the [Good First Issue][good-first-issue] label. ### π Bugs @@ -440,169 +441,177 @@ Thanks goes to these people ([emoji key][emojis]):= { - container: HTMLElement - baseElement: HTMLElement +export type RenderResult< + Q extends Queries = typeof queries, + Container extends Element | DocumentFragment = HTMLElement +> = { + container: Container + baseElement: Element debug: ( baseElement?: - | HTMLElement + | Element | DocumentFragment - | Array, + | Array , maxLength?: number, - options?: PrettyFormatOptions, + options?: prettyFormat.OptionsReceived, ) => void rerender: (ui: React.ReactElement) => void - unmount: () => boolean + unmount: () => void asFragment: () => DocumentFragment } & {[P in keyof Q]: BoundFunction } -export interface RenderOptions{ - container?: HTMLElement - baseElement?: HTMLElement +export interface RenderOptions< + Q extends Queries = typeof queries, + Container extends Element | DocumentFragment = HTMLElement +> { + container?: Container + baseElement?: Element hydrate?: boolean queries?: Q wrapper?: React.ComponentType @@ -35,14 +46,17 @@ type Omit= Pick > /** * Render into a container which is appended to document.body. It should be used with cleanup. */ +export function render< + Q extends Queries = typeof queries, + Container extends Element | DocumentFragment = HTMLElement +>( + ui: React.ReactElement, + options: RenderOptions , +): RenderResultexport function render( ui: React.ReactElement, options?: Omit, ): RenderResult -export function render ( - ui: React.ReactElement, - options: RenderOptions, -): RenderResult/** * Unmounts React trees that were mounted with render. diff --git a/types/test.tsx b/types/test.tsx index c273feb0..01105c06 100644 --- a/types/test.tsx +++ b/types/test.tsx @@ -1,9 +1,9 @@ import * as React from 'react' -import {render, fireEvent, screen, waitFor} from '@testing-library/react' -import * as pure from '@testing-library/react/pure' +import {render, fireEvent, screen, waitFor} from '.' +import * as pure from './pure' -async function testRender() { - const page = render() +export async function testRender() { + const page = render() // single queries page.getByText('foo') @@ -17,10 +17,12 @@ async function testRender() { // helpers const {container, rerender, debug} = page + expectType(container) + return {container, rerender, debug} } -async function testPureRender() { - const page = pure.render() +export async function testPureRender() { + const page = pure.render() // single queries page.getByText('foo') @@ -34,20 +36,33 @@ async function testPureRender() { // helpers const {container, rerender, debug} = page + expectType (container) + return {container, rerender, debug} } -async function testRenderOptions() { +export function testRenderOptions() { const container = document.createElement('div') const options = {container} - render(, options) + const {container: returnedContainer} = render(, options) + expectType (returnedContainer) } -async function testFireEvent() { +export function testSVGRenderOptions() { + const container = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'svg', + ) + const options = {container} + const {container: returnedContainer} = render( , options) + expectType (returnedContainer) +} + +export function testFireEvent() { const {container} = render() fireEvent.click(container) } -async function testDebug() { +export function testDebug() { const {debug, getAllByTestId} = render( <> Hello World
@@ -57,14 +72,62 @@ async function testDebug() { debug(getAllByTestId('testid')) } -async function testScreen() { +export async function testScreen() { render() - screen.findByRole('button') + await screen.findByRole('button') } -async function testWaitFor() { +export async function testWaitFor() { const {container} = render() fireEvent.click(container) await waitFor(() => {}) } + +export function testQueries() { + const {getByLabelText} = render( + , + ) + expectType>( + getByLabelText('Username'), + ) + + const container = document.createElement('div') + const options = {container} + const {getByText} = render( Hello World, options) + expectType>( + getByText('Hello World'), + ) +} + +/* +eslint + testing-library/prefer-explicit-assert: "off", + testing-library/no-wait-for-empty-callback: "off", + testing-library/no-debug: "off", + testing-library/prefer-screen-queries: "off" +*/ + +// https://stackoverflow.com/questions/53807517/how-to-test-if-two-types-are-exactly-the-same +type IfEquals = ( () => G extends T + ? 1 + : 2) extends () => G extends U ? 1 : 2 + ? Yes + : No + +/** + * Issues a type error if `Expected` is not identical to `Actual`. + * + * `Expected` should be declared when invoking `expectType`. + * `Actual` should almost always we be a `typeof value` statement. + * + * Source: https://github.com/mui-org/material-ui/blob/6221876a4b468a3330ffaafa8472de7613933b87/packages/material-ui-types/index.d.ts#L73-L84 + * + * @example `expectType (value)` + * TypeScript issues a type error since `value is not assignable to never`. + * This means `typeof value` is not identical to `number | string` + * @param actual + */ +declare function expectType ( + actual: IfEquals , +): void diff --git a/types/tsconfig.json b/types/tsconfig.json index a6ba6c3d..a7829065 100644 --- a/types/tsconfig.json +++ b/types/tsconfig.json @@ -1,19 +1,4 @@ -// this additional tsconfig is required by dtslint -// see: https://github.com/Microsoft/dtslint#typestsconfigjson { - "compilerOptions": { - "module": "commonjs", - "lib": ["es6", "dom"], - "jsx": "react", - "noImplicitAny": true, - "noImplicitThis": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noEmit": true, - "baseUrl": ".", - "paths": { - "@testing-library/react": ["."], - "@testing-library/react/pure": ["."] - } - } + "extends": "../node_modules/kcd-scripts/shared-tsconfig.json", + "include": ["."] } diff --git a/types/tslint.json b/types/tslint.json deleted file mode 100644 index cb0fce9f..00000000 --- a/types/tslint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": ["dtslint/dtslint.json"], - "rules": { - "no-useless-files": false, - "no-relative-import-in-test": false, - "semicolon": false, - "whitespace": false - } -}