├── .eslintignore ├── .gitignore ├── .coveralls.yml ├── docs ├── diff_prop.png └── error_diff.png ├── .travis.yml ├── tests ├── components │ ├── ChildComponent.js │ └── MyComponent.js ├── real-world-tests.js └── unexpected-react-shallow.spec.js ├── src ├── lib │ ├── ReactShallowRenderer.js │ ├── search.js │ ├── element.js │ ├── write.js │ ├── ReactElement.js │ ├── assertions.js │ ├── equality.js │ └── diff.js └── unexpected-react-shallow.js ├── wallaby.js ├── .eslintrc ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .idea/ 4 | 5 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis 2 | repo_token: 1pgzCCFJNXG3jPvtO3A9MkaUpyz0skG1z 3 | -------------------------------------------------------------------------------- /docs/diff_prop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/unexpected-react-shallow/HEAD/docs/diff_prop.png -------------------------------------------------------------------------------- /docs/error_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruderstein/unexpected-react-shallow/HEAD/docs/error_diff.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | - "0.11" 6 | - "0.12" 7 | - "iojs-v2.5.0" 8 | script: 9 | - npm test 10 | - npm run coveralls 11 | -------------------------------------------------------------------------------- /tests/components/ChildComponent.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | 3 | export default class ChildComponent extends Component { 4 | 5 | render() { 6 | return
; 7 | } 8 | } -------------------------------------------------------------------------------- /src/lib/ReactShallowRenderer.js: -------------------------------------------------------------------------------- 1 | 2 | exports.addTypeTo = function (expect) { 3 | expect.addType({ 4 | name: 'ReactShallowRenderer', 5 | base: 'object', 6 | identify: function (value) { 7 | return typeof value === 'object' && 8 | value !== null && 9 | typeof value.getRenderOutput === 'function'; 10 | }, 11 | 12 | inspect: function (value, depth, output, inspect) { 13 | output.append(inspect(value.getRenderOutput())); 14 | } 15 | }); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/unexpected-react-shallow.js: -------------------------------------------------------------------------------- 1 | var ReactElement = require('./lib/ReactElement'); 2 | var ReactShallowRenderer = require('./lib/ReactShallowRenderer'); 3 | var Assertions = require('./lib/assertions'); 4 | 5 | module.exports = { 6 | 7 | name: 'unexpected-react-shallow', 8 | 9 | installInto: function (expect) { 10 | 11 | expect.installPlugin(require('magicpen-prism')); 12 | 13 | ReactShallowRenderer.addTypeTo(expect); 14 | 15 | ReactElement.addTypeTo(expect); 16 | 17 | Assertions.addAssertionsTo(expect); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /tests/components/MyComponent.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import ChildComponent from './ChildComponent'; 3 | 4 | export default class MyComponent extends Component { 5 | 6 | constructor(props) { 7 | super(); 8 | this.state = { clickCount: 0 }; 9 | this.onClick = this.onClick.bind(this); 10 | } 11 | 12 | onClick() { 13 | this.setState({ 14 | clickCount: this.state.clickCount + 1 15 | }); 16 | } 17 | 18 | render() { 19 | 20 | return ( 21 |
22 | 23 | {this.state.clickCount} 24 | 25 |
26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | var babel = require('babel'); 2 | 3 | module.exports = function (wallaby) { 4 | return { 5 | files: ['src/**/*.js', { 6 | pattern: 'tests/helpers/*.js', 7 | instrument: false 8 | }], 9 | tests: ['tests/**/*.js', { 10 | pattern: 'tests/helpers/**/*.js', 11 | ignore: true 12 | }], 13 | env: { 14 | type: 'node', 15 | runner: 'node' 16 | }, 17 | preprocessors: { 18 | 'src/**/*.js': file => babel.transform(file.content, { sourceMap:true, stage: 0 }), 19 | 'tests/**/*.js': file => babel.transform(file.content, { sourceMap:true, stage: 0 }) 20 | }, 21 | debug: true, 22 | workers: { 23 | initial: 1, 24 | regular: 1, 25 | recycle: false 26 | } 27 | }; 28 | }; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "plugins": [ 8 | "react" 9 | ], 10 | "rules": { 11 | "curly": [2, "multi-line"], 12 | "no-shadow": 0, 13 | "no-trailing-spaces": 0, 14 | "no-underscore-dangle": 0, 15 | "no-unused-expressions": 0, 16 | "object-curly-spacing": [1, "always"], 17 | "quotes": [2, "single", "avoid-escape"], 18 | "react/jsx-boolean-value": 1, 19 | "react/jsx-no-undef": 1, 20 | "react/jsx-quotes": 1, 21 | "react/jsx-sort-prop-types": 1, 22 | "react/jsx-uses-react": 1, 23 | "react/jsx-uses-vars": 1, 24 | "react/no-did-mount-set-state": 1, 25 | "react/no-did-update-set-state": 1, 26 | "react/no-unknown-property": 1, 27 | "react/prop-types": 1, 28 | "react/react-in-jsx-scope": 1, 29 | "react/self-closing-comp": 1, 30 | "react/sort-comp": 1, 31 | "react/wrap-multilines": 1, 32 | "semi": 2, 33 | "strict": 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/search.js: -------------------------------------------------------------------------------- 1 | var Element = require('./element'); 2 | var Equality = require('./equality'); 3 | 4 | exports.findElementIn = function findElementIn(haystack, needle, expect, options) { 5 | 6 | if (Equality.elementsMatch(haystack, needle, expect.equal.bind(expect), options)) { 7 | return true; 8 | } 9 | 10 | var found = false; 11 | var shouldNormalize = !options.exactly; 12 | 13 | if (haystack.props && haystack.props.children) { 14 | 15 | var children = Element.getChildrenArray(haystack.props.children, { 16 | normalize: shouldNormalize 17 | }); 18 | 19 | children.forEach(function (child) { 20 | 21 | if (Equality.elementsMatch(child, needle, expect.equal.bind(expect), options)) { 22 | found = true; 23 | return; 24 | } 25 | 26 | if (findElementIn(child, needle, expect, options)) { 27 | found = true; 28 | } 29 | }); 30 | } 31 | return found; 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dave Brotherstone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/real-world-tests.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { PropTypes, Component } from 'react/addons'; 3 | import MyComponent from './components/MyComponent'; 4 | import ChildComponent from './components/ChildComponent'; 5 | import Unexpected from 'unexpected'; 6 | 7 | import UnexpectedReactShallow from '../src/unexpected-react-shallow'; 8 | 9 | const expect = Unexpected.clone() 10 | .installPlugin(UnexpectedReactShallow); 11 | 12 | const TestUtils = React.addons.TestUtils; 13 | 14 | if (!global.document) { 15 | global.document = {}; 16 | } 17 | 18 | describe('MyComponent', function () { 19 | 20 | let renderer; 21 | 22 | beforeEach(() => { 23 | 24 | renderer = TestUtils.createRenderer(); 25 | }); 26 | 27 | it('renders the initial 0 clicks', function () { 28 | 29 | var element = ; 30 | renderer.render(element); 31 | expect(renderer, 'to have rendered', 32 |
33 | 34 | 0 35 | 36 |
37 | ); 38 | 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unexpected-react-shallow", 3 | "version": "0.7.0", 4 | "description": "Unexpected plugin for React shallow renderer", 5 | "keywords": [ 6 | "unexpected", 7 | "react", 8 | "plugin", 9 | "assertion", 10 | "shallow renderer" 11 | ], 12 | "scripts": { 13 | "cover": "istanbul cover _mocha -- -u exports --compilers js:babel/register tests", 14 | "lint": "eslint .", 15 | "test": "mocha --compilers js:babel/register tests", 16 | "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --compilers js:babel/register tests && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage || true" 17 | }, 18 | "devDependencies": { 19 | "babel": "^5.8.21", 20 | "babel-eslint": "^4.0.7", 21 | "coveralls": "^2.11.4", 22 | "eslint": "^1.1.0", 23 | "eslint-plugin-react": "^3.2.3", 24 | "istanbul": "^0.3.17", 25 | "mocha": "^2.2.5", 26 | "react": "^0.13.3", 27 | "unexpected": "^10.0.0" 28 | }, 29 | "dependencies": { 30 | "array-changes": "^1.0.3", 31 | "magicpen-prism": "^2.2.0" 32 | }, 33 | "peerDependencies": { 34 | "unexpected": "^10.0.0", 35 | "react": "^0.13.0 || ^0.14.0" 36 | }, 37 | "author": "Dave Brotherstone ", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/bruderstein/unexpected-react-shallow.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/bruderstein/unexpected-react-shallow/issues" 44 | }, 45 | "main": "src/unexpected-react-shallow.js", 46 | "files": [ 47 | "src", 48 | "README.md", 49 | "LICENSE" 50 | ], 51 | "license": "MIT" 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/element.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | function isSimpleType(value) { 4 | var type = typeof value; 5 | return type === 'string' || 6 | type === 'number' || 7 | type === 'boolean' || 8 | type === 'undefined' || 9 | value === null; 10 | } 11 | 12 | function convertValueTypeToString(value) { 13 | if (value === null || value === undefined) { 14 | return ''; 15 | } 16 | 17 | return '' + value; 18 | } 19 | 20 | function concatenateStringChildren(accum, value) { 21 | if (isSimpleType(value) && accum.length && 22 | isSimpleType(accum[accum.length - 1])) 23 | { 24 | accum[accum.length - 1] = convertValueTypeToString(accum[accum.length - 1]) + convertValueTypeToString(value); 25 | return accum; 26 | } 27 | accum.push(value); 28 | return accum; 29 | } 30 | 31 | 32 | exports.getName = function getName(element) { 33 | 34 | if (typeof element.type === 'string') { 35 | return element.type; 36 | } 37 | 38 | return element.type.displayName || element.type.name || 'no-display-name'; 39 | }; 40 | 41 | exports.getProps = function getProps(element) { 42 | 43 | var realProps = {}; 44 | if (element.props) { 45 | for(var key in element.props) { 46 | if (key !== 'children') { 47 | realProps[key] = element.props[key]; 48 | } 49 | } 50 | } 51 | return realProps; 52 | }; 53 | 54 | exports.getChildrenArray = function getChildrenArray(children, options) { 55 | 56 | var childrenArray = []; 57 | React.Children.forEach(children, function (child) { 58 | if (child !== null) { 59 | childrenArray.push(child); 60 | } 61 | }); 62 | 63 | if (options.normalize) { 64 | return childrenArray.reduce(concatenateStringChildren, []); 65 | } 66 | return childrenArray; 67 | }; 68 | -------------------------------------------------------------------------------- /src/lib/write.js: -------------------------------------------------------------------------------- 1 | 2 | var internals = {}; 3 | 4 | exports.writeProps = internals.writeProps = function writeProps(output, props) { 5 | 6 | if (props) { 7 | Object.keys(props).forEach(function (prop) { 8 | if (prop === 'children') { 9 | return; 10 | } 11 | output.text(' '); 12 | internals.writeProp(output, prop, props[prop]); 13 | }); 14 | } 15 | }; 16 | 17 | exports.writeProp = internals.writeProp = function writeProp(output, propName, value, inspect) { 18 | 19 | output.prismAttrName(propName) 20 | .prismPunctuation('='); 21 | if (inspect) { 22 | if (typeof (value) === 'string') { 23 | 24 | output.prismPunctuation('"'); 25 | output.append(value); 26 | output.prismPunctuation('"'); 27 | } else { 28 | output.prismPunctuation('{'); 29 | output.append(inspect(value)); 30 | output.prismPunctuation('}'); 31 | } 32 | return; 33 | } 34 | 35 | switch (typeof value) { 36 | case 'number': 37 | case 'boolean': 38 | case 'undefined': 39 | output.text('{') 40 | .text(value) 41 | .text('}'); 42 | break; 43 | case 'string': 44 | output.prismPunctuation('"').prismAttrValue(value).prismPunctuation('"'); 45 | break; 46 | 47 | case 'object': 48 | if (value === null) { 49 | output.prismPunctuation('{').prismAttrValue('null').prismPunctuation('}'); 50 | } else { 51 | output.prismPunctuation('{').prismAttrValue('...').prismPunctuation('}'); 52 | } 53 | break; 54 | case 'function': 55 | output.prismPunctuation('{').prismAttrValue(' function(){...} ').prismPunctuation('}'); 56 | break; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #v0.7.0 2 | * Fixed issue where an element rendered with children, but was not expected to have any children would be accepted 3 | when using `with all children` 4 | * Fixed issue when an element renders a `null`, but was not being ignored as a "child" when using `with all children` 5 | or `exactly` (#8) - thanks to @dchambers for reporting 6 | 7 | #v0.6.2 8 | * Remove unnecessary require()s, causing warnings with React 0.14 (thanks @sunesimonsen for the hint) 9 | 10 | #v0.6.1 11 | * Compatibility with React 0.14 (thanks @sunesimonsen) 12 | 13 | #v0.6.0 14 | * Compatibility with Unexpected v10.x.x 15 | 16 | #v0.5.0 17 | 18 | * Added `'to contain with all children'` and `'to have rendered with all children'` assertions, to check for 19 | missing children, but allow missing props. 20 | 21 | #v0.4.0 22 | 23 | * Fixed issue with non-string native elements (e.g. numbers) comparing to real elements 24 | * Made 'to satisfy' an alias of 'to have rendered' for ReactElement. Makes converting tests easier, and doesn't 25 | error on existing 'to satisfy' ReactElement tests (e.g. non unexpected-react-shallow tests) 26 | 27 | #v0.3.1 28 | 29 | * Improved output when the actual is a string, and the expected is a component 30 | Previously, the actual text was quoted, which was inaccurate 31 | 32 | #v0.3.0 33 | 34 | * Internal refactor 35 | * Fixed error when handling and improved output for strings-that-should-be-components, and vice versa. 36 | 37 | e.g. 38 | ```js 39 | renderer.render( 40 |
41 | abc 42 |
43 | ); 44 | 45 | expect(renderer, 'to have rendered', 46 |
47 | abc 48 |
49 | ); 50 | ``` 51 | 52 | The `` should actually just a straight string `'abc'`, rather than `'abc'` wrapped in a ``. 53 | Works both ways round. 54 | 55 | #v0.2.0 56 | 57 | * Numbers and other non-string value types are concatenated as strings for the non-`exactly` variants 58 | This means `Button was clicked {clickCount} times` expressions can be checked using a simple string check 59 | e.g. 60 | ```js 61 | expect(renderer, 'to have rendered', 'Button was clicked 5 times'); 62 | ``` 63 | 64 | #v0.1.2 65 | 66 | * First version (that worked) 67 | -------------------------------------------------------------------------------- /src/lib/ReactElement.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Diff = require('./diff'); 3 | var Element = require('./element'); 4 | var Equality = require('./equality'); 5 | var Write = require('./write'); 6 | 7 | exports.addTypeTo = function (expect) { 8 | expect.addType({ 9 | name: 'ReactElement', 10 | 11 | identify: function (value) { 12 | return React.isValidElement(value) || 13 | (typeof value === 'object' && 14 | value !== null && 15 | (typeof value.type === 'function' || typeof value.type === 'string') && 16 | value.hasOwnProperty('props') && 17 | value.hasOwnProperty('ref') && 18 | value.hasOwnProperty('key')); 19 | }, 20 | 21 | inspect: function (value, depth, output, inspect) { 22 | 23 | output 24 | .prismPunctuation('<') 25 | .prismTag(Element.getName(value)); 26 | 27 | Write.writeProps(output, value.props); 28 | 29 | if (React.Children.count(value.props.children)) { 30 | output.prismPunctuation('>'); 31 | output.nl().indentLines(); 32 | 33 | var children = Element.getChildrenArray(value.props.children, {normalize: true}); 34 | 35 | children.forEach(function (child) { 36 | 37 | if (typeof child === 'string') { 38 | output.i().prismString(child).nl(); 39 | } else { 40 | output.i().block(inspect(child)).nl(); 41 | } 42 | }); 43 | output.outdentLines(); 44 | output.i() 45 | .prismPunctuation(''); 46 | 47 | } else { 48 | output.prismPunctuation(' />'); 49 | } 50 | }, 51 | 52 | diff: function (actual, expected, output, diff, inspect, equal) { 53 | return Diff.diffElements(actual, expected, output, diff, inspect, equal, { exactly: true }); 54 | }, 55 | 56 | equal: function (a, b, equal) { 57 | return Equality.elementsMatch(a, b, equal, { exactly: true }); 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /src/lib/assertions.js: -------------------------------------------------------------------------------- 1 | 2 | var Diff = require('./diff'); 3 | var Equality = require('./equality'); 4 | var Search = require('./search'); 5 | 6 | exports.addAssertionsTo = function (expect) { 7 | 8 | expect.addAssertion([' to have [exactly] rendered ', 9 | ' to have rendered [with all children] '], function (expect, subject, renderOutput) { 10 | 11 | var exactly = this.flags.exactly; 12 | var withAllChildren = this.flags['with all children']; 13 | 14 | if (exactly && withAllChildren) { 15 | return expect.fail('`exactly` and `with all children` cannot be used together. `exactly` implies `with all children`'); 16 | } 17 | 18 | return expect.withError(function () { 19 | return Equality.assertElementsMatch(subject, renderOutput, expect, { 20 | exactly: exactly, 21 | withAllChildren: withAllChildren 22 | }); 23 | }, function (e) { 24 | return expect.fail({ 25 | diff : function (output, diff, inspect, equal) { 26 | return Diff.diffElements(subject, renderOutput, output, diff, inspect, equal, { 27 | exactly: exactly, 28 | withAllChildren: withAllChildren 29 | }); 30 | } 31 | }); 32 | }); 33 | }); 34 | 35 | expect.addAssertion([' to contain [exactly] ', 36 | ' to contain [with all children] '], function (expect, subject, expected) { 37 | 38 | if (!Search.findElementIn(subject, expected, expect, { 39 | exactly: this.flags.exactly, 40 | withAllChildren: this.flags['with all children'] 41 | })) { 42 | expect.fail(); 43 | } 44 | }); 45 | 46 | expect.addAssertion(' to satisfy ', function (expect, subject, expected) { 47 | 48 | return expect(subject, 'to have rendered', expected); 49 | }); 50 | 51 | expect.addAssertion([' to have [exactly] rendered ', 52 | ' to have rendered [with all children] '], function (expect, subject, renderOutput) { 53 | var actual = subject.getRenderOutput(); 54 | return expect(actual, 'to have [exactly] rendered [with all children]', renderOutput); 55 | }); 56 | 57 | expect.addAssertion([' to contain [with all children] ', 58 | ' to contain [exactly] '], function (expect, subject, expected) { 59 | 60 | var actual = subject.getRenderOutput(); 61 | return expect(actual, 'to contain [exactly] [with all children]', expected); 62 | }); 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /src/lib/equality.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ArrayChanges = require('array-changes'); 3 | var Element = require('./element'); 4 | 5 | 6 | var internals = {}; 7 | 8 | exports.propsMatch = internals.propsMatch = function propsMatch(actual, expected, equal, options) { 9 | if (options && options.exactly) { 10 | return equal(actual, expected); 11 | } 12 | var matching = true; 13 | Object.keys(expected).forEach(function (key) { 14 | if (!equal(actual[key], expected[key])) { 15 | matching = false; 16 | } 17 | }); 18 | return matching; 19 | }; 20 | 21 | exports.elementsMatch = internals.elementsMatch = function elementsMatch(actual, expected, equal, options) { 22 | 23 | if (typeof actual === 'string' && typeof expected === 'string') { 24 | return actual === expected; 25 | } 26 | 27 | if ((typeof actual === 'string' || typeof actual === 'number') && 28 | (typeof expected === 'string' || typeof expected === 'number')) { 29 | return '' + actual === '' + expected; 30 | } 31 | 32 | if (typeof actual !== typeof expected) { // Fundamentally different e.g. string vs ReactElement 33 | return false; 34 | } 35 | 36 | if (Element.getName(actual) !== Element.getName(expected)) { 37 | return false; 38 | } 39 | 40 | if (!internals.propsMatch(Element.getProps(actual), Element.getProps(expected), equal, options)) { 41 | return false; 42 | } 43 | 44 | // For 'exactly', we can just check the count of the actual children matches, 45 | // string children will not be concatenated in this mode, and serves to also check 46 | // the case that the expected does not have children, but the actual does (ignored when exactly=false) 47 | 48 | var shouldNormalize = !options.exactly; 49 | var actualChildren = Element.getChildrenArray(actual.props.children, { 50 | normalize: shouldNormalize 51 | }); 52 | var expectedChildren = Element.getChildrenArray(expected.props.children, { 53 | normalize: shouldNormalize 54 | }); 55 | if ((options.exactly || options.withAllChildren) && actualChildren.length !== expectedChildren.length) { 56 | return false; 57 | } 58 | 59 | if (React.Children.count(expected.props.children)) { 60 | if (React.Children.count(actual.props.children) === 0) { 61 | return false; 62 | } 63 | 64 | 65 | var arrayDiffs = ArrayChanges( 66 | actualChildren, 67 | expectedChildren, 68 | function (a, b) { 69 | return elementsMatch(a, b, equal, options); 70 | }, 71 | function () { 72 | return false; 73 | }); 74 | 75 | var arrayMatches = true; 76 | arrayDiffs.forEach(function (diffItem) { 77 | switch (diffItem.type) { 78 | case 'equal': 79 | return; 80 | case 'remove': 81 | if (options.exactly || options.withAllChildren) { 82 | arrayMatches = false; 83 | } 84 | break; 85 | default: 86 | arrayMatches = false; 87 | break; 88 | 89 | } 90 | }); 91 | 92 | if (!arrayMatches) { 93 | return false; 94 | } 95 | } 96 | return true; 97 | 98 | }; 99 | 100 | 101 | exports.assertElementsMatch = function assertElementsMatch(actual, expected, expect, options) { 102 | 103 | var result = internals.elementsMatch(actual, expected, expect.equal.bind(expect), options); 104 | if (!result) { 105 | return expect.fail('elements are not equal'); 106 | } 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/bruderstein/unexpected-react-shallow.svg?branch=master)](https://travis-ci.org/bruderstein/unexpected-react-shallow) 2 | [![Coverage Status](https://coveralls.io/repos/bruderstein/unexpected-react-shallow/badge.svg?branch=master&service=github)](https://coveralls.io/github/bruderstein/unexpected-react-shallow?branch=master) 3 | # unexpected-react-shallow 4 | 5 | ## Deprecated - use [unexpected-react](https://npmjs.org/package/unexpected-react) instead 6 | 7 | ---- 8 | 9 | 10 | A port of the excellent [unexpected-dom](https://github.com/munter/unexpected-dom) to support 11 | [React](http://reactjs.org) [shallow renderer](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering). 12 | 13 | Diffing of props and content, including detailed diffing of object props. 14 | 15 | ```js 16 | 17 | var renderer = TestUtils.createRenderer(); 18 | 19 | renderer.render(); 20 | 21 | expect(renderer, 'to have rendered', 22 |
23 | Some simple content 24 |
); 25 | 26 | ``` 27 | 28 | And you receive a beautiful error message 29 | 30 | ![error message](https://raw.githubusercontent.com/bruderstein/unexpected-react-shallow/master/docs/error_diff.png) 31 | 32 | Object props are also diffed, when they don't match 33 | 34 | ![object prop diff](https://raw.githubusercontent.com/bruderstein/unexpected-react-shallow/master/docs/diff_prop.png) 35 | 36 | 37 | # Status 38 | 39 | Although this project itself is well tested, it is still in early development, and there may well be issues. 40 | 41 | Use with caution. If you find any case that does not work as expected, please report it via the issues. 42 | 43 | 44 | # Setup 45 | 46 | ```js 47 | 48 | var unexpected = require('unexpected'); 49 | var unexpectedReactShallow = require('unexpected-react-shallow'); 50 | 51 | var expect = unexpected.clone() 52 | .installPlugin(unexpectedReactShallow); 53 | 54 | ``` 55 | 56 | # Assertions 57 | 58 | ## `to have rendered` 59 | 60 | ```js 61 | expect(shallowRenderer, 'to have rendered', reactElement); 62 | ``` 63 | 64 | `reactElement` in this case is a ReactElement, normally created by using JSX 65 | 66 | For example 67 | 68 | ```js 69 | expect(shallowRenderer, 'to have rendered',
); 70 | ``` 71 | 72 | The same thing works for `shallowRenderer.getRenderOutput()`. 73 | 74 | Extra props that are rendered are ignored (see `to have exactly rendered below`), as 75 | are extra children. 76 | 77 | Using the following simple component: 78 | ```js 79 | 80 | var Component = React.createClass({ 81 | render: function () { 82 | return ( 83 |
84 | one 85 | two 86 | three 87 |
88 | ); 89 | } 90 | }); 91 | ``` 92 | 93 | You can make the following assertions: 94 | ## `to have [exactly] rendered [with all children]` 95 | 96 | ```js 97 | // Extra props and children from the render are ignored 98 | expect(renderer, 'to have rendered',
); 99 | 100 | // The span "two" is missing here, but it is ignored. 101 | expect(renderer, 'to have rendered', 102 |
103 | one 104 | three 105 |
106 | ); 107 | 108 | // The following assertion will fail, as 'four' does not exist 109 | expect(renderer, 'to have rendered', 110 |
111 | one 112 | four 113 |
114 | ); 115 | ``` 116 | 117 | If you want to check for an exact render, use `'to have exactly rendered'`. 118 | 119 | Alternatively, if you don't care about extra props, but want to check that there are no extra child nodes, use `'to have rendered with all children'` 120 | Note that `exactly` implies `with all children`, so you using both options is not necessary. 121 | 122 | 123 | ```js 124 | 125 | // The span "two" is missing here, as is `className="parent"` 126 | // The missing span will cause an assertion error, but the extra prop will be ignored 127 | // due to `to have rendered with all children` being used 128 | 129 | expect(renderer, 'to have rendered with all children', 130 |
131 | one 132 | three 133 |
134 | ); 135 | ``` 136 | 137 | ```js 138 | 139 | // The span "two" is missing here, as is `className="parent"` 140 | // This will cause an assertion error, 141 | // due to `to have exactly rendered` being used 142 | 143 | expect(renderer, 'to have exactly rendered', 144 |
145 | one 146 | three 147 |
148 | ); 149 | ``` 150 | 151 | ## `to contain` 152 | 153 | It's possible to check for a part of the subtree, without 154 | testing the entire returned tree. This allows you to test specific elements, without 155 | writing brittle tests that break when the structure changes. 156 | 157 | ```js 158 | // This will pass, as `two` can be found in the renderers output 159 | expect(renderer, 'to contain', two); 160 | ``` 161 | 162 | Notice that the extra `className="middle"` in the `two` is ignored, 163 | in a similar way to the `to have rendered` assertion. 164 | 165 | You can override this behaviour by using `'to contain exactly'`, and `'to contain with all children'` 166 | 167 | 168 | ```js 169 | // This will fail, as `two` cannot be found in the renderers output, due to 170 | // the missing `className="middle"` prop 171 | expect(renderer, 'to contain exactly', two); 172 | 173 | ``` 174 | 175 | The same thing applies to children for `'to contain'` as for `'to have rendered'`. 176 | 177 | # Strings 178 | 179 | String content is split up by React when you have embedded variables. 180 | 181 | For example: 182 | 183 | ```js 184 | { 185 | render: function() { 186 | return ( 187 |
188 | Click on {this.props.clickCount} times 189 |
190 | ); 191 | } 192 | } 193 | ``` 194 | 195 | This actually produces 3 "child" elements of the div, `Click on `, the `clickCount` and the ` times` 196 | To make this simpler, `unexpected-react-shallow` concatenates these values so you can simply test the 197 | previous example as follows: 198 | 199 | ```js 200 | expect(renderer, 'to have rendered', 201 |
202 | Clicked on 3 times 203 |
); 204 | ``` 205 | 206 | If you use the `exactly` variants of the assertions, you will need to split up your assertion in the same way 207 | 208 | e.g. 209 | ```js 210 | expect(renderer, 'to have exactly rendered', 211 |
212 | Clicked on {3} times 213 |
); 214 | ``` 215 | 216 | # Contributing 217 | 218 | We welcome pull requests, bug reports, and extra test cases. If you find something that doesn't work 219 | as you believe it should, raise an issue! 220 | 221 | # Thanks 222 | 223 | Special thanks to all of the [unexpected](http://github.com/unexpectedjs) team. The quality failure 224 | messages and diffs have made a huge difference to the way I test, and how quickly issues are resolved. 225 | 226 | A huge thanks to @Munter, who wrote [unexpected-dom](http://github.com/munter/unexpected-dom), which enables 227 | testing DOM elements easily - it's perfect for more involved tests if you're using React and something 228 | like [jsdom](https://github.com/tmpvar/jsdom). A lot (most!) of the code for `unexpected-react-shallow` was 229 | taken from `unexpected-dom`. 230 | 231 | 232 | # License 233 | 234 | MIT 235 | 236 | -------------------------------------------------------------------------------- /src/lib/diff.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ArrayChanges = require('array-changes'); 3 | var Element = require('./element'); 4 | var Equality = require('./equality'); 5 | var Write = require('./write'); 6 | 7 | var internals = {}; 8 | 9 | internals.isNativeType = function (element) { 10 | var type = typeof element; 11 | return type === 'string' || type === 'number' || type === 'undefined'; 12 | }; 13 | 14 | internals.diffChildren = function diffChildren(actual, expected, output, diff, inspect, equal, options) { 15 | if (typeof actual === 'string' && typeof expected === 'string') { 16 | var stringDiff = diff(actual.trim(), expected.trim()); 17 | output.i().block(stringDiff.diff); 18 | return; 19 | } 20 | 21 | var actualChildren = Element.getChildrenArray(actual, { 22 | normalize: !options || !options.exactly 23 | }); 24 | 25 | var expectedChildren = Element.getChildrenArray(expected, { 26 | normalize: !options || !options.exactly 27 | }); 28 | 29 | var changes = ArrayChanges(actualChildren, expectedChildren, 30 | function (a, b) { 31 | return Equality.elementsMatch(a, b, equal, options); 32 | }, 33 | 34 | function (a, b) { 35 | // Figure out whether a and b are the same element so they can be diffed inline. 36 | var aIsNativeType = internals.isNativeType(a); 37 | var bIsNativeType = internals.isNativeType(b); 38 | if (aIsNativeType && bIsNativeType) { 39 | return true; 40 | } 41 | 42 | if (aIsNativeType !== bIsNativeType) { 43 | return false; 44 | } 45 | 46 | 47 | return ( 48 | Element.getName(a) === Element.getName(b) 49 | ); 50 | } ); 51 | 52 | changes.forEach(function (diffItem, index) { 53 | output.i().block(function () { 54 | var type = diffItem.type; 55 | 56 | if (type === 'insert') { 57 | this.annotationBlock(function () { 58 | this.error('missing '); 59 | if (typeof diffItem.value === 'string') { 60 | this.block(function () { 61 | this.text(diffItem.value); 62 | }); 63 | } else { 64 | this.block(inspect(diffItem.value)); 65 | } 66 | }); 67 | } else if (type === 'remove') { 68 | if (typeof diffItem.value === 'string') { 69 | this.block(function () { 70 | this.text(diffItem.value).sp().error('// should be removed'); 71 | }); 72 | } else { 73 | var actualInspectBlock = inspect(diffItem.value); 74 | this.block(actualInspectBlock).sp().annotationBlock(function () { 75 | this.error('should be removed'); 76 | this.nl(actualInspectBlock.size().height - 1); 77 | }); 78 | } 79 | } else if (type === 'equal') { 80 | if (typeof diffItem.value === 'string') { 81 | this.block(function () { 82 | this.text(diffItem.value); 83 | }); 84 | } else { 85 | this.block(inspect(diffItem.value)); 86 | } 87 | } else { 88 | var valueDiff = internals.diffElements(diffItem.value, diffItem.expected, output.clone(), diff, inspect, equal, options); 89 | 90 | if (valueDiff) { 91 | this.block(valueDiff.diff); 92 | } 93 | } 94 | }).nl(index < changes.length - 1 ? 1 : 0); 95 | }); 96 | }; 97 | 98 | exports.diffElements = internals.diffElements = 99 | function diffElements(actual, expected, output, diff, inspect, equal, options) { 100 | var result = { 101 | diff: output, 102 | inline: true 103 | }; 104 | var actualIsNative = internals.isNativeType(actual); 105 | var expectedIsNative = internals.isNativeType(expected); 106 | if (actualIsNative && expectedIsNative) { 107 | return diff('' + actual, '' + expected); 108 | } 109 | 110 | var stringVsComponentMismatch = actualIsNative !== expectedIsNative; 111 | var emptyElements = !stringVsComponentMismatch && (!actual.props || React.Children.count(actual.props.children) === 0) && 112 | (!expected.props || React.Children.count(expected.props.children) === 0); 113 | 114 | var propsMatching = !stringVsComponentMismatch && Equality.propsMatch(Element.getProps(actual), Element.getProps(expected), equal, options); 115 | 116 | var conflictingElement = stringVsComponentMismatch || 117 | Element.getName(actual) !== Element.getName(expected) || 118 | !propsMatching; 119 | 120 | if (stringVsComponentMismatch) { 121 | 122 | if (actualIsNative) { 123 | output.append('' + actual) 124 | .sp() 125 | .annotationBlock(function () { 126 | this.error('should be').sp().block(inspect(expected)); 127 | }); 128 | } else if (expectedIsNative) { 129 | 130 | var actualOutput = inspect(actual); 131 | output.block(actualOutput) 132 | .annotationBlock(function () { 133 | this.nl(actualOutput.size().height - 1); 134 | this.error('should be').sp().append(inspect(expected)); 135 | }); 136 | } 137 | 138 | } else if (conflictingElement) { 139 | var canContinueLine = true; 140 | output 141 | .prismPunctuation('<') 142 | .prismTag(Element.getName(actual)); 143 | if (Element.getName(actual) !== Element.getName(expected)) { 144 | output.sp().annotationBlock(function () { 145 | this.error('should be').sp().prismTag(Element.getName(expected)); 146 | }).nl(); 147 | canContinueLine = false; 148 | } 149 | var actualProps = Element.getProps(actual); 150 | var expectedProps = Element.getProps(expected); 151 | Object.keys(actualProps).forEach(function (propName) { 152 | output.sp(canContinueLine ? 1 : 2 + Element.getName(actual).length); 153 | if (propName in expectedProps) { 154 | if (actualProps[propName] === expectedProps[propName]) { 155 | Write.writeProp(output, propName, actualProps[propName]); 156 | canContinueLine = true; 157 | } else { 158 | Write.writeProp(output, propName, actualProps[propName], inspect); 159 | output.sp().annotationBlock(function () { 160 | var diffResults = diff(actualProps[propName], expectedProps[propName]); 161 | if (diffResults) { 162 | this.append(diffResults.diff); 163 | } else { 164 | this.error('should equal').sp().append(inspect(expectedProps[propName])); 165 | } 166 | 167 | }).nl(); 168 | canContinueLine = false; 169 | } 170 | delete expectedProps[propName]; 171 | } else if (options.exactly) { 172 | Write.writeProp(output, propName, actualProps[propName]); 173 | output.sp().annotationBlock(function () { 174 | this.error('should be removed'); 175 | }).nl(); 176 | canContinueLine = false; 177 | } 178 | }); 179 | Object.keys(expectedProps).forEach(function (propName) { 180 | output.sp(canContinueLine ? 1 : 2 + Element.getName(actual).length); 181 | output.annotationBlock(function () { 182 | this.error('missing').sp(); 183 | Write.writeProp(this, propName, expectedProps[propName]); 184 | }).nl(); 185 | canContinueLine = false; 186 | }); 187 | output.prismPunctuation('>'); 188 | } else { 189 | output.prismPunctuation('<') 190 | .prismTag(Element.getName(actual)); 191 | Write.writeProps(output, actual.props); 192 | output.prismPunctuation('>'); 193 | } 194 | 195 | if (!stringVsComponentMismatch && !emptyElements) { 196 | output.nl().indentLines(); 197 | internals.diffChildren(actual.props.children, expected.props.children, output, diff, inspect, equal, options); 198 | output.nl().outdentLines(); 199 | } 200 | 201 | if (!stringVsComponentMismatch) { 202 | output.code('', 'html'); 203 | } 204 | return result; 205 | }; 206 | -------------------------------------------------------------------------------- /tests/unexpected-react-shallow.spec.js: -------------------------------------------------------------------------------- 1 | var Unexpected = require('unexpected'); 2 | var UnexpectedReactShallow = require('../src/unexpected-react-shallow'); 3 | var React = require('react/addons'); 4 | 5 | var expect = Unexpected.clone(); 6 | 7 | var ES5Component = React.createClass({ 8 | displayName: 'ES5Component', 9 | render() { return null;} 10 | }); 11 | 12 | function createNoNameComponent() { 13 | return React.createClass({ 14 | displayName: '', 15 | render() { return null;} 16 | }); 17 | } 18 | 19 | var NoNameComponent = createNoNameComponent(); 20 | 21 | class ClassComponent extends React.Component { 22 | render() { 23 | return ( 24 |
25 | {this.props.content} 26 |
27 | ); 28 | } 29 | } 30 | 31 | class MyDiv extends React.Component { 32 | render() { 33 | return React.createElement('div', this.props); 34 | } 35 | } 36 | 37 | const FunctionComp = function (props) { 38 | return (
); 39 | }; 40 | 41 | const versionParts = React.version.split('.'); 42 | const isReact014 = (parseFloat(versionParts[0] + '.' + versionParts[1]) >= 0.14); 43 | 44 | describe('unexpected-react-shallow', () => { 45 | 46 | var testExpect; 47 | var renderer, renderer2; 48 | 49 | beforeEach(function () { 50 | renderer = React.addons.TestUtils.createRenderer(); 51 | renderer2 = React.addons.TestUtils.createRenderer(); 52 | 53 | testExpect = Unexpected.clone() 54 | .installPlugin(UnexpectedReactShallow); 55 | }); 56 | 57 | it('identifies a ReactElement', () => { 58 | 59 | renderer.render(); 60 | var element = renderer.getRenderOutput(); 61 | 62 | testExpect(element, 'to be a', 'ReactElement'); 63 | }); 64 | 65 | if (isReact014) { 66 | it('identifies a pure function component', () => { 67 | 68 | renderer.render(); 69 | var element = renderer.getRenderOutput(); 70 | 71 | testExpect(element, 'to be a', 'ReactElement'); 72 | }); 73 | } 74 | 75 | it('identifies a ShallowRenderer', () => { 76 | 77 | testExpect(renderer, 'to be a', 'ReactShallowRenderer'); 78 | }); 79 | 80 | describe('inspect', () => { 81 | 82 | it('outputs a tag element with no props', () => { 83 | 84 | renderer.render(); 85 | expect(() => testExpect(renderer, 'to equal', ''), 'to throw', 86 | "expected
to equal ''"); 87 | }); 88 | 89 | it('outputs a tag element with string props', () => { 90 | 91 | renderer.render(); 92 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 93 | 'expected
to equal 1'); 94 | }); 95 | 96 | it('outputs a tag element with number props', () => { 97 | 98 | renderer.render(); 99 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 100 | 'expected
to equal 1'); 101 | }); 102 | 103 | it('outputs a tag element with boolean props', () => { 104 | 105 | renderer.render(); 106 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 107 | 'expected
to equal 1'); 108 | }); 109 | 110 | it('outputs a tag element with null props', () => { 111 | 112 | renderer.render(); 113 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 114 | 'expected
to equal 1'); 115 | }); 116 | 117 | it('outputs a tag element with an undefined prop', () => { 118 | 119 | renderer.render(); 120 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 121 | 'expected
to equal 1'); 122 | }); 123 | 124 | it('outputs a tag element with an object prop', () => { 125 | 126 | var obj = { some: 'prop' }; 127 | renderer.render(); 128 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 129 | 'expected
to equal 1'); 130 | }); 131 | 132 | it('outputs a tag element with an function prop', () => { 133 | 134 | var fn = function (a, b) { return a + b; }; 135 | renderer.render(); 136 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 137 | 'expected
to equal 1'); 138 | }); 139 | 140 | it('outputs a tag with a single string child', () => { 141 | 142 | renderer.render(some content); 143 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 144 | 'expected\n' + 145 | '
\n' + 146 | ' some content\n' + 147 | '
\n' + 148 | 'to equal 1'); 149 | }); 150 | 151 | it('outputs an ES5 createClass component props and no children', () => { 152 | 153 | renderer.render(some content); 154 | 155 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 156 | 'expected\n' + 157 | '
\n' + 158 | ' \n' + 159 | ' some content\n' + 160 | ' \n' + 161 | '
\n' + 162 | 'to equal 1'); 163 | }); 164 | 165 | it('outputs an ES5 class component props and children', () => { 166 | 167 | renderer.render(some content); 168 | 169 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 170 | 'expected\n' + 171 | '
\n' + 172 | ' \n' + 173 | ' some content\n' + 174 | ' \n' + 175 | '
\n' + 176 | 'to equal 1'); 177 | }); 178 | 179 | it('outputs a set of deep nested components', () => { 180 | 181 | renderer.render( 182 | 183 | 184 | 185 | some content 186 | 187 | 188 | some different content 189 | 190 | 191 | ); 192 | 193 | expect(() => testExpect(renderer, 'to equal', 1), 'to throw', 194 | 'expected\n' + 195 | '
\n' + 196 | ' \n' + 197 | ' \n' + 198 | ' some content\n' + 199 | ' \n' + 200 | ' \n' + 201 | ' some different content\n' + 202 | ' \n' + 203 | ' \n' + 204 | '
\n' + 205 | 'to equal 1'); 206 | }); 207 | 208 | it('outputs a directly created custom ReactElement', function () { 209 | 210 | expect(() => testExpect(, 'to equal', 1), 211 | 'to throw', 'expected to equal 1'); 212 | }); 213 | 214 | it('outputs a directly created native ReactElement', function () { 215 | 216 | expect(() => testExpect(, 'to equal', 1), 217 | 'to throw', 'expected to equal 1'); 218 | }); 219 | 220 | it('outputs a directly created inline element (React 0.14)', function () { 221 | 222 | var inlineElement = { 223 | type: 'div', 224 | props: { 225 | className: 'foo' 226 | }, 227 | key: null, 228 | ref: null 229 | }; 230 | 231 | expect(() => testExpect(inlineElement, 'to equal', 1), 'to throw', 232 | 'expected
to equal 1'); 233 | }); 234 | 235 | /* This test is disabled. There's something with the way babel(possibly) is 236 | * converting the NoNameComponent - the name is coming out as 'Constructor' 237 | * Wallaby.js runs it fine (although wallaby.js has other issues with displayNames not appearing 238 | * via the babel transform for createClass, and only appearing if displayName is explicitely set. 239 | * 240 | it('outputs a component with no-display-name', function () { 241 | 242 | expect(() => testExpect(, 'to equal', 1), 'to throw', 243 | 'expected to equal 1'); 244 | }); 245 | */ 246 | }); 247 | 248 | describe('diff', () => { 249 | 250 | it('diffs within simple text content inside native element', () => { 251 | 252 | renderer.render(Some simple content); 253 | var expected =
Different content
; 254 | // testExpect(renderer, 'to have rendered', expected); 255 | 256 | expect(() => testExpect(renderer, 'to have rendered', expected), 'to throw', 257 | 'expected\n' + 258 | '
\n' + 259 | ' Some simple content\n' + 260 | '
\n' + 261 | 'to have rendered\n' + 262 | '
\n' + 263 | ' Different content\n' + 264 | '
\n' + 265 | '\n' + 266 | '
\n'+ 267 | ' -Some simple content\n' + 268 | ' +Different content\n' + 269 | '
' 270 | ); 271 | 272 | }); 273 | 274 | it('shows changed props within a simple native element', () => { 275 | 276 | renderer.render(Some simple content); 277 | 278 | expect(() => testExpect(renderer, 'to have rendered', 279 |
280 | Some simple content 281 |
), 'to throw', 282 | 'expected\n' + 283 | '
\n' + 284 | ' Some simple content\n' + 285 | '
\n' + 286 | 'to have rendered\n' + 287 | '
\n' + 288 | ' Some simple content\n' + 289 | '
\n' + 290 | '\n' + 291 | '
\n' + 294 | ' Some simple content\n' + 295 | '
'); 296 | }); 297 | 298 | it('shows missing props within a simple native element', () => { 299 | 300 | renderer.render(Some simple content); 301 | 302 | expect(() => testExpect(renderer, 'to have rendered', 303 |
304 | Some simple content 305 |
), 'to throw', 306 | 'expected\n' + 307 | '
\n' + 308 | ' Some simple content\n' + 309 | '
\n' + 310 | 'to have rendered\n' + 311 | '
\n' + 312 | ' Some simple content\n' + 313 | '
\n' + 314 | '\n' + 315 | '
\n' + 318 | ' Some simple content\n' + 319 | '
'); 320 | }); 321 | 322 | it('ignores extra props within a simple native element', () => { 323 | 324 | renderer.render(Some simple content); 325 | 326 | testExpect(renderer, 'to have rendered', 327 |
328 | Some simple content 329 |
); 330 | }); 331 | 332 | it('does not ignore extra props when using `exactly`', function () { 333 | 334 | renderer.render(Some simple content); 335 | expect(() => { 336 | testExpect(renderer, 'to have exactly rendered', 337 |
338 | Some simple content 339 |
); 340 | }, 'to throw', 341 | 'expected\n' + 342 | '
\n' + 343 | ' Some simple content\n' + 344 | '
\n' + 345 | 'to have exactly rendered\n' + 346 | '
\n' + 347 | ' Some simple content\n' + 348 | '
\n' + 349 | '\n' + 350 | '
\n' + 352 | ' Some simple content\n' + 353 | '
'); 354 | }); 355 | 356 | it('matches props on a custom component', function () { 357 | 358 | renderer.render( 359 | 360 | 361 | foo 362 | 363 | 364 | ); 365 | 366 | testExpect(renderer, 'to have exactly rendered', 367 |
368 | 369 | foo 370 | 371 |
); 372 | }); 373 | 374 | it('matches content rendered as a number', function () { 375 | 376 | renderer.render(); 377 | testExpect(renderer, 'to have rendered',
0
); 378 | }); 379 | 380 | it('matches content as undefined', function () { 381 | 382 | renderer.render(); 383 | testExpect(renderer, 'to have rendered',
); 384 | }); 385 | 386 | it('matches content as null', function () { 387 | 388 | renderer.render(); 389 | testExpect(renderer, 'to have rendered',
); 390 | }); 391 | 392 | it('highlights diffs on a nested custom component', function () { 393 | 394 | renderer.render( 395 | 396 | 397 | foo 398 | 399 | 400 | ); 401 | 402 | expect(() => testExpect(renderer, 'to have exactly rendered', 403 |
404 | 405 | foo 406 | 407 |
), 'to throw', 408 | 'expected\n' + 409 | '
\n' + 410 | ' \n' + 411 | ' \n' + 412 | ' foo\n' + 413 | ' \n' + 414 | ' \n' + 415 | '
\n' + 416 | 'to have exactly rendered\n' + 417 | '
\n' + 418 | ' \n' + 419 | ' \n' + 420 | ' foo\n' + 421 | ' \n' + 422 | ' \n' + 423 | '
\n' + 424 | '\n' + 425 | '
\n' + 426 | ' \n' + 428 | ' \n' + 429 | ' foo\n' + 430 | ' \n' + 431 | ' \n' + 432 | '
'); 433 | 434 | }); 435 | 436 | 437 | it('ignores extra props on a nested custom component when not using `exactly`', function () { 438 | 439 | renderer.render( 440 | 441 | 442 | foo 443 | 444 | 445 | ); 446 | 447 | testExpect(renderer, 'to have rendered', 448 |
449 | 450 | foo 451 | 452 |
); 453 | 454 | }); 455 | 456 | it('highlights extra props on a nested custom component when using `exactly`', function () { 457 | 458 | renderer.render( 459 | 460 | 461 | foo 462 | 463 | 464 | ); 465 | 466 | expect(() => testExpect(renderer, 'to have exactly rendered', 467 |
468 | 469 | foo 470 | 471 |
), 'to throw', 472 | 'expected\n' + 473 | '
\n' + 474 | ' \n' + 475 | ' \n' + 476 | ' foo\n' + 477 | ' \n' + 478 | ' \n' + 479 | '
\n' + 480 | 'to have exactly rendered\n' + 481 | '
\n' + 482 | ' \n' + 483 | ' \n' + 484 | ' foo\n' + 485 | ' \n' + 486 | ' \n' + 487 | '
\n' + 488 | '\n' + 489 | '
\n' + 490 | ' \n' + 492 | ' \n' + 493 | ' foo\n' + 494 | ' \n' + 495 | ' \n' + 496 | '
'); 497 | 498 | 499 | }); 500 | 501 | it('matches array of children in a custom component', function () { 502 | 503 | renderer.render( 504 | 505 | 506 | 1 507 | 2 508 | 509 | 510 | ); 511 | 512 | testExpect(renderer, 'to have exactly rendered', 513 |
514 | 515 | 1 516 | 2 517 | 518 |
); 519 | }); 520 | 521 | it('highlights a removed item in an array of children in a custom component', function () { 522 | 523 | renderer.render( 524 | 525 | 526 | 1 527 | 3 528 | 529 | 530 | ); 531 | 532 | expect(() => testExpect(renderer, 'to have exactly rendered', 533 |
534 | 535 | 1 536 | 2 537 | 3 538 | 539 |
), 'to throw', 540 | 'expected\n' + 541 | '
\n' + 542 | ' \n' + 543 | ' \n' + 544 | ' 1\n' + 545 | ' \n' + 546 | ' \n' + 547 | ' 3\n' + 548 | ' \n' + 549 | ' \n' + 550 | '
\n' + 551 | 'to have exactly rendered\n' + 552 | '
\n' + 553 | ' \n' + 554 | ' \n' + 555 | ' 1\n' + 556 | ' \n' + 557 | ' \n' + 558 | ' 2\n' + 559 | ' \n' + 560 | ' \n' + 561 | ' 3\n' + 562 | ' \n' + 563 | ' \n' + 564 | '
\n' + 565 | '\n' + 566 | '
\n' + 567 | ' \n' + 568 | ' \n' + 569 | ' 1\n' + 570 | ' \n' + 571 | ' \n' + 574 | ' -3\n' + 575 | ' +2\n' + 576 | ' \n' + 577 | ' // missing \n' + 578 | ' // 3\n' + 579 | ' // \n' + 580 | ' \n' + 581 | '
'); 582 | }); 583 | 584 | 585 | it('highlights an added item in an array of children in a custom component', function () { 586 | 587 | renderer.render( 588 | 589 | 590 | 1 591 | 3 592 | 593 | 594 | ); 595 | 596 | expect(() => testExpect(renderer, 'to have exactly rendered', 597 |
598 | 599 | 1 600 | 2 601 | 3 602 | 603 |
604 | ), 'to throw', 605 | 'expected\n' + 606 | '
\n' + 607 | ' \n' + 608 | ' \n' + 609 | ' 1\n' + 610 | ' \n' + 611 | ' \n' + 612 | ' 3\n' + 613 | ' \n' + 614 | ' \n' + 615 | '
\n' + 616 | 'to have exactly rendered\n' + 617 | '
\n' + 618 | ' \n' + 619 | ' \n' + 620 | ' 1\n' + 621 | ' \n' + 622 | ' \n' + 623 | ' 2\n' + 624 | ' \n' + 625 | ' \n' + 626 | ' 3\n' + 627 | ' \n' + 628 | ' \n' + 629 | '
\n' + 630 | '\n' + 631 | '
\n' + 632 | ' \n' + 633 | ' \n' + 634 | ' 1\n' + 635 | ' \n' + 636 | ' \n' + 639 | ' -3\n' + 640 | ' +2\n' + 641 | ' \n' + 642 | ' // missing \n' + 643 | ' // 3\n' + 644 | ' // \n' + 645 | ' \n' + 646 | '
'); 647 | 648 | }); 649 | 650 | it('accepts added children at the end of an array when not using `exactly`', function () { 651 | 652 | renderer.render( 653 | 654 | 655 | 1 656 | 2 657 | 3 658 | 659 | 660 | ); 661 | 662 | testExpect(renderer, 'to have rendered', 663 |
664 | 665 | 1 666 | 2 667 | 668 |
); 669 | }); 670 | 671 | it('accepts added children in the middle of an array when not using `exactly`', function () { 672 | 673 | renderer.render( 674 | 675 | 676 | 1 677 | 2 678 | 3 679 | 680 | 681 | ); 682 | 683 | testExpect(renderer, 'to have rendered', 684 |
685 | 686 | 1 687 | 3 688 | 689 |
); 690 | }); 691 | 692 | it('highlights different typed children', function () { 693 | 694 | renderer.render( 695 | 696 | 697 | 698 | 699 | 700 | ); 701 | 702 | expect(() => testExpect(renderer, 'to have rendered', 703 |
704 | 705 | 706 | 707 |
), 'to throw', 708 | 'expected\n' + 709 | '
\n' + 710 | ' \n' + 711 | ' \n' + 712 | ' \n' + 713 | '
\n' + 714 | 'to have rendered\n' + 715 | '
\n' + 716 | ' \n' + 717 | ' \n' + 718 | ' \n' + 719 | '
\n' + 720 | '\n' + 721 | '
\n' + 722 | ' \n' + 723 | ' \n' + 725 | ' \n' + 726 | '
'); 727 | }); 728 | 729 | it('matches matching objects as props deeply not be reference', function () { 730 | 731 | var objectA = { some: 'prop', arr: [ 1, 2, 3 ] }; 732 | var objectB = { some: 'prop', arr: [ 1, 2, 3 ] }; 733 | 734 | renderer.render( 735 | 736 | 737 | 738 | ); 739 | 740 | testExpect(renderer, 'to have rendered', 741 |
742 | 743 |
); 744 | }); 745 | 746 | it('highlights different objects as props deeply not be reference', function () { 747 | 748 | var objectA = { some: 'prop', arr: [ 1, 2, 3 ] }; 749 | var objectB = { some: 'prop', arr: [ 1, 2, 4 ] }; 750 | 751 | renderer.render( 752 | 753 | 754 | 755 | ); 756 | 757 | expect(() => testExpect(renderer, 'to have rendered', 758 |
759 | 760 |
), 'to throw', 761 | 'expected\n' + 762 | '
\n' + 763 | ' \n' + 764 | '
\n' + 765 | 'to have rendered\n' + 766 | '
\n' + 767 | ' \n' + 768 | '
\n' + 769 | '\n' + 770 | '
\n' + 771 | " \n' + 780 | '
'); 781 | }); 782 | 783 | it('matches a multi-text child', function () { 784 | 785 | var content = 'test'; 786 | var content2 = 'test'; 787 | renderer.render( 788 | 789 | 790 | some text {content} 791 | 792 | 793 | ); 794 | 795 | testExpect(renderer, 'to have rendered', 796 |
797 | 798 | some text {content2} 799 | 800 |
); 801 | }); 802 | 803 | it('matches a multi-text child to a single text child without exactly', function () { 804 | 805 | var content = 'test'; 806 | renderer.render( 807 | 808 | 809 | some text {content} 810 | 811 | 812 | ); 813 | 814 | testExpect(renderer, 'to have rendered', 815 |
816 | 817 | some text test 818 | 819 |
); 820 | }); 821 | 822 | it('matches a multi-text child including a number to a single text child without exactly', function () { 823 | 824 | var content = 5; 825 | renderer.render( 826 | 827 | 828 | some {content} value 829 | 830 | 831 | ); 832 | 833 | testExpect(renderer, 'to have rendered', 834 |
835 | 836 | some 5 value 837 | 838 |
); 839 | }); 840 | 841 | it('matches a multi-text child including a null to a single text child without exactly', function () { 842 | 843 | var content = null; 844 | renderer.render( 845 | 846 | 847 | some {content} value 848 | 849 | 850 | ); 851 | 852 | testExpect(renderer, 'to have rendered', 853 |
854 | 855 | some value 856 | 857 |
); 858 | }); 859 | 860 | it('matches a multi-text child including an undefined to a single text child without exactly', function () { 861 | 862 | var content = undefined; 863 | renderer.render( 864 | 865 | 866 | some {content} value 867 | 868 | 869 | ); 870 | 871 | testExpect(renderer, 'to have rendered', 872 |
873 | 874 | some value 875 | 876 |
); 877 | }); 878 | 879 | it('matches a multi-text child including a boolean to a single text child without exactly', function () { 880 | 881 | var content = true; 882 | renderer.render( 883 | 884 | 885 | some {content} value 886 | 887 | 888 | ); 889 | 890 | // An inline boolean is converted to null, so this "just works" 891 | testExpect(renderer, 'to have rendered', 892 |
893 | 894 | some value 895 | 896 |
); 897 | }); 898 | 899 | it('highlights string break-down changes in a multi-text child with `exactly`', function () { 900 | 901 | var content = 'test'; 902 | renderer.render( 903 | 904 | 905 | some text {content} 906 | 907 | 908 | ); 909 | 910 | expect(() => testExpect(renderer, 'to have exactly rendered', 911 |
912 | 913 | some text test 914 | 915 |
), 'to throw', 916 | 'expected\n' + 917 | '
\n' + 918 | ' \n' + 919 | ' some text test\n' + 920 | ' \n' + 921 | '
\n' + 922 | 'to have exactly rendered\n' + 923 | '
\n' + 924 | ' \n' + 925 | ' some text test\n' + 926 | ' \n' + 927 | '
\n' + 928 | '\n' + 929 | '
\n' + 930 | ' \n' + 931 | ' -some text \n' + 932 | ' +some text test\n' + 933 | ' test // should be removed\n' + 934 | ' \n' + 935 | '
'); 936 | }); 937 | 938 | it('highlights changed in a multi-text child', function () { 939 | 940 | var content = 'foo'; 941 | var content2 = 'bar'; 942 | renderer.render( 943 | 944 | 945 | some text {content} 946 | 947 | 948 | ); 949 | 950 | expect(() => testExpect(renderer, 'to have rendered', 951 |
952 | 953 | some text {content2} 954 | 955 |
), 'to throw', 956 | 'expected\n' + 957 | '
\n' + 958 | ' \n' + 959 | ' some text foo\n' + 960 | ' \n' + 961 | '
\n' + 962 | 'to have rendered\n' + 963 | '
\n' + 964 | ' \n' + 965 | ' some text bar\n' + 966 | ' \n' + 967 | '
\n' + 968 | '\n' + 969 | '
\n' + 970 | ' \n' + 971 | ' -some text foo\n' + 972 | ' +some text bar\n' + 973 | ' \n' + 974 | '
'); 975 | }); 976 | 977 | it('matches a mixed content child', function () { 978 | 979 | var content = ; 980 | var content2 = ; 981 | 982 | renderer.render( 983 | 984 | 985 | some text {content} 986 | 987 | 988 | ); 989 | 990 | testExpect(renderer, 'to have rendered', 991 |
992 | 993 | some text {content2} 994 | 995 |
); 996 | }); 997 | 998 | it('highlights changes in a mixed content child', function () { 999 | 1000 | var content = ; 1001 | var content2 = ; 1002 | 1003 | renderer.render( 1004 | 1005 | 1006 | some text {content} 1007 | 1008 | 1009 | ); 1010 | 1011 | expect(() => testExpect(renderer, 'to have rendered', 1012 |
1013 | 1014 | some text {content2} 1015 | 1016 |
), 'to throw', 1017 | 'expected\n' + 1018 | '
\n' + 1019 | ' \n' + 1020 | ' some text \n' + 1021 | ' \n' + 1022 | ' \n' + 1023 | '
\n' + 1024 | 'to have rendered\n' + 1025 | '
\n' + 1026 | ' \n' + 1027 | ' some text \n' + 1028 | ' \n' + 1029 | ' \n' + 1030 | '
\n' + 1031 | '\n' + 1032 | '
\n' + 1033 | ' \n' + 1034 | ' some text \n' + 1035 | ' \n' + 1038 | ' \n' + 1039 | '
'); 1040 | }); 1041 | 1042 | it('highlights removals in multi-string content', function () { 1043 | var content = 'test'; 1044 | 1045 | renderer.render( 1046 | 1047 | 1048 | some text 1049 | 1050 | 1051 | ); 1052 | expect(() => testExpect(renderer, 'to have exactly rendered', 1053 |
1054 | 1055 | some text {content} 1056 | 1057 |
), 'to throw', 1058 | 'expected\n' + 1059 | '
\n' + 1060 | ' \n' + 1061 | ' some text\n' + 1062 | ' \n' + 1063 | '
\n' + 1064 | 'to have exactly rendered\n' + 1065 | '
\n' + 1066 | ' \n' + 1067 | ' some text test\n' + 1068 | ' \n' + 1069 | '
\n' + 1070 | '\n' + 1071 | '
\n' + 1072 | ' \n' + 1073 | ' -some text\n' + 1074 | ' +some text \n' + 1075 | ' // missing test\n' + 1076 | ' \n' + 1077 | '
'); 1078 | }); 1079 | 1080 | it('highlights removals in complex content with exactly', function () { 1081 | var content = 'test'; 1082 | 1083 | renderer.render( 1084 | 1085 | 1086 |
1087 | 1088 | foo 1089 | 1090 | 1091 | ); 1092 | 1093 | expect(() => testExpect(renderer, 'to have exactly rendered', 1094 |
1095 | 1096 |
1097 | foo 1098 | 1099 |
), 'to throw', 1100 | 'expected\n' + 1101 | '
\n' + 1102 | ' \n' + 1103 | '
\n' + 1104 | ' \n' + 1105 | ' \n' + 1106 | ' foo\n' + 1107 | ' \n' + 1108 | ' \n' + 1109 | '
\n' + 1110 | 'to have exactly rendered\n' + 1111 | '
\n' + 1112 | ' \n' + 1113 | '
\n' + 1114 | ' \n' + 1115 | ' foo\n' + 1116 | ' \n' + 1117 | ' \n' + 1118 | '
\n' + 1119 | '\n' + 1120 | '
\n' + 1121 | ' \n' + 1122 | '
\n' + 1123 | ' // should be removed\n' + 1124 | ' \n' + 1125 | ' foo\n' + 1126 | ' \n' + 1127 | ' \n' + 1128 | '
'); 1129 | }); 1130 | 1131 | it('highlights removals in complex content with `with all children`', function () { 1132 | var content = 'test'; 1133 | 1134 | renderer.render( 1135 | 1136 | 1137 |
1138 | 1139 | foo 1140 | 1141 | 1142 | ); 1143 | 1144 | expect(() => testExpect(renderer, 'to have rendered with all children', 1145 |
1146 | 1147 |
1148 | foo 1149 | 1150 |
), 'to throw', 1151 | 'expected\n' + 1152 | '
\n' + 1153 | ' \n' + 1154 | '
\n' + 1155 | ' \n' + 1156 | ' \n' + 1157 | ' foo\n' + 1158 | ' \n' + 1159 | ' \n' + 1160 | '
\n' + 1161 | 'to have rendered with all children\n' + 1162 | '
\n' + 1163 | ' \n' + 1164 | '
\n' + 1165 | ' \n' + 1166 | ' foo\n' + 1167 | ' \n' + 1168 | ' \n' + 1169 | '
\n' + 1170 | '\n' + 1171 | '
\n' + 1172 | ' \n' + 1173 | '
\n' + 1174 | ' // should be removed\n' + 1175 | ' \n' + 1176 | ' foo\n' + 1177 | ' \n' + 1178 | ' \n' + 1179 | '
'); 1180 | }); 1181 | 1182 | it('highlights the block removal for deep children', function () { 1183 | renderer.render( 1184 | 1185 |
    1186 |
  • one
  • 1187 |
  • two
  • 1188 |
  • three
  • 1189 |
1190 |
1191 | ); 1192 | 1193 | expect(() => testExpect(renderer, 'to have rendered with all children', 1194 |
1195 |
    1196 |
  • one
  • 1197 |
  • two
  • 1198 |
1199 |
), 'to throw', 1200 | 'expected\n' + 1201 | '
\n' + 1202 | '
    \n' + 1203 | '
  • \n' + 1204 | ' one\n' + 1205 | '
  • \n' + 1206 | '
  • \n' + 1207 | ' two\n' + 1208 | '
  • \n' + 1209 | '
  • \n' + 1210 | ' three\n' + 1211 | '
  • \n' + 1212 | '
\n' + 1213 | '
\n' + 1214 | 'to have rendered with all children\n' + 1215 | '
\n' + 1216 | '
    \n' + 1217 | '
  • \n' + 1218 | ' one\n' + 1219 | '
  • \n' + 1220 | '
  • \n' + 1221 | ' two\n' + 1222 | '
  • \n' + 1223 | '
\n' + 1224 | '
\n' + 1225 | '\n' + 1226 | '
\n' + 1227 | '
    \n' + 1228 | '
  • \n' + 1229 | ' one\n' + 1230 | '
  • \n' + 1231 | '
  • \n' + 1232 | ' two\n' + 1233 | '
  • \n' + 1234 | '
  • // should be removed\n' + 1235 | ' three //\n' + 1236 | '
  • //\n' + 1237 | '
\n' + 1238 | '
'); 1239 | }); 1240 | 1241 | it('identifies when a string element should be a real element', function () { 1242 | 1243 | renderer.render( 1244 | 1245 | 123 1246 | ); 1247 | 1248 | expect(() => testExpect(renderer, 'to have rendered', 1249 |
1250 | 123 1251 |
), 'to throw', 1252 | 'expected\n' + 1253 | '
\n' + 1254 | ' \n' + 1255 | ' 123\n' + 1256 | ' \n' + 1257 | '
\n' + 1258 | 'to have rendered\n' + 1259 | '
\n' + 1260 | ' 123\n' + 1261 | '
\n' + 1262 | '\n' + 1263 | '
\n' + 1264 | ' // \n' + 1265 | ' 123 //\n' + 1266 | " // should be '123'\n" + 1267 | '
'); 1268 | }); 1269 | 1270 | it('identifies when a number element should be a real element', function () { 1271 | 1272 | renderer.render( 1273 | 1274 | {123} 1275 | ); 1276 | 1277 | expect(() => testExpect(renderer, 'to have rendered', 1278 |
1279 | {123} 1280 |
), 'to throw', 1281 | 'expected\n' + 1282 | '
\n' + 1283 | ' \n' + 1284 | ' 123\n' + 1285 | ' \n' + 1286 | '
\n' + 1287 | 'to have rendered\n' + 1288 | '
\n' + 1289 | ' 123\n' + 1290 | '
\n' + 1291 | '\n' + 1292 | '
\n' + 1293 | ' // \n' + 1294 | ' 123 //\n' + 1295 | " // should be 123\n" + 1296 | '
'); 1297 | }); 1298 | 1299 | 1300 | 1301 | it('identifies when a real element should be a string element', function () { 1302 | 1303 | renderer.render( 1304 | 1305 | 123 1306 | ); 1307 | 1308 | expect(() => testExpect(renderer, 'to have rendered', 1309 |
1310 | 123 1311 |
), 'to throw', 1312 | 'expected\n' + 1313 | '
\n' + 1314 | ' 123\n' + 1315 | '
\n' + 1316 | 'to have rendered\n' + 1317 | '
\n' + 1318 | ' \n' + 1319 | ' 123\n' + 1320 | ' \n' + 1321 | '
\n' + 1322 | '\n' + 1323 | '
\n' + 1324 | " 123 // should be \n" + 1325 | ' // 123\n' + 1326 | ' // \n' + 1327 | '
'); 1328 | }); 1329 | 1330 | it('identifies when a real element should be a number element', function () { 1331 | 1332 | renderer.render( 1333 | 1334 | {123} 1335 | ); 1336 | 1337 | expect(() => testExpect(renderer, 'to have rendered', 1338 |
1339 | 123 1340 |
), 'to throw', 1341 | 'expected\n' + 1342 | '
\n' + 1343 | ' 123\n' + 1344 | '
\n' + 1345 | 'to have rendered\n' + 1346 | '
\n' + 1347 | ' \n' + 1348 | ' 123\n' + 1349 | ' \n' + 1350 | '
\n' + 1351 | '\n' + 1352 | '
\n' + 1353 | ' 123 // should be \n' + 1354 | ' // 123\n' + 1355 | ' // \n' + 1356 | '
'); 1357 | }); 1358 | 1359 | it('ignores components that render null in an array when using `with all children`', function () { 1360 | 1361 | const RenderNull = React.createClass({ 1362 | 1363 | render: function () { 1364 | 1365 | const children = [ one, null, two ]; 1366 | return ( 1367 | 1368 |
1369 | {children} 1370 |
1371 | ); 1372 | } 1373 | }); 1374 | renderer.render(); 1375 | testExpect(renderer, 'to have rendered with all children', 1376 |
1377 | one 1378 | two 1379 |
); 1380 | }); 1381 | 1382 | it('ignores null children when using `with all children`', function () { 1383 | 1384 | const RenderNull = React.createClass({ 1385 | 1386 | render: function () { 1387 | 1388 | return ( 1389 |
1390 | {null} 1391 |
1392 | ); 1393 | } 1394 | }); 1395 | renderer.render(); 1396 | 1397 | testExpect(renderer, 'to have rendered with all children',
); 1398 | }); 1399 | 1400 | it("highlights when an element renders children when it shouldn't when using `with all children`", function () { 1401 | 1402 | const RenderNull = React.createClass({ 1403 | 1404 | render: function () { 1405 | 1406 | return ( 1407 |
1408 |
1409 |
1410 | ); 1411 | } 1412 | }); 1413 | renderer.render(); 1414 | 1415 | expect(() => testExpect(renderer, 'to have rendered with all children', 1416 |
1417 | ), 'to throw', 1418 | 'expected\n' + 1419 | '
\n' + 1420 | '
\n' + 1421 | '
\n' + 1422 | 'to have rendered with all children
\n' + 1423 | '\n' + 1424 | '
\n' + 1425 | '
// should be removed\n' + 1426 | '
' ); 1427 | }); 1428 | }); 1429 | 1430 | 1431 | describe('`to equal`', function () { 1432 | 1433 | it('matches renderer output to a component tree', function () { 1434 | 1435 | renderer.render(); 1436 | testExpect(renderer.getRenderOutput(), 'to equal',
); 1437 | }); 1438 | 1439 | it('outputs a diff when the expected is different', function () { 1440 | 1441 | renderer.render(); 1442 | 1443 | expect(() => testExpect(renderer.getRenderOutput(), 1444 | 'to equal',
), 1445 | 'to throw', 1446 | 'expected\n' + 1447 | '
\n' + 1448 | ' \n' + 1449 | '
\n' + 1450 | 'to equal\n' + 1451 | '
\n' + 1452 | ' \n' + 1453 | '
\n' + 1454 | '\n' + 1455 | '
\n' + 1456 | ' \n' + 1459 | '
'); 1460 | }); 1461 | }); 1462 | 1463 | 1464 | describe('contains', function () { 1465 | 1466 | it('finds an match at the top level', function () { 1467 | 1468 | renderer.render(); 1469 | testExpect(renderer, 'to contain',
); 1470 | }); 1471 | 1472 | it('finds a match at a deeper level', function () { 1473 | 1474 | renderer.render(); 1475 | testExpect(renderer, 'to contain', ); 1476 | }); 1477 | 1478 | it('finds a string content', function () { 1479 | renderer.render(some content onesome content two); 1480 | testExpect(renderer, 'to contain', 'some content two'); 1481 | }); 1482 | 1483 | it('does not find a string that does not exist', function () { 1484 | 1485 | renderer.render(some content onesome content two); 1486 | expect(() => testExpect(renderer, 'to contain', 'some content three'), 'to throw', 1487 | 'expected\n' + 1488 | '
\n' + 1489 | ' \n' + 1490 | ' some content one\n'+ 1491 | ' \n' + 1492 | ' \n' + 1493 | ' some content two\n'+ 1494 | ' \n' + 1495 | '
\n' + 1496 | "to contain 'some content three'"); 1497 | }); 1498 | 1499 | 1500 | 1501 | it('does not find a partial string', function () { 1502 | 1503 | // This behaviour may change in a major version bump at some point. 1504 | // Currently it's quite difficult to implement sensibly, as it would mean that searching 1505 | // for an element with text content would also match if a partial string matched. 1506 | // Maybe we allow a regex... :) 1507 | renderer.render(some content onesome content two); 1508 | expect(() => testExpect(renderer, 'to contain', 'some content'), 'to throw', 1509 | 'expected\n' + 1510 | '
\n' + 1511 | ' \n' + 1512 | ' some content one\n'+ 1513 | ' \n' + 1514 | ' \n' + 1515 | ' some content two\n'+ 1516 | ' \n' + 1517 | '
\n' + 1518 | "to contain 'some content'"); 1519 | }); 1520 | 1521 | it('finds a multi-part string', function () { 1522 | 1523 | renderer.render(button clicked {5} times); 1524 | testExpect(renderer, 'to contain', 'button clicked 5 times'); 1525 | }); 1526 | 1527 | it('does not find a multi-part string when `exactly` is used', function () { 1528 | 1529 | renderer.render(button clicked {5} times); 1530 | expect(() => testExpect(renderer, 'to contain exactly', 'button clicked 5 times'), 1531 | 'to throw', 1532 | 'expected\n' + 1533 | '
\n' + 1534 | ' \n' + 1535 | ' button clicked 5 times\n' + 1536 | ' \n' + 1537 | '
\n' + 1538 | "to contain exactly 'button clicked 5 times'"); 1539 | }); 1540 | 1541 | it('does not find a part of a multi-part string', function () { 1542 | 1543 | // See the 'does not find a partial string' test above 1544 | // This behaviour may change 1545 | renderer.render(button clicked {5} times); 1546 | expect(() => testExpect(renderer, 'to contain', 'button clicked '), 'to throw', 1547 | 'expected\n' + 1548 | '
\n' + 1549 | ' \n' + 1550 | ' button clicked 5 times\n' + 1551 | ' \n' + 1552 | '
\n' + 1553 | "to contain 'button clicked '"); 1554 | }); 1555 | 1556 | it('finds part of a multi-part string when exactly is used', function () { 1557 | 1558 | renderer.render(button clicked {5} times); 1559 | testExpect(renderer, 'to contain exactly', 'button clicked '); 1560 | }); 1561 | 1562 | it('finds a match in an array of children', function () { 1563 | 1564 | renderer.render( 1565 | 1566 | nested 1567 | 1568 | 1569 | 1570 | 1571 | 1572 | ); 1573 | testExpect(renderer, 'to contain', ); 1574 | }); 1575 | 1576 | it('does not find a match when it does not exist', function () { 1577 | 1578 | renderer.render( 1579 | 1580 | nested 1581 | 1582 | 1583 | 1584 | 1585 | 1586 | ); 1587 | 1588 | expect(() => testExpect(renderer, 'to contain', ), 1589 | 'to throw', 1590 | 'expected\n' + 1591 | '
\n' + 1592 | ' \n' + 1593 | ' nested\n' + 1594 | ' \n' + 1595 | ' \n' + 1596 | ' \n' + 1597 | ' \n' + 1598 | ' \n' + 1599 | ' \n' + 1600 | '
\n' + 1601 | 'to contain '); 1602 | }); 1603 | 1604 | it('does not find a match when the children of a candidate match are different', function () { 1605 | 1606 | renderer.render( 1607 | 1608 | nested 1609 | 1610 | 1611 | 1612 | 1613 | something else 1614 | 1615 | 1616 | ); 1617 | 1618 | expect(() => testExpect(renderer, 'to contain', 1619 | 1620 | cheese 1621 | ), 1622 | 'to throw', 1623 | 'expected\n' + 1624 | '
\n' + 1625 | ' \n' + 1626 | ' nested\n' + 1627 | ' \n' + 1628 | ' \n' + 1629 | ' \n' + 1630 | ' \n' + 1631 | ' \n' + 1632 | ' \n' + 1633 | ' something else\n' + 1634 | ' \n' + 1635 | ' \n' + 1636 | ' \n' + 1637 | '
\n' + 1638 | 'to contain\n' + 1639 | '\n' + 1640 | ' \n' + 1641 | ' cheese\n' + 1642 | ' \n' + 1643 | ''); 1644 | }); 1645 | 1646 | it('finds the match when there are extra children in the render, but `exactly` is not used', function () { 1647 | renderer.render( 1648 | 1649 | nested 1650 | 1651 | 1652 | 1653 | 1654 | one 1655 | two 1656 | three 1657 | 1658 | 1659 | ); 1660 | 1661 | testExpect(renderer, 'to contain', 1662 | 1663 | one 1664 | three 1665 | ); 1666 | }); 1667 | 1668 | it('finds the match when there are extra props in the render, but `exactly` is not used', function () { 1669 | renderer.render( 1670 | 1671 | nested 1672 | 1673 | 1674 | 1675 | 1676 | 1677 | ); 1678 | 1679 | testExpect(renderer, 'to contain', ); 1680 | }); 1681 | 1682 | it('does not find a match when there are extra props in the render, and `exactly` is used', function () { 1683 | renderer.render( 1684 | 1685 | nested 1686 | 1687 | 1688 | 1689 | 1690 | 1691 | ); 1692 | 1693 | expect(() => testExpect(renderer, 'to contain exactly', ), 1694 | 'to throw', 1695 | 'expected\n' + 1696 | '
\n' + 1697 | ' \n' + 1698 | ' nested\n' + 1699 | ' \n' + 1700 | ' \n' + 1701 | ' \n' + 1702 | ' \n' + 1703 | ' \n' + 1704 | ' \n' + 1705 | '
\n' + 1706 | 'to contain exactly '); 1707 | }); 1708 | 1709 | 1710 | it('does not find a match when there are extra children in the render, and `exactly` is used', function () { 1711 | renderer.render( 1712 | 1713 | nested 1714 | 1715 | 1716 | 1717 | 1718 | one 1719 | two 1720 | three 1721 | 1722 | 1723 | ); 1724 | 1725 | expect(() => testExpect(renderer, 'to contain exactly', 1726 | 1727 | one 1728 | three 1729 | ), 'to throw', 1730 | 'expected\n' + 1731 | '
\n' + 1732 | ' \n' + 1733 | ' nested\n' + 1734 | ' \n' + 1735 | ' \n' + 1736 | ' \n' + 1737 | ' \n' + 1738 | ' \n' + 1739 | ' \n' + 1740 | ' one\n' + 1741 | ' \n' + 1742 | ' \n' + 1743 | ' two\n' + 1744 | ' \n' + 1745 | ' \n' + 1746 | ' three\n' + 1747 | ' \n' + 1748 | ' \n' + 1749 | ' \n' + 1750 | '
\n' + 1751 | 'to contain exactly\n' + 1752 | '\n' + 1753 | ' \n' + 1754 | ' one\n' + 1755 | ' \n' + 1756 | ' \n' + 1757 | ' three\n' + 1758 | ' \n' + 1759 | ''); 1760 | }); 1761 | 1762 | it('does not find a match when there are extra children in the render, and `with all children` is used', function () { 1763 | renderer.render( 1764 | 1765 | nested 1766 | 1767 | 1768 | 1769 | 1770 | one 1771 | two 1772 | three 1773 | 1774 | 1775 | ); 1776 | 1777 | expect(() => testExpect(renderer, 'to contain with all children', 1778 | 1779 | one 1780 | three 1781 | ), 'to throw', 1782 | 'expected\n' + 1783 | '
\n' + 1784 | ' \n' + 1785 | ' nested\n' + 1786 | ' \n' + 1787 | ' \n' + 1788 | ' \n' + 1789 | ' \n' + 1790 | ' \n' + 1791 | ' \n' + 1792 | ' one\n' + 1793 | ' \n' + 1794 | ' \n' + 1795 | ' two\n' + 1796 | ' \n' + 1797 | ' \n' + 1798 | ' three\n' + 1799 | ' \n' + 1800 | ' \n' + 1801 | ' \n' + 1802 | '
\n' + 1803 | 'to contain with all children\n' + 1804 | '\n' + 1805 | ' \n' + 1806 | ' one\n' + 1807 | ' \n' + 1808 | ' \n' + 1809 | ' three\n' + 1810 | ' \n' + 1811 | ''); 1812 | }); 1813 | 1814 | it('finds a match when the render contains children, but the expected does not, and `exactly` is not used', function () { 1815 | renderer.render( 1816 | 1817 | nested 1818 | 1819 | 1820 | 1821 | 1822 | one 1823 | two 1824 | three 1825 | 1826 | 1827 | ); 1828 | 1829 | testExpect(renderer, 'to contain', ); 1830 | }); 1831 | 1832 | it('does not find a match when the render contains children, but the expected does not, and `exactly` is used', function () { 1833 | renderer.render( 1834 | 1835 | nested 1836 | 1837 | 1838 | 1839 | 1840 | one 1841 | two 1842 | three 1843 | 1844 | 1845 | ); 1846 | 1847 | expect(() => testExpect(renderer, 'to contain exactly', ), 1848 | 'to throw', 1849 | 'expected\n' + 1850 | '
\n' + 1851 | ' \n' + 1852 | ' nested\n' + 1853 | ' \n' + 1854 | ' \n' + 1855 | ' \n' + 1856 | ' \n' + 1857 | ' \n' + 1858 | ' \n' + 1859 | ' one\n' + 1860 | ' \n' + 1861 | ' \n' + 1862 | ' two\n' + 1863 | ' \n' + 1864 | ' \n' + 1865 | ' three\n' + 1866 | ' \n' + 1867 | ' \n' + 1868 | ' \n' + 1869 | '
\n' + 1870 | 'to contain exactly '); 1871 | }); 1872 | 1873 | it('does not find a match if the expected has children, but the candidate match does not', function () { 1874 | 1875 | renderer.render( 1876 | 1877 | nested 1878 | 1879 | 1880 | 1881 | 1882 | ); 1883 | 1884 | expect(() => testExpect(renderer, 'to contain', 1885 | 1886 | foo 1887 | ), 'to throw', 1888 | 'expected\n' + 1889 | '
\n' + 1890 | ' \n' + 1891 | ' nested\n' + 1892 | ' \n' + 1893 | ' \n' + 1894 | ' \n' + 1895 | ' \n' + 1896 | ' \n' + 1897 | '
\n' + 1898 | 'to contain\n' + 1899 | '\n' + 1900 | ' \n' + 1901 | ' foo\n' + 1902 | ' \n' + 1903 | ''); 1904 | }); 1905 | 1906 | it('matches even with removals in complex content without `exactly`', function () { 1907 | 1908 | renderer.render( 1909 | 1910 | 1911 | 1912 | 1913 | foo 1914 | 1915 | 1916 | ); 1917 | 1918 | testExpect(renderer, 'to contain', 1919 | 1920 | 1921 | foo 1922 | 1923 | ); 1924 | }); 1925 | 1926 | it('does not match with a removal and an addition in complex content with `exactly`', function () { 1927 | 1928 | renderer.render( 1929 | 1930 | 1931 |
1932 | 1933 | foo 1934 | 1935 | 1936 | ); 1937 | 1938 | expect(() => testExpect(renderer, 'to contain exactly', 1939 | 1940 |
1941 | 1942 | foo 1943 | 1944 | ), 'to throw', 1945 | 'expected\n' + 1946 | '
\n' + 1947 | ' \n' + 1948 | '
\n' + 1949 | ' \n' + 1950 | ' \n' + 1951 | ' foo\n' + 1952 | ' \n' + 1953 | ' \n' + 1954 | '
\n' + 1955 | 'to contain exactly\n' + 1956 | '\n' + 1957 | '
\n' + 1958 | ' \n' + 1959 | ' \n' + 1960 | ' foo\n' + 1961 | ' \n' + 1962 | ''); 1963 | }); 1964 | }); 1965 | 1966 | describe('to satisfy', () => { 1967 | 1968 | describe('renderOutput', () => { 1969 | 1970 | it('should satisfy a matching output', () => { 1971 | 1972 | renderer.render( 1973 | 1974 | foo 1975 | bar 1976 | 1977 | ); 1978 | 1979 | testExpect(renderer.getRenderOutput(), 'to satisfy', 1980 |
1981 | foo 1982 | bar 1983 |
); 1984 | }); 1985 | }); 1986 | }); 1987 | 1988 | }); 1989 | --------------------------------------------------------------------------------