├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── docs ├── api │ ├── blurFocusedElement.md │ ├── check.md │ ├── choose.md │ ├── clickButton.md │ ├── clickInput.md │ ├── clickLink.md │ ├── constructor.md │ ├── content.md │ ├── contentMatches.md │ ├── currentPath.md │ ├── destroy.md │ ├── fillIn.md │ ├── fillInTextarea.md │ ├── findWrapperForCheck.md │ ├── findWrapperForChoose.md │ ├── findWrapperForClickButton.md │ ├── findWrapperForClickInput.md │ ├── findWrapperForClickLink.md │ ├── findWrapperForFillIn.md │ ├── findWrapperForFillInTextarea.md │ ├── findWrapperForSelect.md │ ├── outputOpenPageCode.md │ ├── select.md │ ├── uncheck.md │ └── waitUntil.md └── faq │ ├── do-you-have-additional-examples.md │ ├── installation-jest.md │ ├── installation-karma-jasmine.md │ └── installation-karma-mocha-chai.md ├── lib ├── __tests__ │ ├── blurFocusedElement.js │ ├── checkSpec.js │ ├── chooseSpec.js │ ├── clickButtonSpec.js │ ├── clickInputSpec.js │ ├── clickLinkSpec.js │ ├── constructorSpec.js │ ├── contentMatchesSpec.js │ ├── contentSpec.js │ ├── currentPathSpec.js │ ├── destroySpec.js │ ├── fillInSpec.js │ ├── fillInTextAreaSpec.js │ ├── findWrapperForCheckSpec.js │ ├── findWrapperForChooseSpec.js │ ├── findWrapperForClickButtonSpec.js │ ├── findWrapperForClickInputSpec.js │ ├── findWrapperForClickLinkSpec.js │ ├── findWrapperForFillInSpec.js │ ├── findWrapperForFillInTextareaSpec.js │ ├── findWrapperForSelectSpec.js │ ├── outputOpenPageCodeSpec.js │ ├── selectSpec.js │ ├── uncheckSpec.js │ ├── waitUntilSpec.js │ └── wrapperSpec.js ├── findWrapperMethods │ ├── formatPropsToCheck.js │ └── index.js ├── index.js ├── interactionMethods │ ├── blurAndFocusWrappersIfNecessary.js │ ├── checkOnlyOneNodeExists.js │ └── index.js └── utilityMethods │ ├── index.js │ └── interpolateToCode.js ├── package.json ├── webpack.config.babel.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dist/index.js 40 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | David Rodriguez Fuentes 2 | Rachel Mathew 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Intrepid Pursuits LLC 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 Page Object 2 | This library gives you the ability to write the following integration tests: 3 | 4 | ```jsx 5 | import React, { Component } from 'react' 6 | import Page from 'react-page-object' 7 | 8 | class Counter extends Component { 9 | state = { count: 0 } 10 | 11 | addOne = () => this.setState({ count: this.state.count + 1 }) 12 | addOneAsync = () => setTimeout(this.addOne, 100) 13 | 14 | render() { 15 | return ( 16 |
17 |

{this.state.count}

18 | 19 | 20 |
21 | ) 22 | } 23 | } 24 | 25 | describe('Counter component', () => { 26 | let page 27 | 28 | beforeEach(() => { 29 | page = new Page() 30 | }) 31 | 32 | afterEach(() => { 33 | page.destroy() 34 | }) 35 | 36 | it('sets the initial count to 0', () => { 37 | expect(page.content()).toMatch(/0/) 38 | }) 39 | 40 | it('adds one to the count when the \'Add one\' button is clicked', () => { 41 | page.clickButton('Add one') 42 | expect(page.content()).toMatch(/1/) 43 | }) 44 | 45 | it('adds one to the count after a delay when the \'Add one async\' button is clicked', async () => { 46 | page.clickButton('Add one async') 47 | expect(page.content()).not.toMatch(/1/) 48 | await page.waitUntil(() => page.contentMatches(/1/)) 49 | expect(page.content()).toMatch(/1/) 50 | }) 51 | }) 52 | ``` 53 | 54 | This was test written in Jest. However, This library can be used with any test 55 | runner or assertion library that is compatible with 56 | [`Enzyme`](https://github.com/airbnb/enzyme). 57 | 58 | ## Installation 59 | 60 | ``` 61 | $ npm install --save-dev react-page-object 62 | ``` 63 | 64 | `enzyme` is a peer dependency of `react-page-object`, so you will need to 65 | install it if you have not done so already. Additionally, `react-dom` and 66 | `react-addons-test-utils` are peer dependencies of `enzyme`, so install those 67 | as well if you are missing them. 68 | 69 | ``` 70 | $ npm install --save-dev enzyme 71 | $ npm install --save-dev react-dom 72 | $ npm install --save-dev react-test-renderer 73 | ``` 74 | 75 | If you are new to testing in React, check out the following guides to get you up and running: 76 | 77 | * [Set up with Jest in Create React App (Recommended)](docs/faq/installation-jest.md) 78 | * [Set up Karma with Mocha and Chai in Create React App](docs/faq/installation-karma-mocha-chai.md) 79 | * [Set up Karma with Jasmine in Create React App](docs/faq/installation-karma-jasmine.md) 80 | 81 | ## API 82 | ### Set Up Methods 83 | #### [`.constructor(reactElement[, options]) => Page`](docs/api/constructor.md) 84 | Create a `Page` object 85 | 86 | #### [`.destroy()`](docs/api/destroy.md) 87 | Destroy a `Page` object 88 | 89 | ### Find Wrapper Methods 90 | #### [`.findWrapperForCheck(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForCheck.md) 91 | Find a checkbox 92 | 93 | #### [`.findWrapperForChoose(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForChoose.md) 94 | Find a radio button 95 | 96 | #### [`.findWrapperForClickButton(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForClickButton.md) 97 | Find a button 98 | 99 | #### [`.findWrapperForClickInput(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForClickInput.md) 100 | Find a clickable input 101 | 102 | #### [`.findWrapperForClickLink(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForClickLink.md) 103 | Find a link 104 | 105 | #### [`.findWrapperForFillIn(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForFillIn.md) 106 | Find a text input 107 | 108 | #### [`.findWrapperForFillInTextarea(propValue[, options]) => ReactWrapper`](docs/api/findWrapperForFillInTextarea.md) 109 | Find a textarea 110 | 111 | #### [`.findWrapperForSelect(propValue, childrenPropValueForOption, [, options]) => ReactWrapper`](docs/api/findWrapperForSelect.md) 112 | Find a select box 113 | 114 | ### Interaction Methods 115 | #### [`.blurFocusedElement() => Page`](docs/api/blurFocusedElement.md) 116 | Blur the currently focused element. 117 | 118 | #### [`.check(propValue[, options]) => Page`](docs/api/check.md) 119 | Check a checkbox 120 | 121 | #### [`.choose(propValue[, options]) => Page`](docs/api/choose.md) 122 | Choose a radio button 123 | 124 | #### [`.clickButton(propValue[, options]) => Page`](docs/api/clickButton.md) 125 | Click a button 126 | 127 | #### [`.clickInput(propValue[, options]) => Page`](docs/api/clickInput.md) 128 | Click a clickable input 129 | 130 | #### [`.clickLink(propValue[, options]) => Page`](docs/api/clickLink.md) 131 | Click a link 132 | 133 | #### [`.fillIn(propValue, eventTargetValue[, options]) => Page`](docs/api/fillIn.md) 134 | Fill in a text input 135 | 136 | #### [`.fillInTextarea(propValue, eventTargetValue[, options]) => Page`](docs/api/fillInTextarea.md) 137 | Fill in a textarea 138 | 139 | #### [`.select(propValue, childrenPropValueForOption[, options]) => Page`](docs/api/select.md) 140 | Select an option from a select box 141 | 142 | #### [`.uncheck(propValue[, options]) => Page`](docs/api/uncheck.md) 143 | Uncheck a checkbox 144 | 145 | ### Utility Methods 146 | #### [`.content() => String`](docs/api/content.md) 147 | Returns the page text 148 | 149 | #### [`.contentMatches(matcher) => Boolean`](docs/api/contentMatches.md) 150 | Returns whether or not the page text matches the given matcher 151 | 152 | #### [`.currentPath() => String`](docs/api/currentPath.md) 153 | Returns the current URL path 154 | 155 | #### [`.outputOpenPageCode()`](docs/api/outputOpenPageCode.md) 156 | Output to the console a code snippet to view the page HTML 157 | 158 | #### [`.waitUntil(callback[, options]) => Promise`](docs/api/waitUntil.md) 159 | Wait until a certain condition is met. Useful for testing asynchronicity 160 | 161 | ## FAQs 162 | * [Do you have additional examples?](docs/faq/do-you-have-additional-examples.md) 163 | -------------------------------------------------------------------------------- /docs/api/blurFocusedElement.md: -------------------------------------------------------------------------------- 1 | ### `.blurFocusedElement() => Page` 2 | 3 | Blur the focused element. 4 | 5 | #### Returns 6 | 7 | `Page` object which invoked the method. This allow the method to be chained 8 | with another `Page` object method. 9 | 10 | #### Simulated Events 11 | 12 | If there is a React element which is focused. 13 | 14 | 1. Simulates a `blur` event on the focused React element. At this point, there is no 15 | React element which is currently focused. 16 | 17 | If there is no React element which is focused, then nothing occurs. 18 | 19 | #### Example in Jest 20 | 21 | ```js 22 | import React, { Component } from 'react' 23 | import Page from 'react-page-object' 24 | 25 | class App extends Component { 26 | state = { wasBlurred: false } 27 | 28 | onBlur = () => this.setState({ wasBlurred: true }) 29 | 30 | render() { 31 | return ( 32 |
33 | {this.state.wasBlurred ? 'was blurred' : 'was not blurred'} 34 | 35 |
36 | ) 37 | } 38 | } 39 | 40 | describe('blurFocusedElement', () => { 41 | let page 42 | 43 | beforeEach(() => { 44 | page = new Page() 45 | }) 46 | 47 | afterEach(() => { 48 | page.destroy() 49 | }) 50 | 51 | it('blurs the focused element', () => { 52 | page.fillIn('input-id', 'hi') 53 | expect(page.content()).toMatch(/was not blurred/) 54 | page.blurFocusedElement() 55 | expect(page.content()).toMatch(/was blurred/) 56 | }) 57 | }) 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/api/check.md: -------------------------------------------------------------------------------- 1 | ### `.check(propValue[, options]) => Page` 2 | 3 | Check a checkbox 4 | 5 | **Default Checked Props:** `id` and `name` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForCheck`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `options` (`Object`): Optional. 16 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 17 | 18 | #### Returns 19 | 20 | `Page` object which invoked the method. This allow the method to be chained 21 | with another `Page` object method. 22 | 23 | #### Simulated Events 24 | If a [`ReactWrapper`][react-wrapper] is found by 25 | [`.findWrapperForCheck`][find-wrapper-method], then the following events will 26 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 27 | 28 | 1. `blur` event on the React element which is focused. This will occur if there 29 | is a focused React element and it is not the same as the 30 | [`ReactWrapper`][react-wrapper]'s React element. 31 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 32 | it is already in focus. 33 | 3. `change` event on the [`ReactWrapper`][react-wrapper]'s React element. 34 | 35 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 36 | 37 | #### Related Methods 38 | 39 | - [`.findWrapperForCheck(propValue[, options]) => ReactWrapper`][find-wrapper-method] 40 | - [`.uncheck(propValue[, options]) => ReactWrapper`](uncheck.md) 41 | 42 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 43 | [find-wrapper-method]: findWrapperForCheck.md 44 | 45 | #### Example in Jest 46 | 47 | ```js 48 | import React, { Component } from 'react' 49 | import Page from 'react-page-object' 50 | 51 | class App extends Component { 52 | state = { checked: false } 53 | 54 | onChange = event => this.setState({ checked: event.target.checked }) 55 | 56 | render() { 57 | return ( 58 |
59 | {this.state.checked ? 'is checked' : 'is not checked'} 60 | 66 | 72 | 78 |
79 | ) 80 | } 81 | } 82 | 83 | describe('check', () => { 84 | let page 85 | 86 | beforeEach(() => { 87 | page = new Page() 88 | }) 89 | 90 | afterEach(() => { 91 | page.destroy() 92 | }) 93 | 94 | it('checks the input - targeting id', () => { 95 | expect(page.content()).toMatch(/is not checked/) 96 | page.check('input-id') 97 | expect(page.content()).toMatch(/is checked/) 98 | }) 99 | 100 | it('checks the input - targeting name', () => { 101 | expect(page.content()).toMatch(/is not checked/) 102 | page.check('input-name') 103 | expect(page.content()).toMatch(/is checked/) 104 | }) 105 | 106 | it('checks the input - targeting non-default prop', () => { 107 | expect(page.content()).toMatch(/is not checked/) 108 | page.check('input-class', { propToCheck: 'className' }) 109 | expect(page.content()).toMatch(/is checked/) 110 | }) 111 | }) 112 | ``` 113 | -------------------------------------------------------------------------------- /docs/api/choose.md: -------------------------------------------------------------------------------- 1 | ### `.choose(propValue[, options]) => Page` 2 | 3 | Choose a radio button 4 | 5 | **Default Checked Props:** `id` and `name` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForChoose`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `options` (`Object`): Optional. 16 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 17 | 18 | #### Returns 19 | 20 | `Page` object which invoked the method. This allow the method to be chained 21 | with another `Page` object method. 22 | 23 | #### Simulated Events 24 | If a [`ReactWrapper`][react-wrapper] is found by 25 | [`.findWrapperForChoose`][find-wrapper-method], then the following events will 26 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 27 | 28 | 1. `blur` event on the React element which is focused. This will occur if there 29 | is a focused React element and it is not the same as the 30 | [`ReactWrapper`][react-wrapper]'s React element. 31 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 32 | it is already in focus. 33 | 3. `change` event on the [`ReactWrapper`][react-wrapper]'s React element. 34 | 35 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 36 | 37 | #### Related Methods 38 | 39 | - [`.findWrapperForChoose(propValue[, options]) => ReactWrapper`][find-wrapper-method] 40 | 41 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 42 | [find-wrapper-method]: findWrapperForChoose.md 43 | 44 | #### Example in Jest 45 | 46 | ```js 47 | import React, { Component } from 'react' 48 | import Page from 'react-page-object' 49 | 50 | class App extends Component { 51 | state = { selectedOption: "1" } 52 | 53 | onChange = event => this.setState({ selectedOption: event.target.value }) 54 | 55 | render() { 56 | return ( 57 |
58 | {this.state.selectedOption} 59 | 66 | 73 | 80 | 87 |
88 | ) 89 | } 90 | } 91 | 92 | describe('choose', () => { 93 | let page 94 | 95 | beforeEach(() => { 96 | page = new Page() 97 | }) 98 | 99 | afterEach(() => { 100 | page.destroy() 101 | }) 102 | 103 | it('chooses the radio button - targeting id', () => { 104 | expect(page.content()).toMatch(/1/) 105 | page.choose('input-id-option-2') 106 | expect(page.content()).toMatch(/2/) 107 | }) 108 | 109 | it('chooses the radio button - targeting name', () => { 110 | expect(page.content()).toMatch(/1/) 111 | page.choose('input-name-option-2') 112 | expect(page.content()).toMatch(/2/) 113 | }) 114 | 115 | it('chooses the radio button - targeting non-default prop', () => { 116 | expect(page.content()).toMatch(/1/) 117 | page.choose('input-class-option-2', { propToCheck: 'className' }) 118 | expect(page.content()).toMatch(/2/) 119 | }) 120 | }) 121 | ``` 122 | -------------------------------------------------------------------------------- /docs/api/clickButton.md: -------------------------------------------------------------------------------- 1 | ### `.clickButton(propValue[, options]) => Page` 2 | 3 | Click a button 4 | 5 | **Default Checked Props:** `id` and `children` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForClickButton`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `options` (`Object`): Optional. 16 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 17 | 18 | #### Returns 19 | 20 | `Page` object which invoked the method. This allow the method to be chained 21 | with another `Page` object method. 22 | 23 | #### Simulated Events 24 | If a [`ReactWrapper`][react-wrapper] is found by 25 | [`.findWrapperForClickButton`][find-wrapper-method], then the following events will 26 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 27 | 28 | 1. `blur` event on the React element which is focused. This will occur if there 29 | is a focused React element and it is not the same as the 30 | [`ReactWrapper`][react-wrapper]'s React element. 31 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 32 | it is already in focus. 33 | 3. `click` event on the [`ReactWrapper`][react-wrapper]'s React element. 34 | 4. `submit` event on the [`ReactWrapper`][react-wrapper]'s React element. 35 | 36 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 37 | 38 | #### Related Methods 39 | 40 | - [`.findWrapperForClickButton(propValue[, options]) => ReactWrapper`][find-wrapper-method] 41 | 42 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 43 | [find-wrapper-method]: findWrapperForClickButton.md 44 | 45 | #### Example in Jest 46 | 47 | ```js 48 | import React, { Component } from 'react' 49 | import Page from 'react-page-object' 50 | 51 | class App extends Component { 52 | state = { wasClicked: false } 53 | 54 | onClick = event => this.setState({ wasClicked: true }) 55 | 56 | render() { 57 | return ( 58 |
59 | {this.state.wasClicked ? 'was clicked' : 'was not clicked'} 60 | 62 |
64 | ) 65 | } 66 | } 67 | 68 | describe('clickButton', () => { 69 | let page 70 | 71 | beforeEach(() => { 72 | page = new Page() 73 | }) 74 | 75 | afterEach(() => { 76 | page.destroy() 77 | }) 78 | 79 | it('clicks the button - targeting id', () => { 80 | expect(page.content()).toMatch(/was not clicked/) 81 | page.clickButton('button-id') 82 | expect(page.content()).toMatch(/was clicked/) 83 | }) 84 | 85 | it('clicks the button - targeting children', () => { 86 | expect(page.content()).toMatch(/was not clicked/) 87 | page.clickButton('button text') 88 | expect(page.content()).toMatch(/was clicked/) 89 | }) 90 | 91 | it('clicks the button - targeting non-default prop', () => { 92 | expect(page.content()).toMatch(/was not clicked/) 93 | page.clickButton('button-class', { propToCheck: 'className' }) 94 | expect(page.content()).toMatch(/was clicked/) 95 | }) 96 | }) 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/api/clickInput.md: -------------------------------------------------------------------------------- 1 | ### `.clickInput(propValue[, options]) => Page` 2 | 3 | Click a clickable input 4 | 5 | **Default Checked Props:** `id` and `value` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForClickInput`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `options` (`Object`): Optional. 16 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 17 | 18 | #### Returns 19 | 20 | `Page` object which invoked the method. This allow the method to be chained 21 | with another `Page` object method. 22 | 23 | #### Simulated Events 24 | If a [`ReactWrapper`][react-wrapper] is found by 25 | [`.findWrapperForClickInput`][find-wrapper-method], then the following events will 26 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 27 | 28 | 1. `blur` event on the React element which is focused. This will occur if there 29 | is a focused React element and it is not the same as the 30 | [`ReactWrapper`][react-wrapper]'s React element. 31 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 32 | it is already in focus. 33 | 3. `click` event on the [`ReactWrapper`][react-wrapper]'s React element. 34 | 4. `submit` event on the [`ReactWrapper`][react-wrapper]'s React element. 35 | 36 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 37 | 38 | #### Related Methods 39 | 40 | - [`.findWrapperForClickInput(propValue[, options]) => ReactWrapper`][find-wrapper-method] 41 | 42 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 43 | [find-wrapper-method]: findWrapperForClickInput.md 44 | 45 | ### Example in Jest 46 | 47 | ```js 48 | import React, { Component } from 'react' 49 | import Page from 'react-page-object' 50 | 51 | class App extends Component { 52 | state = { wasClicked: false } 53 | 54 | onClick = event => this.setState({ wasClicked: true }) 55 | 56 | render() { 57 | return ( 58 |
59 | {this.state.wasClicked ? 'was clicked' : 'was not clicked'} 60 | 61 | 62 | 63 |
64 | ) 65 | } 66 | } 67 | 68 | describe('clickInput', () => { 69 | let page 70 | 71 | beforeEach(() => { 72 | page = new Page() 73 | }) 74 | 75 | afterEach(() => { 76 | page.destroy() 77 | }) 78 | 79 | it('clicks the input - targeting id', () => { 80 | expect(page.content()).toMatch(/was not clicked/) 81 | page.clickInput('input-id') 82 | expect(page.content()).toMatch(/was clicked/) 83 | }) 84 | 85 | it('clicks the input - targeting value', () => { 86 | expect(page.content()).toMatch(/was not clicked/) 87 | page.clickInput('input text') 88 | expect(page.content()).toMatch(/was clicked/) 89 | }) 90 | 91 | it('clicks the input - targeting non-default prop', () => { 92 | expect(page.content()).toMatch(/was not clicked/) 93 | page.clickInput('input-class', { propToCheck: 'className' }) 94 | expect(page.content()).toMatch(/was clicked/) 95 | }) 96 | }) 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/api/clickLink.md: -------------------------------------------------------------------------------- 1 | ### `.clickLink(propValue[, options]) => Page` 2 | 3 | Click a link 4 | 5 | **Default Checked Props:** `id`, `children`, and `href` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForClickLink`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `options` (`Object`): Optional. 16 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 17 | 18 | #### Returns 19 | 20 | `Page` object which invoked the method. This allow the method to be chained 21 | with another `Page` object method. 22 | 23 | #### Simulated Events 24 | If a [`ReactWrapper`][react-wrapper] is found by 25 | [`.findWrapperForClickLink`][find-wrapper-method], then the following events will 26 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 27 | 28 | 1. `blur` event on the React element which is focused. This will occur if there 29 | is a focused React element and it is not the same as the 30 | [`ReactWrapper`][react-wrapper]'s React element. 31 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 32 | it is already in focus. 33 | 3. `click` event on the [`ReactWrapper`][react-wrapper]'s React element. 34 | 35 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 36 | 37 | #### Related Methods 38 | 39 | - [`.findWrapperForClickLink(propValue[, options]) => ReactWrapper`][find-wrapper-method] 40 | 41 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 42 | [find-wrapper-method]: findWrapperForClickLink.md 43 | 44 | #### Example in Jest 45 | 46 | ```js 47 | import React, { Component } from 'react' 48 | import Page from 'react-page-object' 49 | import { BrowserRouter, Link } from 'react-router-dom' 50 | 51 | class App extends Component { 52 | render() { 53 | return ( 54 | 55 |
56 | 57 | link text 58 | 59 | 60 |
61 |
62 | ) 63 | } 64 | } 65 | 66 | describe('clickLink', () => { 67 | let page 68 | 69 | beforeEach(() => { 70 | page = new Page() 71 | }) 72 | 73 | afterEach(() => { 74 | page.destroy() 75 | }) 76 | 77 | it('clicks the link - targeting id', () => { 78 | expect(page.currentPath()).toMatch('/') 79 | page.clickLink('link-id') 80 | expect(page.currentPath()).toMatch('/first') 81 | }) 82 | 83 | it('clicks the link - targeting children', () => { 84 | expect(page.currentPath()).toMatch('/') 85 | page.clickLink('link text') 86 | expect(page.currentPath()).toMatch('/second') 87 | }) 88 | 89 | it('clicks the link - targeting href', () => { 90 | expect(page.currentPath()).toMatch('/') 91 | page.clickLink('/link-href') 92 | expect(page.currentPath()).toMatch('/link-href') 93 | }) 94 | 95 | it('clicks the link - targeting non-default prop', () => { 96 | expect(page.currentPath()).toMatch('/') 97 | page.clickLink('link-class', { propToCheck: 'className' }) 98 | expect(page.currentPath()).toMatch('/fourth') 99 | }) 100 | }) 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/api/constructor.md: -------------------------------------------------------------------------------- 1 | ### `.constructor(reactElement[, options]) => Page` 2 | Create a `Page` object 3 | 4 | It is recommended to invoke this method in a `beforeEach` callback to generate a new `Page` object for every test. 5 | 6 | #### Arguments 7 | 1. `reactElement` (`String`): React element which should be mounted onto the DOM. 8 | 2. `options` (`Object`): Optional. 9 | * `initialPath` (`String`): The initial path where the page object is mounted. Defaults to `/`. 10 | 11 | #### Returns 12 | 13 | `Page` object 14 | 15 | #### Related Methods 16 | 17 | - [`.destroy()`][destroy-method] 18 | 19 | [destroy-method]: destroy.md 20 | 21 | #### Example in Jest 22 | 23 | ```js 24 | import React from 'react' 25 | import Page from 'react-page-object' 26 | 27 | const App = () =>

My App

28 | 29 | describe('constructor - no options specified', () => { 30 | let page 31 | 32 | beforeEach(() => { 33 | page = new Page() 34 | }) 35 | 36 | afterEach(() => { 37 | page.destroy() 38 | }) 39 | 40 | it('creates a page object at the root path', () => { 41 | expect(page.content()).toMatch(/My App/) 42 | expect(page.currentPath()).toMatch('/') 43 | }) 44 | }) 45 | 46 | describe('constructor - initialPath specified', () => { 47 | let page 48 | beforeEach(() => { 49 | page = new Page(, { initialPath: '/examples' }) 50 | }) 51 | 52 | afterEach(() => { 53 | page.destroy() 54 | }) 55 | 56 | it('creates a page object at the specified path', () => { 57 | expect(page.content()).toMatch(/My App/) 58 | expect(page.currentPath()).toMatch('/examples') 59 | }) 60 | }) 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/api/content.md: -------------------------------------------------------------------------------- 1 | ### `.content() => String` 2 | Returns the page text 3 | 4 | #### Returns 5 | 6 | A string containing all the text in the page. Please note that this does not 7 | include text inside input fields since these are actually HTML attributes. 8 | 9 | #### Related Methods 10 | 11 | - [`.contentMatches(matcher) => Boolean`][content-matches-method] 12 | 13 | [content-matches-method]: contentMatches.md 14 | 15 | #### Example in Jest 16 | 17 | ```js 18 | import React from 'react' 19 | import Page from 'react-page-object' 20 | 21 | const App = () => ( 22 |
23 |

My App

24 | 25 |
26 | ) 27 | 28 | describe('content', () => { 29 | let page 30 | 31 | beforeEach(() => { 32 | page = new Page() 33 | }) 34 | 35 | afterEach(() => { 36 | page.destroy() 37 | }) 38 | 39 | it('returns page text', () => { 40 | expect(page.content()).toMatch(/My App/) 41 | expect(page.content()).not.toMatch(/this does not show up/) 42 | }) 43 | 44 | it('does not contain text inside input fields', () => { 45 | expect(page.content()).not.toMatch(/this does not show up/) 46 | }) 47 | }) 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/api/contentMatches.md: -------------------------------------------------------------------------------- 1 | ### `.contentMatches(matcher) => Boolean` 2 | Returns whether or not the page text matches the given matcher 3 | 4 | #### Arguments 5 | 1. `matcher` (`RegExp` or `String`): If given a `RegExp`, this is used to match 6 | against the page content. If given a `String`, [a `RegExp` is created using the `String` as a `pattern`][reg-exp-constructor]. 7 | This created `RegExp` is used to match against the page content. It is 8 | important to note that this created `RegExp` is case sensitive. 9 | 10 | #### Returns 11 | `true` or `false` depending on whether the `RegExp` matcher matches against the 12 | return value of [`.content() => String`][content-method]. 13 | 14 | #### Related Methods 15 | 16 | - [`.content() => String`][content-method] 17 | 18 | [content-method]: content.md 19 | [reg-exp-constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp 20 | 21 | ### Example in Jest 22 | 23 | ```js 24 | import React from 'react' 25 | import Page from 'react-page-object' 26 | 27 | const App = () =>

My App

28 | 29 | describe('contentMatches', () => { 30 | let page 31 | 32 | beforeEach(() => { 33 | page = new Page() 34 | }) 35 | 36 | afterEach(() => { 37 | page.destroy() 38 | }) 39 | 40 | it('returns true if content matches - RegExp given', () => { 41 | expect(page.contentMatches(/My App/)).toBe(true) 42 | }) 43 | 44 | it('returns true if content matches - string given', () => { 45 | expect(page.contentMatches('My App')).toBe(true) 46 | }) 47 | 48 | it('returns false if content does not match', () => { 49 | expect(page.contentMatches(/should not match/)).toBe(false) 50 | }) 51 | }) 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/api/currentPath.md: -------------------------------------------------------------------------------- 1 | ### `.content() => String` 2 | Returns the current URL path 3 | 4 | #### Returns 5 | 6 | A string of the current URL path 7 | 8 | ### Example in Jest 9 | 10 | ```js 11 | import React from 'react' 12 | import Page from 'react-page-object' 13 | import { BrowserRouter, Link } from 'react-router-dom' 14 | 15 | const App = () => ( 16 | 17 |
18 | 19 |
20 |
21 | ) 22 | 23 | describe('currentPath', () => { 24 | let page 25 | 26 | beforeEach(() => { 27 | page = new Page() 28 | }) 29 | 30 | afterEach(() => { 31 | page.destroy() 32 | }) 33 | 34 | it('returns the current URL path', () => { 35 | expect(page.currentPath()).toEqual('/') 36 | page.clickLink('/first') 37 | expect(page.currentPath()).toEqual('/first') 38 | }) 39 | }) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/destroy.md: -------------------------------------------------------------------------------- 1 | ### `.destroy()` 2 | Destroy a `Page` object 3 | 4 | Unmounts the `reactElement` used to create the `Page` from the DOM and navigates to `/`. 5 | It is recommended to invoke this method in an `afterEach` callback to clean up any side effects that were created during the test. 6 | 7 | #### Related Methods 8 | 9 | - [`.constructor(reactElement[, options]) => Page`][constructor-method] 10 | 11 | [constructor-method]: constructor.md 12 | -------------------------------------------------------------------------------- /docs/api/fillIn.md: -------------------------------------------------------------------------------- 1 | ### `.fillIn(propValue, eventTargetValue[, options]) => Page` 2 | 3 | Fill in a text input 4 | 5 | **Default Checked Props:** `id`, `name`, and `placeholder` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForFillIn`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `eventTargetValue` (`String`): Value which will equal 16 | `event.target.value` in `onChange` event handlers triggered by 17 | the simulated `change` event. 18 | 3. `options` (`Object`): Optional. 19 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 20 | 21 | #### Returns 22 | 23 | `Page` object which invoked the method. This allow the method to be chained 24 | with another `Page` object method. 25 | 26 | #### Simulated Events 27 | If a [`ReactWrapper`][react-wrapper] is found by 28 | [`.findWrapperForFillIn`][find-wrapper-method], then the following events will 29 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 30 | 31 | 1. `blur` event on the React element which is focused. This will occur if there 32 | is a focused React element and it is not the same as the 33 | [`ReactWrapper`][react-wrapper]'s React element. 34 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 35 | it is already in focus. 36 | 3. `change` event on the [`ReactWrapper`][react-wrapper]'s React 37 | element. For `onChange` event handlers triggered by this 38 | simulated `change` event, `event.target.value` will equal 39 | `eventTargetValue`. 40 | 41 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 42 | 43 | #### Related Methods 44 | 45 | - [`.findWrapperForFillIn(propValue, eventTargetValue[, options]) => ReactWrapper`][find-wrapper-method] 46 | 47 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 48 | [find-wrapper-method]: findWrapperForFillIn.md 49 | 50 | #### Example in Jest 51 | 52 | ```js 53 | import React, { Component } from 'react' 54 | import Page from 'react-page-object' 55 | 56 | class App extends Component { 57 | state = { text: '' } 58 | 59 | onChange = event => this.setState({ text: event.target.value }) 60 | 61 | render() { 62 | return ( 63 |
64 | {this.state.text} 65 | 66 | 67 | 68 | 69 |
70 | ) 71 | } 72 | } 73 | 74 | describe('fillIn', () => { 75 | let page 76 | 77 | beforeEach(() => { 78 | page = new Page() 79 | }) 80 | 81 | afterEach(() => { 82 | page.destroy() 83 | }) 84 | 85 | it('fills in the input - targeting id', () => { 86 | page.fillIn('input-id', 'hello') 87 | expect(page.content()).toMatch(/hello/) 88 | }) 89 | 90 | it('fills in the input - targeting name', () => { 91 | page.fillIn('input-name', 'hello') 92 | expect(page.content()).toMatch(/hello/) 93 | }) 94 | 95 | it('fills in the input - targeting placeholder', () => { 96 | page.fillIn('input-placeholder', 'hello') 97 | expect(page.content()).toMatch(/hello/) 98 | }) 99 | 100 | it('fills in the input - targeting non-default prop', () => { 101 | page.fillIn('input-class', 'hello', { propToCheck: 'className' }) 102 | expect(page.content()).toMatch(/hello/) 103 | }) 104 | }) 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/api/fillInTextarea.md: -------------------------------------------------------------------------------- 1 | ### `.fillInTextarea(propValue, eventTargetValue[, options]) => Page` 2 | 3 | Fill in a textarea 4 | 5 | **Default Checked Props:** `id`, `name`, and `placeholder` 6 | 7 | #### Arguments 8 | The `propValue` and `options` arguments are passed to 9 | [`.findWrapperForFillInTextarea`][find-wrapper-method] to find a 10 | [`ReactWrapper`][react-wrapper]. The [`ReactWrapper`][react-wrapper] will be 11 | used to simulate events. 12 | 13 | 1. `propValue` (`String`): Value is compared with the values of the checked 14 | props to assert a match. 15 | 2. `eventTargetValue` (`String`): Value which will equal 16 | `event.target.value` in `onChange` event handlers triggered by 17 | the simulated `change` event. 18 | 3. `options` (`Object`): Optional. 19 | * `propToCheck` (`String`): Name of prop to check against instead of the default checked props. 20 | 21 | #### Returns 22 | 23 | `Page` object which invoked the method. This allow the method to be chained 24 | with another `Page` object method. 25 | 26 | #### Simulated Events 27 | If a [`ReactWrapper`][react-wrapper] is found by 28 | [`.findWrapperForFillInTextarea`][find-wrapper-method], then the following events will 29 | be simulated on the [`ReactWrapper`][react-wrapper]'s React element: 30 | 31 | 1. `blur` event on the React element which is focused. This will occur if there 32 | is a focused React element and it is not the same as the 33 | [`ReactWrapper`][react-wrapper]'s React element. 34 | 2. `focus` event on the [`ReactWrapper`][react-wrapper]'s React element unless 35 | it is already in focus. 36 | 3. `change` event on the [`ReactWrapper`][react-wrapper]'s React 37 | element. For `onChange` event handlers triggered by this 38 | simulated `change` event, `event.target.value` will equal 39 | `eventTargetValue`. 40 | 41 | If no [`ReactWrapper`][react-wrapper] is found, then an error is thrown. 42 | 43 | #### Related Methods 44 | 45 | - [`.findWrapperForFillInTextarea(propValue, eventTargetValue[, options]) => ReactWrapper`][find-wrapper-method] 46 | 47 | [react-wrapper]: https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md#reactwrapper-api 48 | [find-wrapper-method]: findWrapperForFillInTextarea.md 49 | 50 | #### Example in Jest 51 | 52 | ```js 53 | import React, { Component } from 'react' 54 | import Page from 'react-page-object' 55 | 56 | class App extends Component { 57 | state = { text: '' } 58 | 59 | onChange = event => this.setState({ text: event.target.value }) 60 | 61 | render() { 62 | return ( 63 |
64 | {this.state.text} 65 | ') 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /lib/__tests__/findWrapperForSelectSpec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Page from '../index' 3 | 4 | describe('findWrapperForSelectSpec', () => { 5 | let page 6 | const SelectsDefault = () => ( 7 |
8 | 11 | 14 | 17 | 18 | 20 | 26 | 27 | 28 | 31 | 34 | 37 | 38 | 40 | 42 |
43 | ) 44 | const SelectsDebuggingWithNoMatchingSelectOrOption = () => ( 45 |
46 | 49 |
50 | ) 51 | const SelectsDebuggingWithMatchingSelectOnly = () => ( 52 |
53 | 56 |
57 | ) 58 | const SelectsDebuggingWithMatch = () => ( 59 |
60 | 63 |
64 | ) 65 | const SelectsDebuggingWithMultipleOptionMatches = () => ( 66 |
67 | 71 |
72 | ) 73 | 74 | afterEach(() => { 75 | page.destroy() 76 | }) 77 | 78 | describe('no options given', () => { 79 | it('should return ReactWrappers for selects whose id or name props match the propValue and have a child option wrapper that has its children prop value match the option value', () => { 80 | page = new Page() 81 | const wrapper = page.findWrapperForSelect('propValue', 'optionText') 82 | expect(wrapper.length).toEqual(2) 83 | expect(wrapper.every('select')).toEqual(true) 84 | }) 85 | }) 86 | 87 | describe('propToCheck option is specified with className', () => { 88 | it('should return ReactWrappers for selects whose className prop matches the propValue and has a child option wrapper that has its children prop value match the option value', () => { 89 | page = new Page() 90 | const wrapper = page.findWrapperForSelect('propValue', 'optionText', { propToCheck: 'className' }) 91 | expect(wrapper.html()).toEqual('') 92 | }) 93 | }) 94 | 95 | describe('showDebuggingInfo option is specified with true', () => { 96 | describe('component with select wrapper does not match neither its prop value or its option value', () => { 97 | it('should output a message that a select wrapper was matched by its prop value, but not its option value', () => { 98 | window.spyOn(console, 'log') 99 | page = new Page() 100 | page.findWrapperForSelect('propValue', 'optionText', { showDebuggingInfo: true }) 101 | expect(console.log).toHaveBeenCalledWith(`no select React element was found whose id or name prop value matches 'propValue'`) 102 | }) 103 | }) 104 | 105 | describe('component with select wrapper with matching prop value only', () => { 106 | it('should output a message that a select wrapper was matched by its prop value, but not its option value', () => { 107 | window.spyOn(console, 'log') 108 | page = new Page() 109 | page.findWrapperForSelect('propValue', 'optionText', { showDebuggingInfo: true }) 110 | expect(console.log).toHaveBeenCalledWith(`matching select React element was found, but its children did not include an option React element whose children prop value matched 'optionText': `) 111 | }) 112 | }) 113 | 114 | describe('component with select wrapper with matching prop value and option value', () => { 115 | it('should output a message that a select wrapper was matched by both its prop value and option value', () => { 116 | window.spyOn(console, 'log') 117 | page = new Page() 118 | page.findWrapperForSelect('propValue', 'optionText', { showDebuggingInfo: true }) 119 | expect(console.log).toHaveBeenCalledWith(`matching select React element whose children include a matching option React element was found: `) 120 | }) 121 | }) 122 | 123 | describe('component with select wrapper with matching prop value and multiple matching option values', () => { 124 | it('should output a message that a select wrapper was matched by its prop value and multiple option values', () => { 125 | window.spyOn(console, 'log') 126 | page = new Page() 127 | page.findWrapperForSelect('propValue', 'optionText', { showDebuggingInfo: true }) 128 | expect(console.log).toHaveBeenCalledWith(`matching select React element was found, but its children include more than one option React element whose children prop value matched 'optionText': `) 129 | }) 130 | }) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /lib/__tests__/outputOpenPageCodeSpec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Page from '../index' 3 | 4 | describe('outputOpenPageCodeSpec', () => { 5 | let page 6 | const TestComponent = () =>

I am Component

7 | 8 | beforeEach(() => { 9 | page = new Page() 10 | }) 11 | 12 | afterEach(() => { 13 | page.destroy() 14 | }) 15 | 16 | it('should output code to open the page in a browser', () => { 17 | window.spyOn(console, 'log') 18 | page.outputOpenPageCode() 19 | expect(console.log).toHaveBeenCalledWith(`PASTE THE FOLLOWING INTO YOUR BROWSER CONSOLE:\nwindow.open().document.write(\`React Page Object Preview

I am Component

\`)\n`) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /lib/__tests__/selectSpec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Page from '../index' 3 | 4 | describe('selectSpec', () => { 5 | let onBlur, onChange, onFocus, page 6 | const SelectsId = ({ onBlur, onChange, onFocus }) => ( 7 |
8 | 11 | 14 |
15 | ) 16 | const SelectsName = ({ onBlur, onChange, onFocus }) => ( 17 |
18 | 21 |
22 | ) 23 | const SelectsCustom = ({ onBlur, onChange, onFocus }) => ( 24 |
25 | 28 |
29 | ) 30 | 31 | beforeEach(() => { 32 | onBlur = window.jasmine.createSpy('onBlur'); 33 | onChange = window.jasmine.createSpy('onChange'); 34 | onFocus = window.jasmine.createSpy('onFocus'); 35 | }) 36 | 37 | afterEach(() => { 38 | page.destroy() 39 | }) 40 | 41 | describe('no options given', () => { 42 | describe('select whose id prop value matches the propValue', () => { 43 | beforeEach(() => { 44 | page = new Page( 45 | 50 | ) 51 | }) 52 | 53 | it('should trigger onChange event handler', () => { 54 | page.select('propValue', 'childrenPropValueForOption') 55 | expect(onChange).toHaveBeenCalled() 56 | const event = onChange.calls.first().args[0] 57 | expect(event.target).toEqual({ value: 'optionValue', name: undefined }) 58 | }) 59 | 60 | it('should trigger onFocus event handler upon first interaction', () => { 61 | page.select('propValue', 'childrenPropValueForOption') 62 | page.select('propValue', 'childrenPropValueForOption') 63 | expect(onFocus.calls.count()).toEqual(1) 64 | }) 65 | 66 | it('should trigger onBlur event handler when a new wrapper is focused', () => { 67 | page.select('propValue', 'childrenPropValueForOption') 68 | page.select('propValue', 'childrenPropValueForOption') 69 | expect(onBlur).not.toHaveBeenCalled() 70 | page.select('secondPropValue', 'childrenPropValueForOption') 71 | expect(onBlur).toHaveBeenCalled() 72 | }) 73 | 74 | it('should return the page object itself', () => { 75 | expect(page.select('propValue', 'childrenPropValueForOption')).toBe(page) 76 | }) 77 | }) 78 | 79 | describe('select whose name prop value matches the propValue', () => { 80 | beforeEach(() => { 81 | page = new Page( 82 | 87 | ) 88 | }) 89 | 90 | it('should not raise an error describing that no matching React elements were found', () => { 91 | expect(() => page.select('propValue', 'childrenPropValueForOption')).not.toThrow() 92 | }) 93 | }) 94 | 95 | describe('no matching select', () => { 96 | beforeEach(() => { 97 | page = new Page(
) 98 | }) 99 | 100 | it('should raise an error describing that no matching React elements were found', () => { 101 | expect(() => page.select('propValue', 'childrenPropValueForOption')).toThrowError( 102 | Error, 103 | '.select(\'propValue\', \'childrenPropValueForOption\') failed because no matching React elements were found by .findWrapperForSelect(\'propValue\', \'childrenPropValueForOption\')' 104 | ) 105 | }) 106 | }) 107 | 108 | describe('two matching selects', () => { 109 | beforeEach(() => { 110 | page = new Page( 111 |
112 | 115 | 118 |
119 | ) 120 | }) 121 | 122 | it('should raise an error describing that two matching React elements were found', () => { 123 | expect(() => page.select('propValue', 'childrenPropValueForOption')).toThrowError( 124 | Error, 125 | '.select(\'propValue\', \'childrenPropValueForOption\') failed because 2 matching React elements were found by .findWrapperForSelect(\'propValue\', \'childrenPropValueForOption\')' 126 | ) 127 | }) 128 | }) 129 | }) 130 | 131 | describe('propToCheck option specified with className', () => { 132 | describe('select whose className prop value matches the propValue', () => { 133 | beforeEach(() => { 134 | page = new Page( 135 | 140 | ) 141 | }) 142 | 143 | it('should not raise an error describing that no matching React elements were found', () => { 144 | expect(() => page.select('propValue', 'childrenPropValueForOption', { propToCheck: 'className' })).not.toThrow() 145 | }) 146 | }) 147 | }) 148 | }) 149 | -------------------------------------------------------------------------------- /lib/__tests__/uncheckSpec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Page from '../index' 3 | 4 | describe('uncheckSpec', () => { 5 | let onBlur, onChange, onFocus, page, secondOnChange 6 | const ChecksId = ({ onBlur, onChange, onFocus, secondOnChange }) => ( 7 |
8 | 9 | 10 |
11 | ) 12 | const ChecksName = ({ onBlur, onChange, onFocus }) => ( 13 |
14 | 15 |
16 | ) 17 | const ChecksCustom = ({ onBlur, onChange, onFocus }) => ( 18 |
19 | 20 |
21 | ) 22 | 23 | beforeEach(() => { 24 | onBlur = window.jasmine.createSpy('onBlur'); 25 | onChange = window.jasmine.createSpy('onChange'); 26 | onFocus = window.jasmine.createSpy('onFocus'); 27 | secondOnChange = window.jasmine.createSpy('secondOnChange'); 28 | }) 29 | 30 | afterEach(() => { 31 | page.destroy() 32 | }) 33 | 34 | describe('no options given', () => { 35 | describe('input type checkbox whose id prop value matches the propValue', () => { 36 | beforeEach(() => { 37 | page = new Page( 38 | 44 | ) 45 | }) 46 | 47 | describe('input type checkbox with no value prop', () => { 48 | it('should trigger onChange event handler with an event whose target.value is \'on\'', () => { 49 | page.uncheck('propValue') 50 | expect(onChange).toHaveBeenCalled() 51 | const event = onChange.calls.first().args[0] 52 | expect(event.target).toEqual({ checked: false, type: 'checkbox', value: 'on', name: undefined }) 53 | }) 54 | }) 55 | 56 | describe('input type checkbox with value prop', () => { 57 | it('should trigger onChange event handler with an event whose target.value equals the input\'s value prop', () => { 58 | page.uncheck('secondPropValue') 59 | expect(secondOnChange).toHaveBeenCalled() 60 | const event = secondOnChange.calls.first().args[0] 61 | expect(event.target).toEqual({ checked: false, type: 'checkbox', value: 'customValue', name: 'customName' }) 62 | }) 63 | }) 64 | 65 | it('should trigger onFocus event handler upon first interaction', () => { 66 | page.uncheck('propValue') 67 | page.uncheck('propValue') 68 | expect(onFocus.calls.count()).toEqual(1) 69 | }) 70 | 71 | it('should trigger onBlur event handler when a new wrapper is focused', () => { 72 | page.uncheck('propValue') 73 | page.uncheck('propValue') 74 | expect(onBlur).not.toHaveBeenCalled() 75 | page.uncheck('secondPropValue') 76 | expect(onBlur).toHaveBeenCalled() 77 | }) 78 | 79 | it('should return the page object itself', () => { 80 | expect(page.uncheck('propValue')).toBe(page) 81 | }) 82 | }) 83 | 84 | describe('input type checkbox whose name prop value matches the propValue', () => { 85 | beforeEach(() => { 86 | page = new Page( 87 | 92 | ) 93 | }) 94 | 95 | it('should not raise an error describing that no matching React elements were found', () => { 96 | expect(() => page.uncheck('propValue')).not.toThrow() 97 | }) 98 | }) 99 | 100 | describe('no matching input type checkbox', () => { 101 | beforeEach(() => { 102 | page = new Page(
) 103 | }) 104 | 105 | it('should raise an error describing that no matching React elements were found', () => { 106 | expect(() => page.uncheck('propValue')).toThrowError( 107 | Error, 108 | '.check(\'propValue\') failed because no matching React elements were found by .findWrapperForCheck(\'propValue\')' 109 | ) 110 | }) 111 | }) 112 | 113 | describe('two matching input type checkbox', () => { 114 | beforeEach(() => { 115 | page = new Page( 116 |
117 | 118 | 119 |
120 | ) 121 | }) 122 | 123 | it('should raise an error describing that two matching React elements were found', () => { 124 | expect(() => page.uncheck('propValue')).toThrowError( 125 | Error, 126 | '.check(\'propValue\') failed because 2 matching React elements were found by .findWrapperForCheck(\'propValue\')' 127 | ) 128 | }) 129 | }) 130 | }) 131 | 132 | describe('propToCheck option specified with className', () => { 133 | describe('input type checkbox whose className prop value matches the propValue', () => { 134 | beforeEach(() => { 135 | page = new Page( 136 | 141 | ) 142 | }) 143 | 144 | it('should not raise an error describing that no matching React elements were found', () => { 145 | expect(() => page.uncheck('propValue', { propToCheck: 'className' })).not.toThrow() 146 | }) 147 | }) 148 | }) 149 | }) 150 | -------------------------------------------------------------------------------- /lib/__tests__/waitUntilSpec.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Page from '../index' 3 | 4 | describe('waitUntilSpec', () => { 5 | let page 6 | class TestComponent extends Component { 7 | constructor() { 8 | super() 9 | this.state = {} 10 | } 11 | 12 | componentDidMount() { 13 | new Promise(resolve => setTimeout(resolve, 100)) 14 | .then(() => this.setState({ message: 'hello' })) 15 | } 16 | 17 | render() { 18 | return

{this.state.message}

19 | } 20 | } 21 | 22 | beforeEach(() => { 23 | page = new Page() 24 | }) 25 | 26 | afterEach(() => { 27 | page.destroy() 28 | }) 29 | 30 | it('should call the done function once the page renders the message', done => { 31 | expect(page.wrapper.html()).toEqual('

') 32 | page.waitUntil(() => page.wrapper.html() === '

hello

') 33 | .then(done, error => done.fail(error.message)) 34 | }) 35 | 36 | it('should invoke the callback as often as specified in the delay', async done => { 37 | expect(page.wrapper.html()).toEqual('

') 38 | return page.waitUntil(() => page.wrapper.html() === '

hello

', { delay: 80 }) 39 | .then(callbackInvocationCount => { 40 | expect(callbackInvocationCount).toEqual(2) 41 | }) 42 | .then(done, error => done.fail(error.message)) 43 | }) 44 | 45 | it('should invoke the callback at most 50 times by default', async done => { 46 | return page.waitUntil(() => false, { delay: 1 }) 47 | .catch(error => { 48 | expect(error.message).toEqual('Invoked function () {return false;} every 1 millisecond(s) 50 time(s), but the callback never returned true') 49 | }) 50 | .then(done, error => done.fail(error.message)) 51 | }) 52 | 53 | it('should invoke the callback every 10 ms by default', async done => { 54 | return page.waitUntil(() => false, { numberOfTries: 1 }) 55 | .catch(error => { 56 | expect(error.message).toEqual('Invoked function () {return false;} every 10 millisecond(s) 1 time(s), but the callback never returned true') 57 | }) 58 | .then(done, error => done.fail(error.message)) 59 | }) 60 | 61 | it('should invoke the callback at most as many times as specified by numberOfTries', async done => { 62 | return page.waitUntil(() => false, { delay: 1, numberOfTries: 1 }) 63 | .catch(error => { 64 | expect(error.message).toEqual('Invoked function () {return false;} every 1 millisecond(s) 1 time(s), but the callback never returned true') 65 | }) 66 | .then(done, error => done.fail(error.message)) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /lib/__tests__/wrapperSpec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Page from '../index' 3 | 4 | describe('wrapperSpec', () => { 5 | let page 6 | const TestComponent = ({ id }) =>

I am Component

7 | 8 | beforeEach(() => { 9 | page = new Page() 10 | }) 11 | 12 | afterEach(() => { 13 | page.destroy() 14 | }) 15 | 16 | it('should return an enzyme ReactWrapper', () => { 17 | expect(page.wrapper.constructor.name).toEqual('ReactWrapper') 18 | }) 19 | 20 | it('should return an enzyme ReactWrapper representing the passed in reactElement', () => { 21 | expect(page.wrapper.html()).toEqual('

I am Component

') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /lib/findWrapperMethods/formatPropsToCheck.js: -------------------------------------------------------------------------------- 1 | export default function formatPropsToCheck(propsToCheck) { 2 | const propsToCheckLength = propsToCheck.length 3 | switch (propsToCheckLength) { 4 | case 1: 5 | return propsToCheck[0] 6 | case 2: 7 | return `${propsToCheck[0]} or ${propsToCheck[1]}` 8 | default: 9 | const lastProp = propsToCheck[propsToCheckLength - 1] 10 | return `${propsToCheck.slice(0, -1).join(', ')}, or ${lastProp}` 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/findWrapperMethods/index.js: -------------------------------------------------------------------------------- 1 | import formatPropsToCheck from './formatPropsToCheck' 2 | 3 | export function findWrapperForCheck(propValue, { propToCheck } = {}) { 4 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'name'] 5 | return this.findWrapper('input', propValue, { 6 | propsToCheck, 7 | requiredProps: { type: ['checkbox'] } 8 | }) 9 | } 10 | 11 | export function findWrapperForChoose(propValue, { propToCheck } = {}) { 12 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'name'] 13 | return this.findWrapper('input', propValue, { 14 | propsToCheck, 15 | requiredProps: { type: ['radio'] } 16 | }) 17 | } 18 | 19 | export function findWrapperForClickButton(propValue, { propToCheck } = {}) { 20 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'children'] 21 | return this.findWrapper('button', propValue, { propsToCheck }) 22 | } 23 | 24 | export function findWrapperForClickInput(propValue, { propToCheck } = {}) { 25 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'value'] 26 | return this.findWrapper('input', propValue, { 27 | propsToCheck, 28 | requiredProps: { type: ['submit'] } 29 | }) 30 | } 31 | 32 | export function findWrapperForClickLink(propValue, { propToCheck } = {}) { 33 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'children', 'href'] 34 | return this.findWrapper('a', propValue, { propsToCheck }) 35 | } 36 | 37 | export function findWrapperForFillIn(propValue, { propToCheck } = {}) { 38 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'name', 'placeholder'] 39 | return this.findWrapper('input', propValue, { 40 | propsToCheck, 41 | requiredProps: { type: [undefined, 'email', 'password', 'text', 'tel'] } 42 | }) 43 | } 44 | 45 | export function findWrapperForFillInTextarea(propValue, { propToCheck } = {}) { 46 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'name', 'placeholder'] 47 | return this.findWrapper('textarea', propValue, { propsToCheck }) 48 | } 49 | 50 | export function findWrapperForSelect(propValue, childrenPropValueForOption, { propToCheck, showDebuggingInfo } = {}) { 51 | const propsToCheck = propToCheck ? [propToCheck] : ['id', 'name'] 52 | let foundSelectWrapper = false 53 | 54 | const wrapper = this.wrapper.findWhere(wrapper => { 55 | if (wrapper.type() !== 'select') { 56 | return false 57 | } 58 | 59 | const wrapperProps = wrapper.props() 60 | const hasMatchingPropValue = propsToCheck.some(propName => { 61 | const wrapperPropValue = wrapperProps[propName] 62 | return wrapperPropValue === propValue 63 | }) 64 | const matchingOptions = wrapper.find(`option[children="${childrenPropValueForOption}"]`) 65 | const hasMatchingOptions = matchingOptions.exists() 66 | const hasOnlyOneMatchingOption = hasMatchingOptions && matchingOptions.length === 1 67 | 68 | if (showDebuggingInfo && hasMatchingPropValue) { 69 | foundSelectWrapper = true 70 | hasMatchingOptions ? 71 | hasOnlyOneMatchingOption ? 72 | console.log(`matching select React element whose children include a matching option React element was found: ${wrapper.debug().replace(/\n\s*/g, '')}`) : 73 | console.log(`matching select React element was found, but its children include more than one option React element whose children prop value matched '${childrenPropValueForOption}': ${wrapper.debug().replace(/\n\s*/g, '')}`) : 74 | console.log(`matching select React element was found, but its children did not include an option React element whose children prop value matched '${childrenPropValueForOption}': ${wrapper.debug().replace(/\n\s*/g, '')}`) 75 | } 76 | 77 | return hasMatchingPropValue && hasOnlyOneMatchingOption 78 | }) 79 | 80 | if (showDebuggingInfo && !foundSelectWrapper) { 81 | console.log(`no select React element was found whose ${formatPropsToCheck(propsToCheck)} prop value matches '${propValue}'`) 82 | } 83 | 84 | return wrapper 85 | } 86 | 87 | export function findWrapper(type, propValue, { propsToCheck = ['id'], requiredProps = {} } = {}) { 88 | return this.wrapper.findWhere(wrapper => { 89 | if (wrapper.type() !== type) { 90 | return false 91 | } 92 | 93 | const wrapperProps = wrapper.props() 94 | const hasRequiredProps = Object.keys(requiredProps).every(propName => { 95 | const validValuesForRequiredProp = requiredProps[propName] 96 | const wrapperRequiredPropValue = wrapperProps[propName] 97 | return validValuesForRequiredProp.indexOf(wrapperRequiredPropValue) !== -1 98 | }) 99 | 100 | return hasRequiredProps && propsToCheck.some(propName => { 101 | const wrapperPropValue = wrapperProps[propName] 102 | return wrapperPropValue === propValue 103 | }) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'enzyme' 2 | import * as findWrapperMethods from './findWrapperMethods' 3 | import * as interactionMethods from './interactionMethods' 4 | import * as utilityMethods from './utilityMethods' 5 | 6 | const navigateTo = path => window.history.pushState({}, '', path) 7 | 8 | class Page { 9 | constructor(reactElement, { initialPath = '/' } = {}) { 10 | navigateTo(initialPath) 11 | this.wrapper = mount(reactElement) 12 | this.focusedWrapper = null 13 | } 14 | 15 | destroy() { 16 | this.wrapper.unmount() 17 | navigateTo('/') 18 | } 19 | } 20 | 21 | Object.assign( 22 | Page.prototype, 23 | findWrapperMethods, 24 | interactionMethods, 25 | utilityMethods 26 | ) 27 | 28 | export default Page 29 | -------------------------------------------------------------------------------- /lib/interactionMethods/blurAndFocusWrappersIfNecessary.js: -------------------------------------------------------------------------------- 1 | export default function blurAndFocusWrappersIfNecessary(page, wrapper) { 2 | const shouldFocusWrapper = !page.focusedWrapper || 3 | (page.focusedWrapper.getNode() !== wrapper.getNode()) 4 | 5 | if (shouldFocusWrapper) { 6 | if (page.focusedWrapper) { 7 | page.focusedWrapper.simulate('blur') 8 | } 9 | 10 | wrapper.simulate('focus') 11 | page.focusedWrapper = wrapper 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/interactionMethods/checkOnlyOneNodeExists.js: -------------------------------------------------------------------------------- 1 | const formatArgs = args => { 2 | return args 3 | .filter(arg => arg !== undefined) 4 | .map(arg => { 5 | switch (typeof arg) { 6 | case 'string': 7 | return `'${arg}'` 8 | case 'object': 9 | return JSON.stringify(arg) 10 | default: 11 | return '' 12 | } 13 | }) 14 | .join(', ') 15 | } 16 | 17 | export default function checkOnlyOneNodeExists(wrapper, methodName, findWrapperMethodArguments, args) { 18 | const wrapperLength = wrapper.length 19 | 20 | if (wrapperLength !== 1) { 21 | let message 22 | const formattedArgs = formatArgs(Array.prototype.slice.call(args)) 23 | const formattedFindWrapperMethodArgs = formatArgs(findWrapperMethodArguments) 24 | const findWrapperMethodName = `findWrapperFor${methodName.charAt(0).toUpperCase() + methodName.slice(1)}` 25 | if (wrapperLength === 0) { 26 | message = `.${methodName}(${formattedArgs}) failed because no matching React elements were found by .${findWrapperMethodName}(${formattedFindWrapperMethodArgs})` 27 | } 28 | 29 | if (wrapperLength > 1) { 30 | message = `.${methodName}(${formattedArgs}) failed because ${wrapperLength} matching React elements were found by .${findWrapperMethodName}(${formattedFindWrapperMethodArgs})` 31 | } 32 | 33 | throw new Error(message) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/interactionMethods/index.js: -------------------------------------------------------------------------------- 1 | import checkOnlyOneNodeExists from './checkOnlyOneNodeExists' 2 | import blurAndFocusWrappersIfNecessary from './blurAndFocusWrappersIfNecessary' 3 | 4 | export function blurFocusedElement() { 5 | if (this.focusedWrapper) { 6 | this.focusedWrapper.simulate('blur') 7 | this.focusedWrapper = null 8 | } 9 | return this 10 | } 11 | 12 | export function check(propValue, options) { 13 | const findWrapperMethodArguments = [propValue, options] 14 | const wrapper = this.findWrapperForCheck(...findWrapperMethodArguments) 15 | checkOnlyOneNodeExists(wrapper, 'check', findWrapperMethodArguments, arguments) 16 | blurAndFocusWrappersIfNecessary(this, wrapper) 17 | const { name, value = 'on' } = wrapper.props() 18 | wrapper.simulate('change', { 19 | target: { 20 | checked: true, 21 | type: 'checkbox', 22 | name, 23 | value 24 | } 25 | }) 26 | 27 | return this 28 | } 29 | 30 | export function choose(propValue, options) { 31 | const findWrapperMethodArguments = [propValue, options] 32 | const wrapper = this.findWrapperForChoose(...findWrapperMethodArguments) 33 | checkOnlyOneNodeExists(wrapper, 'choose', findWrapperMethodArguments, arguments) 34 | blurAndFocusWrappersIfNecessary(this, wrapper) 35 | const { name, value = 'on' } = wrapper.props() 36 | wrapper.simulate('change', { 37 | target: { 38 | checked: true, 39 | type: 'radio', 40 | name, 41 | value 42 | } 43 | }) 44 | 45 | return this 46 | } 47 | 48 | export function clickButton(propValue, options) { 49 | const findWrapperMethodArguments = [propValue, options] 50 | const wrapper = this.findWrapperForClickButton(...findWrapperMethodArguments) 51 | checkOnlyOneNodeExists(wrapper, 'clickButton', findWrapperMethodArguments, arguments) 52 | blurAndFocusWrappersIfNecessary(this, wrapper) 53 | wrapper.simulate('click') 54 | wrapper.simulate('submit') 55 | return this 56 | } 57 | 58 | export function clickInput(propValue, options) { 59 | const findWrapperMethodArguments = [propValue, options] 60 | const wrapper = this.findWrapperForClickInput(...findWrapperMethodArguments) 61 | checkOnlyOneNodeExists(wrapper, 'clickInput', findWrapperMethodArguments, arguments) 62 | blurAndFocusWrappersIfNecessary(this, wrapper) 63 | wrapper.simulate('click') 64 | wrapper.simulate('submit') 65 | return this 66 | } 67 | 68 | export function clickLink(propValue, options) { 69 | const findWrapperMethodArguments = [propValue, options] 70 | const wrapper = this.findWrapperForClickLink(...findWrapperMethodArguments) 71 | checkOnlyOneNodeExists(wrapper, 'clickLink', findWrapperMethodArguments, arguments) 72 | blurAndFocusWrappersIfNecessary(this, wrapper) 73 | wrapper.simulate('click', { button: 0 }) 74 | return this 75 | } 76 | 77 | export function fillIn(propValue, eventTargetValue, options) { 78 | const findWrapperMethodArguments = [propValue, options] 79 | const wrapper = this.findWrapperForFillIn(...findWrapperMethodArguments) 80 | checkOnlyOneNodeExists(wrapper, 'fillIn', findWrapperMethodArguments, arguments) 81 | blurAndFocusWrappersIfNecessary(this, wrapper) 82 | wrapper.simulate('change', { 83 | target: { 84 | value: eventTargetValue, 85 | name: wrapper.props().name 86 | } 87 | }) 88 | return this 89 | } 90 | 91 | export function fillInTextarea(propValue, eventTargetValue, options) { 92 | const findWrapperMethodArguments = [propValue, options] 93 | const wrapper = this.findWrapperForFillInTextarea(...findWrapperMethodArguments) 94 | checkOnlyOneNodeExists(wrapper, 'fillInTextarea', findWrapperMethodArguments, arguments) 95 | blurAndFocusWrappersIfNecessary(this, wrapper) 96 | wrapper.simulate('change', { 97 | target: { 98 | value: eventTargetValue, 99 | name: wrapper.props().name 100 | } 101 | }) 102 | return this 103 | } 104 | 105 | export function select(propValue, childrenPropValueForOption, options) { 106 | const findWrapperMethodArguments = [propValue, childrenPropValueForOption, options] 107 | const wrapper = this.findWrapperForSelect(...findWrapperMethodArguments) 108 | checkOnlyOneNodeExists(wrapper, 'select', findWrapperMethodArguments, arguments) 109 | const option = wrapper.find(`option[children="${childrenPropValueForOption}"]`) 110 | const optionValue = option.props().value 111 | blurAndFocusWrappersIfNecessary(this, wrapper) 112 | wrapper.simulate('change', { 113 | target: { 114 | value: optionValue, 115 | name: wrapper.props().name 116 | } 117 | }) 118 | return this 119 | } 120 | 121 | export function uncheck(propValue, options) { 122 | const findWrapperMethodArguments = [propValue, options] 123 | const wrapper = this.findWrapperForCheck(...findWrapperMethodArguments) 124 | checkOnlyOneNodeExists(wrapper, 'check', findWrapperMethodArguments, arguments) 125 | blurAndFocusWrappersIfNecessary(this, wrapper) 126 | const { name, value = 'on' } = wrapper.props() 127 | wrapper.simulate('change', { 128 | target: { 129 | checked: false, 130 | type: 'checkbox', 131 | name, 132 | value 133 | } 134 | }) 135 | return this 136 | } 137 | -------------------------------------------------------------------------------- /lib/utilityMethods/index.js: -------------------------------------------------------------------------------- 1 | import interpolateToCode from './interpolateToCode' 2 | 3 | export function content() { 4 | return this.wrapper.text() 5 | } 6 | 7 | export function contentMatches(matcher) { 8 | const regularExpression = matcher.constructor.name === 'RegExp' ? 9 | matcher : 10 | new RegExp(matcher) 11 | 12 | return regularExpression.test(this.wrapper.text()) 13 | } 14 | 15 | export function currentPath() { 16 | return window.location.pathname 17 | } 18 | 19 | 20 | export function outputOpenPageCode() { 21 | const message = interpolateToCode(this.wrapper.html()) 22 | console.log(message) 23 | } 24 | 25 | const formatCallbackOutput = callback => callback.toString().replace(/\s*\n\s*/g, ' ') 26 | 27 | function waitUntil(callback, { delay = waitUntil.defaultDelay, numberOfTries = waitUntil.numberOfTries } = {}) { 28 | return new Promise((resolve, reject) => { 29 | let intervalId, 30 | callbackInvocationCount = 0 31 | 32 | intervalId = setInterval(() => { 33 | callbackInvocationCount += 1 34 | 35 | if (callback()) { 36 | clearInterval(intervalId) 37 | resolve(callbackInvocationCount) 38 | } else { 39 | if (callbackInvocationCount === numberOfTries) { 40 | const message = `Invoked ${formatCallbackOutput(callback)} every ${delay} millisecond(s) ${numberOfTries} time(s), but the callback never returned true` 41 | reject(new Error(message)) 42 | } 43 | } 44 | }, delay) 45 | }) 46 | } 47 | 48 | waitUntil.defaultDelay = 10 49 | waitUntil.numberOfTries = 50 50 | 51 | export { waitUntil } 52 | -------------------------------------------------------------------------------- /lib/utilityMethods/interpolateToCode.js: -------------------------------------------------------------------------------- 1 | const interpolatedHTML = body => ( 2 | `React Page Object Preview${body}` 3 | ).replace(/`/g, '\\`') 4 | 5 | export default function interpolateToCode(body) { 6 | return `PASTE THE FOLLOWING INTO YOUR BROWSER CONSOLE:\nwindow.open().document.write(\`${interpolatedHTML(body)}\`)\n` 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-page-object", 3 | "version": "1.1.0", 4 | "description": "Declarative integration testing for React", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "jest --watch", 8 | "webpack": "node_modules/.bin/webpack", 9 | "release": "npm run webpack && npm publish" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/IntrepidPursuits/react-page-object.git" 14 | }, 15 | "keywords": [ 16 | "page-object", 17 | "react", 18 | "test" 19 | ], 20 | "author": "David Rodriguez Fuentes", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/IntrepidPursuits/react-page-object/issues" 24 | }, 25 | "homepage": "https://github.com/IntrepidPursuits/react-page-object#readme", 26 | "dependencies": {}, 27 | "devDependencies": { 28 | "babel-core": "^6.21.0", 29 | "babel-eslint": "^7.1.1", 30 | "babel-jest": "^20.0.3", 31 | "babel-loader": "^6.2.10", 32 | "babel-polyfill": "^6.20.0", 33 | "babel-preset-latest": "^6.16.0", 34 | "babel-preset-react": "^6.16.0", 35 | "clean-webpack-plugin": "^0.1.15", 36 | "enzyme": "^2.8.2", 37 | "eslint": "^3.12.2", 38 | "eslint-config-react-app": "^0.5.0", 39 | "eslint-loader": "^1.6.1", 40 | "eslint-plugin-flowtype": "^2.29.1", 41 | "eslint-plugin-import": "^2.2.0", 42 | "eslint-plugin-jsx-a11y": "^3.0.2", 43 | "eslint-plugin-react": "^6.8.0", 44 | "jasmine-core": "^2.5.2", 45 | "jest": "^20.0.3", 46 | "phantomjs-prebuilt": "^2.1.14", 47 | "react": "^15.5.4", 48 | "react-dom": "^15.5.4", 49 | "react-router-dom": "^4.1.1", 50 | "react-test-renderer": "^15.5.4", 51 | "regenerator-runtime": "^0.10.5", 52 | "webpack": "^1.14.0" 53 | }, 54 | "peerDependencies": { 55 | "enzyme": "^2.8.2" 56 | }, 57 | "babel": { 58 | "presets": [ 59 | "latest", 60 | "react" 61 | ] 62 | }, 63 | "eslintConfig": { 64 | "extends": "react-app" 65 | }, 66 | "jest": { 67 | "testURL": "http://localhost" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import CleanPlugin from 'clean-webpack-plugin' 2 | 3 | export default { 4 | entry: { 5 | bundle: [ 6 | './lib/index.js', 7 | ] 8 | }, 9 | output: { 10 | path: './dist', 11 | filename: 'index.js', 12 | libraryTarget: 'umd' 13 | }, 14 | externals: { 15 | 'enzyme': { 16 | commonjs: 'enzyme', 17 | commonjs2: 'enzyme', 18 | amd: 'enzyme', 19 | root: 'enzyme' 20 | } 21 | }, 22 | module: { 23 | loaders: [ 24 | { 25 | test: /\.jsx?$/, 26 | exclude: /node_modules/, 27 | loader: 'babel', 28 | } 29 | ], 30 | }, 31 | plugins: [ 32 | new CleanPlugin('dist') 33 | ] 34 | } 35 | --------------------------------------------------------------------------------