├── .codecov.yml ├── .eslintignore ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .jest └── config.js ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── examples └── composition │ ├── package.json │ └── test.js ├── flow-typed └── npm │ ├── jest_v23.x.x.js │ ├── lodash_v4.x.x.js │ └── react-test-renderer_v16.x.x.js ├── lerna.json ├── new-package.png ├── package.json ├── packages ├── fetch │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── setup-global-fetch.js │ └── src │ │ ├── __tests__ │ │ ├── get.js │ │ ├── inspect.js │ │ ├── multi.js │ │ ├── network-fallback.js │ │ ├── options.js │ │ ├── post.js │ │ └── unmount.js │ │ ├── index.js │ │ └── index.js.flow ├── localstorage │ ├── .npmignore │ ├── README.md │ ├── package.json │ └── src │ │ ├── StorageMock.js │ │ ├── index.js │ │ ├── index.js.flow │ │ └── index.test.js ├── state │ ├── .npmignore │ ├── README.md │ ├── package.json │ └── src │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.js.flow │ │ └── index.test.js └── xhr │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── get.js │ │ ├── multi.js │ │ ├── post.js │ │ └── unmount.js │ ├── index.js │ └── index.js.flow │ └── testHelpers │ └── request.js ├── scripts ├── build.js ├── generate-readme.js ├── new-package.js ├── shared │ ├── args.js │ ├── glob.js │ ├── packages.js │ ├── paths.js │ ├── print.js │ ├── process.js │ └── rimraf.js └── templates │ ├── README.md │ └── new-package │ ├── .npmignore │ ├── README.md │ ├── package.json │ └── src │ ├── index.js │ ├── index.js.flow │ └── index.test.js └── yarn.lock /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: 90% 8 | threshold: 5% 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | flow-typed/** 3 | node_modules 4 | packages/*/dist 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { TEST_GLOBS } = require('./scripts/shared/paths'); 2 | 3 | module.exports = { 4 | parser: 'babel-eslint', 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:import/errors', 8 | 'plugin:import/warnings', 9 | 'plugin:react/recommended', 10 | 'plugin:flowtype/recommended' 11 | ], 12 | env: { 13 | 'shared-node-browser': true, 14 | commonjs: true, 15 | es6: true 16 | }, 17 | settings: { 18 | react: { 19 | version: '16.5' 20 | } 21 | }, 22 | rules: { 23 | 'spaced-comment': 2, 24 | 'no-console': 1, 25 | // I don't believe in default exports anymore 26 | 'import/default': 0, 27 | 'react/prop-types': 1, 28 | 'react/no-unescaped-entities': 1, 29 | // flowtype/generic-spacing conflicts with Prettier 30 | 'flowtype/generic-spacing': 0 31 | }, 32 | overrides: [ 33 | { 34 | files: ['.jest/*.js', ...TEST_GLOBS], 35 | env: { 36 | jest: true 37 | } 38 | }, 39 | { 40 | files: ['scripts/**/*.js'], 41 | env: { 42 | node: true 43 | }, 44 | rules: { 45 | 'no-console': 0 46 | } 47 | } 48 | ] 49 | }; 50 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | flow-typed 7 | 8 | [lints] 9 | 10 | [options] 11 | 12 | [strict] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Project build 64 | packages/*/dist 65 | -------------------------------------------------------------------------------- /.jest/config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | rootDir: join(__dirname, '..'), 5 | testEnvironment: 'node', 6 | setupFiles: ['/packages/fetch/setup-global-fetch.js'], 7 | // Maybe someday: collectCoverageFrom: [], 8 | coverageDirectory: '/coverage' 9 | }; 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | flow-typed/** 3 | node_modules 4 | packages/*/dist 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | node_js: 4 | - 10 5 | - 8 6 | before_install: 7 | - sudo sysctl fs.inotify.max_user_watches=524288 8 | - curl -o- -L https://yarnpkg.com/install.sh | bash 9 | - export PATH=$HOME/.yarn/bin:$PATH 10 | install: 'yarn install --ignore-engines' 11 | script: 12 | # Build source (required for integration tests from examples) 13 | - yarn build 14 | # Test source 15 | - yarn flow 16 | - yarn lint 17 | - yarn test --maxWorkers=2 --coverage 18 | after_success: 19 | # Report coverage 20 | - yarn codecov 21 | cache: 22 | yarn: true 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | 3 | ### Intention 4 | 5 | Please take a minute to understand this project's purpose and ensure your contribution is thoughtful and relevant. Preserving the integrity of an open source project is hard. Thanks! 6 | 7 | ### Check your code 8 | 9 | You have the following weapons at your disposal: `yarn lint`, `yarn flow` and `yarn test`. Use them. 10 | 11 | ### New package 12 | 13 | Run `yarn new-package` and you'll follow this friendly flow that will generate initial boilerplate. 14 | 15 | ![New package](new-package.png) 16 | 17 | ### Docs 18 | 19 | Each package has its own README. This is useful for keeping docs close to code, as well as for showing docs on each package's npm page. 20 | 21 | **The root README is generated using a script. Do not edit it by hand.** It's assembled from a [template](scripts/templates/README.md), individual package docs and the CONTRIBUTING.md. 22 | 23 | Run `npm generate-readme` to update the root README. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ovidiu Cherecheș 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React mock 2 | 3 | [![Build](https://travis-ci.com/skidding/react-mock.svg?branch=master)](https://travis-ci.com/skidding/react-mock) [![Coverage](https://codecov.io/gh/skidding/react-mock/branch/master/graph/badge.svg)](https://codecov.io/gh/skidding/react-mock) 4 | 5 | Declarative mocks for React state and global APIs. 6 | 7 | ## Jump to 8 | 9 | - **[Why?](#why)** 10 | - [Component state](#component-state) 11 | - [Fetch requests](#fetch-requests) 12 | - [LocalStorage](#localstorage) 13 | - [XHR requests](#xhr-requests) 14 | - **[How to contribute](#how-to-contribute)** 15 | 16 | ## Why? 17 | 18 | The motivation for this project comes from wanting to load any type of React component in isolation—inside automated tests as well as in component explorers such as [Cosmos](https://github.com/react-cosmos/react-cosmos) or [Storybook](https://github.com/storybooks/storybook). Some components as stateful, while others fetch data or interact with some other external input. 19 | 20 | The aim here is to isolate _all_ components, not just presentational and stateless components. 21 | 22 | ### Declarative 23 | 24 | Tools like [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) and [xhr-mock](https://github.com/jameslnewell/xhr-mock) are already used by many of us in component tests. But they require imperative _setups_ and _teardowns_, which has two drawbacks: 25 | 26 | 1. They require before/after orchestration in component tests, which is tedious and can create convoluted test cases. 27 | 28 | 2. They're difficult to integrate in component explorers where a usage file is a declarative component element. 29 | 30 | To overcome these drawbacks, `react-mock` offers mocking techniques as declarative [React elements](https://reactjs.org/docs/rendering-elements.html). Lifecycle methods take care of setting up and reverting mocks behind the hood. 31 | 32 | ### Composition 33 | 34 | Two or more mocks can be composed into a single React element. 35 | 36 | ```js 37 | render( 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | ``` 49 | 50 | ### Limitations 51 | 52 | - Some react-mock components mock a global API entirely, like _fetch_ or _localStorage_. For this reason only one instance of each should be rendered at once. There might be ways to compose these mocks in the future. 53 | 54 | - To keep this codebase light, the declarative APIs mirror the params of their underlying APIs. Eg. Although they both mock server requests, the `FetchMock` API is different from the `XhrMock` API because they rely on different libs. More concise interfaces are possible, but they increase the scope of this project. 55 | 56 | ## Component state 57 | 58 | Inject React component state declaratively. 59 | 60 | > `StateMock` must be the direct parent of the stateful component for the state injection to work. 61 | 62 | ```js 63 | import { StateMock } from '@react-mock/state'; 64 | 65 | render( 66 | 67 | 68 | 69 | ); 70 | ``` 71 | 72 | > **Warning:** StateMock delays ref calls. This means refs can get called _after_ componentDidMount, instead of before as you [might expect](https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback). 73 | 74 | ## Fetch requests 75 | 76 | A declarative wrapper for the wonderful [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/). 77 | 78 | > **Note:** FetchMock mocks the global Fetch API, so only one FetchMock instance should be rendered at once. 79 | 80 | ```js 81 | import { FetchMock } from '@react-mock/fetch'; 82 | 83 | // Passing fetch-mock options 84 | render( 85 | 86 | 87 | 88 | ); 89 | 90 | // Passing fetch-mock config 91 | render( 92 | 97 | 98 | 99 | ); 100 | ``` 101 | 102 | ### Multiple mocks 103 | 104 | ```js 105 | render( 106 | 112 | 113 | 114 | ); 115 | ``` 116 | 117 | ### Inspection 118 | 119 | See fetch-mock's [inspection methods](http://www.wheresrhys.co.uk/fetch-mock/#api-inspectionfiltering) to check how fetch was called. 120 | 121 | > **Note:** Import `fetchMock` from @react-mock/fetch to ensure you're inspecting on the right fetch-mock instance. 122 | 123 | ```js 124 | import { fetchMock } from '@react-mock/fetch'; 125 | 126 | const [, { body }] = fetchMock.lastCall('/login', 'POST'); 127 | expect(JSON.parse(body)).toEqual({ user: 'harry' }); 128 | ``` 129 | 130 | ## LocalStorage 131 | 132 | Mock LocalStorage data declaratively. 133 | 134 | > **Note:** LocalStorageMock mocks the global localStorage API, so only one LocalStorageMock instance should be rendered at once. 135 | 136 | ```js 137 | import { LocalStorageMock } from '@react-mock/localstorage'; 138 | 139 | render( 140 | 141 | 142 | 143 | ); 144 | ``` 145 | 146 | ## XHR requests 147 | 148 | A declarative wrapper for the great [xhr-mock](https://github.com/jameslnewell/xhr-mock). 149 | 150 | > **Note:** XhrMock mocks the global XMLHttpRequest API, so only one XhrMock instance should be rendered at once. 151 | 152 | ```js 153 | import { XhrMock } from '@react-mock/xhr'; 154 | 155 | // GET 156 | render( 157 | res.body(JSON.stringify(users))} 160 | > 161 | 162 | 163 | ); 164 | 165 | // POST 166 | render( 167 | res.status(401)}> 168 | 169 | 170 | ); 171 | ``` 172 | 173 | ### Multiple mocks 174 | 175 | ```js 176 | const res = body => (req, res) => res.body(JSON.stringify(body)); 177 | 178 | render( 179 | 185 | 186 | 187 | ); 188 | ``` 189 | 190 | ## How to contribute 191 | 192 | ### Intention 193 | 194 | Please take a minute to understand this project's purpose and ensure your contribution is thoughtful and relevant. Preserving the integrity of an open source project is hard. Thanks! 195 | 196 | ### Check your code 197 | 198 | You have the following weapons at your disposal: `yarn lint`, `yarn flow` and `yarn test`. Use them. 199 | 200 | ### New package 201 | 202 | Run `yarn new-package` and you'll follow this friendly flow that will generate initial boilerplate. 203 | 204 | ![New package](new-package.png) 205 | 206 | ### Docs 207 | 208 | Each package has its own README. This is useful for keeping docs close to code, as well as for showing docs on each package's npm page. 209 | 210 | **The root README is generated using a script. Do not edit it by hand.** It's assembled from a [template](scripts/templates/README.md), individual package docs and the CONTRIBUTING.md. 211 | 212 | Run `npm generate-readme` to update the root README. 213 | 214 | ## License 215 | 216 | MIT © [Ovidiu Cherecheș](https://ovidiu.ch) 217 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/env', '@babel/react', '@babel/flow'], 3 | plugins: [ 4 | '@babel/plugin-proposal-object-rest-spread', 5 | '@babel/plugin-proposal-class-properties', 6 | '@babel/transform-runtime' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /examples/composition/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "react-mocks-example-composition", 4 | "version": "0.1.1", 5 | "dependencies": { 6 | "@react-mock/fetch": "^0.3.0", 7 | "@react-mock/localstorage": "^0.1.2", 8 | "@react-mock/state": "^0.1.8", 9 | "lodash": "^4.17.11" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/composition/test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global localStorage, fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { StateMock } from '@react-mock/state'; 8 | import { FetchMock } from '@react-mock/fetch'; 9 | import { LocalStorageMock } from '@react-mock/localstorage'; 10 | 11 | import type { Node } from 'react'; 12 | 13 | class ToggleShow extends Component<{ children: Node }, { show: boolean }> { 14 | state = { 15 | show: false 16 | }; 17 | 18 | render() { 19 | return this.state.show && this.props.children; 20 | } 21 | } 22 | 23 | class UserGreeting extends Component<{}, { name: string }> { 24 | state = { 25 | name: 'Guest' 26 | }; 27 | 28 | async componentDidMount() { 29 | const userId = localStorage.getItem('userId'); 30 | const { name } = await (await fetch(`/user/${String(userId)}`)).json(); 31 | 32 | this.setState({ 33 | name 34 | }); 35 | } 36 | 37 | render() { 38 | return `Hello ${this.state.name}!`; 39 | } 40 | } 41 | 42 | it('composes mocks', async () => { 43 | const renderer = create( 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | 55 | await retry(() => { 56 | expect(renderer.toJSON()).toEqual('Hello Jessica!'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v23.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 268d738ecf65f4bcc4a5455efa90619f 2 | // flow-typed version: 0e8507a159/jest_v23.x.x/flow_>=v0.39.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array, 21 | /** 22 | * An array that contains all the object results that have been 23 | * returned by this mock function call 24 | */ 25 | results: Array<{ isThrow: boolean, value: TReturn }> 26 | }, 27 | /** 28 | * Resets all information stored in the mockFn.mock.calls and 29 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 30 | * up a mock's usage data between two assertions. 31 | */ 32 | mockClear(): void, 33 | /** 34 | * Resets all information stored in the mock. This is useful when you want to 35 | * completely restore a mock back to its initial state. 36 | */ 37 | mockReset(): void, 38 | /** 39 | * Removes the mock and restores the initial implementation. This is useful 40 | * when you want to mock functions in certain test cases and restore the 41 | * original implementation in others. Beware that mockFn.mockRestore only 42 | * works when mock was created with jest.spyOn. Thus you have to take care of 43 | * restoration yourself when manually assigning jest.fn(). 44 | */ 45 | mockRestore(): void, 46 | /** 47 | * Accepts a function that should be used as the implementation of the mock. 48 | * The mock itself will still record all calls that go into and instances 49 | * that come from itself -- the only difference is that the implementation 50 | * will also be executed when the mock is called. 51 | */ 52 | mockImplementation( 53 | fn: (...args: TArguments) => TReturn 54 | ): JestMockFn, 55 | /** 56 | * Accepts a function that will be used as an implementation of the mock for 57 | * one call to the mocked function. Can be chained so that multiple function 58 | * calls produce different results. 59 | */ 60 | mockImplementationOnce( 61 | fn: (...args: TArguments) => TReturn 62 | ): JestMockFn, 63 | /** 64 | * Accepts a string to use in test result output in place of "jest.fn()" to 65 | * indicate which mock function is being referenced. 66 | */ 67 | mockName(name: string): JestMockFn, 68 | /** 69 | * Just a simple sugar function for returning `this` 70 | */ 71 | mockReturnThis(): void, 72 | /** 73 | * Accepts a value that will be returned whenever the mock function is called. 74 | */ 75 | mockReturnValue(value: TReturn): JestMockFn, 76 | /** 77 | * Sugar for only returning a value once inside your mock 78 | */ 79 | mockReturnValueOnce(value: TReturn): JestMockFn, 80 | /** 81 | * Sugar for jest.fn().mockImplementation(() => Promise.resolve(value)) 82 | */ 83 | mockResolvedValue(value: TReturn): JestMockFn>, 84 | /** 85 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.resolve(value)) 86 | */ 87 | mockResolvedValueOnce(value: TReturn): JestMockFn>, 88 | /** 89 | * Sugar for jest.fn().mockImplementation(() => Promise.reject(value)) 90 | */ 91 | mockRejectedValue(value: TReturn): JestMockFn>, 92 | /** 93 | * Sugar for jest.fn().mockImplementationOnce(() => Promise.reject(value)) 94 | */ 95 | mockRejectedValueOnce(value: TReturn): JestMockFn> 96 | }; 97 | 98 | type JestAsymmetricEqualityType = { 99 | /** 100 | * A custom Jasmine equality tester 101 | */ 102 | asymmetricMatch(value: mixed): boolean 103 | }; 104 | 105 | type JestCallsType = { 106 | allArgs(): mixed, 107 | all(): mixed, 108 | any(): boolean, 109 | count(): number, 110 | first(): mixed, 111 | mostRecent(): mixed, 112 | reset(): void 113 | }; 114 | 115 | type JestClockType = { 116 | install(): void, 117 | mockDate(date: Date): void, 118 | tick(milliseconds?: number): void, 119 | uninstall(): void 120 | }; 121 | 122 | type JestMatcherResult = { 123 | message?: string | (() => string), 124 | pass: boolean 125 | }; 126 | 127 | type JestMatcher = (actual: any, expected: any) => 128 | | JestMatcherResult 129 | | Promise; 130 | 131 | type JestPromiseType = { 132 | /** 133 | * Use rejects to unwrap the reason of a rejected promise so any other 134 | * matcher can be chained. If the promise is fulfilled the assertion fails. 135 | */ 136 | rejects: JestExpectType, 137 | /** 138 | * Use resolves to unwrap the value of a fulfilled promise so any other 139 | * matcher can be chained. If the promise is rejected the assertion fails. 140 | */ 141 | resolves: JestExpectType 142 | }; 143 | 144 | /** 145 | * Jest allows functions and classes to be used as test names in test() and 146 | * describe() 147 | */ 148 | type JestTestName = string | Function; 149 | 150 | /** 151 | * Plugin: jest-styled-components 152 | */ 153 | 154 | type JestStyledComponentsMatcherValue = 155 | | string 156 | | JestAsymmetricEqualityType 157 | | RegExp 158 | | typeof undefined; 159 | 160 | type JestStyledComponentsMatcherOptions = { 161 | media?: string; 162 | modifier?: string; 163 | supports?: string; 164 | } 165 | 166 | type JestStyledComponentsMatchersType = { 167 | toHaveStyleRule( 168 | property: string, 169 | value: JestStyledComponentsMatcherValue, 170 | options?: JestStyledComponentsMatcherOptions 171 | ): void, 172 | }; 173 | 174 | /** 175 | * Plugin: jest-enzyme 176 | */ 177 | type EnzymeMatchersType = { 178 | toBeChecked(): void, 179 | toBeDisabled(): void, 180 | toBeEmpty(): void, 181 | toBeEmptyRender(): void, 182 | toBePresent(): void, 183 | toContainReact(element: React$Element): void, 184 | toExist(): void, 185 | toHaveClassName(className: string): void, 186 | toHaveHTML(html: string): void, 187 | toHaveProp: ((propKey: string, propValue?: any) => void) & ((props: Object) => void), 188 | toHaveRef(refName: string): void, 189 | toHaveState: ((stateKey: string, stateValue?: any) => void) & ((state: Object) => void), 190 | toHaveStyle: ((styleKey: string, styleValue?: any) => void) & ((style: Object) => void), 191 | toHaveTagName(tagName: string): void, 192 | toHaveText(text: string): void, 193 | toIncludeText(text: string): void, 194 | toHaveValue(value: any): void, 195 | toMatchElement(element: React$Element): void, 196 | toMatchSelector(selector: string): void 197 | }; 198 | 199 | // DOM testing library extensions https://github.com/kentcdodds/dom-testing-library#custom-jest-matchers 200 | type DomTestingLibraryType = { 201 | toBeInTheDOM(): void, 202 | toHaveTextContent(content: string): void, 203 | toHaveAttribute(name: string, expectedValue?: string): void 204 | }; 205 | 206 | // Jest JQuery Matchers: https://github.com/unindented/custom-jquery-matchers 207 | type JestJQueryMatchersType = { 208 | toExist(): void, 209 | toHaveLength(len: number): void, 210 | toHaveId(id: string): void, 211 | toHaveClass(className: string): void, 212 | toHaveTag(tag: string): void, 213 | toHaveAttr(key: string, val?: any): void, 214 | toHaveProp(key: string, val?: any): void, 215 | toHaveText(text: string | RegExp): void, 216 | toHaveData(key: string, val?: any): void, 217 | toHaveValue(val: any): void, 218 | toHaveCss(css: {[key: string]: any}): void, 219 | toBeChecked(): void, 220 | toBeDisabled(): void, 221 | toBeEmpty(): void, 222 | toBeHidden(): void, 223 | toBeSelected(): void, 224 | toBeVisible(): void, 225 | toBeFocused(): void, 226 | toBeInDom(): void, 227 | toBeMatchedBy(sel: string): void, 228 | toHaveDescendant(sel: string): void, 229 | toHaveDescendantWithText(sel: string, text: string | RegExp): void 230 | }; 231 | 232 | 233 | // Jest Extended Matchers: https://github.com/jest-community/jest-extended 234 | type JestExtendedMatchersType = { 235 | /** 236 | * Note: Currently unimplemented 237 | * Passing assertion 238 | * 239 | * @param {String} message 240 | */ 241 | // pass(message: string): void; 242 | 243 | /** 244 | * Note: Currently unimplemented 245 | * Failing assertion 246 | * 247 | * @param {String} message 248 | */ 249 | // fail(message: string): void; 250 | 251 | /** 252 | * Use .toBeEmpty when checking if a String '', Array [] or Object {} is empty. 253 | */ 254 | toBeEmpty(): void; 255 | 256 | /** 257 | * Use .toBeOneOf when checking if a value is a member of a given Array. 258 | * @param {Array.<*>} members 259 | */ 260 | toBeOneOf(members: any[]): void; 261 | 262 | /** 263 | * Use `.toBeNil` when checking a value is `null` or `undefined`. 264 | */ 265 | toBeNil(): void; 266 | 267 | /** 268 | * Use `.toSatisfy` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean`. 269 | * @param {Function} predicate 270 | */ 271 | toSatisfy(predicate: (n: any) => boolean): void; 272 | 273 | /** 274 | * Use `.toBeArray` when checking if a value is an `Array`. 275 | */ 276 | toBeArray(): void; 277 | 278 | /** 279 | * Use `.toBeArrayOfSize` when checking if a value is an `Array` of size x. 280 | * @param {Number} x 281 | */ 282 | toBeArrayOfSize(x: number): void; 283 | 284 | /** 285 | * Use `.toIncludeAllMembers` when checking if an `Array` contains all of the same members of a given set. 286 | * @param {Array.<*>} members 287 | */ 288 | toIncludeAllMembers(members: any[]): void; 289 | 290 | /** 291 | * Use `.toIncludeAnyMembers` when checking if an `Array` contains any of the members of a given set. 292 | * @param {Array.<*>} members 293 | */ 294 | toIncludeAnyMembers(members: any[]): void; 295 | 296 | /** 297 | * Use `.toSatisfyAll` when you want to use a custom matcher by supplying a predicate function that returns a `Boolean` for all values in an array. 298 | * @param {Function} predicate 299 | */ 300 | toSatisfyAll(predicate: (n: any) => boolean): void; 301 | 302 | /** 303 | * Use `.toBeBoolean` when checking if a value is a `Boolean`. 304 | */ 305 | toBeBoolean(): void; 306 | 307 | /** 308 | * Use `.toBeTrue` when checking a value is equal (===) to `true`. 309 | */ 310 | toBeTrue(): void; 311 | 312 | /** 313 | * Use `.toBeFalse` when checking a value is equal (===) to `false`. 314 | */ 315 | toBeFalse(): void; 316 | 317 | /** 318 | * Use .toBeDate when checking if a value is a Date. 319 | */ 320 | toBeDate(): void; 321 | 322 | /** 323 | * Use `.toBeFunction` when checking if a value is a `Function`. 324 | */ 325 | toBeFunction(): void; 326 | 327 | /** 328 | * Use `.toHaveBeenCalledBefore` when checking if a `Mock` was called before another `Mock`. 329 | * 330 | * Note: Required Jest version >22 331 | * Note: Your mock functions will have to be asynchronous to cause the timestamps inside of Jest to occur in a differentJS event loop, otherwise the mock timestamps will all be the same 332 | * 333 | * @param {Mock} mock 334 | */ 335 | toHaveBeenCalledBefore(mock: JestMockFn): void; 336 | 337 | /** 338 | * Use `.toBeNumber` when checking if a value is a `Number`. 339 | */ 340 | toBeNumber(): void; 341 | 342 | /** 343 | * Use `.toBeNaN` when checking a value is `NaN`. 344 | */ 345 | toBeNaN(): void; 346 | 347 | /** 348 | * Use `.toBeFinite` when checking if a value is a `Number`, not `NaN` or `Infinity`. 349 | */ 350 | toBeFinite(): void; 351 | 352 | /** 353 | * Use `.toBePositive` when checking if a value is a positive `Number`. 354 | */ 355 | toBePositive(): void; 356 | 357 | /** 358 | * Use `.toBeNegative` when checking if a value is a negative `Number`. 359 | */ 360 | toBeNegative(): void; 361 | 362 | /** 363 | * Use `.toBeEven` when checking if a value is an even `Number`. 364 | */ 365 | toBeEven(): void; 366 | 367 | /** 368 | * Use `.toBeOdd` when checking if a value is an odd `Number`. 369 | */ 370 | toBeOdd(): void; 371 | 372 | /** 373 | * Use `.toBeWithin` when checking if a number is in between the given bounds of: start (inclusive) and end (exclusive). 374 | * 375 | * @param {Number} start 376 | * @param {Number} end 377 | */ 378 | toBeWithin(start: number, end: number): void; 379 | 380 | /** 381 | * Use `.toBeObject` when checking if a value is an `Object`. 382 | */ 383 | toBeObject(): void; 384 | 385 | /** 386 | * Use `.toContainKey` when checking if an object contains the provided key. 387 | * 388 | * @param {String} key 389 | */ 390 | toContainKey(key: string): void; 391 | 392 | /** 393 | * Use `.toContainKeys` when checking if an object has all of the provided keys. 394 | * 395 | * @param {Array.} keys 396 | */ 397 | toContainKeys(keys: string[]): void; 398 | 399 | /** 400 | * Use `.toContainAllKeys` when checking if an object only contains all of the provided keys. 401 | * 402 | * @param {Array.} keys 403 | */ 404 | toContainAllKeys(keys: string[]): void; 405 | 406 | /** 407 | * Use `.toContainAnyKeys` when checking if an object contains at least one of the provided keys. 408 | * 409 | * @param {Array.} keys 410 | */ 411 | toContainAnyKeys(keys: string[]): void; 412 | 413 | /** 414 | * Use `.toContainValue` when checking if an object contains the provided value. 415 | * 416 | * @param {*} value 417 | */ 418 | toContainValue(value: any): void; 419 | 420 | /** 421 | * Use `.toContainValues` when checking if an object contains all of the provided values. 422 | * 423 | * @param {Array.<*>} values 424 | */ 425 | toContainValues(values: any[]): void; 426 | 427 | /** 428 | * Use `.toContainAllValues` when checking if an object only contains all of the provided values. 429 | * 430 | * @param {Array.<*>} values 431 | */ 432 | toContainAllValues(values: any[]): void; 433 | 434 | /** 435 | * Use `.toContainAnyValues` when checking if an object contains at least one of the provided values. 436 | * 437 | * @param {Array.<*>} values 438 | */ 439 | toContainAnyValues(values: any[]): void; 440 | 441 | /** 442 | * Use `.toContainEntry` when checking if an object contains the provided entry. 443 | * 444 | * @param {Array.} entry 445 | */ 446 | toContainEntry(entry: [string, string]): void; 447 | 448 | /** 449 | * Use `.toContainEntries` when checking if an object contains all of the provided entries. 450 | * 451 | * @param {Array.>} entries 452 | */ 453 | toContainEntries(entries: [string, string][]): void; 454 | 455 | /** 456 | * Use `.toContainAllEntries` when checking if an object only contains all of the provided entries. 457 | * 458 | * @param {Array.>} entries 459 | */ 460 | toContainAllEntries(entries: [string, string][]): void; 461 | 462 | /** 463 | * Use `.toContainAnyEntries` when checking if an object contains at least one of the provided entries. 464 | * 465 | * @param {Array.>} entries 466 | */ 467 | toContainAnyEntries(entries: [string, string][]): void; 468 | 469 | /** 470 | * Use `.toBeExtensible` when checking if an object is extensible. 471 | */ 472 | toBeExtensible(): void; 473 | 474 | /** 475 | * Use `.toBeFrozen` when checking if an object is frozen. 476 | */ 477 | toBeFrozen(): void; 478 | 479 | /** 480 | * Use `.toBeSealed` when checking if an object is sealed. 481 | */ 482 | toBeSealed(): void; 483 | 484 | /** 485 | * Use `.toBeString` when checking if a value is a `String`. 486 | */ 487 | toBeString(): void; 488 | 489 | /** 490 | * Use `.toEqualCaseInsensitive` when checking if a string is equal (===) to another ignoring the casing of both strings. 491 | * 492 | * @param {String} string 493 | */ 494 | toEqualCaseInsensitive(string: string): void; 495 | 496 | /** 497 | * Use `.toStartWith` when checking if a `String` starts with a given `String` prefix. 498 | * 499 | * @param {String} prefix 500 | */ 501 | toStartWith(prefix: string): void; 502 | 503 | /** 504 | * Use `.toEndWith` when checking if a `String` ends with a given `String` suffix. 505 | * 506 | * @param {String} suffix 507 | */ 508 | toEndWith(suffix: string): void; 509 | 510 | /** 511 | * Use `.toInclude` when checking if a `String` includes the given `String` substring. 512 | * 513 | * @param {String} substring 514 | */ 515 | toInclude(substring: string): void; 516 | 517 | /** 518 | * Use `.toIncludeRepeated` when checking if a `String` includes the given `String` substring the correct number of times. 519 | * 520 | * @param {String} substring 521 | * @param {Number} times 522 | */ 523 | toIncludeRepeated(substring: string, times: number): void; 524 | 525 | /** 526 | * Use `.toIncludeMultiple` when checking if a `String` includes all of the given substrings. 527 | * 528 | * @param {Array.} substring 529 | */ 530 | toIncludeMultiple(substring: string[]): void; 531 | }; 532 | 533 | interface JestExpectType { 534 | not: 535 | & JestExpectType 536 | & EnzymeMatchersType 537 | & DomTestingLibraryType 538 | & JestJQueryMatchersType 539 | & JestStyledComponentsMatchersType 540 | & JestExtendedMatchersType, 541 | /** 542 | * If you have a mock function, you can use .lastCalledWith to test what 543 | * arguments it was last called with. 544 | */ 545 | lastCalledWith(...args: Array): void, 546 | /** 547 | * toBe just checks that a value is what you expect. It uses === to check 548 | * strict equality. 549 | */ 550 | toBe(value: any): void, 551 | /** 552 | * Use .toBeCalledWith to ensure that a mock function was called with 553 | * specific arguments. 554 | */ 555 | toBeCalledWith(...args: Array): void, 556 | /** 557 | * Using exact equality with floating point numbers is a bad idea. Rounding 558 | * means that intuitive things fail. 559 | */ 560 | toBeCloseTo(num: number, delta: any): void, 561 | /** 562 | * Use .toBeDefined to check that a variable is not undefined. 563 | */ 564 | toBeDefined(): void, 565 | /** 566 | * Use .toBeFalsy when you don't care what a value is, you just want to 567 | * ensure a value is false in a boolean context. 568 | */ 569 | toBeFalsy(): void, 570 | /** 571 | * To compare floating point numbers, you can use toBeGreaterThan. 572 | */ 573 | toBeGreaterThan(number: number): void, 574 | /** 575 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 576 | */ 577 | toBeGreaterThanOrEqual(number: number): void, 578 | /** 579 | * To compare floating point numbers, you can use toBeLessThan. 580 | */ 581 | toBeLessThan(number: number): void, 582 | /** 583 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 584 | */ 585 | toBeLessThanOrEqual(number: number): void, 586 | /** 587 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 588 | * class. 589 | */ 590 | toBeInstanceOf(cls: Class<*>): void, 591 | /** 592 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 593 | * nicer. 594 | */ 595 | toBeNull(): void, 596 | /** 597 | * Use .toBeTruthy when you don't care what a value is, you just want to 598 | * ensure a value is true in a boolean context. 599 | */ 600 | toBeTruthy(): void, 601 | /** 602 | * Use .toBeUndefined to check that a variable is undefined. 603 | */ 604 | toBeUndefined(): void, 605 | /** 606 | * Use .toContain when you want to check that an item is in a list. For 607 | * testing the items in the list, this uses ===, a strict equality check. 608 | */ 609 | toContain(item: any): void, 610 | /** 611 | * Use .toContainEqual when you want to check that an item is in a list. For 612 | * testing the items in the list, this matcher recursively checks the 613 | * equality of all fields, rather than checking for object identity. 614 | */ 615 | toContainEqual(item: any): void, 616 | /** 617 | * Use .toEqual when you want to check that two objects have the same value. 618 | * This matcher recursively checks the equality of all fields, rather than 619 | * checking for object identity. 620 | */ 621 | toEqual(value: any): void, 622 | /** 623 | * Use .toHaveBeenCalled to ensure that a mock function got called. 624 | */ 625 | toHaveBeenCalled(): void, 626 | toBeCalled(): void; 627 | /** 628 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 629 | * number of times. 630 | */ 631 | toHaveBeenCalledTimes(number: number): void, 632 | toBeCalledTimes(number: number): void; 633 | /** 634 | * 635 | */ 636 | toHaveBeenNthCalledWith(nthCall: number, ...args: Array): void; 637 | nthCalledWith(nthCall: number, ...args: Array): void; 638 | /** 639 | * 640 | */ 641 | toHaveReturned(): void; 642 | toReturn(): void; 643 | /** 644 | * 645 | */ 646 | toHaveReturnedTimes(number: number): void; 647 | toReturnTimes(number: number): void; 648 | /** 649 | * 650 | */ 651 | toHaveReturnedWith(value: any): void; 652 | toReturnWith(value: any): void; 653 | /** 654 | * 655 | */ 656 | toHaveLastReturnedWith(value: any): void; 657 | lastReturnedWith(value: any): void; 658 | /** 659 | * 660 | */ 661 | toHaveNthReturnedWith(nthCall: number, value: any): void; 662 | nthReturnedWith(nthCall: number, value: any): void; 663 | /** 664 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 665 | * specific arguments. 666 | */ 667 | toHaveBeenCalledWith(...args: Array): void, 668 | toBeCalledWith(...args: Array): void, 669 | /** 670 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 671 | * with specific arguments. 672 | */ 673 | toHaveBeenLastCalledWith(...args: Array): void, 674 | lastCalledWith(...args: Array): void, 675 | /** 676 | * Check that an object has a .length property and it is set to a certain 677 | * numeric value. 678 | */ 679 | toHaveLength(number: number): void, 680 | /** 681 | * 682 | */ 683 | toHaveProperty(propPath: string, value?: any): void, 684 | /** 685 | * Use .toMatch to check that a string matches a regular expression or string. 686 | */ 687 | toMatch(regexpOrString: RegExp | string): void, 688 | /** 689 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 690 | */ 691 | toMatchObject(object: Object | Array): void, 692 | /** 693 | * Use .toStrictEqual to check that a javascript object matches a subset of the properties of an object. 694 | */ 695 | toStrictEqual(value: any): void, 696 | /** 697 | * This ensures that an Object matches the most recent snapshot. 698 | */ 699 | toMatchSnapshot(propertyMatchers?: {[key: string]: JestAsymmetricEqualityType}, name?: string): void, 700 | /** 701 | * This ensures that an Object matches the most recent snapshot. 702 | */ 703 | toMatchSnapshot(name: string): void, 704 | 705 | toMatchInlineSnapshot(snapshot?: string): void, 706 | toMatchInlineSnapshot(propertyMatchers?: {[key: string]: JestAsymmetricEqualityType}, snapshot?: string): void, 707 | /** 708 | * Use .toThrow to test that a function throws when it is called. 709 | * If you want to test that a specific error gets thrown, you can provide an 710 | * argument to toThrow. The argument can be a string for the error message, 711 | * a class for the error, or a regex that should match the error. 712 | * 713 | * Alias: .toThrowError 714 | */ 715 | toThrow(message?: string | Error | Class | RegExp): void, 716 | toThrowError(message?: string | Error | Class | RegExp): void, 717 | /** 718 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 719 | * matching the most recent snapshot when it is called. 720 | */ 721 | toThrowErrorMatchingSnapshot(): void, 722 | toThrowErrorMatchingInlineSnapshot(snapshot?: string): void, 723 | } 724 | 725 | type JestObjectType = { 726 | /** 727 | * Disables automatic mocking in the module loader. 728 | * 729 | * After this method is called, all `require()`s will return the real 730 | * versions of each module (rather than a mocked version). 731 | */ 732 | disableAutomock(): JestObjectType, 733 | /** 734 | * An un-hoisted version of disableAutomock 735 | */ 736 | autoMockOff(): JestObjectType, 737 | /** 738 | * Enables automatic mocking in the module loader. 739 | */ 740 | enableAutomock(): JestObjectType, 741 | /** 742 | * An un-hoisted version of enableAutomock 743 | */ 744 | autoMockOn(): JestObjectType, 745 | /** 746 | * Clears the mock.calls and mock.instances properties of all mocks. 747 | * Equivalent to calling .mockClear() on every mocked function. 748 | */ 749 | clearAllMocks(): JestObjectType, 750 | /** 751 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 752 | * mocked function. 753 | */ 754 | resetAllMocks(): JestObjectType, 755 | /** 756 | * Restores all mocks back to their original value. 757 | */ 758 | restoreAllMocks(): JestObjectType, 759 | /** 760 | * Removes any pending timers from the timer system. 761 | */ 762 | clearAllTimers(): void, 763 | /** 764 | * The same as `mock` but not moved to the top of the expectation by 765 | * babel-jest. 766 | */ 767 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 768 | /** 769 | * The same as `unmock` but not moved to the top of the expectation by 770 | * babel-jest. 771 | */ 772 | dontMock(moduleName: string): JestObjectType, 773 | /** 774 | * Returns a new, unused mock function. Optionally takes a mock 775 | * implementation. 776 | */ 777 | fn, TReturn>( 778 | implementation?: (...args: TArguments) => TReturn 779 | ): JestMockFn, 780 | /** 781 | * Determines if the given function is a mocked function. 782 | */ 783 | isMockFunction(fn: Function): boolean, 784 | /** 785 | * Given the name of a module, use the automatic mocking system to generate a 786 | * mocked version of the module for you. 787 | */ 788 | genMockFromModule(moduleName: string): any, 789 | /** 790 | * Mocks a module with an auto-mocked version when it is being required. 791 | * 792 | * The second argument can be used to specify an explicit module factory that 793 | * is being run instead of using Jest's automocking feature. 794 | * 795 | * The third argument can be used to create virtual mocks -- mocks of modules 796 | * that don't exist anywhere in the system. 797 | */ 798 | mock( 799 | moduleName: string, 800 | moduleFactory?: any, 801 | options?: Object 802 | ): JestObjectType, 803 | /** 804 | * Returns the actual module instead of a mock, bypassing all checks on 805 | * whether the module should receive a mock implementation or not. 806 | */ 807 | requireActual(moduleName: string): any, 808 | /** 809 | * Returns a mock module instead of the actual module, bypassing all checks 810 | * on whether the module should be required normally or not. 811 | */ 812 | requireMock(moduleName: string): any, 813 | /** 814 | * Resets the module registry - the cache of all required modules. This is 815 | * useful to isolate modules where local state might conflict between tests. 816 | */ 817 | resetModules(): JestObjectType, 818 | /** 819 | * Exhausts the micro-task queue (usually interfaced in node via 820 | * process.nextTick). 821 | */ 822 | runAllTicks(): void, 823 | /** 824 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 825 | * setInterval(), and setImmediate()). 826 | */ 827 | runAllTimers(): void, 828 | /** 829 | * Exhausts all tasks queued by setImmediate(). 830 | */ 831 | runAllImmediates(): void, 832 | /** 833 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 834 | * or setInterval() and setImmediate()). 835 | */ 836 | advanceTimersByTime(msToRun: number): void, 837 | /** 838 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 839 | * or setInterval() and setImmediate()). 840 | * 841 | * Renamed to `advanceTimersByTime`. 842 | */ 843 | runTimersToTime(msToRun: number): void, 844 | /** 845 | * Executes only the macro-tasks that are currently pending (i.e., only the 846 | * tasks that have been queued by setTimeout() or setInterval() up to this 847 | * point) 848 | */ 849 | runOnlyPendingTimers(): void, 850 | /** 851 | * Explicitly supplies the mock object that the module system should return 852 | * for the specified module. Note: It is recommended to use jest.mock() 853 | * instead. 854 | */ 855 | setMock(moduleName: string, moduleExports: any): JestObjectType, 856 | /** 857 | * Indicates that the module system should never return a mocked version of 858 | * the specified module from require() (e.g. that it should always return the 859 | * real module). 860 | */ 861 | unmock(moduleName: string): JestObjectType, 862 | /** 863 | * Instructs Jest to use fake versions of the standard timer functions 864 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 865 | * setImmediate and clearImmediate). 866 | */ 867 | useFakeTimers(): JestObjectType, 868 | /** 869 | * Instructs Jest to use the real versions of the standard timer functions. 870 | */ 871 | useRealTimers(): JestObjectType, 872 | /** 873 | * Creates a mock function similar to jest.fn but also tracks calls to 874 | * object[methodName]. 875 | */ 876 | spyOn(object: Object, methodName: string, accessType?: "get" | "set"): JestMockFn, 877 | /** 878 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 879 | * Note: The default timeout interval is 5 seconds if this method is not called. 880 | */ 881 | setTimeout(timeout: number): JestObjectType 882 | }; 883 | 884 | type JestSpyType = { 885 | calls: JestCallsType 886 | }; 887 | 888 | /** Runs this function after every test inside this context */ 889 | declare function afterEach( 890 | fn: (done: () => void) => ?Promise, 891 | timeout?: number 892 | ): void; 893 | /** Runs this function before every test inside this context */ 894 | declare function beforeEach( 895 | fn: (done: () => void) => ?Promise, 896 | timeout?: number 897 | ): void; 898 | /** Runs this function after all tests have finished inside this context */ 899 | declare function afterAll( 900 | fn: (done: () => void) => ?Promise, 901 | timeout?: number 902 | ): void; 903 | /** Runs this function before any tests have started inside this context */ 904 | declare function beforeAll( 905 | fn: (done: () => void) => ?Promise, 906 | timeout?: number 907 | ): void; 908 | 909 | /** A context for grouping tests together */ 910 | declare var describe: { 911 | /** 912 | * Creates a block that groups together several related tests in one "test suite" 913 | */ 914 | (name: JestTestName, fn: () => void): void, 915 | 916 | /** 917 | * Only run this describe block 918 | */ 919 | only(name: JestTestName, fn: () => void): void, 920 | 921 | /** 922 | * Skip running this describe block 923 | */ 924 | skip(name: JestTestName, fn: () => void): void, 925 | 926 | /** 927 | * each runs this test against array of argument arrays per each run 928 | * 929 | * @param {table} table of Test 930 | */ 931 | each( 932 | table: Array | mixed> 933 | ): ( 934 | name: JestTestName, 935 | fn?: (...args: Array) => ?Promise 936 | ) => void, 937 | }; 938 | 939 | /** An individual test unit */ 940 | declare var it: { 941 | /** 942 | * An individual test unit 943 | * 944 | * @param {JestTestName} Name of Test 945 | * @param {Function} Test 946 | * @param {number} Timeout for the test, in milliseconds. 947 | */ 948 | ( 949 | name: JestTestName, 950 | fn?: (done: () => void) => ?Promise, 951 | timeout?: number 952 | ): void, 953 | /** 954 | * each runs this test against array of argument arrays per each run 955 | * 956 | * @param {table} table of Test 957 | */ 958 | each( 959 | table: Array | mixed> 960 | ): ( 961 | name: JestTestName, 962 | fn?: (...args: Array) => ?Promise 963 | ) => void, 964 | /** 965 | * Only run this test 966 | * 967 | * @param {JestTestName} Name of Test 968 | * @param {Function} Test 969 | * @param {number} Timeout for the test, in milliseconds. 970 | */ 971 | only( 972 | name: JestTestName, 973 | fn?: (done: () => void) => ?Promise, 974 | timeout?: number 975 | ): { 976 | each( 977 | table: Array | mixed> 978 | ): ( 979 | name: JestTestName, 980 | fn?: (...args: Array) => ?Promise 981 | ) => void, 982 | }, 983 | /** 984 | * Skip running this test 985 | * 986 | * @param {JestTestName} Name of Test 987 | * @param {Function} Test 988 | * @param {number} Timeout for the test, in milliseconds. 989 | */ 990 | skip( 991 | name: JestTestName, 992 | fn?: (done: () => void) => ?Promise, 993 | timeout?: number 994 | ): void, 995 | /** 996 | * Run the test concurrently 997 | * 998 | * @param {JestTestName} Name of Test 999 | * @param {Function} Test 1000 | * @param {number} Timeout for the test, in milliseconds. 1001 | */ 1002 | concurrent( 1003 | name: JestTestName, 1004 | fn?: (done: () => void) => ?Promise, 1005 | timeout?: number 1006 | ): void, 1007 | /** 1008 | * each runs this test against array of argument arrays per each run 1009 | * 1010 | * @param {table} table of Test 1011 | */ 1012 | each( 1013 | table: Array | mixed> 1014 | ): ( 1015 | name: JestTestName, 1016 | fn?: (...args: Array) => ?Promise 1017 | ) => void, 1018 | }; 1019 | declare function fit( 1020 | name: JestTestName, 1021 | fn: (done: () => void) => ?Promise, 1022 | timeout?: number 1023 | ): void; 1024 | /** An individual test unit */ 1025 | declare var test: typeof it; 1026 | /** A disabled group of tests */ 1027 | declare var xdescribe: typeof describe; 1028 | /** A focused group of tests */ 1029 | declare var fdescribe: typeof describe; 1030 | /** A disabled individual test */ 1031 | declare var xit: typeof it; 1032 | /** A disabled individual test */ 1033 | declare var xtest: typeof it; 1034 | 1035 | type JestPrettyFormatColors = { 1036 | comment: { close: string, open: string }, 1037 | content: { close: string, open: string }, 1038 | prop: { close: string, open: string }, 1039 | tag: { close: string, open: string }, 1040 | value: { close: string, open: string }, 1041 | }; 1042 | 1043 | type JestPrettyFormatIndent = string => string; 1044 | type JestPrettyFormatRefs = Array; 1045 | type JestPrettyFormatPrint = any => string; 1046 | type JestPrettyFormatStringOrNull = string | null; 1047 | 1048 | type JestPrettyFormatOptions = {| 1049 | callToJSON: boolean, 1050 | edgeSpacing: string, 1051 | escapeRegex: boolean, 1052 | highlight: boolean, 1053 | indent: number, 1054 | maxDepth: number, 1055 | min: boolean, 1056 | plugins: JestPrettyFormatPlugins, 1057 | printFunctionName: boolean, 1058 | spacing: string, 1059 | theme: {| 1060 | comment: string, 1061 | content: string, 1062 | prop: string, 1063 | tag: string, 1064 | value: string, 1065 | |}, 1066 | |}; 1067 | 1068 | type JestPrettyFormatPlugin = { 1069 | print: ( 1070 | val: any, 1071 | serialize: JestPrettyFormatPrint, 1072 | indent: JestPrettyFormatIndent, 1073 | opts: JestPrettyFormatOptions, 1074 | colors: JestPrettyFormatColors, 1075 | ) => string, 1076 | test: any => boolean, 1077 | }; 1078 | 1079 | type JestPrettyFormatPlugins = Array; 1080 | 1081 | /** The expect function is used every time you want to test a value */ 1082 | declare var expect: { 1083 | /** The object that you want to make assertions against */ 1084 | (value: any): 1085 | & JestExpectType 1086 | & JestPromiseType 1087 | & EnzymeMatchersType 1088 | & DomTestingLibraryType 1089 | & JestJQueryMatchersType 1090 | & JestStyledComponentsMatchersType 1091 | & JestExtendedMatchersType, 1092 | 1093 | /** Add additional Jasmine matchers to Jest's roster */ 1094 | extend(matchers: { [name: string]: JestMatcher }): void, 1095 | /** Add a module that formats application-specific data structures. */ 1096 | addSnapshotSerializer(pluginModule: JestPrettyFormatPlugin): void, 1097 | assertions(expectedAssertions: number): void, 1098 | hasAssertions(): void, 1099 | any(value: mixed): JestAsymmetricEqualityType, 1100 | anything(): any, 1101 | arrayContaining(value: Array): Array, 1102 | objectContaining(value: Object): Object, 1103 | /** Matches any received string that contains the exact expected string. */ 1104 | stringContaining(value: string): string, 1105 | stringMatching(value: string | RegExp): string, 1106 | not: { 1107 | arrayContaining: (value: $ReadOnlyArray) => Array, 1108 | objectContaining: (value: {}) => Object, 1109 | stringContaining: (value: string) => string, 1110 | stringMatching: (value: string | RegExp) => string, 1111 | }, 1112 | }; 1113 | 1114 | // TODO handle return type 1115 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 1116 | declare function spyOn(value: mixed, method: string): Object; 1117 | 1118 | /** Holds all functions related to manipulating test runner */ 1119 | declare var jest: JestObjectType; 1120 | 1121 | /** 1122 | * The global Jasmine object, this is generally not exposed as the public API, 1123 | * using features inside here could break in later versions of Jest. 1124 | */ 1125 | declare var jasmine: { 1126 | DEFAULT_TIMEOUT_INTERVAL: number, 1127 | any(value: mixed): JestAsymmetricEqualityType, 1128 | anything(): any, 1129 | arrayContaining(value: Array): Array, 1130 | clock(): JestClockType, 1131 | createSpy(name: string): JestSpyType, 1132 | createSpyObj( 1133 | baseName: string, 1134 | methodNames: Array 1135 | ): { [methodName: string]: JestSpyType }, 1136 | objectContaining(value: Object): Object, 1137 | stringMatching(value: string): string 1138 | }; 1139 | -------------------------------------------------------------------------------- /flow-typed/npm/react-test-renderer_v16.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9b9f4128694a7f68659d945b81fb78ff 2 | // flow-typed version: 46dfe79a54/react-test-renderer_v16.x.x/flow_>=v0.47.x 3 | 4 | // Type definitions for react-test-renderer 16.x.x 5 | // Ported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer 6 | 7 | type ReactComponentInstance = React$Component; 8 | 9 | type ReactTestRendererJSON = { 10 | type: string, 11 | props: { [propName: string]: any }, 12 | children: null | ReactTestRendererJSON[] 13 | }; 14 | 15 | type ReactTestRendererTree = ReactTestRendererJSON & { 16 | nodeType: "component" | "host", 17 | instance: ?ReactComponentInstance, 18 | rendered: null | ReactTestRendererTree 19 | }; 20 | 21 | type ReactTestInstance = { 22 | instance: ?ReactComponentInstance, 23 | type: string, 24 | props: { [propName: string]: any }, 25 | parent: null | ReactTestInstance, 26 | children: Array, 27 | 28 | find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance, 29 | findByType(type: React$ElementType): ReactTestInstance, 30 | findByProps(props: { [propName: string]: any }): ReactTestInstance, 31 | 32 | findAll( 33 | predicate: (node: ReactTestInstance) => boolean, 34 | options?: { deep: boolean } 35 | ): ReactTestInstance[], 36 | findAllByType( 37 | type: React$ElementType, 38 | options?: { deep: boolean } 39 | ): ReactTestInstance[], 40 | findAllByProps( 41 | props: { [propName: string]: any }, 42 | options?: { deep: boolean } 43 | ): ReactTestInstance[] 44 | }; 45 | 46 | type TestRendererOptions = { 47 | createNodeMock(element: React$Element): any 48 | }; 49 | 50 | declare module "react-test-renderer" { 51 | declare export type ReactTestRenderer = { 52 | toJSON(): null | ReactTestRendererJSON, 53 | toTree(): null | ReactTestRendererTree, 54 | unmount(nextElement?: React$Element): void, 55 | update(nextElement: React$Element): void, 56 | getInstance(): ?ReactComponentInstance, 57 | root: ReactTestInstance 58 | }; 59 | 60 | declare function create( 61 | nextElement: React$Element, 62 | options?: TestRendererOptions 63 | ): ReactTestRenderer; 64 | } 65 | 66 | declare module "react-test-renderer/shallow" { 67 | declare export default class ShallowRenderer { 68 | static createRenderer(): ShallowRenderer; 69 | getMountedInstance(): ReactTestInstance; 70 | getRenderOutput>(): E; 71 | getRenderOutput(): React$Element; 72 | render(element: React$Element, context?: any): void; 73 | unmount(): void; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /new-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ovidiuch/react-mock/c33dfa1d6f0c9ce7b3eaba073618d61731a0e82e/new-package.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mock", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "examples/*" 7 | ], 8 | "scripts": { 9 | "flow": "flow", 10 | "lint": "eslint '**/*.js' --quiet", 11 | "test": "jest --config .jest/config.js", 12 | "test:watch": "yarn test --watch", 13 | "build": "yarn babel-node ./scripts/build.js", 14 | "new-package": "yarn babel-node ./scripts/new-package.js", 15 | "generate-readme": "yarn babel-node ./scripts/generate-readme.js", 16 | "release:check": "yarn flow && yarn lint && yarn test", 17 | "release:prepare": "yarn generate-readme && yarn build", 18 | "release:lerna": "lerna publish --registry=https://registry.npmjs.org/", 19 | "release": "yarn release:check && yarn release:prepare && yarn release:lerna" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.1.2", 23 | "@babel/core": "^7.1.2", 24 | "@babel/node": "^7.0.0", 25 | "@babel/plugin-proposal-class-properties": "^7.1.0", 26 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 27 | "@babel/plugin-transform-runtime": "^7.1.0", 28 | "@babel/preset-env": "^7.1.0", 29 | "@babel/preset-flow": "^7.0.0", 30 | "@babel/preset-react": "^7.0.0", 31 | "@babel/runtime": "^7.1.2", 32 | "@skidding/async-retry": "^1.0.2", 33 | "async-until": "^1.2.2", 34 | "babel-core": "7.0.0-bridge.0", 35 | "babel-eslint": "^10.0.1", 36 | "babel-jest": "^23.6.0", 37 | "chalk": "^2.4.1", 38 | "child-process-promise": "^2.2.1", 39 | "codecov": "^3.1.0", 40 | "cpy": "^7.0.1", 41 | "eslint": "^5.7.0", 42 | "eslint-plugin-flowtype": "^3.0.0", 43 | "eslint-plugin-import": "^2.14.0", 44 | "eslint-plugin-react": "^7.11.1", 45 | "flow-bin": "^0.83.0", 46 | "flow-typed": "^2.5.1", 47 | "fs-extra": "^7.0.0", 48 | "glob": "^7.1.3", 49 | "inquirer": "^6.2.0", 50 | "jest": "^23.6.0", 51 | "lerna": "^3.13.1", 52 | "lodash": "^4.17.11", 53 | "prettier": "^1.14.3", 54 | "react": "^16.5.2", 55 | "react-dom": "^16.5.2", 56 | "react-redux": "^5.0.7", 57 | "react-test-renderer": "^16.5.2", 58 | "redux": "^4.0.1", 59 | "redux-thunk": "^2.3.0", 60 | "rimraf": "^2.6.2", 61 | "yargs": "^12.0.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/fetch/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | setup-global-fetch.js 3 | -------------------------------------------------------------------------------- /packages/fetch/README.md: -------------------------------------------------------------------------------- 1 | ## Fetch requests 2 | 3 | A declarative wrapper for the wonderful [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/). 4 | 5 | > **Note:** FetchMock mocks the global Fetch API, so only one FetchMock instance should be rendered at once. 6 | 7 | ```js 8 | import { FetchMock } from '@react-mock/fetch'; 9 | 10 | // Passing fetch-mock options 11 | render( 12 | 13 | 14 | 15 | ); 16 | 17 | // Passing fetch-mock config 18 | render( 19 | 24 | 25 | 26 | ); 27 | ``` 28 | 29 | ### Multiple mocks 30 | 31 | ```js 32 | render( 33 | 39 | 40 | 41 | ); 42 | ``` 43 | 44 | ### Inspection 45 | 46 | See fetch-mock's [inspection methods](http://www.wheresrhys.co.uk/fetch-mock/#api-inspectionfiltering) to check how fetch was called. 47 | 48 | > **Note:** Import `fetchMock` from @react-mock/fetch to ensure you're inspecting on the right fetch-mock instance. 49 | 50 | ```js 51 | import { fetchMock } from '@react-mock/fetch'; 52 | 53 | const [, { body }] = fetchMock.lastCall('/login', 'POST'); 54 | expect(JSON.parse(body)).toEqual({ user: 'harry' }); 55 | ``` 56 | -------------------------------------------------------------------------------- /packages/fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mock/fetch", 3 | "version": "0.3.0", 4 | "description": "Mock Fetch requests declaratively", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/skidding/react-mock", 7 | "author": "Ovidiu Cherecheș ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.1.2", 14 | "fetch-mock": "^7.0.7", 15 | "lodash": "^4.17.11" 16 | }, 17 | "devDependencies": { 18 | "node-fetch": "^2.2.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/fetch/setup-global-fetch.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | global.fetch = fetch; 4 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/get.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { users: Array<{ name: string }> }> { 10 | state = { 11 | users: [] 12 | }; 13 | 14 | async componentDidMount() { 15 | this.setState({ 16 | users: await (await fetch('/users')).json() 17 | }); 18 | } 19 | 20 | render() { 21 | return this.state.users.map(user => user.name); 22 | } 23 | } 24 | 25 | it('mocks GET response', async () => { 26 | const renderer = create( 27 | 31 | 32 | 33 | ); 34 | 35 | await retry(() => { 36 | expect(renderer.toJSON()).toEqual(['Paul', 'Jessica']); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/inspect.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock, fetchMock } from '..'; 8 | 9 | class MyComponent extends Component<{}> { 10 | async componentDidMount() { 11 | await fetch('/login', { 12 | method: 'POST', 13 | body: JSON.stringify({ user: 'harry' }) 14 | }); 15 | } 16 | 17 | render() { 18 | return null; 19 | } 20 | } 21 | 22 | it('matches POST body', async () => { 23 | create( 24 | 25 | 26 | 27 | ); 28 | 29 | await retry(() => { 30 | const [, { body }] = fetchMock.lastCall('/login', 'POST'); 31 | expect(JSON.parse(body)).toEqual({ user: 'harry' }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/multi.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { name: null | string }> { 10 | state = { 11 | name: null 12 | }; 13 | 14 | async componentDidMount() { 15 | const users = await (await fetch('/users')).json(); 16 | const { name } = await (await fetch(`/user/${users[0].id}`)).json(); 17 | 18 | this.setState({ 19 | name 20 | }); 21 | } 22 | 23 | render() { 24 | return this.state.name; 25 | } 26 | } 27 | 28 | it('mocks multi GET responses', async () => { 29 | const renderer = create( 30 | 36 | 37 | 38 | ); 39 | 40 | await retry(() => { 41 | expect(renderer.toJSON()).toEqual('Jessica'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/network-fallback.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { errorMsg: null | string }> { 10 | state = { 11 | errorMsg: null 12 | }; 13 | 14 | async componentDidMount() { 15 | try { 16 | await fetch('/comments'); 17 | } catch (err) { 18 | this.setState({ errorMsg: String(err) }); 19 | } 20 | } 21 | 22 | render() { 23 | return this.state.errorMsg; 24 | } 25 | } 26 | 27 | it('captures fetch-mock error on fallback', async () => { 28 | const renderer = create( 29 | 34 | 35 | 36 | ); 37 | 38 | await retry(() => { 39 | expect(renderer.toJSON()).toMatch( 40 | 'Error: fetch-mock: No fallback response defined for GET to /comments' 41 | ); 42 | }); 43 | }); 44 | 45 | it('captures node-fetch error on fallback', async () => { 46 | const renderer = create( 47 | 52 | 53 | 54 | ); 55 | 56 | await retry(() => { 57 | expect(renderer.toJSON()).toMatch( 58 | 'TypeError: Only absolute URLs are supported' 59 | ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/options.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { users: Array<{ name: string }> }> { 10 | state = { 11 | users: [] 12 | }; 13 | 14 | async componentDidMount() { 15 | this.setState({ 16 | users: await (await fetch('/users')).json() 17 | }); 18 | } 19 | 20 | render() { 21 | return this.state.users.map(user => user.name); 22 | } 23 | } 24 | 25 | it('mocks GET response', async () => { 26 | const renderer = create( 27 | 34 | 35 | 36 | ); 37 | 38 | await retry(() => { 39 | expect(renderer.toJSON()).toEqual(['Paul', 'Jessica']); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/post.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React, { Component } from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import retry from '@skidding/async-retry'; 7 | import { FetchMock } from '..'; 8 | 9 | class MyComponent extends Component< 10 | {}, 11 | { status: 'loading' | 'success' | 'error' } 12 | > { 13 | state = { 14 | status: 'loading' 15 | }; 16 | 17 | async componentDidMount() { 18 | const response = await fetch('/create', { method: 'POST' }); 19 | 20 | this.setState({ 21 | status: response.status === 200 ? 'success' : 'error' 22 | }); 23 | } 24 | 25 | render() { 26 | return this.state.status; 27 | } 28 | } 29 | 30 | it('mocks POST 200 response', async () => { 31 | const renderer = create( 32 | 33 | 34 | 35 | ); 36 | 37 | await retry(() => { 38 | expect(renderer.toJSON()).toEqual('success'); 39 | }); 40 | }); 41 | 42 | it('mocks POST 500 response', async () => { 43 | const renderer = create( 44 | 45 | 46 | 47 | ); 48 | 49 | await retry(() => { 50 | expect(renderer.toJSON()).toEqual('error'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/fetch/src/__tests__/unmount.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global fetch */ 3 | 4 | import React from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import nodeFetch from 'node-fetch'; 7 | import { FetchMock } from '..'; 8 | 9 | it('restores global fetch on unmount', async () => { 10 | const renderer = create( 11 | 12 |
13 | 14 | ); 15 | 16 | renderer.unmount(); 17 | 18 | expect(fetch).toEqual(nodeFetch); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/fetch/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Component } from 'react'; 4 | import fetchMock from 'fetch-mock'; 5 | 6 | import type { Props } from './index.js.flow'; 7 | 8 | // Allow consumers to run assertions on the same instance of fetchMock 9 | export { default as fetchMock } from 'fetch-mock'; 10 | 11 | export class FetchMock extends Component { 12 | constructor(props: Props) { 13 | super(props); 14 | 15 | this.mock(); 16 | } 17 | 18 | render() { 19 | return this.props.children; 20 | } 21 | 22 | componentWillUnmount() { 23 | // Make sure we don't clear a mock from a newer instance (since React 16 24 | // B.constructor is called before A.componentWillUnmount) 25 | if (fetchMock.__fetchMockInst === this) { 26 | this.unmock(); 27 | } 28 | } 29 | 30 | mock() { 31 | // Clear mocks from a previous FetchMock instance 32 | // NOTE: The last rendered FetchProxy instance will override mocks from 33 | // the previous ones 34 | this.unmock(); 35 | 36 | const { props } = this; 37 | const { config } = props; 38 | 39 | if (config) { 40 | Object.keys(config).forEach(key => { 41 | fetchMock.config[key] = config[key]; 42 | }); 43 | } 44 | 45 | if (props.mocks) { 46 | props.mocks.forEach(options => { 47 | fetchMock.mock(options); 48 | }); 49 | } else if (props.matcher) { 50 | fetchMock.mock(props.matcher, props.response, props.options); 51 | } else if (props.options) { 52 | // NOTE: We shouldn't check `props.options` at this point, but for some 53 | // reason Flow doesn't get it. 54 | fetchMock.mock(props.options); 55 | } 56 | 57 | fetchMock.__fetchMockInst = this; 58 | } 59 | 60 | unmock() { 61 | if (typeof fetchMock.restore === 'function') { 62 | fetchMock.restore(); 63 | delete fetchMock.__fetchMockInst; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/fetch/src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { ComponentType, Element } from 'react'; 4 | 5 | // For all fetch-mock options see 6 | // http://www.wheresrhys.co.uk/fetch-mock/#api-mockingmock 7 | 8 | type FetchMatcher = 9 | | string 10 | | RegExp 11 | | ((url: string, options: Object) => boolean); 12 | 13 | type FetchResponse = any; 14 | 15 | type RequiredOptions = { 16 | matcher: FetchMatcher, 17 | response: FetchResponse 18 | }; 19 | 20 | type SharedProps = {| 21 | children: Element, 22 | config?: {} 23 | |}; 24 | 25 | type SingleShorthandProps = {| 26 | ...SharedProps, 27 | matcher: FetchMatcher, 28 | response: FetchResponse, 29 | options?: {} 30 | |}; 31 | 32 | type SingleExplicitProps = {| 33 | ...SharedProps, 34 | options: RequiredOptions 35 | |}; 36 | 37 | type MultiMockProps = {| 38 | ...SharedProps, 39 | mocks: RequiredOptions[] 40 | |}; 41 | 42 | export type Props = SingleShorthandProps | SingleExplicitProps | MultiMockProps; 43 | 44 | declare export var FetchMock: ComponentType; 45 | 46 | declare export var fetchMock: Function; 47 | -------------------------------------------------------------------------------- /packages/localstorage/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /packages/localstorage/README.md: -------------------------------------------------------------------------------- 1 | ## LocalStorage 2 | 3 | Mock LocalStorage data declaratively. 4 | 5 | > **Note:** LocalStorageMock mocks the global localStorage API, so only one LocalStorageMock instance should be rendered at once. 6 | 7 | ```js 8 | import { LocalStorageMock } from '@react-mock/localstorage'; 9 | 10 | render( 11 | 12 | 13 | 14 | ); 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/localstorage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mock/localstorage", 3 | "version": "0.1.2", 4 | "description": "Mock LocalStorage data declaratively", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/skidding/react-mock", 7 | "author": "Ovidiu Cherecheș ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.1.2", 14 | "lodash": "^4.17.11" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/localstorage/src/StorageMock.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Store = { [string]: mixed }; 4 | 5 | // Mocking LocalStorage (or similar) ensures no conflict with existing browser 6 | // data and works in test environments like Jest 7 | export class StorageMock { 8 | store: Store; 9 | 10 | constructor(store: Store) { 11 | this.store = { ...store }; 12 | } 13 | 14 | getItem(key: string) { 15 | return this.store[key] || null; 16 | } 17 | 18 | setItem(key: string, value: { toString: () => string }) { 19 | this.store[key] = value.toString(); 20 | } 21 | 22 | removeItem(key: string) { 23 | delete this.store[key]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/localstorage/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Component } from 'react'; 4 | import { StorageMock } from './StorageMock'; 5 | 6 | import type { Props } from './index.js.flow'; 7 | 8 | export class LocalStorageMock extends Component { 9 | _storageOrig: Object; 10 | _storageMock: StorageMock; 11 | 12 | constructor(props: Props) { 13 | super(props); 14 | 15 | const { items } = props; 16 | 17 | this._storageOrig = global.localStorage; 18 | this._storageMock = new StorageMock(items); 19 | 20 | setLocalStorage(this._storageMock); 21 | } 22 | 23 | componentWillUnmount() { 24 | // Make sure we don't clear a mock from a newer instance (since React 16 25 | // B.constructor is called before A.componentWillUnmount) 26 | if (getLocalStorage() === this._storageMock) { 27 | setLocalStorage(this._storageOrig); 28 | } 29 | } 30 | 31 | render() { 32 | return this.props.children; 33 | } 34 | } 35 | 36 | function getLocalStorage() { 37 | return getGlobalObj().localStorage; 38 | } 39 | 40 | function setLocalStorage(value) { 41 | Object.defineProperty(getGlobalObj(), 'localStorage', { 42 | writable: true, 43 | value 44 | }); 45 | } 46 | 47 | function getGlobalObj() { 48 | // eslint-disable-next-line no-undef 49 | return typeof window !== 'undefined' ? window : global; 50 | } 51 | -------------------------------------------------------------------------------- /packages/localstorage/src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { ComponentType, Element } from 'react'; 4 | 5 | export type Props = { 6 | children: Element, 7 | items: { [string]: mixed } 8 | }; 9 | 10 | declare export var LocalStorageMock: ComponentType; 11 | -------------------------------------------------------------------------------- /packages/localstorage/src/index.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global localStorage */ 3 | 4 | import React from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import { LocalStorageMock } from '.'; 7 | 8 | const MyComponent = () => 'Hello World!'; 9 | 10 | it('renders children', () => { 11 | const renderer = create( 12 | 13 | 14 | 15 | ); 16 | 17 | expect(renderer.toJSON()).toMatch(`Hello World!`); 18 | }); 19 | 20 | it('mocks getItem', () => { 21 | create( 22 | 23 | 24 | 25 | ); 26 | 27 | expect(localStorage.getItem('displayName')).toEqual('Jessica'); 28 | }); 29 | 30 | it('mocks setItem', () => { 31 | create( 32 | 33 | 34 | 35 | ); 36 | 37 | localStorage.setItem('displayName', 'Paul'); 38 | 39 | expect(localStorage.getItem('displayName')).toEqual('Paul'); 40 | }); 41 | 42 | it('mocks removeItem', () => { 43 | create( 44 | 45 | 46 | 47 | ); 48 | 49 | localStorage.removeItem('displayName'); 50 | 51 | expect(localStorage.getItem('displayName')).toEqual(null); 52 | }); 53 | 54 | it('reverts localStorage on unmount', () => { 55 | const localStorageOrig = {}; 56 | global.localStorage = localStorageOrig; 57 | 58 | const wrapper = create( 59 | 60 | 61 | 62 | ); 63 | wrapper.unmount(); 64 | 65 | expect(global.localStorage).toBe(localStorageOrig); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/state/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /packages/state/README.md: -------------------------------------------------------------------------------- 1 | ## Component state 2 | 3 | Inject React component state declaratively. 4 | 5 | > `StateMock` must be the direct parent of the stateful component for the state injection to work. 6 | 7 | ```js 8 | import { StateMock } from '@react-mock/state'; 9 | 10 | render( 11 | 12 | 13 | 14 | ); 15 | ``` 16 | 17 | > **Warning:** StateMock delays ref calls. This means refs can get called _after_ componentDidMount, instead of before as you [might expect](https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback). 18 | -------------------------------------------------------------------------------- /packages/state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mock/state", 3 | "version": "0.1.8", 4 | "description": "Inject React component state declaratively", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/skidding/react-mock", 7 | "author": "Ovidiu Cherecheș ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.1.2", 14 | "lodash": "^4.17.11" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/state/src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@react-mock/state' { 2 | import * as React from 'react'; 3 | 4 | type Props = { 5 | children: React.ReactElement; 6 | state?: object; 7 | }; 8 | 9 | export declare function StateMock(props: Props); 10 | } 11 | -------------------------------------------------------------------------------- /packages/state/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { isEqual } from 'lodash'; 4 | import { Component, cloneElement } from 'react'; 5 | 6 | import type { Ref } from 'react'; 7 | import type { Props, ComponentRef } from './index.js.flow'; 8 | 9 | // `state` prop must be an object, as does React component state. 10 | // "The state is user-defined, and it should be a plain JavaScript object." 11 | // https://reactjs.org/docs/react-component.html#state 12 | export class StateMock extends Component { 13 | static cosmosCapture = false; 14 | 15 | childRef: ?ComponentRef; 16 | 17 | render() { 18 | const { children } = this.props; 19 | 20 | // Flow users will get a static error when trying to wrap more elements with 21 | // StateMock. Others might bypass this limitation and find out at run time. 22 | if (Array.isArray(children)) { 23 | throw new Error('StateMock only accepts a single child element'); 24 | } 25 | 26 | return cloneElement(children, { ref: this.handleRef }); 27 | } 28 | 29 | componentDidUpdate(prevProps: Props) { 30 | const { childRef } = this; 31 | const { state } = this.props; 32 | 33 | if (!childRef) { 34 | throw new Error('childRef missing in StateMock.componentDidUpdate'); 35 | } 36 | 37 | if (state && !isEqual(state, prevProps.state)) { 38 | replaceState(childRef, state); 39 | } 40 | } 41 | 42 | handleRef = (childRef: ?ComponentRef) => { 43 | const prevRef: ?Ref = this.props.children.ref; 44 | 45 | this.childRef = childRef; 46 | 47 | if (!childRef) { 48 | handleRef(prevRef, childRef); 49 | 50 | // Nothing else to do on the unmount branch (when refs are set to NULL) 51 | return; 52 | } 53 | 54 | if (this.props.state) { 55 | // Wait until state has been set to call prev ref. This will give the 56 | // impression that the mocked state is the initial state. 57 | replaceState(childRef, this.props.state, () => { 58 | handleRef(prevRef, childRef); 59 | }); 60 | } else { 61 | handleRef(prevRef, childRef); 62 | } 63 | }; 64 | } 65 | 66 | function replaceState(childRef, state, cb) { 67 | // We need to unset existing state keys because React doesn't provide a 68 | // replaceState method (anymore) 69 | // https://reactjs.org/docs/react-component.html#setstate 70 | const nextState = resetOriginalKeys(childRef.state, state); 71 | 72 | if (!isEqual(nextState, childRef.state)) { 73 | childRef.setState(nextState, cb); 74 | } 75 | } 76 | 77 | function resetOriginalKeys(original, current) { 78 | const { keys } = Object; 79 | 80 | return keys(original).reduce( 81 | (result, key) => 82 | keys(result).indexOf(key) === -1 83 | ? { ...result, [key]: undefined } 84 | : result, 85 | current 86 | ); 87 | } 88 | 89 | function handleRef(ref: ?Ref, elRef: ?ComponentRef) { 90 | if (typeof ref === 'string') { 91 | throw new Error('StateMock does not support string refs'); 92 | } 93 | 94 | // https://reactjs.org/docs/refs-and-the-dom.html#creating-refs 95 | if (typeof ref === 'function') { 96 | ref(elRef); 97 | } else if (ref && typeof ref === 'object') { 98 | ref.current = elRef; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/state/src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { ComponentType, Element } from 'react'; 4 | 5 | export type State = Object; 6 | 7 | export type ComponentRef = React$Component<{}, State>; 8 | 9 | export type Props = { 10 | children: Element, 11 | state?: State 12 | }; 13 | 14 | declare export var StateMock: ComponentType; 15 | -------------------------------------------------------------------------------- /packages/state/src/index.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, createRef } from 'react'; 4 | import { create } from 'react-test-renderer'; 5 | import { StateMock } from '.'; 6 | 7 | class Counter extends Component<{}, { count: number }> { 8 | state = { count: 0 }; 9 | 10 | render() { 11 | return `${this.state.count} times`; 12 | } 13 | } 14 | 15 | it('renders children', () => { 16 | const renderer = create( 17 | 18 | 19 | 20 | ); 21 | 22 | expect(renderer.toJSON()).toEqual(`0 times`); 23 | }); 24 | 25 | it('sets state', () => { 26 | const renderer = create( 27 | 28 | 29 | 30 | ); 31 | 32 | expect(renderer.toJSON()).toEqual(`5 times`); 33 | }); 34 | 35 | it('resets state', () => { 36 | const renderer = create( 37 | 38 | 39 | 40 | ); 41 | 42 | expect(renderer.toJSON()).toEqual(`undefined times`); 43 | }); 44 | 45 | it('sets state on update', () => { 46 | const getElement = count => ( 47 | 48 | 49 | 50 | ); 51 | 52 | const renderer = create(getElement(5)); 53 | renderer.update(getElement(10)); 54 | 55 | expect(renderer.toJSON()).toEqual(`10 times`); 56 | }); 57 | 58 | it('preserves ref fn', () => { 59 | const ref = jest.fn(); 60 | create( 61 | 62 | 63 | 64 | ); 65 | 66 | expect(ref).toBeCalledWith(expect.any(Counter)); 67 | }); 68 | 69 | it('preserves ref obj', () => { 70 | const ref = createRef(); 71 | create( 72 | 73 | 74 | 75 | ); 76 | 77 | expect(ref.current).toEqual(expect.any(Counter)); 78 | }); 79 | 80 | it('unmounts gracefully', () => { 81 | const renderer = create( 82 | 83 | 84 | 85 | ); 86 | 87 | expect(() => { 88 | renderer.unmount(); 89 | }).not.toThrow(); 90 | }); 91 | 92 | it('sets state before calling ref', async () => { 93 | const state = await new Promise(res => { 94 | create( 95 | 96 | { 98 | if (elRef) { 99 | res(elRef.state); 100 | } 101 | }} 102 | /> 103 | 104 | ); 105 | }); 106 | expect(state).toEqual({ count: 5 }); 107 | }); 108 | -------------------------------------------------------------------------------- /packages/xhr/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | testHelpers 3 | -------------------------------------------------------------------------------- /packages/xhr/README.md: -------------------------------------------------------------------------------- 1 | ## XHR requests 2 | 3 | A declarative wrapper for the great [xhr-mock](https://github.com/jameslnewell/xhr-mock). 4 | 5 | > **Note:** XhrMock mocks the global XMLHttpRequest API, so only one XhrMock instance should be rendered at once. 6 | 7 | ```js 8 | import { XhrMock } from '@react-mock/xhr'; 9 | 10 | // GET 11 | render( 12 | res.body(JSON.stringify(users))} 15 | > 16 | 17 | 18 | ); 19 | 20 | // POST 21 | render( 22 | res.status(401)}> 23 | 24 | 25 | ); 26 | ``` 27 | 28 | ### Multiple mocks 29 | 30 | ```js 31 | const res = body => (req, res) => res.body(JSON.stringify(body)); 32 | 33 | render( 34 | 40 | 41 | 42 | ); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/xhr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mock/xhr", 3 | "version": "0.2.0", 4 | "description": "Mock XHR requests declaratively", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/skidding/react-mock", 7 | "author": "Ovidiu Cherecheș ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.1.2", 14 | "lodash": "^4.17.11", 15 | "xhr-mock": "^2.4.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/xhr/src/__tests__/get.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { create } from 'react-test-renderer'; 5 | import retry from '@skidding/async-retry'; 6 | import { request } from '../../testHelpers/request'; 7 | import { XhrMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { users: Array<{ name: string }> }> { 10 | state = { 11 | users: [] 12 | }; 13 | 14 | async componentDidMount() { 15 | this.setState({ 16 | users: JSON.parse((await request('/users')).responseText) 17 | }); 18 | } 19 | 20 | render() { 21 | return this.state.users.map(user => user.name); 22 | } 23 | } 24 | 25 | it('mocks GET response', async () => { 26 | const renderer = create( 27 | 30 | res.body(JSON.stringify([{ name: 'Paul' }, { name: 'Jessica' }])) 31 | } 32 | > 33 | 34 | 35 | ); 36 | 37 | await retry(() => { 38 | expect(renderer.toJSON()).toEqual(['Paul', 'Jessica']); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/xhr/src/__tests__/multi.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { create } from 'react-test-renderer'; 5 | import retry from '@skidding/async-retry'; 6 | import { request } from '../../testHelpers/request'; 7 | import { XhrMock } from '..'; 8 | 9 | class MyComponent extends Component<{}, { name: null | string }> { 10 | state = { 11 | name: null 12 | }; 13 | 14 | async componentDidMount() { 15 | const users = JSON.parse((await request('/users')).responseText); 16 | const { name } = JSON.parse( 17 | (await request(`/user/${users[0].id}`)).responseText 18 | ); 19 | 20 | this.setState({ 21 | name 22 | }); 23 | } 24 | 25 | render() { 26 | return this.state.name; 27 | } 28 | } 29 | 30 | it('mocks multi GET responses', async () => { 31 | const res = body => (req, res) => res.body(JSON.stringify(body)); 32 | 33 | const renderer = create( 34 | 40 | 41 | 42 | ); 43 | 44 | await retry(() => { 45 | expect(renderer.toJSON()).toEqual('Jessica'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/xhr/src/__tests__/post.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { create } from 'react-test-renderer'; 5 | import retry from '@skidding/async-retry'; 6 | import { request } from '../../testHelpers/request'; 7 | import { XhrMock } from '..'; 8 | 9 | class MyComponent extends Component< 10 | {}, 11 | { status: 'loading' | 'success' | 'error' } 12 | > { 13 | state = { 14 | status: 'loading' 15 | }; 16 | 17 | async componentDidMount() { 18 | const xhr = await request('/create', { method: 'POST' }); 19 | 20 | this.setState({ 21 | status: xhr.status === 200 ? 'success' : 'error' 22 | }); 23 | } 24 | 25 | render() { 26 | return this.state.status; 27 | } 28 | } 29 | 30 | it('mocks POST 200 response', async () => { 31 | const renderer = create( 32 | res.status(200)} 36 | > 37 | 38 | 39 | ); 40 | 41 | await retry(() => { 42 | expect(renderer.toJSON()).toEqual('success'); 43 | }); 44 | }); 45 | 46 | it('mocks POST 500 response', async () => { 47 | const renderer = create( 48 | res.status(500)} 52 | > 53 | 54 | 55 | ); 56 | 57 | await retry(() => { 58 | expect(renderer.toJSON()).toEqual('error'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/xhr/src/__tests__/unmount.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* global XMLHttpRequest */ 3 | 4 | import React from 'react'; 5 | import { create } from 'react-test-renderer'; 6 | import { XhrMock } from '..'; 7 | 8 | it('restores global XMLHttpRequest on unmount', async () => { 9 | const renderer = create( 10 | res.status(200)}> 11 |
12 | 13 | ); 14 | 15 | expect(XMLHttpRequest.name).toBe('MockXMLHttpRequest'); 16 | 17 | renderer.unmount(); 18 | 19 | expect(XMLHttpRequest).toBe(undefined); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/xhr/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Component } from 'react'; 4 | import xhrMock from 'xhr-mock'; 5 | 6 | import type { Props } from './index.js.flow'; 7 | 8 | export class XhrMock extends Component { 9 | constructor(props: Props) { 10 | super(props); 11 | 12 | // Context: https://github.com/react-cosmos/react-cosmos/issues/430 13 | // $FlowFixMe 14 | if (module.hot) { 15 | module.hot.status(status => { 16 | if (status === 'check') { 17 | xhrMock.teardown(); 18 | } 19 | }); 20 | } 21 | 22 | this.mock(); 23 | } 24 | 25 | render() { 26 | return this.props.children; 27 | } 28 | 29 | componentWillUnmount() { 30 | // Make sure we don't clear a mock from a newer instance (since React 16 31 | // B.constructor is called before A.componentWillUnmount) 32 | if (xhrMock.__xhrMockInst === this) { 33 | this.unmock(); 34 | } 35 | } 36 | 37 | mock() { 38 | // Clear mocks from a previous XhrMock instance 39 | // NOTE: The last rendered XhrProxy instance will override mocks from 40 | // the previous ones 41 | this.unmock(); 42 | 43 | xhrMock.setup(); 44 | 45 | if (this.props.mocks) { 46 | this.props.mocks.forEach(options => { 47 | mockSingle(options); 48 | }); 49 | } else { 50 | mockSingle(this.props); 51 | } 52 | 53 | xhrMock.__xhrMockInst = this; 54 | } 55 | 56 | unmock() { 57 | xhrMock.teardown(); 58 | } 59 | } 60 | 61 | function mockSingle({ method = 'GET', url, response }) { 62 | xhrMock.use(method, url, response); 63 | } 64 | -------------------------------------------------------------------------------- /packages/xhr/src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { ComponentType, Element } from 'react'; 4 | 5 | // For all options see 6 | // https://github.com/jameslnewell/xhr-mock/tree/master/packages/xhr-mock#usage 7 | 8 | type MockOptions = {| 9 | url: string, 10 | method?: string, 11 | response: Function 12 | |}; 13 | 14 | type SingleProps = {| 15 | children: Element, 16 | ...MockOptions 17 | |}; 18 | 19 | type MultiProps = {| 20 | children: Element, 21 | mocks: MockOptions[] 22 | |}; 23 | 24 | export type Props = SingleProps | MultiProps; 25 | 26 | declare export var XhrMock: ComponentType; 27 | -------------------------------------------------------------------------------- /packages/xhr/testHelpers/request.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* istanbul ignore file */ 3 | /* global XMLHttpRequest */ 4 | 5 | export async function request( 6 | url: string, 7 | { method = 'GET' }: { method?: string } = {} 8 | ) { 9 | return new Promise((resolve, reject) => { 10 | const xhr = new XMLHttpRequest(); 11 | xhr.open(method, url); 12 | xhr.addEventListener('load', () => { 13 | try { 14 | resolve(xhr); 15 | } catch (err) { 16 | reject(err); 17 | } 18 | }); 19 | // $FlowFixMe 20 | xhr.addEventListener('error', err => { 21 | reject(err); 22 | }); 23 | xhr.send(); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { join } from 'path'; 4 | import cpy from 'cpy'; 5 | import { ROOT_PATH, TEST_GLOBS } from './shared/paths'; 6 | import { rimraf } from './shared/rimraf'; 7 | import { getUnnamedArgs, getBoolArg } from './shared/args'; 8 | import { run } from './shared/process'; 9 | import { done, error, bold } from './shared/print'; 10 | import { getPackages, getFormattedPackageList } from './shared/packages'; 11 | 12 | (async () => { 13 | try { 14 | const watch = getBoolArg('watch'); 15 | const packages = await getTargetPackages(); 16 | 17 | const verb = watch ? 'Build-watching' : 'Building'; 18 | console.log(`${verb} packages...`); 19 | 20 | await Promise.all(packages.map(pkg => build({ pkg, watch }))); 21 | } catch (err) { 22 | if (err instanceof InvalidPackageError) { 23 | console.error(error(`${bold(err.pkg)} package doesn't exist!`)); 24 | console.error(`Packages: ${await getFormattedPackageList()}`); 25 | process.exit(1); 26 | } else { 27 | throw err; 28 | } 29 | } 30 | })(); 31 | 32 | async function getTargetPackages() { 33 | const allPackages = await getPackages(); 34 | const args = getUnnamedArgs(); 35 | 36 | if (args.length === 0) { 37 | return allPackages; 38 | } 39 | 40 | let packages = []; 41 | args.forEach(arg => { 42 | if (typeof arg !== 'string' || allPackages.indexOf(arg) === -1) { 43 | throw new InvalidPackageError(arg); 44 | } 45 | 46 | packages = [...packages, arg]; 47 | }); 48 | 49 | return packages; 50 | } 51 | 52 | async function build({ pkg, watch }) { 53 | await clearBuild(pkg); 54 | await copyFlowDefs(pkg); 55 | await copyTsDefs(pkg); 56 | 57 | const pkgLabel = `@react-mock/${bold(pkg)}`; 58 | await run({ 59 | cmd: 'babel', 60 | args: getBabelCliArgs({ pkg, watch }), 61 | successMsg: done(pkgLabel), 62 | errorMsg: error(pkgLabel) 63 | }); 64 | } 65 | 66 | async function clearBuild(pkg: string) { 67 | await rimraf(`./packages/${pkg}/dist/**`); 68 | } 69 | 70 | async function copyFlowDefs(pkg) { 71 | return cpy('**/*.js.flow', '../dist', { 72 | cwd: join(ROOT_PATH, `packages/${pkg}/src`), 73 | parents: true 74 | }); 75 | } 76 | 77 | async function copyTsDefs(pkg) { 78 | return cpy('**/*.d.ts', '../dist', { 79 | cwd: join(ROOT_PATH, `packages/${pkg}/src`), 80 | parents: true 81 | }); 82 | } 83 | 84 | function getBabelCliArgs({ pkg, watch }) { 85 | let args = [ 86 | `packages/${pkg}/src`, 87 | '--out-dir', 88 | `packages/${pkg}/dist`, 89 | '--ignore', 90 | TEST_GLOBS.join(',') 91 | ]; 92 | 93 | // Show Babel output in watch mode because it's nice to get a confirmation 94 | // that something happened after saving a file 95 | if (watch) { 96 | return [...args, '--watch', '--verbose']; 97 | } 98 | 99 | return args; 100 | } 101 | 102 | function InvalidPackageError(pkg) { 103 | this.name = 'InvalidTargetPackage'; 104 | this.pkg = pkg; 105 | } 106 | -------------------------------------------------------------------------------- /scripts/generate-readme.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { join } from 'path'; 4 | import { sortBy } from 'lodash'; 5 | import { readFile, writeFile } from 'fs-extra'; 6 | import { ROOT_PATH } from './shared/paths'; 7 | import { getPackages } from './shared/packages'; 8 | 9 | const TEMPLATE_PATH = join(__dirname, './templates/README.md'); 10 | const CONTRIBUTING_PATH = join(__dirname, '../CONTRIBUTING.md'); 11 | const OUTPUT_PATH = join(__dirname, '../README.md'); 12 | 13 | (async () => { 14 | const packages = await getPackages(); 15 | 16 | let pkgReadmes = {}; 17 | await Promise.all( 18 | packages.map(async pkg => { 19 | const pkgReadme = await readPackageReadme(pkg); 20 | 21 | pkgReadmes = { 22 | ...pkgReadmes, 23 | [pkg]: pkgReadme 24 | }; 25 | }) 26 | ); 27 | 28 | await writeMainReadme({ pkgReadmes }); 29 | })(); 30 | 31 | async function readPackageReadme(pkg) { 32 | const p = join(ROOT_PATH, `packages/${pkg}/README.md`); 33 | 34 | return readFile(p, 'utf8'); 35 | } 36 | 37 | async function writeMainReadme({ pkgReadmes }) { 38 | // Ensure `state` is first and sort the rest alphabetically 39 | const pkgNames = sortBy( 40 | Object.keys(pkgReadmes), 41 | p => (p === 'state' ? '' : p) 42 | ); 43 | 44 | let packageSections = []; 45 | let packageLinks = []; 46 | 47 | pkgNames.forEach(pkg => { 48 | packageSections = [...packageSections, pkgReadmes[pkg]]; 49 | packageLinks = [...packageLinks, getLinkFromPkgReadme(pkgReadmes[pkg])]; 50 | }); 51 | 52 | const template = await readFile(TEMPLATE_PATH, 'utf8'); 53 | const contributing = await readFile(CONTRIBUTING_PATH, 'utf8'); 54 | const output = template 55 | .replace(/- \$PACKAGE_LINKS/g, packageLinks.map(l => `- ${l}`).join(`\n`)) 56 | .replace(/\$PACKAGE_SECTIONS\n/g, packageSections.join(`\n`)) 57 | .replace(/\$CONTRIBUTING\n/g, contributing); 58 | 59 | await writeFile(OUTPUT_PATH, output, 'utf8'); 60 | } 61 | 62 | function getLinkFromPkgReadme(pkgReadme) { 63 | const title = pkgReadme.split(`\n`)[0].replace(/^#+ (.+)$/, '$1'); 64 | 65 | return `[${title}](#${title.toLowerCase().replace(/\s+/g, '-')})`; 66 | } 67 | -------------------------------------------------------------------------------- /scripts/new-package.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { join } from 'path'; 4 | import { startCase } from 'lodash'; 5 | import { readFile, outputFile, pathExists } from 'fs-extra'; 6 | import inquirer from 'inquirer'; 7 | import { ROOT_PATH } from './shared/paths'; 8 | import { done, error, bold } from './shared/print'; 9 | import { glob } from './shared/glob'; 10 | 11 | const TEMPLATE_PATH = join(__dirname, './templates/new-package'); 12 | 13 | (async () => { 14 | const { pkgName } = await inquirer.prompt([ 15 | { 16 | type: 'input', 17 | name: 'pkgName', 18 | message: `What's the package name?`, 19 | validate: val => val.length > 0 20 | } 21 | ]); 22 | 23 | if (await pathExists(getPackagePath(pkgName))) { 24 | console.error(error(`${bold(pkgName)} package already exists!`)); 25 | process.exit(1); 26 | return; 27 | } 28 | 29 | const { pkgDesc, compName } = await inquirer.prompt([ 30 | { 31 | type: 'input', 32 | name: 'pkgDesc', 33 | message: `What's the package description?`, 34 | validate: val => val.length > 0 35 | }, 36 | { 37 | type: 'input', 38 | name: 'compName', 39 | message: `What's the component name?`, 40 | validate: val => val.length > 0 41 | } 42 | ]); 43 | 44 | const pkgTitle = startCase(pkgName); 45 | 46 | const templateFiles = await glob('**/*', { 47 | cwd: TEMPLATE_PATH, 48 | nodir: true, 49 | dot: true 50 | }); 51 | await Promise.all( 52 | templateFiles.map(async relPath => { 53 | const templatePath = getTemplatePath(relPath); 54 | const pkgPath = getPackagePath(pkgName, relPath); 55 | 56 | const template = await readFile(templatePath, 'utf8'); 57 | await outputFile( 58 | pkgPath, 59 | replaceTemplateVars(template, { 60 | pkgName, 61 | pkgDesc, 62 | pkgTitle, 63 | compName 64 | }), 65 | 'utf8' 66 | ); 67 | }) 68 | ); 69 | 70 | console.error(done(`${bold(pkgName)} package created!`)); 71 | })(); 72 | 73 | function getPackagePath(pkgName, relPath = '') { 74 | return join(ROOT_PATH, `packages/${pkgName}`, relPath); 75 | } 76 | 77 | function getTemplatePath(relPath) { 78 | return join(TEMPLATE_PATH, relPath); 79 | } 80 | 81 | function replaceTemplateVars( 82 | template, 83 | { pkgName, pkgDesc, pkgTitle, compName } 84 | ) { 85 | return template 86 | .replace(/\$PACKAGE_NAME/g, pkgName) 87 | .replace(/\$PACKAGE_DESC/g, pkgDesc) 88 | .replace(/\$PACKAGE_TITLE/g, pkgTitle) 89 | .replace(/\$COMPONENT_NAME/g, compName); 90 | } 91 | -------------------------------------------------------------------------------- /scripts/shared/args.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { argv } from 'yargs'; 4 | 5 | type Arg = true | number | string; 6 | 7 | export function getNamedArg(name: string): void | Arg { 8 | return argv[name]; 9 | } 10 | 11 | export function getBoolArg(name: string): boolean { 12 | return getNamedArg(name) === true; 13 | } 14 | 15 | export function getUnnamedArgs(): Arg[] { 16 | let args = []; 17 | let arg = getUnnamedArg(0); 18 | 19 | while (typeof arg !== 'undefined') { 20 | args = [...args, arg]; 21 | arg = getUnnamedArg(args.length); 22 | } 23 | 24 | return args; 25 | } 26 | 27 | function getUnnamedArg(index: number = 0): void | Arg { 28 | return argv._[index]; 29 | } 30 | -------------------------------------------------------------------------------- /scripts/shared/glob.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { promisify } from 'util'; 4 | import _glob from 'glob'; 5 | 6 | export const glob = promisify(_glob); 7 | -------------------------------------------------------------------------------- /scripts/shared/packages.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { basename } from 'path'; 4 | import { glob } from './glob'; 5 | import { ROOT_PATH } from './paths'; 6 | 7 | export async function getPackages(): Promise { 8 | return (await glob('packages/*/', { cwd: ROOT_PATH })).map(f => basename(f)); 9 | } 10 | 11 | export async function getFormattedPackageList() { 12 | return ['', ...(await getPackages())].join('\n - '); 13 | } 14 | -------------------------------------------------------------------------------- /scripts/shared/paths.js: -------------------------------------------------------------------------------- 1 | // NOTE: This is a CommonJS module so .eslintrc can import it. 2 | // @flow 3 | 4 | const { join } = require('path'); 5 | 6 | exports.ROOT_PATH = join(__dirname, '../../'); 7 | 8 | exports.TEST_GLOBS = [ 9 | '**/__mocks__/**/*.js', 10 | '**/__tests__/**/*.js', 11 | '**/*.test.js', 12 | '**/test.js' 13 | ]; 14 | -------------------------------------------------------------------------------- /scripts/shared/print.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export { bold } from 'chalk'; 4 | 5 | import { bold } from 'chalk'; 6 | 7 | export function done(text: string) { 8 | return `${bold.inverse.green(` DONE `)} ${text}`; 9 | } 10 | 11 | export function error(text: string) { 12 | return `${bold.inverse.red(` ERROR `)} ${text}`; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/shared/process.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { spawn } from 'child-process-promise'; 4 | import { ROOT_PATH } from './paths'; 5 | 6 | export async function run({ 7 | cmd, 8 | args, 9 | successMsg, 10 | errorMsg 11 | }: { 12 | cmd: string, 13 | args: string[], 14 | successMsg: string, 15 | errorMsg: string 16 | }) { 17 | const promise = spawn(cmd, args, { 18 | cwd: ROOT_PATH 19 | }); 20 | 21 | const { stdout, stderr, exit } = process; 22 | const { childProcess } = promise; 23 | 24 | childProcess.stdout.on('data', data => { 25 | stdout.write(data); 26 | }); 27 | 28 | childProcess.stderr.on('data', data => { 29 | stderr.write(data); 30 | }); 31 | 32 | childProcess.on('close', code => { 33 | if (code) { 34 | console.error(errorMsg); 35 | exit(code); 36 | } else { 37 | console.log(successMsg); 38 | } 39 | }); 40 | 41 | return promise; 42 | } 43 | -------------------------------------------------------------------------------- /scripts/shared/rimraf.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { promisify } from 'util'; 4 | import _rimraf from 'rimraf'; 5 | 6 | export const rimraf = promisify(_rimraf); 7 | -------------------------------------------------------------------------------- /scripts/templates/README.md: -------------------------------------------------------------------------------- 1 | # React mock 2 | 3 | [![Build](https://travis-ci.com/skidding/react-mock.svg?branch=master)](https://travis-ci.com/skidding/react-mock) [![Coverage](https://codecov.io/gh/skidding/react-mock/branch/master/graph/badge.svg)](https://codecov.io/gh/skidding/react-mock) 4 | 5 | Declarative mocks for React state and global APIs. 6 | 7 | ## Jump to 8 | 9 | - **[Why?](#why)** 10 | - $PACKAGE_LINKS 11 | - **[How to contribute](#how-to-contribute)** 12 | 13 | ## Why? 14 | 15 | The motivation for this project comes from wanting to load any type of React component in isolation—inside automated tests as well as in component explorers such as [Cosmos](https://github.com/react-cosmos/react-cosmos) or [Storybook](https://github.com/storybooks/storybook). Some components as stateful, while others fetch data or interact with some other external input. 16 | 17 | The aim here is to isolate _all_ components, not just presentational and stateless components. 18 | 19 | ### Declarative 20 | 21 | Tools like [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) and [xhr-mock](https://github.com/jameslnewell/xhr-mock) are already used by many of us in component tests. But they require imperative _setups_ and _teardowns_, which has two drawbacks: 22 | 23 | 1. They require before/after orchestration in component tests, which is tedious and can create convoluted test cases. 24 | 25 | 2. They're difficult to integrate in component explorers where a usage file is a declarative component element. 26 | 27 | To overcome these drawbacks, `react-mock` offers mocking techniques as declarative [React elements](https://reactjs.org/docs/rendering-elements.html). Lifecycle methods take care of setting up and reverting mocks behind the hood. 28 | 29 | ### Composition 30 | 31 | Two or more mocks can be composed into a single React element. 32 | 33 | ```js 34 | render( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | ``` 46 | 47 | ### Limitations 48 | 49 | - Some react-mock components mock a global API entirely, like _fetch_ or _localStorage_. For this reason only one instance of each should be rendered at once. There might be ways to compose these mocks in the future. 50 | 51 | - To keep this codebase light, the declarative APIs mirror the params of their underlying APIs. Eg. Although they both mock server requests, the `FetchMock` API is different from the `XhrMock` API because they rely on different libs. More concise interfaces are possible, but they increase the scope of this project. 52 | 53 | $PACKAGE_SECTIONS 54 | 55 | $CONTRIBUTING 56 | 57 | ## License 58 | 59 | MIT © [Ovidiu Cherecheș](https://ovidiu.ch) 60 | -------------------------------------------------------------------------------- /scripts/templates/new-package/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /scripts/templates/new-package/README.md: -------------------------------------------------------------------------------- 1 | ## $PACKAGE_TITLE 2 | 3 | $PACKAGE_DESC. 4 | 5 | ```js 6 | import { $COMPONENT_NAME } from '@react-mock/$PACKAGE_NAME'; 7 | 8 | render( 9 | <$COMPONENT_NAME> 10 | 11 | 12 | ); 13 | ``` 14 | -------------------------------------------------------------------------------- /scripts/templates/new-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-mock/$PACKAGE_NAME", 3 | "version": "0.0.0", 4 | "description": "$PACKAGE_DESC", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/skidding/react-mock", 7 | "author": "Ovidiu Cherecheș ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.0.0", 14 | "lodash": "^4.17.11" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/templates/new-package/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Component } from 'react'; 4 | 5 | import type { Props } from './index.js.flow'; 6 | 7 | export class $COMPONENT_NAME extends Component { 8 | render() { 9 | return this.props.children; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/templates/new-package/src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { ComponentType, Element } from 'react'; 4 | 5 | export type Props = { 6 | children: Element 7 | }; 8 | 9 | declare export var $COMPONENT_NAME: ComponentType; 10 | -------------------------------------------------------------------------------- /scripts/templates/new-package/src/index.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { create } from 'react-test-renderer'; 5 | import { $COMPONENT_NAME } from '.'; 6 | 7 | const MyComponent = () => 'Hello world!'; 8 | 9 | it('renders children', () => { 10 | const renderer = create( 11 | <$COMPONENT_NAME> 12 | 13 | 14 | ); 15 | 16 | expect(renderer.toJSON()).toEqual(`Hello world!`); 17 | }); 18 | --------------------------------------------------------------------------------