diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 00000000..5d154811 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,23 @@ +{ + "projectName": "react-testing-library", + "projectOwner": "kentcdodds", + "files": [ + "README.md" + ], + "imageSize": 100, + "commit": false, + "contributors": [ + { + "login": "kentcdodds", + "name": "Kent C. Dodds", + "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", + "profile": "https://kentcdodds.com", + "contributions": [ + "code", + "doc", + "infra", + "test" + ] + } + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..391f0a4e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.js text eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..6a7b4bff --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ + + +* `react-testing-library` version: +* `node` version: +* `npm` (or `yarn`) version: + +Relevant code or config + +```javascript +``` + +What you did: + +What happened: + + + +Reproduction repository: + + + +Problem description: + +Suggested solution: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..aa0dc2b8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,42 @@ + + + + +**What**: + + + +**Why**: + + + +**How**: + + + +**Checklist**: + + + + + +* [ ] Documentation +* [ ] Tests +* [ ] Ready to be merged +* [ ] Added myself to contributors table + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..09048d22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules +coverage +dist +.opt-in +.opt-out +.DS_Store +.eslintcache + +# these cause more harm than good +# when working with contributors +package-lock.json +yarn.lock diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..d2722898 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=http://registry.npmjs.org/ +package-lock=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..30117ea2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +package.json +node_modules +dist +coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..68f30bd7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": 'all', + "bracketSpacing": false, + "jsxBracketSameLine": false +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..08be7ec0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +sudo: false +language: node_js +cache: + directories: + - ~/.npm +notifications: + email: false +node_js: '8' +install: npm install +script: npm run validate +after_success: kcd-scripts travis-after-success +branches: + only: master diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..06d221aa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# CHANGELOG + +The changelog is automatically updated using [semantic-release](https://github.com/semantic-release/semantic-release). +You can see it on the [releases page](../../releases). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2cb6bdbf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,72 @@ +# Contributing + +Thanks for being willing to contribute! + +**Working on your first Pull Request?** You can learn how from this _free_ series +[How to Contribute to an Open Source Project on GitHub][egghead] + +## Project setup + +1. Fork and clone the repo +2. Run `npm run setup -s` to install dependencies and run validation +3. Create a branch for your PR with `git checkout -b pr/your-branch-name` + +> Tip: Keep your `master` branch pointing at the original repository and make +> pull requests from branches on your fork. To do this, run: +> +> ``` +> git remote add upstream https://github.com/kentcdodds/react-testing-library.git +> git fetch upstream +> git branch --set-upstream-to=upstream/master master +> ``` +> +> This will add the original repository as a "remote" called "upstream," +> Then fetch the git information from that remote, then set your local `master` +> branch to use the upstream master branch whenever you run `git pull`. +> Then you can make all of your pull request branches based on this `master` +> branch. Whenever you want to update your version of `master`, do a regular +> `git pull`. + +## Add yourself as a contributor + +This project follows the [all contributors][all-contributors] specification. +To add yourself to the table of contributors on the `README.md`, please use the +automated script as part of your PR: + +```console +npm run add-contributor +``` + +Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. +If you've already added yourself to the list and are making +a new type of contribution, you can run it again and select the added +contribution type. + +## Committing and Pushing changes + +Please make sure to run the tests before you commit your changes. You can run +`npm run test:update` which will update any snapshots that need updating. +Make sure to include those changes (if they exist) in your commit. + +### opt into git hooks + +There are git hooks set up with this project that are automatically installed +when you install dependencies. They're really handy, but are turned off by +default (so as to not hinder new contributors). You can opt into these by +creating a file called `.opt-in` at the root of the project and putting this +inside: + +``` +pre-commit +``` + +## Help needed + +Please checkout the [the open issues][issues] + +Also, please watch the repo and respond to questions/bug reports/feature +requests! Thanks! + +[egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github +[all-contributors]: https://github.com/kentcdodds/all-contributors +[issues]: https://github.com/kentcdodds/react-testing-library/issues diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4c43675b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2017 Kent C. Dodds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/other/CODE_OF_CONDUCT.md b/other/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..afe24327 --- /dev/null +++ b/other/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at kent+coc@doddsfamily.us. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/other/MAINTAINING.md b/other/MAINTAINING.md new file mode 100644 index 00000000..025b6732 --- /dev/null +++ b/other/MAINTAINING.md @@ -0,0 +1,63 @@ +# Maintaining + +This is documentation for maintainers of this project. + +## Code of Conduct + +Please review, understand, and be an example of it. Violations of the code of conduct are +taken seriously, even (especially) for maintainers. + +## Issues + +We want to support and build the community. We do that best by helping people learn to solve +their own problems. We have an issue template and hopefully most folks follow it. If it's +not clear what the issue is, invite them to create a minimal reproduction of what they're trying +to accomplish or the bug they think they've found. + +Once it's determined that a code change is necessary, point people to +[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a pull request. +If they're the one who needs the feature, they're the one who can build it. If they need +some hand holding and you have time to lend a hand, please do so. It's an investment into +another human being, and an investment into a potential maintainer. + +Remember that this is open source, so the code is not yours, it's ours. If someone needs a change +in the codebase, you don't have to make it happen yourself. Commit as much time to the project +as you want/need to. Nobody can ask any more of you than that. + +## Pull Requests + +As a maintainer, you're fine to make your branches on the main repo or on your own fork. Either +way is fine. + +When we receive a pull request, a travis build is kicked off automatically (see the `.travis.yml` +for what runs in the travis build). We avoid merging anything that breaks the travis build. + +Please review PRs and focus on the code rather than the individual. You never know when this is +someone's first ever PR and we want their experience to be as positive as possible, so be +uplifting and constructive. + +When you merge the pull request, 99% of the time you should use the +[Squash and merge](https://help.github.com/articles/merging-a-pull-request/) feature. This keeps +our git history clean, but more importantly, this allows us to make any necessary changes to the +commit message so we release what we want to release. See the next section on Releases for more +about that. + +## Release + +Our releases are automatic. They happen whenever code lands into `master`. A travis build gets +kicked off and if it's successful, a tool called +[`semantic-release`](https://github.com/semantic-release/semantic-release) is used to +automatically publish a new release to npm as well as a changelog to GitHub. It is only able to +determine the version and whether a release is necessary by the git commit messages. With this +in mind, **please brush up on [the commit message convention][commit] which drives our releases.** + +> One important note about this: Please make sure that commit messages do NOT contain the words +> "BREAKING CHANGE" in them unless we want to push a major version. I've been burned by this +> more than once where someone will include "BREAKING CHANGE: None" and it will end up releasing +> a new major version. Not a huge deal honestly, but kind of annoying... + +## Thanks! + +Thank you so much for helping to maintain this project! + +[commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md diff --git a/other/USERS.md b/other/USERS.md new file mode 100644 index 00000000..4bc12816 --- /dev/null +++ b/other/USERS.md @@ -0,0 +1,12 @@ +# Users + +If you or your company uses this project, add your name to this list! Eventually +we may have a website to showcase these (wanna build it!?) + +> No users have been added yet! + + diff --git a/other/manual-releases.md b/other/manual-releases.md new file mode 100644 index 00000000..a058d797 --- /dev/null +++ b/other/manual-releases.md @@ -0,0 +1,43 @@ +# manual-releases + +This project has an automated release set up. So things are only released when there are +useful changes in the code that justify a release. But sometimes things get messed up one way or another +and we need to trigger the release ourselves. When this happens, simply bump the number below and commit +that with the following commit message based on your needs: + +**Major** + +``` +fix(release): manually release a major version + +There was an issue with a major release, so this manual-releases.md +change is to release a new major version. + +Reference: # + +BREAKING CHANGE: +``` + +**Minor** + +``` +feat(release): manually release a minor version + +There was an issue with a minor release, so this manual-releases.md +change is to release a new minor version. + +Reference: # +``` + +**Patch** + +``` +fix(release): manually release a patch version + +There was an issue with a patch release, so this manual-releases.md +change is to release a new patch version. + +Reference: # +``` + +The number of times we've had to do a manual release is: 0 diff --git a/src/__mocks__/axios.js b/src/__mocks__/axios.js new file mode 100644 index 00000000..57eb52ec --- /dev/null +++ b/src/__mocks__/axios.js @@ -0,0 +1,3 @@ +module.exports = { + get: jest.fn(() => Promise.resolve({data: {}})), +} diff --git a/src/__tests__/__snapshots__/fetch.js.snap b/src/__tests__/__snapshots__/fetch.js.snap new file mode 100644 index 00000000..ff2344ca --- /dev/null +++ b/src/__tests__/__snapshots__/fetch.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Fetch makes an API call and displays the greeting when load-greeting is clicked 1`] = ` +
+ + + hello there + +
+`; diff --git a/src/__tests__/fetch.js b/src/__tests__/fetch.js new file mode 100644 index 00000000..f6fc9546 --- /dev/null +++ b/src/__tests__/fetch.js @@ -0,0 +1,52 @@ +import React from 'react' +import axiosMock from 'axios' +import {render, Simulate, flushPromises} from '../' + +// instead of importing it, we'll define it inline here +// import Fetch from '../fetch' + +class Fetch extends React.Component { + state = {} + componentDidUpdate(prevProps) { + if (this.props.url !== prevProps.url) { + this.fetch() + } + } + fetch = async () => { + const response = await axiosMock.get(this.props.url) + this.setState({data: response.data}) + } + render() { + const {data} = this.state + return ( +
+ + {data ? {data.greeting} : null} +
+ ) + } +} + +test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => { + // Arrange + axiosMock.get.mockImplementationOnce(() => + Promise.resolve({ + data: {greeting: 'hello there'}, + }), + ) + const url = '/greeting' + const {queryByTestId, container} = render() + + // Act + Simulate.click(queryByTestId('load-greeting')) + + await flushPromises() + + // Assert + expect(axiosMock.get).toHaveBeenCalledTimes(1) + expect(axiosMock.get).toHaveBeenCalledWith(url) + expect(queryByTestId('greeting-text').textContent).toBe('hello there') + expect(container.firstChild).toMatchSnapshot() +}) diff --git a/src/__tests__/mock.react-transition-group.js b/src/__tests__/mock.react-transition-group.js new file mode 100644 index 00000000..7467c41e --- /dev/null +++ b/src/__tests__/mock.react-transition-group.js @@ -0,0 +1,50 @@ +import React from 'react' +import {CSSTransition} from 'react-transition-group' +import {render, Simulate} from '../' + +function Fade({children, ...props}) { + return ( + + {children} + + ) +} + +class HiddenMessage extends React.Component { + state = {show: this.props.initialShow || false} + toggle = () => { + this.setState(({show}) => ({show: !show})) + } + render() { + return ( +
+ + +
Hello world
+
+
+ ) + } +} + +jest.mock('react-transition-group', () => { + const FakeTransition = jest.fn(({children}) => children) + const FakeCSSTransition = jest.fn( + props => + props.in ? {props.children} : null, + ) + return {CSSTransition: FakeCSSTransition, Transition: FakeTransition} +}) + +test('you can mock things with jest.mock', () => { + const {queryByTestId} = render() + expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists + // hide the message + Simulate.click(queryByTestId('toggle-message')) + // in the real world, the CSSTransition component would take some time + // before finishing the animation which would actually hide the message. + // So we've mocked it out for our tests to make it happen instantly + expect(queryByTestId('hidden-message')).toBeFalsy() // we just care it doesn't exist +}) diff --git a/src/__tests__/number-display.js b/src/__tests__/number-display.js new file mode 100644 index 00000000..fedf7ac1 --- /dev/null +++ b/src/__tests__/number-display.js @@ -0,0 +1,29 @@ +import React from 'react' +import {render} from '../' + +let idCounter = 1 + +class NumberDisplay extends React.Component { + id = idCounter++ // to ensure we don't remount a different instance + render() { + return ( +
+ {this.props.number} + {this.id} +
+ ) + } +} + +test('calling render with the same component on the same container does not remount', () => { + const {container, queryByTestId} = render() + expect(queryByTestId('number-display').textContent).toBe('1') + + // re-render the same component with different props + // but pass the same container in the options argument. + // which will cause a re-render of the same instance (normal React behavior). + render(, {container}) + expect(queryByTestId('number-display').textContent).toBe('2') + + expect(queryByTestId('instance-id').textContent).toBe('1') +}) diff --git a/src/__tests__/shallow.react-transition-group.js b/src/__tests__/shallow.react-transition-group.js new file mode 100644 index 00000000..6e03337d --- /dev/null +++ b/src/__tests__/shallow.react-transition-group.js @@ -0,0 +1,51 @@ +import React from 'react' +import {CSSTransition} from 'react-transition-group' +import {render, Simulate} from '../' + +function Fade({children, ...props}) { + return ( + + {children} + + ) +} + +class HiddenMessage extends React.Component { + state = {show: this.props.initialShow || false} + toggle = () => { + this.setState(({show}) => ({show: !show})) + } + render() { + return ( +
+ + +
Hello world
+
+
+ ) + } +} + +jest.mock('react-transition-group', () => { + const FakeCSSTransition = jest.fn(() => null) + return {CSSTransition: FakeCSSTransition} +}) + +test('you can mock things with jest.mock', () => { + const {queryByTestId} = render() + const context = expect.any(Object) + const children = expect.any(Object) + const defaultProps = {children, timeout: 1000, className: 'fade'} + expect(CSSTransition).toHaveBeenCalledWith( + {in: true, ...defaultProps}, + context, + ) + Simulate.click(queryByTestId('toggle-message')) + expect(CSSTransition).toHaveBeenCalledWith( + {in: true, ...defaultProps}, + expect.any(Object), + ) +}) diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..23c450e7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,28 @@ +import ReactDOM from 'react-dom' +import {Simulate} from 'react-dom/test-utils' + +// we may expose this eventually +function sel(id) { + return `[data-test="${id}"]` +} + +// we may expose this eventually +function queryDivByTestId(div, id) { + return div.querySelector(sel(id)) +} + +function render(ui, {container = document.createElement('div')} = {}) { + ReactDOM.render(ui, container) + return { + container, + queryByTestId: queryDivByTestId.bind(null, container), + } +} + +// this returns a new promise and is just a simple way to +// wait until the next tick so resolved promises chains will continue +function flushPromises() { + return new Promise(resolve => setImmediate(resolve)) +} + +export {render, flushPromises, Simulate}