├── .travis.yml ├── .npmignore ├── example ├── UnnamedComponent.jsx ├── test │ ├── UnnamedComponent.test.js │ └── CheckboxWithLabel.test.js └── CheckboxWithLabel.jsx ├── .gitignore ├── test ├── simulateNativeEvent.test.js ├── unmountComponent.test.js ├── renderComponent.test.js ├── simulateEvent.test.js ├── stubComponent.test.js ├── elementQuerySelector.test.js ├── assertRenderMatch.test.js ├── withContext.test.js └── assertRender.test.js ├── LICENSE ├── package.json ├── index.js ├── README.md └── lib ├── assert.js └── helper.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /artifacts 2 | /example 3 | /node_modules 4 | /test 5 | -------------------------------------------------------------------------------- /example/UnnamedComponent.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var createReactClass = require('create-react-class'); 3 | 4 | var UnnamedComponent = createReactClass({ 5 | render: function() { 6 | return
UNNAMED_COMPONENT
; 7 | } 8 | }); 9 | 10 | module.exports = UnnamedComponent; 11 | -------------------------------------------------------------------------------- /example/test/UnnamedComponent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../../index').jsxTranspile(process.env.COVERAGE); 3 | var assert = require('assert'); 4 | 5 | describe('UnnamedComponent', function() { 6 | var UnnamedComponent = require('../UnnamedComponent.jsx'); 7 | 8 | describe('#render', function () { 9 | it('renders with
UNNAMED_COMPONENT
', function () { 10 | jsx.assertRender(UnnamedComponent, {}, '
UNNAMED_COMPONENT
'); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | artifacts 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | node_modules 25 | 26 | # Users Environment Variables 27 | .lock-wscript 28 | -------------------------------------------------------------------------------- /test/simulateNativeEvent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var assert = require('assert'); 5 | 6 | // This has been deprecated for quite some time 7 | // https://github.com/facebook/react/pull/13407 8 | describe.skip('#simulateNativeEvent', function () { 9 | var Component = jsx.stubComponent('button'); 10 | 11 | it('simulates an event', function () { 12 | var clickCount = 0; 13 | 14 | function clickHandler() { 15 | clickCount++; 16 | }; 17 | 18 | var comp = jsx.renderComponent(Component, { 19 | onClick: clickHandler 20 | }); 21 | 22 | jsx.simulateNativeEvent(comp, 'click'); 23 | assert.equal(clickCount, 1); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/unmountComponent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | var assert = require('assert'); 6 | 7 | var Component = createReactClass({ 8 | displayName: 'TestComp', 9 | componentWillUnmount: function () { 10 | this.isUnmountCalled = true; 11 | }, 12 | render: function () { 13 | return React.createElement('div'); 14 | } 15 | }); 16 | 17 | describe('#unmountComponent', function() { 18 | it('unmountComponent the component', function () { 19 | var comp = jsx.renderComponent(Component); 20 | 21 | assert.equal(comp.isMounted(), true); 22 | jsx.unmountComponent(comp); 23 | assert.equal(comp.isUnmountCalled, true); 24 | assert.notEqual(comp.isMounted(), true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/renderComponent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | var PropTypes = require('prop-types'); 6 | var ReactDOM = require('react-dom'); 7 | var assert = require('assert'); 8 | 9 | var Component = createReactClass({ 10 | displayName: 'TestComp', 11 | propTypes: { 12 | foo: PropTypes.string 13 | }, 14 | render: function () { 15 | var props = { 16 | 'data-foo': this.props.foo 17 | }; 18 | return React.createElement('div', props, this.props.children); 19 | } 20 | }); 21 | 22 | describe('#renderComponent', function() { 23 | it('renders props & children on the component', function () { 24 | var comp = jsx.renderComponent(Component, { foo: 'bar' }, 'child'); 25 | 26 | // Verify that it's Off by default 27 | assert.equal(ReactDOM.findDOMNode(comp).textContent, 'child'); 28 | assert.equal(ReactDOM.findDOMNode(comp).getAttribute('data-foo'), 'bar'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/simulateEvent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var assert = require('assert'); 5 | 6 | describe('#simulateEvent', function () { 7 | var Component = jsx.stubComponent('button'); 8 | 9 | it('simulates an event', function () { 10 | var clickCount = 0; 11 | 12 | function clickHandler() { 13 | clickCount++; 14 | }; 15 | 16 | var comp = jsx.renderComponent(Component, { 17 | onClick: clickHandler 18 | }); 19 | 20 | jsx.simulateEvent(comp, 'click'); 21 | assert.equal(clickCount, 1); 22 | 23 | jsx.simulateEvent(comp, 'click'); 24 | assert.equal(clickCount, 2); 25 | }); 26 | 27 | it('simulates an event with custom options', function () { 28 | var expected; 29 | 30 | function clickHandler(e) { 31 | expected = e; 32 | }; 33 | 34 | var comp = jsx.renderComponent(Component, { 35 | onClick: clickHandler 36 | }); 37 | 38 | jsx.simulateEvent(comp, 'click', {x: 'men'}); 39 | assert.equal(expected.x, 'men'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yahoo! 4 | Author: Marcelo Eden 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /test/stubComponent.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | 4 | describe('#stubComponent', function() { 5 | it('renders props on the component', function () { 6 | var ComponentStub = jsx.stubComponent('div'); 7 | 8 | jsx.assertRender(ComponentStub, { 9 | name: 'Marcelo Eden', 10 | className: 'big' 11 | }, '
'); 12 | }); 13 | 14 | it('renders children content the component', function () { 15 | var ComponentStub = jsx.stubComponent('p', 'hello world!'); 16 | jsx.assertRender(ComponentStub, {}, '

hello world!

'); 17 | }); 18 | 19 | it('preserves children content the component', function () { 20 | var ComponentStub = jsx.stubComponent('p'); 21 | jsx.assertRender(ComponentStub, { 22 | children: 'blah' 23 | }, '

blah

'); 24 | }); 25 | 26 | it('adds all props to data props', function () { 27 | var ComponentStub = jsx.stubComponent('a', null, true); 28 | 29 | jsx.assertRender(ComponentStub, { 30 | name: 'Jake The Dog', 31 | what: 'is it?' 32 | }, ''); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/elementQuerySelector.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | var assert = require('assert'); 6 | 7 | var Component = createReactClass({ 8 | displayName: 'TestComp', 9 | render: function () { 10 | var props = { 11 | className: 'test-class' 12 | }; 13 | var children = [ 14 | React.createElement('li', props), 15 | React.createElement('li', props), 16 | React.createElement('li', props), 17 | ]; 18 | return React.createElement('ul', {}, children); 19 | } 20 | }); 21 | 22 | describe('#elementQuerySelector', function () { 23 | it('finds the element on the component', function () { 24 | var comp = jsx.renderComponent(Component); 25 | 26 | var child = jsx.elementQuerySelector(comp, '.test-class'); 27 | assert.equal(child.tagName, 'LI'); 28 | }); 29 | }); 30 | 31 | describe('#elementQuerySelectorAll', function () { 32 | it('finds the element on the component', function () { 33 | var comp = jsx.renderComponent(Component); 34 | 35 | var children = jsx.elementQuerySelectorAll(comp, '.test-class'); 36 | assert.equal(children.length, 3); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /example/CheckboxWithLabel.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var createReactClass = require('create-react-class'); 3 | 4 | module.exports = createReactClass({ 5 | displayName: 'CheckboxWithLabel', 6 | 7 | getDefaultProps: function () { 8 | return { 9 | labelOn: 'On', 10 | labelOff: 'Off', 11 | isChecked: false 12 | }; 13 | }, 14 | 15 | getInitialState: function() { 16 | return { isChecked: this.props.isChecked }; 17 | }, 18 | 19 | onChange: function() { 20 | this.setState({isChecked: !this.state.isChecked}); 21 | }, 22 | 23 | onHover: function () { 24 | this.setState({isHover: true}); 25 | }, 26 | 27 | onHoverLeave: function () { 28 | this.setState({isHover: false}); 29 | }, 30 | 31 | render: function() { 32 | return ( 33 | 43 | ); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsx-test", 3 | "version": "3.0.3", 4 | "description": "An easy way to test your React Components (`.jsx` files).", 5 | "main": "index.js", 6 | "scripts": { 7 | "cover": "COVERAGE=true jenkins-mocha example/**/*.test.js test/**/*.test.js", 8 | "test": "jenkins-mocha --no-coverage example/**/*.test.js test/**/*.test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/yahoo/jsx-test.git" 13 | }, 14 | "keywords": [ 15 | "test", 16 | "reactjs", 17 | "jsx" 18 | ], 19 | "author": "Marcelo Eden", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/yahoo/jsx-test/issues" 23 | }, 24 | "homepage": "https://github.com/yahoo/jsx-test", 25 | "devDependencies": { 26 | "babel-preset-react": "^6.0.0", 27 | "jenkins-mocha": "^7.0.0" 28 | }, 29 | "dependencies": { 30 | "babel-core": "^6.0", 31 | "create-react-class": "^15.6.2", 32 | "istanbul-lib-instrument": "^3.0.0", 33 | "jsdom": "^13.0.0", 34 | "prop-types": "^15.6.0", 35 | "react": "^15.5.0 || ^16.0.0 || ^17.0.0", 36 | "react-dom": "^15.5.0 || ^16.0.0 || ^17.0.0" 37 | }, 38 | "peerDependencies": { 39 | "react": "^15.5.0 || ^16.0.0 || ^17.0.0", 40 | "react-dom": "^15.5.0 || ^16.0.0 || ^17.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/assertRenderMatch.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | 6 | var Alert = createReactClass({ 7 | displayName: 'Alert', 8 | render: function () { 9 | return React.createElement('div', { 10 | className: 'alert ' + this.props.className 11 | }, this.props.message); 12 | } 13 | }); 14 | 15 | describe('#assertRenderMatch', function() { 16 | var props = { 17 | className: 'notice', 18 | message: 'Looks good' 19 | }; 20 | 21 | it('asserts using a regex', function () { 22 | jsx.assertRenderMatch(Alert, props, /
(.+)<\/div>/); 23 | }); 24 | 25 | it('asserts convertion a string to regex', function () { 26 | jsx.assertRenderMatch(Alert, props, '
(.+)
'); 27 | }); 28 | }); 29 | 30 | describe('#assertNoRenderMatch', function() { 31 | var props = { 32 | className: 'notice', 33 | message: 'Looks good' 34 | }; 35 | 36 | it('asserts using a regex', function () { 37 | jsx.assertNoRenderMatch(Alert, props, /class="-+"/); 38 | }); 39 | 40 | it('asserts convertion a string to regex', function () { 41 | jsx.assertNoRenderMatch(Alert, props, 'class="-+"'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014, Yahoo! Inc. 3 | * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. 4 | */ 5 | 'use strict'; 6 | 7 | // Set browser globals, needs to happen before react is required 8 | // You must require jsxTest before any React stuff 9 | const { JSDOM } = require('jsdom'); 10 | const jsdom = new JSDOM(''); 11 | const { window } = jsdom; 12 | global.window = window; 13 | global.document = window.document; 14 | global.appContainer = document.createElement('section'); 15 | global.navigator = { 16 | userAgent: 'node.js' 17 | }; 18 | global.Event = window.Event; 19 | document.body.appendChild(appContainer); 20 | 21 | // Require other dependencies 22 | var fs = require('fs'); 23 | 24 | // Require libs 25 | var assert = require('./lib/assert'); 26 | var helper = require('./lib/helper'); 27 | 28 | /** 29 | * Allow `jsx` files to be required. 30 | * This module should be required before any React class. 31 | * @module jsx-test 32 | * 33 | * @example 34 | * var jsx = require('jst-test'); 35 | * var Component = require('path/to/YourComponet.jsx'); 36 | * 37 | * describe('YourComponet', function () { 38 | * ... test your stuff 39 | * }); 40 | **/ 41 | module.exports = { 42 | // Helpers 43 | simulateEvent: helper.simulateEvent, 44 | simulateNativeEvent: helper.simulateNativeEvent, 45 | renderComponent: helper.renderComponent, 46 | unmountComponent: helper.unmountComponent, 47 | elementQuerySelector: helper.elementQuerySelector, 48 | elementQuerySelectorAll: helper.elementQuerySelectorAll, 49 | stubComponent: helper.stubComponent, 50 | withContext: helper.withContext, 51 | jsxTranspile: helper.jsxTranspile, 52 | 53 | // Assertions 54 | assertRender: assert.assertRender, 55 | assertNotRender: assert.assertNotRender, 56 | assertRenderMatch: assert.assertRenderMatch, 57 | assertNoRenderMatch: assert.assertNoRenderMatch 58 | }; 59 | -------------------------------------------------------------------------------- /test/withContext.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var PropTypes = require('prop-types'); 5 | var createReactClass = require('create-react-class'); 6 | var assert = require('assert'); 7 | 8 | describe('#withContext', function() { 9 | it('gives child context', function () { 10 | var ContextUser = createReactClass({ 11 | contextTypes: { 12 | method: PropTypes.func 13 | }, 14 | render: function () { 15 | return React.createElement('span', null, this.context.method()); 16 | } 17 | }); 18 | 19 | var context = { 20 | method: function () { 21 | return 'Awesome String!'; 22 | } 23 | }; 24 | 25 | jsx.assertRender(jsx.withContext(ContextUser, context), {}, 'Awesome String!'); 26 | }); 27 | 28 | it('passes props through context wrapper to child', function () { 29 | var ContextUser = createReactClass({ 30 | render: function () { 31 | return React.createElement('span', null, this.props.str); 32 | } 33 | }); 34 | 35 | jsx.assertRender(jsx.withContext(ContextUser, {}), {str: 'Awesome String!'}, 'Awesome String!'); 36 | }); 37 | 38 | it('allows calling methods from underlying component', function () { 39 | var UnderlyingComponent = createReactClass({ 40 | underlyingMethod: function (n1, n2) { 41 | return n1 + n2; 42 | }, 43 | render: function () { 44 | return null; 45 | } 46 | }); 47 | 48 | var wrapper = jsx.renderComponent(jsx.withContext(UnderlyingComponent, {})); 49 | assert.equal(wrapper.runChildMethod('underlyingMethod', [1, 2]), 3); 50 | }); 51 | 52 | it('creates a readable displayName using Component.displayName', function () { 53 | var NamedComponent = jsx.stubComponent('Name'); 54 | assert.equal(jsx.withContext(NamedComponent, {}).displayName, 'Name:withContext'); 55 | }); 56 | 57 | it('creates a readable displayName even if Component did not have one', function () { 58 | var UnnamedComponent = require('../example/UnnamedComponent.jsx'); 59 | assert.equal(jsx.withContext(UnnamedComponent, {}).displayName, 'UnnamedComponent:withContext'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/assertRender.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var jsx = require('../index'); 3 | var React = require('react'); 4 | var createReactClass = require('create-react-class'); 5 | 6 | describe('#assertRender', function() { 7 | var Alert = createReactClass({ 8 | displayName: 'Alert', 9 | render: function () { 10 | return React.createElement('div', { 11 | className: 'alert ' + this.props.className 12 | }, this.props.message); 13 | } 14 | }); 15 | 16 | it('renders the exact HTML', function () { 17 | jsx.assertRender(Alert, { 18 | className: 'notice', 19 | message: 'Looks good' 20 | }, '
Looks good
'); 21 | 22 | jsx.assertRender(Alert, { 23 | className: 'warning', 24 | message: 'Looks weird' 25 | }, '
Looks weird
'); 26 | }); 27 | 28 | it('renders the attomic classes HTML', function () { 29 | jsx.assertRender(Alert, { 30 | className: 'P(10px) C($blue)' 31 | }, '
'); 32 | }); 33 | 34 | it('should handle data-react-* truncation correctly', function () { 35 | var Alerts = createReactClass({ 36 | displayName: 'Alerts', 37 | render: function () { 38 | return React.createElement('div', null, [ 39 | // Note the space in the key 40 | React.createElement(Alert, { message: 'foo bar', key: 'foo bar' }), 41 | React.createElement(Alert, { message: 'foo2 bar2', key: 'foo bar2' }) 42 | ]); 43 | } 44 | }); 45 | 46 | jsx.assertRender(Alerts, null, '
foo*
foo2 bar2
'); 47 | }); 48 | 49 | it('includes some expecific content on the rendered HTML', function () { 50 | var props = { 51 | className: 'notice', 52 | message: 'Looks good' 53 | }; 54 | 55 | jsx.assertRender(Alert, props, 'Looks good'); 56 | jsx.assertRender(Alert, props, 'class="alert notice"'); 57 | jsx.assertRender(Alert, props, 'div'); 58 | }); 59 | 60 | it('matches a regex on the rendered HTML', function () { 61 | var props = { 62 | className: 'notice', 63 | message: 'Looks good' 64 | }; 65 | 66 | // use + to match atributes of a tag 67 | jsx.assertRender(Alert, props, 'Looks good'); 68 | jsx.assertRender(Alert, props, 'Looks good'); 69 | jsx.assertRender(Alert, props, '<+>Looks good'); 70 | 71 | // use * to match anything 72 | jsx.assertRender(Alert, props, ''); 73 | jsx.assertRender(Alert, props, ''); 74 | jsx.assertRender(Alert, props, '
*
'); 75 | 76 | // use + and * just to show off 77 | jsx.assertRender(Alert, props, '* good'); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /example/test/CheckboxWithLabel.test.js: -------------------------------------------------------------------------------- 1 | // jsx-test 2 | var ReactDOM = require('react-dom'); 3 | var jsx = require('../../index').jsxTranspile(process.env.COVERAGE); 4 | var assert = require('assert'); 5 | 6 | describe('CheckboxWithLabel', function() { 7 | var CheckboxWithLabel = require('../CheckboxWithLabel.jsx'); 8 | 9 | it('changes the text after click', function() { 10 | // Render a checkbox with label in the document 11 | var checkbox = jsx.renderComponent(CheckboxWithLabel, { 12 | labelOn: "On", 13 | labelOff: "Off", 14 | }); 15 | 16 | // Verify that it's Off by default 17 | assert.equal(ReactDOM.findDOMNode(checkbox).textContent, 'Off'); 18 | 19 | // Simulate a click and verify that it is now On 20 | var input = jsx.elementQuerySelector(checkbox, 'input'); 21 | 22 | // Toggle On 23 | jsx.simulateEvent(input, 'change'); 24 | assert.equal(ReactDOM.findDOMNode(checkbox).textContent, 'On'); 25 | 26 | // Toggle Off again 27 | jsx.simulateEvent(input, 'change'); 28 | assert.equal(ReactDOM.findDOMNode(checkbox).textContent, 'Off'); 29 | }); 30 | 31 | it('triggers native events', function () { 32 | var checkbox = jsx.renderComponent(CheckboxWithLabel); 33 | 34 | jsx.simulateEvent(checkbox, 'mouseOver'); 35 | assert.equal(checkbox.state.isHover, true); 36 | 37 | jsx.simulateEvent(checkbox, 'mouseOut'); 38 | assert.equal(checkbox.state.isHover, false); 39 | }); 40 | 41 | describe('#render', function () { 42 | it('renders with a custom label OFF', function () { 43 | jsx.assertRender(CheckboxWithLabel, { 44 | labelOff: 'I am OFF', 45 | isChecked: false 46 | }, 'I am OFF') 47 | 48 | jsx.assertRender(CheckboxWithLabel, { 49 | labelOff: 'Offline', 50 | isChecked: false 51 | }, 'Offline') 52 | }); 53 | 54 | it('does not render a the unchecked label', function () { 55 | jsx.assertNotRender(CheckboxWithLabel, { 56 | labelOn: 'On', 57 | labelOff: 'Off', 58 | isChecked: false 59 | }, 'On') 60 | 61 | jsx.assertNotRender(CheckboxWithLabel, { 62 | labelOn: 'On', 63 | labelOff: 'Off', 64 | isChecked: true 65 | }, 'Off') 66 | }); 67 | 68 | it('renders with a custom label ON', function () { 69 | jsx.assertRender(CheckboxWithLabel, { 70 | labelOn: 'I am ON', 71 | isChecked: true 72 | }, 'I am ON') 73 | 74 | jsx.assertRender(CheckboxWithLabel, { 75 | labelOn: 'Online', 76 | isChecked: true 77 | }, 'Online') 78 | }); 79 | 80 | it('renders with a checkbox field', function () { 81 | jsx.assertRender(CheckboxWithLabel, {}, ''); 82 | 83 | jsx.assertRender(CheckboxWithLabel, { 84 | isChecked: true 85 | }, ''); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ⚠️ **DEPRECATED / END-OF-LIFE (EOL)** — `jsx-test` is unmaintained as of **2025-10-01**. 2 | > No new features, issues, PRs, or security fixes will be provided. 3 | 4 | 5 | # jsx-test 6 | [![npm version](https://img.shields.io/npm/v/jsx-test.svg?style=flat-square)](https://www.npmjs.com/package/jsx-test) 7 | [![Build Status](https://travis-ci.org/yahoo/jsx-test.svg?branch=master)](https://travis-ci.org/yahoo/jsx-test) 8 | [![Dependency Status](https://img.shields.io/david/yahoo/jsx-test.svg?style=flat-square)](https://david-dm.org/yahoo/jsx-test) 9 | [![devDependency Status](https://img.shields.io/david/dev/yahoo/jsx-test.svg?style=flat-square)](https://david-dm.org/yahoo/jsx-test#info=devDependencies) 10 | 11 | `jsx-test` is a tool that makes it easy to test '.jsx' files and includes some helpers for testing react components. 12 | 13 | The main features of `jsx-test` are: 14 | 15 | * Includes some helpers to simplify the test of React Components. 16 | * Assertion methods to check the component renders the correct html based on the given `props`. 17 | * Does NOT automock your dependencies. 18 | * Is much simpler and faster than Jest. 19 | * Works with `mocha`, `jasmine` or any other test framework. 20 | 21 | *Note:* If you would like to require jsx files directly please follow [these instructions](https://babeljs.io/docs/setup/) 22 | 23 | ## Install 24 | 25 | ``` 26 | npm install --save-dev jsx-test 27 | ``` 28 | 29 | ## Usage 30 | ### simulateEvent 31 | ```js 32 | simulateEvent(ReactComponent element, String event, Object? eventData) 33 | simulateEvent(DOMElement element, String event, Object? eventData) 34 | ``` 35 | 36 | Simulates an `event` on an `element`. 37 | 38 | ### renderComponent 39 | ```js 40 | ReactElement renderComponent(ReactComponent comp, Object? props, any? children) 41 | ``` 42 | 43 | Renders a component w/ its props and children. 44 | 45 | ### unmountComponent 46 | ```js 47 | Boolean unmountComponent(ReactComponent comp) 48 | ``` 49 | 50 | Unmount a component. 51 | 52 | ### elementQuerySelector 53 | ```js 54 | DOMElement elementQuerySelector(ReactComponent comp, String selector) 55 | ``` 56 | 57 | Gets 1st child of `comp` using selector `selector` 58 | 59 | ### elementQuerySelectorAll 60 | ```js 61 | DOMElement[] elementQuerySelectorAll(ReactComponent comp, String selector) 62 | ``` 63 | 64 | Gets children of `comp` using selector `selector` 65 | 66 | ### stubComponent 67 | ```js 68 | ReactComponent stubComponent(ReactElement tag, any? children, boolean? showDataProps) 69 | ReactComponent stubComponent(String tag, any? children, boolean? showDataProps) 70 | ``` 71 | Creates a stub component with `tag` and its `children`. If `showDataProps` is true, all props will be set in the rendered `DOMElement` in the form of `data- = `. 72 | 73 | ### withContext 74 | ```js 75 | ReactComponent withContext(ReactComponent Component, Object context) 76 | ``` 77 | Wraps component in a context creating component. 78 | 79 | ### assertRender 80 | ```js 81 | assertRender(ReactComponent Component, Object props, String expectedHTML) 82 | ``` 83 | Assert component render matches the `expectedHTML`. The `expectedHTML` regex conversion can be seen [here](https://github.com/yahoo/jsx-test/blob/master/test/assertRender.test.js). 84 | 85 | ### assertNotRender 86 | ```js 87 | assertNotRender(ReactComponent Component, Object props, String unexpectedHTML) 88 | ``` 89 | The reverse of `assertRender`. 90 | 91 | ## Example 92 | 93 | Check our [examples](https://github.com/3den/jsx-test/tree/master/example) and [tests](https://github.com/3den/jsx-test/tree/master/test). 94 | -------------------------------------------------------------------------------- /lib/assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var React = require('react'); 4 | var ReactDOMServer = require('react-dom/server'); 5 | 6 | var REACT_ATTR_REGEX = /\s+data-react[-\w]+=".*?"/g; 7 | var TOKEN_REGEX = /(\*)|(\+)|(\s+)/g; 8 | var ESCAPE_REGEX = /[<>\/\\?=^$()]/g; 9 | 10 | function renderToString(Component, props) { 11 | return ReactDOMServer 12 | .renderToString(React.createElement(Component, props)) 13 | .replace(REACT_ATTR_REGEX, ''); 14 | } 15 | 16 | function buildRegEx(text) { 17 | return new RegExp(text 18 | .replace(ESCAPE_REGEX, '\\$&') 19 | .replace(TOKEN_REGEX, function (matcher, star, plus, space) { 20 | if (star) { 21 | return '.*'; 22 | } 23 | 24 | if (plus) { 25 | return '[^<>]+'; 26 | } 27 | 28 | if (space) { 29 | return ' ' 30 | } 31 | })); 32 | } 33 | 34 | /** 35 | * Assert component render matches the expectedHTML 36 | * @method assertRender 37 | * 38 | * @param {ReactComponent} Component class 39 | * @param {Object} props used to initialize the component 40 | * @param {String} expectedHTML can contain wildcards "+" or "*" 41 | * 42 | * @example 43 | * // The code bellow will raise an error unless the component renders 44 | * // a div with any attributes and the string "something" inside any 45 | * // inner element. 46 | * assertRender(MyComponent, {}, "*something*"); 47 | **/ 48 | function assertRender(Component, props, expectedHTML) { 49 | assertRenderMatch(Component, props, buildRegEx(expectedHTML)); 50 | } 51 | 52 | /** 53 | * Assert component render to not matches the unexpectedHTML 54 | * @method assertNotRender 55 | * 56 | * @param {ReactComponent} Component class 57 | * @param {Object} props used to initialize the component 58 | * @param {String} unexpectedHTML can contain wildcards "+" or "*" 59 | * 60 | * @example 61 | * // The code bellow will raise an error if the component renders 62 | * // a div with any attributes and the string "something" inside any 63 | * // inner element. 64 | * assertNotRender(MyComponent, {}, "*something*"); 65 | **/ 66 | function assertNotRender(Component, props, unexpectedHTML) { 67 | assertNoRenderMatch(Component, props, buildRegEx(unexpectedHTML)); 68 | } 69 | 70 | /** 71 | * Assert component render matches the expectedRegex 72 | * @method assertRenderMatch 73 | * 74 | * @param {ReactComponent} Component class 75 | * @param {Object} props used to initialize the component 76 | * @param {Regex} expected can contain any regex 77 | **/ 78 | function assertRenderMatch(Component, props, expected) { 79 | var rendered = renderToString(Component, props); 80 | var regEx = new RegExp(expected); 81 | 82 | assert( 83 | regEx.test(rendered), 84 | '\nExpected: \n' + rendered + '\n\n To match: \n' + regEx 85 | ); 86 | } 87 | 88 | /** 89 | * Assert component render to not matches the unexpectedRegex 90 | * @method assertNoRenderMatch 91 | * 92 | * @param {ReactComponent} Component class 93 | * @param {Object} props used to initialize the component 94 | * @param {Regex} unexpected can contain any regex 95 | **/ 96 | function assertNoRenderMatch(Component, props, unexpected) { 97 | var rendered = renderToString(Component, props); 98 | var regEx = new RegExp(unexpected); 99 | 100 | assert( 101 | !regEx.test(rendered), 102 | '\nExpected: \n' + rendered + '\n\n to NOT match: \n' + regEx 103 | ); 104 | } 105 | 106 | module.exports = { 107 | assertRenderMatch: assertRenderMatch, 108 | assertNoRenderMatch: assertNoRenderMatch, 109 | assertRender: assertRender, 110 | assertNotRender: assertNotRender 111 | }; 112 | -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var createReactClass = require('create-react-class'); 4 | var PropTypes = require('prop-types'); 5 | var ReactDOM = require('react-dom'); 6 | var ReactTestUtils = require('react-dom/test-utils'); 7 | 8 | 9 | /** 10 | * Renders a component. Method signature is the same as 11 | * https://facebook.github.io/react/docs/top-level-api.html#react.createelement 12 | * 13 | * @method renderComponent 14 | * @param {ReactComponent} Component class 15 | * @param {Object} props used to initialize the componen 16 | * @param {Object|Object[]} children children to populate inside Component 17 | * @return {ReactElement} rendered react element 18 | **/ 19 | function renderComponent() { 20 | var element = React.createElement.apply(React, arguments); 21 | return ReactDOM.render(element, appContainer) 22 | } 23 | 24 | /** 25 | * Unmount a component. 26 | * 27 | * @method unmountComponent 28 | * @param {ReactElement} the element to be unmounted 29 | * @return {Boolean} true if a component was unmounted 30 | **/ 31 | function unmountComponent(element) { 32 | return ReactDOM.unmountComponentAtNode(appContainer); 33 | } 34 | 35 | /** 36 | * Simulates an event triggered on given element 37 | * 38 | * @method simulateEvent 39 | * @param {ReactElement or DOMNode} element that will trigger the event 40 | * @param {String} event that will be triggered 41 | * @param {Object} data that will be sent to the event 42 | **/ 43 | function simulateEvent(element, event, data) { 44 | ReactTestUtils.Simulate[event]( 45 | ReactDOM.findDOMNode(element) || element, 46 | data 47 | ); 48 | } 49 | 50 | /** 51 | * Simulates an native event on given element 52 | * 53 | * @method simulateNativeEvent 54 | * @param {ReactElement or DOMNode} element that will trigger the event 55 | * @param {String} event that will be triggered 56 | **/ 57 | function simulateNativeEvent(element, event) { 58 | console.warn('simulateNativeEvent: is dreprecated in React 0.14') 59 | ReactTestUtils.SimulateNative[event]( 60 | ReactDOM.findDOMNode(element) || element 61 | ); 62 | } 63 | 64 | /** 65 | * Element by query selector 66 | * 67 | * @method elementQuerySelector 68 | * @param {ReactElement} element node where you will search 69 | * @param {string} query css selector 70 | * @return {DOMNode} the first DOM node that matches the query 71 | **/ 72 | function elementQuerySelector(element, query) { 73 | return ReactDOM.findDOMNode(element).querySelector(query); 74 | } 75 | 76 | /** 77 | * Elements by query selector 78 | * 79 | * @method elementQuerySelectorAll 80 | * @param {ReactElement} element node where you will search 81 | * @param {String} query css selector 82 | * @return {DOMNode} all DOM nodes that matches the query 83 | **/ 84 | function elementQuerySelectorAll(element, query) { 85 | return ReactDOM.findDOMNode(element).querySelectorAll(query); 86 | } 87 | 88 | /** 89 | * Creates a ComponentStub 90 | * 91 | * @method stubComponent 92 | * @param {ReactElement|String} tag element to create 93 | * @param {Object} assertProps raises an error if component props don't match 94 | * @return {ReactComponent} ComponentStub 95 | **/ 96 | function stubComponent(tag, children, showDataProps) { 97 | return createReactClass({ 98 | displayName: tag.displayName || tag, 99 | 100 | getStubProps: function () { 101 | var props = this.props; 102 | 103 | return Object.keys(props).reduce(function (clonedProps, key) { 104 | clonedProps[key] = props[key]; 105 | if (showDataProps) { 106 | clonedProps['data-' + key.toLowerCase()] = props[key]; 107 | } 108 | return clonedProps; 109 | }, {}); 110 | }, 111 | 112 | render: function () { 113 | return React.createElement( 114 | tag, 115 | this.getStubProps(), 116 | this.props.children || children 117 | ); 118 | } 119 | }); 120 | } 121 | 122 | /** 123 | * wraps component in a context creating component 124 | * 125 | * @method withcontext 126 | * @param {reactcomponent} component class 127 | * @param {object} context the context to be passed to component 128 | * @return {reactcomponent} react class with context setup 129 | **/ 130 | function withContext(Component, context) { 131 | var childContextTypes = {}; 132 | 133 | // Do not use hasOwnProperty, we need all keys from the entire prototype chain 134 | for (var key in context) { 135 | childContextTypes[key] = PropTypes.any; 136 | } 137 | 138 | return createReactClass({ 139 | displayName: (Component.displayName || 'Component') + ':withContext', 140 | childContextTypes: childContextTypes, 141 | getDefaultProps: function () { 142 | return { 143 | ref: 'child' 144 | }; 145 | }, 146 | runChildMethod: function (name, args) { 147 | var child = this.refs.child; 148 | return child[name].apply(child, args); 149 | }, 150 | getChildContext: function () { 151 | return context; 152 | }, 153 | render: function () { 154 | return React.createElement(Component, this.props); 155 | } 156 | }); 157 | } 158 | 159 | function instrumentCode(code, filename) { 160 | var createInstrumenter = require('istanbul-lib-instrument').createInstrumenter; 161 | return createInstrumenter().instrumentSync(code, filename); 162 | } 163 | /** 164 | * Compiles jsx files on require 165 | * this method is not needed if you are using babel 166 | * 167 | * @method jsxTranspile 168 | * @param {Boolean} instrument the original code 169 | **/ 170 | function jsxTranspile(instrument) { 171 | var fs = require('fs'); 172 | var babel = require('babel-core'); 173 | 174 | require.extensions['.jsx'] = function (module, filename) { 175 | var content = fs.readFileSync(filename, 'utf8'); 176 | var code = babel.transform(content, { 177 | presets: ['react'], 178 | retainLines: true 179 | }).code; 180 | 181 | if ( 182 | instrument && 183 | instrument !== 'false' && 184 | !/(tests|node_modules)/.test(filename) 185 | ) { 186 | code = instrumentCode(code, filename); 187 | } 188 | 189 | return module._compile(code, filename); 190 | }; 191 | 192 | return this; 193 | } 194 | 195 | /** 196 | * @module helper 197 | **/ 198 | module.exports = { 199 | simulateEvent: simulateEvent, 200 | simulateNativeEvent: simulateNativeEvent, 201 | renderComponent: renderComponent, 202 | unmountComponent: unmountComponent, 203 | elementQuerySelector: elementQuerySelector, 204 | elementQuerySelectorAll: elementQuerySelectorAll, 205 | stubComponent: stubComponent, 206 | withContext: withContext, 207 | jsxTranspile: jsxTranspile 208 | }; 209 | --------------------------------------------------------------------------------