├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── circle.yml ├── index.js ├── package.json ├── src └── index.js ├── tests ├── components │ ├── address-line │ │ ├── address-line-styles.js │ │ ├── address-line-tests.js │ │ └── address-line.jsx │ ├── address │ │ ├── address-tests.js │ │ └── address.jsx │ ├── app │ │ ├── app-styles.js │ │ ├── app-tests.js │ │ └── app.jsx │ ├── message │ │ ├── message-styles.js │ │ ├── message-tests.js │ │ └── message.jsx │ ├── postcard │ │ ├── postcard-styles.js │ │ ├── postcard-tests.js │ │ └── postcard.jsx │ ├── signature │ │ ├── signature-styles.js │ │ ├── signature-tests.js │ │ └── signature.jsx │ ├── stamp │ │ ├── stamp-dumb-tests.js │ │ ├── stamp-dumb.jsx │ │ ├── stamp-smart-tests.js │ │ ├── stamp-smart.jsx │ │ ├── stamp-styles.js │ │ └── stamp-types.js │ └── welcome │ │ ├── welcome-styles.js │ │ ├── welcome-tests.js │ │ └── welcome.jsx ├── index.html ├── index.jsx ├── stand-alone │ ├── bad-components │ │ ├── multi-root │ │ │ ├── item-list-tests.js │ │ │ ├── item-list.jsx │ │ │ └── item.jsx │ │ └── some-error │ │ │ ├── some-error-tests.js │ │ │ └── some-error.js │ ├── link-list │ │ ├── link-list-tests.js │ │ ├── link-list.jsx │ │ └── link.jsx │ ├── map-to │ │ ├── on-change-tests.js │ │ └── on-change.jsx │ ├── null-component │ │ ├── null-component-tests.js │ │ └── null-component.js │ ├── spies-config │ │ ├── spies-config-tests.js │ │ └── spies-config.jsx │ ├── spies-teardown │ │ ├── spies-teardown-tests.js │ │ └── spies-teardown.jsx │ └── unordered-list │ │ ├── unordered-list-tests.jsx │ │ └── unordered-list.jsx └── webpack.config.test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "func-names": 0, 5 | "prefer-rest-params": 0, 6 | "no-underscore-dangle": 0, 7 | "import/no-extraneous-dependencies": 0 8 | }, 9 | "globals": { 10 | "describe": true, 11 | "it": true, 12 | "beforeEach": true, 13 | "afterEach": true, 14 | } 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | tests/index.min* 4 | npm-debug.log 5 | coverage 6 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # We're going through changes 2 | 3 | ## 0.1.0 - 28 January, 2016 4 | 5 | * Add `withArgs` to help with `propFunc` `mapsTo` 6 | * Add defensive spy code and tidied methods to spy on 7 | 8 | ## 0.0.23 - 12 January, 2016 9 | 10 | * Fake context when using radium (after v0.16 breaking changes) 11 | 12 | ## 0.0.22 - 24 December, 2015 13 | 14 | * Upgrade to babel 6 15 | 16 | ## 0.0.21 - 12 December, 2015 17 | 18 | * Fix child event mapping 19 | 20 | ## 0.0.20 - 12 December, 2015 21 | 22 | * Fix unspyable methods 23 | * Throw error if more than one root element 24 | 25 | ## 0.0.19 - 9 December, 2015 26 | 27 | * Add spy config 28 | 29 | ## 0.0.18 - 12 November, 2015 30 | 31 | * Add teardown functionality 32 | 33 | ## 0.0.16 - 11 November, 2015 34 | 35 | * Allow for render methods that return null!? 36 | 37 | ## 0.0.15 - 11 November, 2015 38 | 39 | * Add event mapping for child elements 40 | 41 | ## 0.0.14 - 11 November, 2015 42 | 43 | * Fix event mapping for multiple events in component 44 | 45 | ## 0.0.12 - 5 November, 2015 46 | 47 | * Fix deep child nesting 48 | 49 | ## 0.0.9 - 5 November, 2015 50 | 51 | * Fix value property for elements with no children -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Circle CI](https://circleci.com/gh/craigbilner/react-component-tester/tree/master.svg?style=svg)](https://circleci.com/gh/craigbilner/react-component-tester/tree/master) 2 | [![Coverage Status](https://coveralls.io/repos/craigbilner/react-component-tester/badge.svg?branch=master&service=github)](https://coveralls.io/github/craigbilner/react-component-tester?branch=master) 3 | ![dependencies](https://david-dm.org/craigbilner/react-component-tester.svg) 4 | [![npm Version](https://img.shields.io/npm/v/react-component-tester.svg)](https://www.npmjs.com/package/react-component-tester) 5 | [![License](https://img.shields.io/npm/l/react-component-tester.svg)](https://www.npmjs.com/package/react-component-tester) 6 | 7 | # react component tester 8 | 9 | because testing components with shallow rendering is fantastic...but can be a bit of a pain and other testing libraries focus on finding components rather than providing an opinionated way of testing them, including their state. 10 | 11 | # requirements 12 | 13 | * test a component quickly (~5ms per test) 14 | * test the style of components easily 15 | * test the props of child components easily 16 | * test various "flavours" of props succinctly in a consistent uniform manner 17 | * find child components easily in a non-brittle manner 18 | * test prop wire-up on smart components 19 | * test changes of state of smart components 20 | * abstract away from the react components themselves and their API 21 | 22 | # usage 23 | 24 | a component system that builds a basic responsive postcard can be found in the tests directory with example usage (which also self tests the library) 25 | 26 | ## flavours 27 | 28 | often you want to test how components are rendered with different prop values. this tester allows the addition of "flavours" to make the test code more terse 29 | 30 | to create a tester, run create and pass in the component under test to "be used" 31 | 32 | ```javascript 33 | const tester = ReactTester.create().use(Address); 34 | ``` 35 | 36 | then add a flavour which takes a name and a props object 37 | 38 | ```javascript 39 | const NO_ADDRESS = tester.addFlavour('NO_ADDRESS', { 40 | addressee: 'Mr Robert Smith', 41 | }); 42 | ``` 43 | 44 | then the flavour can be tested on, here the NONE flavour's type is checked 45 | 46 | ```javascript 47 | const actual = NONE.type; 48 | const expected = 'div'; 49 | 50 | assert.deepEqual(actual, expected); 51 | ``` 52 | 53 | ## methods 54 | 55 | ### .countComponents(Component{ReactComponent}) => count{int} 56 | 57 | takes a react component and returns the number of times it appears in the rendered tree for the component under test 58 | 59 | here the THREE_LINES flavour is tested for the number of AddressLine components it would render if given three lines of address 60 | 61 | ```javascript 62 | it('render four address lines if there are three lines in the given address', () => { 63 | const actual = THREE_LINES.countComponents(AddressLine); 64 | const expected = 4; 65 | 66 | assert.deepEqual(actual, expected); 67 | }); 68 | ``` 69 | 70 | ### .findChild(path{string}) => child{ReactComponent} 71 | 72 | takes a zero based decimal delimited string path and returns the resulting rendered component. this will make your tests more brittle to DOM changes but can come in handy 73 | 74 | here the first child is found. if we wanted the second child of the third child of the fourth child we would pass "3.2.1" 75 | 76 | ```javascript 77 | const value = NOTHING.findChild('0'); 78 | const actual = typeof value; 79 | const expected = 'undefined'; 80 | 81 | assert.deepEqual(actual, expected); 82 | ``` 83 | 84 | ### .findComponents(Component{ReactComponent}) => [Component{ReactComponent}] 85 | 86 | takes a react component and returns an array of all child rendered components of that type 87 | 88 | here we find all AddressLine components for the NO_ADDRESS flavour, take the first one and check that it is the addressee 89 | 90 | ```javascript 91 | const addressee = NO_ADDRESS.findComponents(AddressLine)[0]; 92 | const actual = addressee.props.text; 93 | const expected = 'Mr Robert Smith'; 94 | 95 | assert.deepEqual(actual, expected); 96 | ``` 97 | 98 | ### .getState() => state{object} 99 | 100 | returns the current state of the given component flavour. resetState should be run after each test to ensure tests do not collide 101 | 102 | here we fire the click function given to the dumb component and confirm that the smart component's state is updated correctly 103 | 104 | ```javascript 105 | NONE.component.props.onClick(); 106 | 107 | const actual = NONE.getState().type; 108 | const expected = stampTypes.FIRST_CLASS; 109 | 110 | assert.deepEqual(actual, expected); 111 | ``` 112 | 113 | ### .propFunc(propName{string}), .withArgs(arguments{array}) [optional], .mapsTo(methodName{string}) => isMapped{boolean} 114 | 115 | `propFunc` takes a string which is the prop of a dumb component to which you are passing a function 116 | 117 | `mapsTo` takes a string which maps to the method on the smart component class 118 | 119 | if your `mapsTo` method expects some specific arguments, these can be passed as an array using `withArgs` 120 | 121 | the returned value will be a boolean indicating if the given prop function maps to the expected class method 122 | 123 | here we test that the stamp dumb component was correctly given the smart component's `handleOnClick` method 124 | 125 | ```javascript 126 | const isMapped = 127 | NONE 128 | .component 129 | .propFunc('onClick') 130 | .withArgs({ test: 123 }) 131 | .mapsTo('handleOnClick'); 132 | 133 | assert(isMapped); 134 | ``` 135 | 136 | ### .resetState() 137 | 138 | returns the state of the flavour of the component under test to the initial state 139 | 140 | ### .teardown() => {ReactComponentTester} 141 | 142 | If for any reason you need to restore spies placed on a component as part of set up you can use the teardown method. Returns the tester. 143 | 144 | ```javascript 145 | const tester = ReactTester.create().use(Address); 146 | // Do stuff... 147 | tester.teardown(); 148 | ``` 149 | 150 | ## properties 151 | 152 | ### component{renderedReactComponent} 153 | 154 | the rendered component that is under test 155 | 156 | ### props{object} 157 | 158 | the props of the rendered component will be returned for each component found 159 | 160 | ### style{object} 161 | 162 | for ease of use, the style prop is placed on each component too 163 | 164 | ### type{componentType} 165 | 166 | each returned component will have a type 167 | 168 | ### value{string} 169 | 170 | if a component doesn't have any children, it's content will be added as the value property 171 | 172 | ## config 173 | 174 | all methods are spied on by default, if for some reason you would like to remove them you can pass a config on create 175 | 176 | ```javascript 177 | ReactTester.create({ 178 | spyOnDefault: false, 179 | spyOn: { 180 | someMethod: false, 181 | }, 182 | }).use(SomeComponent); 183 | ``` 184 | 185 | You can either pass: 186 | 187 | * `spyOnDefault` as false to disable all spies 188 | * a `spyOn` config to disable per method 189 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | cache_directories: 3 | - ~/.cache/yarn 4 | override: 5 | - yarn 6 | machine: 7 | environment: 8 | PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" 9 | node: 10 | version: "6.10.0" 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const ReactTester = require('./src/index'); 2 | 3 | module.exports = ReactTester; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-component-tester", 3 | "version": "0.4.0", 4 | "description": "A simple helper to make testing react components with shallow rendering easier", 5 | "main": "index.js", 6 | "scripts": { 7 | "build:test": "webpack --config ./tests/webpack.config.test.js", 8 | "build:testrig": "npm run build:test && open ./tests/index.html", 9 | "watch:testrig": "npm run build:test -- --progress --colors --watch", 10 | "test": "babel-node ./node_modules/istanbul/lib/cli cover _mocha ./tests/**/*tests.js", 11 | "posttest": "npm run lint && cat ./coverage/lcov.info | coveralls", 12 | "test:simple": "mocha ./tests/**/*tests.js --compilers js:babel-core/register", 13 | "test:spec": "mocha ./tests/components/address-line --compilers js:babel-core/register", 14 | "lint": "eslint ./src ./tests ./index.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/craigbilner/react-component-tester.git" 19 | }, 20 | "keywords": [ 21 | "react", 22 | "testing", 23 | "shallow", 24 | "rendering", 25 | "react-test", 26 | "react-testing", 27 | "react-tester", 28 | "react-component", 29 | "react-shallow-rendering", 30 | "react-test-help", 31 | "react-testing-helpers" 32 | ], 33 | "author": "Craig Bilner", 34 | "license": "Apache-2.0", 35 | "bugs": { 36 | "url": "https://github.com/craigbilner/react-component-tester/issues" 37 | }, 38 | "homepage": "https://github.com/craigbilner/react-component-tester#readme", 39 | "devDependencies": { 40 | "babel-cli": "6.14.0", 41 | "babel-core": "6.14.0", 42 | "babel-loader": "6.4.1", 43 | "babel-preset-es2015": "6.14.0", 44 | "babel-preset-react": "6.23.0", 45 | "coveralls": "2.11.16", 46 | "eslint": "3.18.0", 47 | "eslint-config-airbnb": "14.1.0", 48 | "eslint-plugin-import": "2.2.0", 49 | "eslint-plugin-jsx-a11y": "4.0.0", 50 | "eslint-plugin-react": "6.10.3", 51 | "istanbul": "0.4.5", 52 | "mocha": "3.2.0", 53 | "radium": "0.18.2", 54 | "react-dom": "15.3.1", 55 | "webpack": "1.13.2" 56 | }, 57 | "dependencies": { 58 | "lodash": "4.17.4", 59 | "react": "15.3.1", 60 | "react-addons-test-utils": "15.3.1", 61 | "sinon": "2.1.0", 62 | "stampit": "3.1.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const stampit = require('stampit'); 2 | const _ = require('lodash'); // eslint-disable-line id-length 3 | const React = require('react'); 4 | const TestUtils = require('react-addons-test-utils'); 5 | const sinon = require('sinon'); 6 | 7 | const defaultConfig = { 8 | spyOnDefault: true, 9 | spyOn: {}, 10 | }; 11 | 12 | const doNotSpyOn = { 13 | constructor: false, 14 | componentWillUnmount: false, 15 | render: false, 16 | getChildContext: false, 17 | getNextStampType: false, 18 | }; 19 | 20 | // FLAVOUR COMPONENT 21 | 22 | // METHODS 23 | 24 | const propFunc = function (propToTest) { 25 | this.propToTest = propToTest; 26 | return this; 27 | }; 28 | 29 | const withArgs = function () { 30 | this.mapTestArgs = [].slice.call(arguments); 31 | return this; 32 | }; 33 | 34 | const mapsTo = function (method) { 35 | const symbolToTest = Symbol(method); 36 | 37 | this.reactClass.prototype[method].reset(); 38 | this.props[this.propToTest].apply(this.reactClass, this.mapTestArgs.concat(symbolToTest)); 39 | 40 | return this.reactClass.prototype[method].lastCall.args.indexOf(symbolToTest) > -1; 41 | }; 42 | 43 | const flavourComponentMethods = { 44 | propFunc, 45 | withArgs, 46 | mapsTo, 47 | }; 48 | 49 | const flavourComponentInit = function (opts) { 50 | Object.assign(this, opts); 51 | 52 | this.mapTestArgs = []; 53 | 54 | if (opts.props) { 55 | this.style = opts.props.style; 56 | 57 | if (!TestUtils.isElement(opts.props.children)) { 58 | this.value = opts.props.children; 59 | } 60 | } 61 | }; 62 | 63 | const flavourComponent = stampit() 64 | .init(flavourComponentInit) 65 | .methods(flavourComponentMethods); 66 | 67 | // FLAVOUR 68 | 69 | // METHODS 70 | 71 | /* eslint-disable no-use-before-define */ 72 | const reduceChildren = 73 | function (parentComponent, reactClass, parentIndx, childMap, child, indx) { 74 | const thisMap = _.assign({}, childMap); 75 | const childIsElement = TestUtils.isElement(child); 76 | const id = parseInt(parentIndx, 10) >= 0 ? `${parentIndx}.${indx}` : indx; 77 | 78 | if (childIsElement) { 79 | thisMap[id] = flavourComponent(_.assign( 80 | {}, 81 | child, 82 | { 83 | parentComponent, 84 | reactClass, 85 | })); 86 | 87 | _.assign( 88 | thisMap, 89 | convertAndReduce( 90 | parentComponent, 91 | reactClass, 92 | child.props.children, 93 | id)); 94 | } else { 95 | thisMap[id] = { 96 | value: child, 97 | }; 98 | } 99 | 100 | return thisMap; 101 | }; 102 | /* eslint-enable no-use-before-define */ 103 | 104 | const convertAndReduce = function (parentComponent, reactClass, children, parentIndx) { 105 | return React 106 | .Children 107 | .toArray(children) 108 | .reduce( 109 | reduceChildren.bind(null, parentComponent, reactClass, parentIndx), 110 | {}); 111 | }; 112 | 113 | const reduceTypes = function (childMap, typeMap, key) { 114 | const components = (typeMap.get(childMap[key].type) || []).slice(0); 115 | components.push(childMap[key]); 116 | 117 | typeMap.set(childMap[key].type, components); 118 | 119 | return typeMap; 120 | }; 121 | 122 | const flavourInit = function (opts) { 123 | Object.assign(this, opts); 124 | 125 | const output = opts.shallowRenderer.getRenderOutput(); 126 | 127 | this.initialState = _.assign({}, opts.shallowRenderer._instance._instance.state); 128 | this.state = _.assign({}, this.initialState); 129 | this.component = flavourComponent(_.assign( 130 | {}, 131 | output, 132 | { 133 | reactClass: opts.reactClass, 134 | })); 135 | 136 | if (output) { 137 | this.type = output.type; 138 | this.childMap = convertAndReduce( 139 | this.component, 140 | opts.reactClass, 141 | output.props.children); 142 | } else { 143 | this.type = output; 144 | this.childMap = []; 145 | } 146 | 147 | this.typeMap = 148 | Object 149 | .keys(this.childMap) 150 | .reduce( 151 | reduceTypes.bind(null, this.childMap), 152 | new Map()); 153 | 154 | // console.log('childMap', this.childMap); 155 | // console.log('typeMap', this.typeMap); 156 | }; 157 | 158 | const resetState = function () { 159 | this.shallowRenderer._instance._instance.state = _.assign({}, this.initialState); 160 | }; 161 | 162 | const getState = function () { 163 | return this.shallowRenderer._instance._instance.state; 164 | }; 165 | 166 | const findChild = function (path) { 167 | return this.childMap[path]; 168 | }; 169 | 170 | const findComponents = function (component) { 171 | return this.typeMap.get(component) || []; 172 | }; 173 | 174 | const countComponents = function (component) { 175 | return this.findComponents(component).length; 176 | }; 177 | 178 | const flavourMethods = { 179 | getState, 180 | resetState, 181 | findChild, 182 | findComponents, 183 | countComponents, 184 | }; 185 | 186 | const flavour = stampit() 187 | .init(flavourInit) 188 | .methods(flavourMethods); 189 | 190 | // TESTER 191 | 192 | // INIT 193 | const testerInit = function (opts) { 194 | Object.assign(this, opts); 195 | 196 | this.ComponentToUse = null; 197 | 198 | this.config = { 199 | spyOnDefault: opts.spyOnDefault, 200 | spyOn: opts.spyOn, 201 | }; 202 | }; 203 | 204 | // METHODS 205 | const restoreSpy = function (method) { 206 | if (method.isSinonProxy) { 207 | method.restore(); 208 | } 209 | }; 210 | 211 | const use = function (Component) { 212 | const options = _.merge( 213 | {}, 214 | defaultConfig, 215 | this.config, 216 | { 217 | spyOn: doNotSpyOn, 218 | }); 219 | 220 | Object.getOwnPropertyNames(Component.prototype).forEach((method) => { 221 | if (options.spyOn[method] === false) return; 222 | 223 | if (options.spyOnDefault || options.spyOn[method]) { 224 | restoreSpy(Component.prototype[method]); 225 | sinon.spy(Component.prototype, method); 226 | } 227 | }); 228 | 229 | this.ComponentToUse = Component; 230 | 231 | return this; 232 | }; 233 | 234 | const createFlavour = function (name, reactClass, shallowRenderer) { 235 | return flavour({ 236 | name, 237 | reactClass, 238 | shallowRenderer, 239 | }); 240 | }; 241 | 242 | const getShallowRenderer = function (component, props) { 243 | const shallowRenderer = TestUtils.createRenderer(); 244 | const context = {}; 245 | 246 | const componentWithProps = React.createElement(component, props); 247 | 248 | if (componentWithProps.type.contextTypes && componentWithProps.type.contextTypes._radiumConfig) { 249 | context._radiumConfig = function () { 250 | }; 251 | context._radiumConfig.plugins = []; 252 | 253 | componentWithProps.type.contextTypes._radiumConfig = context._radiumConfig; 254 | } 255 | 256 | shallowRenderer 257 | .render(componentWithProps, context); 258 | 259 | return shallowRenderer; 260 | }; 261 | 262 | const addFlavour = function (name, props) { 263 | return createFlavour( 264 | name, 265 | this.ComponentToUse, 266 | getShallowRenderer(this.ComponentToUse, props)); 267 | }; 268 | 269 | const teardown = function () { 270 | const proto = this.ComponentToUse.prototype; 271 | 272 | Object.getOwnPropertyNames(proto).forEach((method) => { 273 | restoreSpy(proto[method]); 274 | }); 275 | 276 | return this; 277 | }; 278 | 279 | const testerMethods = { 280 | use, 281 | addFlavour, 282 | teardown, 283 | }; 284 | 285 | // STAMP 286 | const ReactTester = 287 | stampit() 288 | .init(testerInit) 289 | .methods(testerMethods); 290 | 291 | module.exports = ReactTester; 292 | -------------------------------------------------------------------------------- /tests/components/address-line/address-line-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | fontSize: '1.25rem', 4 | padding: '0.5rem 0 0.25rem 0', 5 | borderBottom: '1px solid black', 6 | boxSizing: 'border-box', 7 | textAlign: 'right', 8 | '@media (min-width: 1200px)': { 9 | fontSize: '2.5rem', 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /tests/components/address-line/address-line-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import AddressLine from './address-line'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(AddressLine); 6 | const NOTHING = tester.addFlavour('NOTHING', {}); 7 | const SOMETHING = tester.addFlavour('SOMETHING', { 8 | text: 'some line of the address', 9 | }); 10 | 11 | describe('line should', () => { 12 | it('render nothing if no line is given', () => { 13 | const value = NOTHING.findChild('0'); 14 | const actual = typeof value; 15 | const expected = 'undefined'; 16 | 17 | assert.deepEqual(actual, expected); 18 | }); 19 | 20 | it('render the given text', () => { 21 | const actual = SOMETHING.findChild('0').value; 22 | const expected = 'some line of the address'; 23 | 24 | assert.deepEqual(actual, expected); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/components/address-line/address-line.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './address-line-styles'; 4 | 5 | export default radium(({ text }) => ( 6 |
7 | {text} 8 |
9 | )); 10 | -------------------------------------------------------------------------------- /tests/components/address/address-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Address from './address'; 3 | import AddressLine from '../address-line/address-line'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | const tester = ReactTester.create().use(Address); 7 | const NO_ADDRESS = tester.addFlavour('NO_ADDRESS', { 8 | addressee: 'Mr Robert Smith', 9 | }); 10 | const THREE_LINES = tester.addFlavour('THREE_LINES', { 11 | addressee: 'Mr Joanne Robinson', 12 | address: ['line 31', 'line 32', 'line 33'], 13 | }); 14 | const FIVE_LINES = tester.addFlavour('FIVE_LINES', { 15 | addressee: 'Mrs Jack Jones', 16 | address: ['line 51', 'line 52', 'line 53', 'Line 54', 'Line 55'], 17 | }); 18 | 19 | describe('address should', () => { 20 | it('render the addressee as the first line if there is no address', () => { 21 | const addressee = NO_ADDRESS.findComponents(AddressLine)[0]; 22 | const actual = addressee.props.text; 23 | const expected = 'Mr Robert Smith'; 24 | 25 | assert.deepEqual(actual, expected); 26 | }); 27 | 28 | it('render the addressee as the first line if there is an address', () => { 29 | const addressee = THREE_LINES.findComponents(AddressLine)[0]; 30 | const actual = addressee.props.text; 31 | const expected = 'Mr Joanne Robinson'; 32 | 33 | assert.deepEqual(actual, expected); 34 | }); 35 | 36 | it('render four address lines if there are three lines in the given address', () => { 37 | const actual = THREE_LINES.countComponents(AddressLine); 38 | const expected = 4; 39 | 40 | assert.deepEqual(actual, expected); 41 | }); 42 | 43 | it('render six address lines if there are five lines in the given address', () => { 44 | const actual = FIVE_LINES.countComponents(AddressLine); 45 | const expected = 6; 46 | 47 | assert.deepEqual(actual, expected); 48 | }); 49 | 50 | it('render the expected first line of the address if three lines are given', () => { 51 | const actual = THREE_LINES.findComponents(AddressLine)[1].props.text; 52 | const expected = 'line 31'; 53 | 54 | assert.deepEqual(actual, expected); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/components/address/address.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import AddressLine from '../address-line/address-line'; 3 | 4 | const Address = ({ address = [], addressee = '' }) => ( 5 |
6 | 7 | { 8 | address.map((text, indx) => ( 9 | 10 | )) 11 | } 12 |
13 | ); 14 | 15 | Address.propTypes = { 16 | address: PropTypes.array, 17 | addressee: PropTypes.string, 18 | }; 19 | 20 | export default Address; 21 | -------------------------------------------------------------------------------- /tests/components/app/app-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | display: 'flex', 4 | flexFlow: 'row', 5 | }, 6 | gutter: { 7 | flex: 0, 8 | '@media (min-width: 400px)': { 9 | flex: 1, 10 | }, 11 | }, 12 | body: { 13 | flex: 3, 14 | position: 'relative', 15 | }, 16 | cardContainer: { 17 | position: 'absolute', 18 | width: '100%', 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tests/components/app/app-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import App from './app'; 3 | import Postcard from '../postcard/postcard'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | const tester = ReactTester.create().use(App); 7 | const NONE = tester.addFlavour('NONE', { 8 | comingFrom: 'Some random place', 9 | message: 'Some random message', 10 | signature: 'Joe Bloggs', 11 | addressee: 'Mrs Random Person', 12 | address: ['line 1', 'line 2'], 13 | }); 14 | const WITH_SIGNATURE = tester.addFlavour('WITH_SIGNATURE', { 15 | comingFrom: 'Some random place', 16 | message: 'Some random message', 17 | signature: 'Joe Bloggs', 18 | addressee: 'Mrs Random Person', 19 | address: ['line 1', 'line 2'], 20 | }); 21 | const NO_SIGNATURE = tester.addFlavour('NO_SIGNATURE', { 22 | comingFrom: 'Some random place', 23 | message: 'Some random message', 24 | addressee: 'Mrs Random Person', 25 | address: ['line 1', 'line 2'], 26 | }); 27 | 28 | describe('app should', () => { 29 | it('render as a div', () => { 30 | const actual = NONE.type; 31 | const expected = 'div'; 32 | 33 | assert.deepEqual(actual, expected); 34 | }); 35 | 36 | it('display as a flex row', () => { 37 | const actual = NONE.component.style; 38 | const expected = { 39 | display: 'flex', 40 | flexFlow: 'row', 41 | }; 42 | 43 | assert.deepEqual(actual, expected); 44 | }); 45 | 46 | it('have a right gutter', () => { 47 | const rightGutter = NONE.findChild('2'); 48 | const actual = rightGutter.style; 49 | const expected = { 50 | flex: 0, 51 | '@media (min-width: 400px)': { 52 | flex: 1, 53 | }, 54 | }; 55 | 56 | assert.deepEqual(actual, expected); 57 | }); 58 | 59 | it('render a Postcard component', () => { 60 | const actual = NONE.countComponents(Postcard); 61 | const expected = 1; 62 | 63 | assert.deepEqual(actual, expected); 64 | }); 65 | 66 | it('render the Postcard component in the expected position', () => { 67 | const actual = NONE.findChild('1.0.0').type; 68 | const expected = Postcard; 69 | 70 | assert.deepEqual(actual, expected); 71 | }); 72 | 73 | it('render the Postcard component with the given signature', () => { 74 | const actual = 75 | WITH_SIGNATURE 76 | .findComponents(Postcard)[0] 77 | .props 78 | .signature; 79 | const expected = 'Joe Bloggs'; 80 | 81 | assert.deepEqual(actual, expected); 82 | }); 83 | 84 | it('render the Postcard component with no signature if one is not given', () => { 85 | const signature = 86 | NO_SIGNATURE 87 | .findComponents(Postcard)[0] 88 | .props 89 | .signature; 90 | const actual = typeof signature; 91 | const expected = 'undefined'; 92 | 93 | assert.deepEqual(actual, expected); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/components/app/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './app-styles'; 4 | import Postcard from '../postcard/postcard'; 5 | 6 | export default radium(({ 7 | comingFrom, 8 | message, 9 | signature, 10 | addressee, 11 | address, 12 | }) => ( 13 |
14 |
15 |
16 |
17 | 24 |
25 |
26 |
27 |
28 | )); 29 | -------------------------------------------------------------------------------- /tests/components/message/message-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | marginTop: '1rem', 4 | fontSize: '1rem', 5 | '@media (min-width: 1200px)': { 6 | fontSize: '1.25rem', 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/components/message/message-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Message from './message'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(Message); 6 | const NOTHING = tester.addFlavour('NOTHING', {}); 7 | const SOMETHING = tester.addFlavour('SOMETHING', { 8 | text: 'some text here', 9 | }); 10 | 11 | describe('message should', () => { 12 | it('render nothing if no message is given', () => { 13 | const value = NOTHING.findChild('0'); 14 | const actual = typeof value; 15 | const expected = 'undefined'; 16 | 17 | assert.deepEqual(actual, expected); 18 | }); 19 | 20 | it('render the given text', () => { 21 | const actual = SOMETHING.findChild('0').value; 22 | const expected = 'some text here'; 23 | 24 | assert.deepEqual(actual, expected); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/components/message/message.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './message-styles'; 4 | 5 | export default radium(({ text }) => ( 6 |
7 | {text} 8 |
9 | )); 10 | -------------------------------------------------------------------------------- /tests/components/postcard/postcard-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | fontFamily: 'Brush Script MT, Helvetica, Arial', 4 | border: '1px solid black', 5 | display: 'flex', 6 | flexFlow: 'column', 7 | padding: '0.5rem', 8 | '@media (min-width: 800px)': { 9 | padding: '2rem', 10 | marginTop: '2.5rem', 11 | }, 12 | '@media (min-width: 1200px)': { 13 | flexFlow: 'row', 14 | }, 15 | }, 16 | left: { 17 | flex: 1, 18 | padding: '1rem', 19 | position: 'relative', 20 | order: 2, 21 | '@media (min-width: 1200px)': { 22 | padding: '0 1rem', 23 | borderRight: '1px solid black', 24 | order: 1, 25 | }, 26 | }, 27 | right: { 28 | flex: 1, 29 | padding: '0 1rem', 30 | display: 'flex', 31 | flexFlow: 'column', 32 | order: 1, 33 | '@media (min-width: 480px)': { 34 | flexFlow: 'row', 35 | }, 36 | '@media (min-width: 1200px)': { 37 | flexFlow: 'column', 38 | order: 2, 39 | }, 40 | }, 41 | head: { 42 | display: 'flex', 43 | flexDirection: 'row', 44 | justifyContent: 'flex-end', 45 | order: 1, 46 | flex: 1, 47 | '@media (min-width: 480px)': { 48 | order: 2, 49 | }, 50 | '@media (min-width: 1200px)': { 51 | order: 1, 52 | }, 53 | }, 54 | body: { 55 | order: 2, 56 | flex: 2, 57 | '@media (min-width: 480px)': { 58 | order: 1, 59 | }, 60 | '@media (min-width: 680px)': { 61 | flex: 4, 62 | }, 63 | '@media (min-width: 1200px)': { 64 | flex: 1, 65 | order: 2, 66 | }, 67 | }, 68 | signatureContainer: { 69 | textAlign: 'right', 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /tests/components/postcard/postcard-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Postcard from './postcard'; 3 | import Welcome from '../welcome/welcome'; 4 | import Message from '../message/message'; 5 | import Signature from '../signature/signature'; 6 | import Stamp from '../stamp/stamp-smart'; 7 | import Address from '../address/address'; 8 | import ReactTester from '../../../src/index'; 9 | 10 | const tester = ReactTester.create().use(Postcard); 11 | const STANDARD = tester.addFlavour('STANDARD', { 12 | comingFrom: 'some place', 13 | message: 'some message', 14 | signature: 'some signature', 15 | address: ['line 1', 'postcode'], 16 | addressee: 'some name', 17 | }); 18 | 19 | describe('postcard should', () => { 20 | it('render a welcome component', () => { 21 | const actual = STANDARD.countComponents(Welcome); 22 | const expected = 1; 23 | 24 | assert.deepEqual(actual, expected); 25 | }); 26 | 27 | it('give comingFrom to the welcome component', () => { 28 | const actual = 29 | STANDARD 30 | .findComponents(Welcome)[0] 31 | .props 32 | .text; 33 | const expected = 'some place'; 34 | 35 | assert.deepEqual(actual, expected); 36 | }); 37 | 38 | it('render a message component', () => { 39 | const actual = STANDARD.countComponents(Message); 40 | const expected = 1; 41 | 42 | assert.deepEqual(actual, expected); 43 | }); 44 | 45 | it('give message to the message component', () => { 46 | const actual = 47 | STANDARD 48 | .findComponents(Message)[0] 49 | .props 50 | .text; 51 | const expected = 'some message'; 52 | 53 | assert.deepEqual(actual, expected); 54 | }); 55 | 56 | it('render a signature component', () => { 57 | const actual = STANDARD.countComponents(Signature); 58 | const expected = 1; 59 | 60 | assert.deepEqual(actual, expected); 61 | }); 62 | 63 | it('give signature to the signature component', () => { 64 | const actual = 65 | STANDARD 66 | .findComponents(Signature)[0] 67 | .props 68 | .text; 69 | const expected = 'some signature'; 70 | 71 | assert.deepEqual(actual, expected); 72 | }); 73 | 74 | it('render a stamp component', () => { 75 | const actual = STANDARD.countComponents(Stamp); 76 | const expected = 1; 77 | 78 | assert.deepEqual(actual, expected); 79 | }); 80 | 81 | it('render an address component', () => { 82 | const actual = STANDARD.countComponents(Address); 83 | const expected = 1; 84 | 85 | assert.deepEqual(actual, expected); 86 | }); 87 | 88 | it('give addressee to the address component', () => { 89 | const actual = 90 | STANDARD 91 | .findComponents(Address)[0] 92 | .props 93 | .addressee; 94 | const expected = 'some name'; 95 | 96 | assert.deepEqual(actual, expected); 97 | }); 98 | 99 | it('give address to the address component', () => { 100 | const actual = 101 | STANDARD 102 | .findComponents(Address)[0] 103 | .props 104 | .address; 105 | const expected = ['line 1', 'postcode']; 106 | 107 | assert.deepEqual(actual, expected); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /tests/components/postcard/postcard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './postcard-styles'; 4 | import Welcome from '../welcome/welcome'; 5 | import Message from '../message/message'; 6 | import Signature from '../signature/signature'; 7 | import Stamp from '../stamp/stamp-smart'; 8 | import Address from '../address/address'; 9 | 10 | export default radium(({ comingFrom, message, signature, address, addressee }) => ( 11 |
12 |
13 | 16 | 19 | 20 |
21 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
35 |
36 |
37 |
38 | )); 39 | -------------------------------------------------------------------------------- /tests/components/signature/signature-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | fontSize: '1.5rem', 4 | transform: 'rotate(-30deg)', 5 | transformOrigin: 'right', 6 | margin: '0 2rem 2rem 0', 7 | '@media (min-width: 1200px)': { 8 | fontSize: '2rem', 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /tests/components/signature/signature-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Signature from './signature'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(Signature); 6 | const NOTHING = tester.addFlavour('NOTHING', {}); 7 | const SOMETHING = tester.addFlavour('SOMETHING', { 8 | text: 'Jack Johnson', 9 | }); 10 | 11 | describe('signature should', () => { 12 | it('render nothing if no signature is given', () => { 13 | const value = NOTHING.findChild('0'); 14 | const actual = typeof value; 15 | const expected = 'undefined'; 16 | 17 | assert.deepEqual(actual, expected); 18 | }); 19 | 20 | it('render the given text', () => { 21 | const actual = SOMETHING.findChild('0').value; 22 | const expected = 'Jack Johnson'; 23 | 24 | assert.deepEqual(actual, expected); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/components/signature/signature.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './signature-styles'; 4 | 5 | export default radium(({ text }) => ( 6 |
7 | {text} 8 |
9 | )); 10 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-dumb-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Stamp from './stamp-dumb'; 3 | import types from './stamp-types'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | const tester = ReactTester.create().use(Stamp); 7 | const NONE = tester.addFlavour('NONE', { 8 | types, 9 | type: types.NONE, 10 | }); 11 | 12 | describe('dumb stamp should', () => { 13 | it('render the place stamp here text if the type is none', () => { 14 | const actual = NONE.findChild('0').value; 15 | const expected = 'Place stamp here'; 16 | 17 | assert.deepEqual(actual, expected); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-dumb.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | import React, { PropTypes } from 'react'; 3 | import styles from './stamp-styles'; 4 | 5 | const StampDumb = ({ types, type, onClick }) => ( 6 |
7 | { 8 | (() => { 9 | switch (type) { 10 | case types.NONE: 11 | return ( 12 |
Place stamp here
13 | ); 14 | case types.FIRST_CLASS: 15 | return ( 16 |
1st
17 | ); 18 | case types.SECOND_CLASS: 19 | return ( 20 |
2nd
21 | ); 22 | default: 23 | return ( 24 |
Unknown stamp
25 | ); 26 | } 27 | })() 28 | } 29 |
30 | ); 31 | 32 | StampDumb.propTypes = { 33 | type: PropTypes.number, 34 | types: PropTypes.object, 35 | onClick: PropTypes.func, 36 | }; 37 | 38 | export default StampDumb; 39 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-smart-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Stamp from './stamp-smart'; 3 | import stampTypes from './stamp-types'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | const tester = ReactTester.create().use(Stamp); 7 | const NONE = tester.addFlavour('NONE', {}); 8 | const SECOND_CLASS = tester.addFlavour('SECOND_CLASS', { 9 | state: { 10 | type: stampTypes.SECOND_CLASS, 11 | }, 12 | }); 13 | 14 | describe('smart stamp should', () => { 15 | afterEach(() => { 16 | NONE.resetState(); 17 | }); 18 | 19 | it('render a type of none by default', () => { 20 | const actual = 21 | NONE 22 | .component 23 | .props 24 | .type; 25 | 26 | const expected = stampTypes.NONE; 27 | 28 | assert.deepEqual(actual, expected); 29 | }); 30 | 31 | it('pass the stamp type from state to the stamp', () => { 32 | const actual = 33 | SECOND_CLASS 34 | .component 35 | .props 36 | .type; 37 | const expected = stampTypes.SECOND_CLASS; 38 | 39 | assert.deepEqual(actual, expected); 40 | }); 41 | 42 | it('pass the expected onClick function to the dumb component', () => { 43 | const isMapped = 44 | NONE 45 | .component 46 | .propFunc('onClick') 47 | .mapsTo('handleOnClick'); 48 | 49 | assert(isMapped); 50 | }); 51 | 52 | it('set the stamp to first class on the first click', () => { 53 | NONE.component.props.onClick(); 54 | 55 | const actual = NONE.getState().type; 56 | const expected = stampTypes.FIRST_CLASS; 57 | 58 | assert.deepEqual(actual, expected); 59 | }); 60 | 61 | it('set the stamp to second class on the second click', () => { 62 | NONE.component.props.onClick(); 63 | NONE.component.props.onClick(); 64 | 65 | const actual = NONE.getState().type; 66 | const expected = stampTypes.SECOND_CLASS; 67 | 68 | assert.deepEqual(actual, expected); 69 | }); 70 | 71 | it('set the stamp to none on the third click', () => { 72 | NONE.component.props.onClick(); 73 | NONE.component.props.onClick(); 74 | NONE.component.props.onClick(); 75 | 76 | const actual = NONE.getState().type; 77 | const expected = stampTypes.NONE; 78 | 79 | assert.deepEqual(actual, expected); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-smart.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable indent */ 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | import Stamp from './stamp-dumb'; 5 | import stampTypes from './stamp-types'; 6 | 7 | export default class StampSmartComponent extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = props.state || { 12 | type: stampTypes.NONE, 13 | }; 14 | 15 | this.handleOnClick = this.handleOnClick.bind(this); 16 | } 17 | 18 | getNextStampType(types, currentType) { 19 | let nextType; 20 | 21 | switch (currentType) { 22 | case types.NONE: 23 | nextType = types.FIRST_CLASS; 24 | break; 25 | case types.FIRST_CLASS: 26 | nextType = types.SECOND_CLASS; 27 | break; 28 | case types.SECOND_CLASS: 29 | nextType = types.NONE; 30 | break; 31 | default: 32 | nextType = types.NONE; 33 | } 34 | 35 | return nextType; 36 | } 37 | 38 | handleOnClick() { 39 | this.setState({ 40 | type: this.getNextStampType(stampTypes, this.state.type), 41 | }); 42 | } 43 | 44 | render() { 45 | return ( 46 | 51 | ); 52 | } 53 | } 54 | 55 | StampSmartComponent.propTypes = { 56 | state: PropTypes.shape({ 57 | type: PropTypes.number, 58 | }), 59 | }; 60 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | fontFamily: 'Tahoma, Helvetica, Arial', 4 | width: '4rem', 5 | height: '5rem', 6 | border: '1px solid black', 7 | padding: '0.75rem', 8 | boxSizing: 'border-box', 9 | cursor: 'pointer', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /tests/components/stamp/stamp-types.js: -------------------------------------------------------------------------------- 1 | export default { 2 | NONE: 1, 3 | FIRST_CLASS: 2, 4 | SECOND_CLASS: 3, 5 | }; 6 | -------------------------------------------------------------------------------- /tests/components/welcome/welcome-styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comp: { 3 | fontSize: '1.25rem', 4 | fontWeight: 'bold', 5 | '@media (min-width: 1200px)': { 6 | fontSize: '1.5rem', 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/components/welcome/welcome-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import Welcome from './welcome'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(Welcome); 6 | const NOTHING = tester.addFlavour('NOTHING', {}); 7 | const SOMETHING = tester.addFlavour('SOMETHING', { 8 | text: 'Welcome from this place', 9 | }); 10 | 11 | describe('welcome should', () => { 12 | it('render nothing if no welcome is given', () => { 13 | const value = NOTHING.findChild('0'); 14 | const actual = typeof value; 15 | const expected = 'undefined'; 16 | 17 | assert.deepEqual(actual, expected); 18 | }); 19 | 20 | it('render the given text', () => { 21 | const actual = SOMETHING.findChild('0').value; 22 | const expected = 'Welcome from this place'; 23 | 24 | assert.deepEqual(actual, expected); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/components/welcome/welcome.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import radium from 'radium'; 3 | import styles from './welcome-styles'; 4 | 5 | export default radium(({ text }) => ( 6 |
7 | {text} 8 |
9 | )); 10 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { StyleRoot } from 'radium'; 4 | import App from './components/app/app'; 5 | 6 | const comingFrom = 'Welcome from some sort of React holiday'; 7 | const message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + 8 | 'Donec commodo venenatis tortor, ' + 9 | 'in viverra libero ullamcorper quis. Nam blandit volutpat ante sit amet sagittis. ' + 10 | 'Cras ultrices augue sem, ' + 11 | 'id congue felis efficitur a. Mauris luctus, nisl eu dignissim molestie, ' + 12 | 'sem libero fringilla ex, ' + 13 | 'id faucibus ligula justo et nibh. Donec posuere rhoncus leo eget fringilla. ' + 14 | 'Nulla ex arcu, ' + 15 | 'gravida nec malesuada sit amet, faucibus ut lectus. ' + 16 | 'Donec elementum sapien a lorem tristique egestas. ' + 17 | 'Etiam a orci tincidunt, interdum leo blandit, consequat risus.'; 18 | const signature = 'John Smith'; 19 | const addressee = 'Mrs. Joanne Bloggs'; 20 | const address = [ 21 | '25 random road', 22 | 'Random town', 23 | 'Random county', 24 | 'ID8 9JD', 25 | ]; 26 | 27 | render( 28 | 29 | 36 | 37 | , document.getElementById('root')); 38 | -------------------------------------------------------------------------------- /tests/stand-alone/bad-components/multi-root/item-list-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import ItemListComponent from './item-list'; 3 | import ReactTester from '../../../../src/index'; 4 | 5 | describe('the ItemListComponent should', () => { 6 | it('result in the expected error thrown', () => { 7 | const errorFunc = () => { 8 | const tester = ReactTester.create().use(ItemListComponent); 9 | tester.addFlavour('THROW_ERROR', { 10 | items: [ 11 | { 12 | text: 'one', 13 | }, 14 | { 15 | text: 'two', 16 | }, 17 | { 18 | text: 'two', 19 | }, 20 | ], 21 | }); 22 | }; 23 | 24 | assert.throws(errorFunc, /a valid React element \(or null\) must be returned/i); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/stand-alone/bad-components/multi-root/item-list.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Item from './item'; 3 | 4 | const ItemListComponent = 5 | ({ items }) => items.map(({ text }, indx) => ); 6 | 7 | ItemListComponent.propTypes = { 8 | items: PropTypes.array, 9 | }; 10 | 11 | export default ItemListComponent; 12 | -------------------------------------------------------------------------------- /tests/stand-alone/bad-components/multi-root/item.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Item = ({ text }) => {text}; 4 | 5 | Item.propTypes = { 6 | text: PropTypes.string, 7 | }; 8 | 9 | export default Item; 10 | -------------------------------------------------------------------------------- /tests/stand-alone/bad-components/some-error/some-error-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import SomeErrorComponent from './some-error'; 3 | import ReactTester from '../../../../src/index'; 4 | 5 | describe('the SomeErrorComponent should', () => { 6 | it('result in the expected error thrown', () => { 7 | const errorFunc = () => { 8 | const tester = ReactTester.create().use(SomeErrorComponent); 9 | tester.addFlavour('THROW_ERROR', {}); 10 | }; 11 | 12 | assert.throws(errorFunc, /something went wrong/); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/stand-alone/bad-components/some-error/some-error.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | throw new Error('something went wrong'); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/stand-alone/link-list/link-list-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import LinkListComponent from './link-list'; 3 | import Link from './link'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | describe('the LinkListComponent should', () => { 7 | const tester = ReactTester.create().use(LinkListComponent); 8 | const THREE_LINKS = tester.addFlavour('THREE_LINKS', { 9 | links: [ 10 | { 11 | text: 'one', 12 | }, 13 | { 14 | text: 'two', 15 | }, 16 | { 17 | text: 'two', 18 | }, 19 | ], 20 | }); 21 | 22 | it('render three links if given an array of three items', () => { 23 | const actual = THREE_LINKS.countComponents(Link); 24 | const expected = 3; 25 | 26 | assert.deepEqual(actual, expected); 27 | }); 28 | 29 | it('contain no div elements', () => { 30 | const actual = THREE_LINKS.findComponents('div'); 31 | const expected = []; 32 | 33 | assert.deepEqual(actual, expected); 34 | }); 35 | 36 | it('hook up the expected click handler to the first link', () => { 37 | const firstLink = THREE_LINKS.findComponents('li')[0]; 38 | 39 | const isMapped = firstLink 40 | .propFunc('onClick') 41 | .mapsTo('handleLink1Click'); 42 | 43 | assert(isMapped); 44 | }); 45 | 46 | it('hook up the expected click handler to the second link', () => { 47 | const secondLink = THREE_LINKS.findComponents('li')[1]; 48 | 49 | const isMapped = secondLink 50 | .propFunc('onClick') 51 | .mapsTo('handleLink2Click'); 52 | 53 | assert(isMapped); 54 | }); 55 | 56 | it('hook up the expected click handler to the third link', () => { 57 | const thirdLink = THREE_LINKS.findComponents('li')[2]; 58 | 59 | const isMapped = thirdLink 60 | .propFunc('onClick') 61 | .mapsTo('handleLink3Click'); 62 | 63 | assert(isMapped); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/stand-alone/link-list/link-list.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import Link from './link'; 3 | 4 | export default class LinkListComponent extends Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.handleLink1Click = this.handleLink1Click.bind(this); 9 | this.handleLink2Click = this.handleLink2Click.bind(this); 10 | this.handleLink3Click = this.handleLink3Click.bind(this); 11 | } 12 | 13 | handleLink1Click() { 14 | // console.log('one clicked'); 15 | } 16 | 17 | handleLink2Click() { 18 | // console.log('two clicked'); 19 | } 20 | 21 | handleLink3Click() { 22 | // console.log('three clicked'); 23 | } 24 | 25 | render() { 26 | const { links } = this.props; 27 | return ( 28 | 39 | ); 40 | } 41 | } 42 | 43 | LinkListComponent.propTypes = { 44 | links: PropTypes.array, 45 | }; 46 | -------------------------------------------------------------------------------- /tests/stand-alone/link-list/link.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Link = ({ text }) => {text}; 4 | 5 | Link.propTypes = { 6 | text: PropTypes.string, 7 | }; 8 | 9 | export default Link; 10 | -------------------------------------------------------------------------------- /tests/stand-alone/map-to/on-change-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import OnChangeComponent from './on-change'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(OnChangeComponent); 6 | const MAP_TEST = tester.addFlavour('MAP_TEST', {}); 7 | 8 | describe('the OnChangeComponent should', () => { 9 | it('map the onChange method to the inputs onChange provided test arguments', () => { 10 | const actual = 11 | MAP_TEST 12 | .component 13 | .propFunc('onChange') 14 | .withArgs({ 15 | target: { 16 | name: 'test name', 17 | value: 'test value', 18 | }, 19 | }) 20 | .mapsTo('onChange'); 21 | const expected = true; 22 | 23 | assert.deepEqual(actual, expected); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/stand-alone/map-to/on-change.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class OnChangeComponent extends Component { 4 | onChange(evt) { 5 | const { name, value } = evt.target; 6 | console.log(name, value); // eslint-disable-line no-console 7 | } 8 | 9 | render() { 10 | return ( 11 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/stand-alone/null-component/null-component-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import NullComponent from './null-component'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | const tester = ReactTester.create().use(NullComponent); 6 | const antipattern = tester.addFlavour('NULL', {}); 7 | 8 | describe('null component should', () => { 9 | it('return a test component of type null', () => { 10 | assert.deepEqual(antipattern.type, null); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/stand-alone/null-component/null-component.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | export default class NullComponent extends Component { 4 | render() { 5 | return null; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/stand-alone/spies-config/spies-config-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import SpiesConfig from './spies-config'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | describe('spies config', () => { 6 | describe('should', () => { 7 | let tester; 8 | 9 | const shouldNeverBeSpied = (method) => { 10 | const componentMethod = tester.ComponentToUse.prototype[method]; 11 | 12 | if (!componentMethod) return; 13 | 14 | const actual = componentMethod.isSinonProxy; 15 | const expected = undefined; 16 | 17 | assert.deepEqual(actual, expected, `${method} should never be spied`); 18 | }; 19 | 20 | afterEach(() => { 21 | shouldNeverBeSpied('constructor'); 22 | shouldNeverBeSpied('render'); 23 | shouldNeverBeSpied('componentWillUnmount'); 24 | 25 | tester.teardown(); 26 | }); 27 | 28 | it('add spies automatically by default', () => { 29 | tester = ReactTester.create().use(SpiesConfig); 30 | 31 | tester.addFlavour('CHOCOLATE', {}); 32 | 33 | const actual = tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 34 | const expected = true; 35 | 36 | assert.deepEqual(actual, expected); 37 | }); 38 | 39 | it('allow explicitly disabling specific spies', () => { 40 | tester = ReactTester.create({ 41 | spyOn: { 42 | spiedOn: false, 43 | }, 44 | }).use(SpiesConfig); 45 | 46 | tester.addFlavour('CHOCOLATE', {}); 47 | 48 | const actual = !!tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 49 | const expected = false; 50 | 51 | assert.deepEqual(actual, expected); 52 | }); 53 | 54 | it('allow disabling all spies', () => { 55 | tester = ReactTester.create({ 56 | spyOnDefault: false, 57 | }).use(SpiesConfig); 58 | 59 | tester.addFlavour('CHOCOLATE', {}); 60 | 61 | const actual = !!tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 62 | const expected = false; 63 | 64 | assert.deepEqual(actual, expected); 65 | }); 66 | 67 | it('allow explicitly enabling specific spies', () => { 68 | tester = ReactTester.create({ 69 | spyOnDefault: false, 70 | spyOn: { 71 | spiedOn: true, 72 | }, 73 | }).use(SpiesConfig); 74 | 75 | tester.addFlavour('CHOCOLATE', {}); 76 | 77 | const actual = !!tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 78 | const expected = true; 79 | 80 | assert.deepEqual(actual, expected); 81 | }); 82 | 83 | it('does not allow overriding render', () => { 84 | tester = ReactTester.create({ 85 | spyOnDefault: false, 86 | spyOn: { 87 | render: true, 88 | }, 89 | }).use(SpiesConfig); 90 | 91 | tester.addFlavour('CHOCOLATE', {}); 92 | 93 | const actual = !!tester.ComponentToUse.prototype.render.isSinonProxy; 94 | const expected = false; 95 | 96 | assert.deepEqual(actual, expected); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /tests/stand-alone/spies-config/spies-config.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class SpiesConfig extends Component { 4 | 5 | spiedOn() { 6 | return 'foo'; 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 | {this.spiedOn()} 13 |
14 | ); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/stand-alone/spies-teardown/spies-teardown-tests.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import SpiesTeardown from './spies-teardown'; 3 | import ReactTester from '../../../src/index'; 4 | 5 | describe('spies teardown', () => { 6 | let tester; 7 | 8 | beforeEach(() => { 9 | tester = ReactTester 10 | .create() 11 | .use(SpiesTeardown); 12 | }); 13 | 14 | describe('refresh spies each time should', () => { 15 | beforeEach(() => tester.addFlavour('LEMON', {})); 16 | 17 | it('allow normal spy behaviour on the first test pass', () => { 18 | const actual = tester.ComponentToUse.prototype.spiedOn.callCount; 19 | const expected = 1; 20 | 21 | assert.deepEqual(actual, expected); 22 | }); 23 | 24 | it('allow normal spy behaviour on the second test pass', () => { 25 | const actual = tester.ComponentToUse.prototype.spiedOn.callCount; 26 | const expected = 1; 27 | 28 | assert.deepEqual(actual, expected); 29 | }); 30 | }); 31 | 32 | describe('should', () => { 33 | beforeEach(() => tester.addFlavour('CHOCOLATE', {})); 34 | 35 | it('add spies automatically', () => { 36 | const actual = tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 37 | const expected = true; 38 | assert.deepEqual(actual, expected); 39 | }); 40 | 41 | it('allow them to be unwrapped', () => { 42 | tester.teardown(); 43 | const actual = typeof tester.ComponentToUse.prototype.spiedOn.isSinonProxy; 44 | const expected = 'undefined'; 45 | 46 | assert.deepEqual(actual, expected); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/stand-alone/spies-teardown/spies-teardown.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class SpiesTeardown extends Component { 4 | 5 | spiedOn() { 6 | return 'foo'; 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 | {this.spiedOn()} 13 |
14 | ); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /tests/stand-alone/unordered-list/unordered-list-tests.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import assert from 'assert'; 3 | import UnorderedList from './unordered-list'; 4 | import ReactTester from '../../../src/index'; 5 | 6 | const tester = ReactTester.create().use(UnorderedList); 7 | const i18n = { 8 | help: 'test help', 9 | something: 'test something', 10 | guest: 'test guest', 11 | }; 12 | const NO_LINKS = tester.addFlavour('NO_LINKS', { 13 | i18n, 14 | links: [], 15 | }); 16 | const WITH_LINKS = tester.addFlavour('WITH_LINKS', { 17 | i18n, 18 | links: [ 19 | { 20 | title: 'title-1', 21 | text: 'text-1', 22 | }, 23 | { 24 | title: 'title-2', 25 | text: 'text-2', 26 | }, 27 | ], 28 | }); 29 | const WITH_REACT_LINKS = tester.addFlavour('WITH_REACT_LINKS', { 30 | i18n, 31 | rlinks: [ 32 | test-content-1, 33 | test-content-2, 34 | ], 35 | }); 36 | const WITH_DYNAMIC_REACT_LINKS = tester.addFlavour('WITH_DYNAMIC_REACT_LINKS', { 37 | i18n, 38 | drlinkssrc: [ 39 | test-content-1, 40 | test-content-2, 41 | ], 42 | randomString: 'test test test', 43 | email: 'some@thing', 44 | }); 45 | 46 | describe('unordered list should', () => { 47 | it('render the first anchor with the expected title with no dynamic links', () => { 48 | const actual = NO_LINKS.findChild('0.0').props.title; 49 | const expected = 'test help'; 50 | 51 | assert.deepEqual(actual, expected); 52 | }); 53 | 54 | it('render the first anchor with the expected value with no dynamic links', () => { 55 | const actual = NO_LINKS.findChild('0.0').value; 56 | const expected = 'test help'; 57 | 58 | assert.deepEqual(actual, expected); 59 | }); 60 | 61 | it('render the dynamic links', () => { 62 | const actual = WITH_LINKS.countComponents('li'); 63 | const expected = 4; 64 | 65 | assert.deepEqual(actual, expected); 66 | }); 67 | 68 | it('render the first dynamic link with the expected text', () => { 69 | const actual = WITH_LINKS.findChild('2').value; 70 | const expected = 'text-1'; 71 | 72 | assert.deepEqual(actual, expected); 73 | }); 74 | 75 | it('render the second dynamic link with the expected text', () => { 76 | const actual = WITH_LINKS.findChild('3').value; 77 | const expected = 'text-2'; 78 | 79 | assert.deepEqual(actual, expected); 80 | }); 81 | 82 | it('render the first react link with the expected title', () => { 83 | const actual = WITH_REACT_LINKS.findChild('2.0').props.title; 84 | const expected = 'test-title-1'; 85 | 86 | assert.deepEqual(actual, expected); 87 | }); 88 | 89 | it('render the first dynamic react link as expected', () => { 90 | const actual = WITH_DYNAMIC_REACT_LINKS.findChild('2.0.1.1.0').value; 91 | const expected = 'test-content-1'; 92 | 93 | assert.deepEqual(actual, expected); 94 | }); 95 | 96 | it('map the expected click event to the component', () => { 97 | const isMapped = 98 | NO_LINKS 99 | .component 100 | .propFunc('onClick') 101 | .mapsTo('handleOnClick'); 102 | 103 | assert(isMapped); 104 | }); 105 | 106 | it('map the expected key press event to the help link', () => { 107 | const helpLink = NO_LINKS.findComponents('a')[0]; 108 | 109 | const isMapped = 110 | helpLink 111 | .propFunc('onKeyPress') 112 | .mapsTo('handleKeyPress'); 113 | 114 | assert(isMapped); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /tests/stand-alone/unordered-list/unordered-list.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class UnorderedListComponent extends Component { 4 | getDrlinks(i18n, email, links, randomString) { 5 | const someComponent = ( 6 |
10 | 11 | {i18n.guest} test 12 | 13 |
14 |
  • 15 | {email} 16 |
  • 17 | {links.map((link, indx) => ( 18 |
  • {link}
  • 19 | ))} 20 |
    21 |
    22 | ); 23 | 24 | return [ 25 | someComponent, 26 | randomString, 27 | ]; 28 | } 29 | 30 | handleOnClick() { 31 | // do something 32 | } 33 | 34 | handleKeyPress() { 35 | // do something 36 | } 37 | 38 | render() { 39 | const { 40 | i18n = {}, 41 | links = [], 42 | rlinks = [], 43 | drlinkssrc = [], 44 | randomString = '', 45 | email = '', 46 | } = this.props; 47 | const drlinks = email ? this.getDrlinks(i18n, email, drlinkssrc, randomString) : []; 48 | 49 | return ( 50 | 77 | ); 78 | } 79 | } 80 | 81 | UnorderedListComponent.propTypes = { 82 | i18n: PropTypes.object, 83 | links: PropTypes.array, 84 | rlinks: PropTypes.array, 85 | drlinkssrc: PropTypes.array, 86 | randomString: PropTypes.string, 87 | email: PropTypes.string, 88 | }; 89 | -------------------------------------------------------------------------------- /tests/webpack.config.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | entry: [ 7 | './tests/index', 8 | ], 9 | output: { 10 | path: path.join(__dirname), 11 | filename: 'index.min.js', 12 | }, 13 | plugins: [ 14 | new webpack.optimize.OccurenceOrderPlugin(), 15 | new webpack.DefinePlugin({ 16 | 'process.env': { 17 | NODE_ENV: JSON.stringify('development'), 18 | }, 19 | }), 20 | ], 21 | module: { 22 | loaders: [{ 23 | test: /\.js$/, 24 | loaders: ['babel'], 25 | include: path.join(__dirname), 26 | }], 27 | }, 28 | }; 29 | --------------------------------------------------------------------------------