diff --git a/.eslintrc b/.eslintrc index cf63913a..5ae772fc 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "extends": "@callstack", "rules": { - "flowtype/no-weak-types": 0 + "flowtype/no-weak-types": 0, + "react/no-multi-comp": 0 } } diff --git a/README.md b/README.md index 85a91793..fe633141 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ As you may have noticed, it's not tied to React Native at all – you can safely ## API / Usage -The [public API](docs/api.md) of `react-native-testing-library` is focused around these essential methods: +The [public API](docs/API.md) of `react-native-testing-library` is focused around these essential methods: -- [`render`](docs/api.md#render) – deeply renders given React element and returns helpers to query the output components. -- [`shallow`](docs/api.md#shallow) – shallowly renders given React component. It doesn't return any helpers to query the output. -- [`fireEvent`](docs/api.md#fireevent) - invokes named event handler on the element. -- [`waitForElement`](docs/api.md#waitforelement) - waits for non-deterministic periods of time until your element appears or times out. -- [`flushMicrotasksQueue`](docs/api.md#flushmicrotasksqueue) - waits for microtasks queue to flush. +- [`render`](docs/API.md#render) – deeply renders given React element and returns helpers to query the output components. +- [`shallow`](docs/API.md#shallow) – shallowly renders given React component. It doesn't return any helpers to query the output. +- [`fireEvent`](docs/API.md#fireevent) - invokes named event handler on the element. +- [`waitForElement`](docs/API.md#waitforelement) - waits for non-deterministic periods of time until your element appears or times out. +- [`flushMicrotasksQueue`](docs/API.md#flushmicrotasksqueue) - waits for microtasks queue to flush. diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md new file mode 100644 index 00000000..820cd893 --- /dev/null +++ b/docs/GettingStarted.md @@ -0,0 +1,57 @@ +--- +id: getting-started +title: Getting Started +--- + +## The problem + +You want to write maintainable tests for your React Native components without testing implementation details, but then you're told to use Enzyme, which you learn has no React Native adapter, meaning only shallow rendering is supported. And you want to render deep! But deep rendering may otherwise require jsdom (React Native isn't the web!), while doing deep rendering with `react-test-renderer` is so painful. + +You would also like to use the newest React features, but you need to wait for your testing library's abstractions to catch up and it takes a while. + +You finally want to approach testing using only best practices, while Enzyme may encourage assertions on implementation details. + +## This solution + +The `react-native-testing-library` is a lightweight solution for testing your React Native components. It provides light utility functions on top of `react-test-renderer` letting you always be up to date with latest React features and write any component tests you like, be it shallow or deeply rendered ones. But really not any, it prevents you from testing implementation details because we stand this is a very bad practice. + +This library is a replacement for [Enzyme](http://airbnb.io/enzyme/). + +## Example + +```jsx +import { render, fireEvent } from 'react-native-testing-library'; +import { QuestionsBoard } from '../QuestionsBoard'; +import { Question } from '../Question'; + +function setAnswer(question, answer) { + fireEvent.changeText(question, answer); +} + +test('should verify two questions', () => { + const { getAllByType, getByText } = render(); + const allQuestions = getAllByType(Question); + + setAnswer(allQuestions[0], 'a1'); + setAnswer(allQuestions[1], 'a2'); + + fireEvent.press(getByText('submit')); + + expect(props.verifyQuestions).toBeCalledWith({ + '1': { q: 'q1', a: 'a1' }, + '2': { q: 'q2', a: 'a2' }, + }); +}); +``` + +## Installation + +Open a Terminal in your project's folder and run: + +```sh +yarn add --dev react-native-testing-library +``` + +This library has a peerDependencies listing for `react-test-renderer` and, of course, `react`. Make sure to install them too! + +As you may have noticed, it's not tied to React Native at all – you can safely use it in your React components if you feel like not interacting directly with DOM. diff --git a/docs/api.md b/docs/api.md index e94e8d24..4dd52974 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,4 +1,7 @@ -# API / Usage +--- +id: api +title: API +--- This page gathers public API of `react-native-testing-library` along with usage examples. diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 00000000..5395ea79 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,12 @@ +.DS_Store + +node_modules + +lib/core/metadata.js +lib/core/MetadataBlog.js + +website/translated_docs +website/build/ +website/yarn.lock +website/node_modules +website/i18n/* diff --git a/website/README.md b/website/README.md new file mode 100644 index 00000000..f3da77ff --- /dev/null +++ b/website/README.md @@ -0,0 +1,193 @@ +This website was created with [Docusaurus](https://docusaurus.io/). + +# What's In This Document + +* [Get Started in 5 Minutes](#get-started-in-5-minutes) +* [Directory Structure](#directory-structure) +* [Editing Content](#editing-content) +* [Adding Content](#adding-content) +* [Full Documentation](#full-documentation) + +# Get Started in 5 Minutes + +1. Make sure all the dependencies for the website are installed: + +```sh +# Install dependencies +$ yarn +``` +2. Run your dev server: + +```sh +# Start the site +$ yarn start +``` + +## Directory Structure + +Your project file structure should look something like this + +``` +my-docusaurus/ + docs/ + doc-1.md + doc-2.md + doc-3.md + website/ + blog/ + 2016-3-11-oldest-post.md + 2017-10-24-newest-post.md + core/ + node_modules/ + pages/ + static/ + css/ + img/ + package.json + sidebar.json + siteConfig.js +``` + +# Editing Content + +## Editing an existing docs page + +Edit docs by navigating to `docs/` and editing the corresponding document: + +`docs/doc-to-be-edited.md` + +```markdown +--- +id: page-needs-edit +title: This Doc Needs To Be Edited +--- + +Edit me... +``` + +For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Editing an existing blog post + +Edit blog posts by navigating to `website/blog` and editing the corresponding post: + +`website/blog/post-to-be-edited.md` +```markdown +--- +id: post-needs-edit +title: This Blog Post Needs To Be Edited +--- + +Edit me... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +# Adding Content + +## Adding a new docs page to an existing sidebar + +1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: + +```md +--- +id: newly-created-doc +title: This Doc Needs To Be Edited +--- + +My new content here.. +``` + +1. Refer to that doc's ID in an existing sidebar in `website/sidebar.json`: + +```javascript +// Add newly-created-doc to the Getting Started category of docs +{ + "docs": { + "Getting Started": [ + "quick-start", + "newly-created-doc" // new doc here + ], + ... + }, + ... +} +``` + +For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding a new blog post + +1. Make sure there is a header link to your blog in `website/siteConfig.js`: + +`website/siteConfig.js` +```javascript +headerLinks: [ + ... + { blog: true, label: 'Blog' }, + ... +] +``` + +2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: + +`website/blog/2018-05-21-New-Blog-Post.md` + +```markdown +--- +author: Frank Li +authorURL: https://twitter.com/foobarbaz +authorFBID: 503283835 +title: New Blog Post +--- + +Lorem Ipsum... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +## Adding items to your site's top navigation bar + +1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: + +`website/siteConfig.js` +```javascript +{ + headerLinks: [ + ... + /* you can add docs */ + { doc: 'my-examples', label: 'Examples' }, + /* you can add custom pages */ + { page: 'help', label: 'Help' }, + /* you can add external links */ + { href: 'https://github.com/facebook/Docusaurus', label: 'GitHub' }, + ... + ], + ... +} +``` + +For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding custom pages + +1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: +1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: + +`website/siteConfig.js` +```javascript +{ + headerLinks: [ + ... + { page: 'my-new-custom-page', label: 'My New Custom Page' }, + ... + ], + ... +} +``` + +For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). + +# Full Documentation + +Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/website/core/Footer.js b/website/core/Footer.js new file mode 100644 index 00000000..4f6e807a --- /dev/null +++ b/website/core/Footer.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); + +class Footer extends React.Component { + docUrl(doc, language) { + const { baseUrl } = this.props.config; + return `${baseUrl}docs/${language ? `${language}/` : ''}${doc}`; + } + + pageUrl(doc, language) { + const { baseUrl } = this.props.config; + return baseUrl + (language ? `${language}/` : '') + doc; + } + + render() { + return ( + + ); + } +} + +module.exports = Footer; diff --git a/website/docker/.dockerignore b/website/docker/.dockerignore new file mode 100644 index 00000000..27d2dae2 --- /dev/null +++ b/website/docker/.dockerignore @@ -0,0 +1,2 @@ +*/node_modules +*.log diff --git a/website/docker/Dockerfile b/website/docker/Dockerfile new file mode 100644 index 00000000..d369844d --- /dev/null +++ b/website/docker/Dockerfile @@ -0,0 +1,10 @@ +FROM node:8.11.4 + +WORKDIR /app/website + +EXPOSE 3000 35729 +COPY ./docs /app/docs +COPY ./website /app/website +RUN yarn install + +CMD ["yarn", "start"] diff --git a/website/docker/docker-compose.yml b/website/docker/docker-compose.yml new file mode 100644 index 00000000..6711192a --- /dev/null +++ b/website/docker/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3" + +services: + docusaurus: + build: . + ports: + - 3000:3000 + - 35729:35729 + volumes: + - ./docs:/app/docs + - ./website/blog:/app/website/blog + - ./website/core:/app/website/core + - ./website/i18n:/app/website/i18n + - ./website/pages:/app/website/pages + - ./website/static:/app/website/static + - ./website/sidebars.json:/app/website/sidebars.json + - ./website/siteConfig.js:/app/website/siteConfig.js + working_dir: /app/website diff --git a/website/i18n/en.json b/website/i18n/en.json new file mode 100644 index 00000000..02f68aa5 --- /dev/null +++ b/website/i18n/en.json @@ -0,0 +1,29 @@ +{ + "_comment": "This file is auto-generated by write-translations.js", + "localized-strings": { + "next": "Next", + "previous": "Previous", + "tagline": "Helps you to write better tests with less effort.", + "docs": { + "api": { + "title": "API" + }, + "getting-started": { + "title": "Getting Started" + } + }, + "links": { + "Docs": "Docs", + "GitHub": "GitHub" + }, + "categories": { + "Introduction": "Introduction", + "API Reference": "API Reference" + } + }, + "pages-strings": { + "Help Translate|recruit community translators for your project": "Help Translate", + "Edit this Doc|recruitment message asking to edit the doc source": "Edit", + "Translate this Doc|recruitment message asking to translate the docs": "Translate" + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 00000000..04776b1e --- /dev/null +++ b/website/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "examples": "docusaurus-examples", + "start": "docusaurus-start", + "build": "docusaurus-build", + "publish-gh-pages": "docusaurus-publish", + "write-translations": "docusaurus-write-translations", + "version": "docusaurus-version", + "rename-version": "docusaurus-rename-version" + }, + "devDependencies": { + "docusaurus": "^1.5.1" + } +} diff --git a/website/pages/en/help.js b/website/pages/en/help.js new file mode 100644 index 00000000..60809c36 --- /dev/null +++ b/website/pages/en/help.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); + +const CompLibrary = require('../../core/CompLibrary.js'); + +const Container = CompLibrary.Container; +const GridBlock = CompLibrary.GridBlock; + +const siteConfig = require(`${process.cwd()}/siteConfig.js`); + +function docUrl(doc, language) { + return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${doc}`; +} + +class Help extends React.Component { + render() { + const language = this.props.language || ''; + const supportLinks = [ + { + content: 'Ask questions about the documentation and project', + title: 'Join the community', + }, + { + content: "Find out what's new with this project", + title: 'Stay up to date', + }, + ]; + + return ( +
+ +
+
+

Need help?

+
+

This project is maintained by a dedicated group of people.

+ +
+
+
+ ); + } +} + +module.exports = Help; diff --git a/website/pages/en/index.js b/website/pages/en/index.js new file mode 100644 index 00000000..c2614859 --- /dev/null +++ b/website/pages/en/index.js @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); // eslint-disable-line import/no-extraneous-dependencies + +const { + // MarkdownBlock, + Container, + GridBlock, +} = require('../../core/CompLibrary.js'); // eslint-disable-line import/no-unresolved + +const siteConfig = require(`${process.cwd()}/siteConfig.js`); + +// function imgUrl(img) { +// return `${siteConfig.baseUrl}img/${img}`; +// } + +function docUrl(doc, language) { + return `${siteConfig.baseUrl}docs/${language ? `${language}/` : ''}${doc}`; +} + +// function pageUrl(page, language) { +// return siteConfig.baseUrl + (language ? `${language}/` : '') + page; +// } + +class Button extends React.Component { + render() { + return ( +
+ + {this.props.children} + +
+ ); + } +} + +Button.defaultProps = { + target: '_self', +}; + +const SplashContainer = props => ( +
+
+
{props.children}
+
+
+); + +// const Logo = props => ( +//
+// Project Logo +//
+// ); + +const ProjectTitle = () => ( + +

+ {siteConfig.title} + {siteConfig.tagline} +

+ +
+); + +class HomeSplash extends React.Component { + render() { + return ( + + {/* */} +
+ +
+
+ ); + } +} + +const Block = props => ( + + + +); + +const Features = () => ( + + {[ + { + content: + 'Built on top of `react-test-renderer`, letting you always be up to date with latest React features', + // image: imgUrl('docusaurus.svg'), + imageAlign: 'top', + title: 'Lightweight', + }, + { + content: + 'Prevents you from testing implementation details because we stand this is a very bad practice', + // image: imgUrl('docusaurus.svg'), + imageAlign: 'top', + title: 'Opinionated', + }, + ]} + +); + +// const FeatureCallout = () => ( +//
+//

Feature Callout

+// These are features of this project +//
+// ); + +class Index extends React.Component { + render() { + const language = this.props.language || ''; + + return ( +
+ +
+ + {/* */} +
+
+ ); + } +} + +module.exports = Index; diff --git a/website/pages/en/users.js b/website/pages/en/users.js new file mode 100644 index 00000000..b03fb812 --- /dev/null +++ b/website/pages/en/users.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); + +const CompLibrary = require('../../core/CompLibrary.js'); + +const Container = CompLibrary.Container; + +const siteConfig = require(`${process.cwd()}/siteConfig.js`); + +class Users extends React.Component { + render() { + if ((siteConfig.users || []).length === 0) { + return null; + } + + const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`; + const showcase = siteConfig.users.map(user => ( + + {user.caption} + + )); + + return ( +
+ +
+
+

Who is Using This?

+

This project is used by many folks

+
+
{showcase}
+

Are you using this project?

+ + Add your company + +
+
+
+ ); + } +} + +module.exports = Users; diff --git a/website/sidebars.json b/website/sidebars.json new file mode 100644 index 00000000..762af3ec --- /dev/null +++ b/website/sidebars.json @@ -0,0 +1,6 @@ +{ + "docs": { + "Introduction": ["getting-started"], + "API Reference": ["api"] + } +} diff --git a/website/siteConfig.js b/website/siteConfig.js new file mode 100644 index 00000000..8f46e2a3 --- /dev/null +++ b/website/siteConfig.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// See https://docusaurus.io/docs/site-config for all the possible +// site configuration options. + +const repoUrl = 'https://github.com/callstack/react-native-testing-library'; + +const siteConfig = { + title: 'React Native Testing Library', // Title for your website. + tagline: 'Helps you to write better tests with less effort.', + url: 'https://callstack.github.io', // Your website URL + baseUrl: '/react-native-testing-library/', // Base URL for your project + + // Used for publishing and more + projectName: 'react-native-testing-library', + organizationName: 'callstack', + // For top-level user or org sites, the organization is still the same. + // e.g., for the https://JoelMarcey.github.io site, it would be set like... + // organizationName: 'JoelMarcey' + + // For no header links in the top nav bar -> headerLinks: [], + headerLinks: [ + { doc: 'getting-started', label: 'Docs' }, + { href: repoUrl, label: 'GitHub' }, + ], + + /* path to images for header/footer */ + // headerIcon: 'img/docusaurus.svg', + // footerIcon: 'img/docusaurus.svg', + // favicon: 'img/favicon.png', + + /* Colors for website */ + colors: { + primaryColor: '#3274ff', + secondaryColor: '#3bffed', + }, + + /* Custom fonts for website */ + /* + fonts: { + myFont: [ + "Times New Roman", + "Serif" + ], + myOtherFont: [ + "-apple-system", + "system-ui" + ] + }, + */ + + // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. + copyright: `Copyright © ${new Date().getFullYear()} Callstack Open Source`, + + highlight: { + // Highlight.js theme to use for syntax highlighting in code blocks. + theme: 'default', + }, + + // Add custom scripts here that would be placed in