├── .gitignore ├── .node-version ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── rquery.js └── test ├── events.spec.js ├── index.html ├── rquery.spec.js └── selectors.spec.js /.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 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | /bower_components -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | script: 5 | - npm test 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Hanna 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Archived 2 | 3 | This repository is no longer being actively developed. Many newer, more modern, and well-supported alternatives have become available since this project was first created. 4 | 5 | # rquery 6 | A [React](http://facebook.github.io/react/) tree traversal utility similar to 7 | jQuery, which can be useful for making assertions on your components in your 8 | tests. 9 | 10 | ## Vision 11 | [`chai-react`](https://github.com/percyhanna/chai-react/) was originally built 12 | to help with test assertions of React components. However, it quickly started 13 | adding too much complexity because it was attempting to solve two problems: 1) 14 | making assertions of properties/rendered content and 2) traversing the rendered 15 | React tree to make those assertions. 16 | 17 | `rquery` is meant to take over the rendered tree traversing responsibility from 18 | `chai-react`, which will allow it to be used with any testing framework. It will 19 | also provide convenience wrappers for various common test actions, such as event 20 | dispatching. 21 | 22 | ## React Version Support 23 | 24 | * For React v15, use `rquery` version 5+ 25 | * For React v0.14, use `rquery` version 4.x 26 | 27 | ## Setup 28 | 29 | ### Node.js, Webpack, Browserify 30 | 31 | ```javascript 32 | var _ = require('lodash'); 33 | var React = require('react'); 34 | var ReactDOM = require('react-dom'); 35 | var TestUtils = require('react-addons-test-utils'); 36 | var $R = require('rquery')(_, React, ReactDOM, TestUtils); 37 | ``` 38 | 39 | ### Browser with Scripttags 40 | 41 | Include React, lodash, and rquery in the page, then you get the `$R` global. 42 | 43 | Sample usage: 44 | 45 | ```html 46 | 47 | 48 | 49 | 50 | 66 | ``` 67 | 68 | ## Usage 69 | 70 | ### $R Factory 71 | 72 | The `$R` factory method returns a new instance of an `rquery` object. 73 | 74 | **Example**: 75 | 76 | ```javascript 77 | var $r = $R(component); 78 | ``` 79 | 80 | ### Class Methods 81 | 82 | #### `.extend` 83 | 84 | ```javascript 85 | $R.extend({ 86 | customMethod: function () { 87 | // your own custom method 88 | } 89 | }); 90 | ``` 91 | 92 | The `extend` method allows you to add extra methods to the `rquery` prototype. It 93 | does not allow you to override internal methods, though. 94 | 95 | #### `.isRQuery` 96 | 97 | ```javascript 98 | $R.isRQuery('abc'); // false 99 | $R.isRQuery($R([])); // true 100 | ``` 101 | 102 | Returns `true` if the provided argument is an instance of the `rquery` 103 | prototype. 104 | 105 | ### *Instance Methods* 106 | 107 | An instance of the `rquery` class contains an array of components, and provides 108 | an `Array`-like interface to directly access each component. 109 | 110 | **Example**: 111 | 112 | ```javascript 113 | var $r = $R([component1, component2 /* , componentN */]); 114 | $r.length === 2; // true 115 | $r[0] === component1; // true 116 | $r[1] === component2; // true 117 | ``` 118 | 119 | #### `#find` 120 | 121 | ```javascript 122 | $r.find(selector) 123 | ``` 124 | 125 | Returns a new `rquery` instance with the components that match the provided 126 | selector (see [Selector](#selectors) documentation). 127 | 128 | #### `#prop` 129 | 130 | ```javascript 131 | $r.prop('a') 132 | ``` 133 | 134 | Returns the value for the given prop for the first component in the scope. It 135 | throws an error if the scope has no components. 136 | 137 | #### `#style` 138 | 139 | ```javascript 140 | $r.style('a') 141 | ``` 142 | 143 | Returns the value for the given style for the first component in the scope. The 144 | style value is loaded from the style property. It throws an error if the scope 145 | has no components. 146 | 147 | #### `#state` 148 | 149 | ```javascript 150 | $r.state('a') 151 | ``` 152 | 153 | Returns the value for the given state for the first component in the scope. It 154 | throws an error if the scope has no components. 155 | 156 | #### `#nodes` 157 | 158 | ```javascript 159 | $r.nodes() 160 | ``` 161 | 162 | Returns an array of each DOM node in the current scope. 163 | 164 | #### `#text` 165 | 166 | ```javascript 167 | $r.text() 168 | ``` 169 | 170 | Returns the text contents of the component(s) in the `$r` object. Similar to 171 | jQuery's `text()` method (read-only). 172 | 173 | #### `#html` 174 | 175 | ```javascript 176 | $r.html() 177 | ``` 178 | 179 | Returns the HTML contents of the component(s) in the `$r` object. Similar to 180 | jQuery's `html()` method (read-only). 181 | 182 | #### `#simulateEvent` 183 | 184 | ```javascript 185 | simulateEvent(eventName, eventData) 186 | ``` 187 | 188 | Simulates triggering the `eventName` DOM event on the component(s) in the rquery 189 | object. 190 | 191 | #### Event helpers 192 | 193 | ```javascript 194 | [eventName](eventData) 195 | ``` 196 | 197 | Convenience helper methods to trigger any supported React DOM event. See the 198 | [React documentation](http://facebook.github.io/react/docs/events.html) to read 199 | about the events that are currently supported. 200 | 201 | ## Selectors 202 | 203 | ### Scope Selectors 204 | 205 | #### Descendant Selector 206 | 207 | **Example**: 208 | 209 | ```javascript 210 | $R(component).find('div p'); 211 | ``` 212 | 213 | **Description**: 214 | 215 | Finds all elements that are descendants of a parent component/node. Note that 216 | the root element of a component will be matched as a descendant of it 217 | (e.g. `MyComponent > div` will match `
` at root of `MyComponent#render`). 218 | 219 | #### Child Selector 220 | 221 | **Example**: 222 | 223 | ```javascript 224 | $R(component).find('div > p'); 225 | ``` 226 | 227 | **Description**: 228 | 229 | Finds all elements that are children (direct descendant) of a parent 230 | component/node. 231 | 232 | #### Union Selector 233 | 234 | **Example**: 235 | 236 | ```javascript 237 | $R(component).find('div, p'); 238 | ``` 239 | 240 | **Description**: 241 | 242 | Matches a union of all selectors on both sides of the `,`. 243 | 244 | #### Not Selector 245 | 246 | **Example**: 247 | 248 | ```javascript 249 | $R(component).find('div :not(p)'); 250 | ``` 251 | 252 | **Description**: 253 | 254 | Matches all elements that do _not_ match the nested selector. **NB**: Note the 255 | difference between `div :not(p)` and `div:not(p)`. The latter will match nothing 256 | as the `:not` is being applied directly on the `div`. However, `div :not(p)` 257 | applies a descendant scope selector first, which means it will match all 258 | elements that are descendants of `div` but not a `p`. 259 | 260 | ### Element/Component Selectors 261 | 262 | #### Component Selector 263 | 264 | **Example**: 265 | 266 | ```javascript 267 | $R(component).find('MyComponentName'); 268 | $R(component, 'MyButton'); 269 | ``` 270 | 271 | **Description**: 272 | 273 | Traverses the tree to find components based on their `displayName` value. *NB*: 274 | the selector must start with an upper-case letter, to signify a 275 | CompositeComponent vs. a DOM component. 276 | 277 | #### DOM Tag Selector 278 | 279 | **Example**: 280 | 281 | ```javascript 282 | $R(component).find('div'); 283 | $R(component, 'p'); 284 | ``` 285 | 286 | **Description**: 287 | 288 | Traverses the tree to find DOM components based on their `tagName`. *NB*: the 289 | selector must start with a lower-case letter, to signify a CompositeComponent 290 | vs. a DOM component. 291 | 292 | #### DOM Class Selector 293 | 294 | **Example**: 295 | 296 | ```javascript 297 | $R(component).find('.button'); 298 | $R(component, '.green'); 299 | ``` 300 | 301 | **Description**: 302 | 303 | Traverses the tree to find components with `className`s that contain the 304 | specified class. 305 | 306 | #### Index Selector 307 | 308 | **Example**: 309 | 310 | ```javascript 311 | $R(component).find('div[2]'); // matches the third div 312 | ``` 313 | 314 | **Description**: 315 | 316 | Matches the element at the given index. This is shorthand for `.at(index)` on 317 | the `rquery` object. 318 | 319 | #### Attribute Selector 320 | 321 | **Example**: 322 | 323 | ```javascript 324 | $R(component).find('[target]'); 325 | $R(component, '[onClick]'); 326 | ``` 327 | 328 | **Description**: 329 | 330 | Traverses the tree to find components that have a value defined for the given 331 | property name. 332 | 333 | *Note:* Although these are labeled as *attribute* selectors, they are really 334 | *property* selectors. In other words, they match properties being passed to a 335 | DOM/Composite component, not actual DOM attributes being rendered. 336 | 337 | #### Attribute Value Selectors 338 | 339 | **Example**: 340 | 341 | ```javascript 342 | $R(component).find('[target="_blank"]'); 343 | $R(component, '[href="http://www.github.com/"]'); 344 | ``` 345 | 346 | **Supported Operators**: 347 | 348 | `rquery` supports the [CSS Selectors level 3 spec](http://dev.w3.org/csswg/selectors-3/#attribute-selectors): 349 | 350 | * `[att="val"]`: equality 351 | * `[att~="val"]`: whitespace-separated list 352 | * `[att|="val"]`: namespace-prefixed (e.g. `val` or `val-*`) 353 | * `[att^="val"]`: prefix 354 | * `[att$="val"]`: suffix 355 | * `[att*="val"]`: substring 356 | 357 | **Description**: 358 | 359 | Traverses the tree to find components with a property value that matches the 360 | given key/value pair. 361 | 362 | *Note:* Although these are labeled as *attribute* selectors, they are really 363 | *property* selectors. In other words, they match properties being passed to a 364 | DOM/Composite component, not actual DOM attributes being rendered. For complex 365 | property values (e.g. arrays, objects, etc.), the value matchers are less useful 366 | as `rquery` doesn't currently support any complex value matching. 367 | 368 | *Note:* All values *must* be provided as double-quoted strings. `[att="val"]` is 369 | valid, but `[att=val]` and `[att='val']` are not. 370 | 371 | ## Usage with Test Suites 372 | 373 | The rquery interface is meant to be generic enough to use with any assertion 374 | library/test runner. 375 | 376 | Sample usage with Chai BDD style assertions: 377 | 378 | ```javascript 379 | expect($R(component).find('MyComponent')).to.have.length(1); 380 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rquery", 3 | "main": "rquery.js", 4 | "version": "5.1.0", 5 | "authors": [ 6 | "Andrew Hanna " 7 | ], 8 | "description": "jQuery-like functionality for React to facilitate testing.", 9 | "moduleType": [ 10 | "globals" 11 | ], 12 | "keywords": [ 13 | "react", 14 | "jquery", 15 | "selector", 16 | "query", 17 | "find", 18 | "testing" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "dependencies": { 29 | "lodash": "~3.4.0" 30 | }, 31 | "homepage": "https://github.com/percyhanna/rquery" 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrew Hanna ", 3 | "name": "rquery", 4 | "description": "jQuery-like functionality for React to facilitate testing.", 5 | "keywords": [ 6 | "react", 7 | "jquery", 8 | "selector", 9 | "query", 10 | "find", 11 | "testing" 12 | ], 13 | "version": "5.1.0", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/percyhanna/rquery" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/percyhanna/rquery/issues" 20 | }, 21 | "scripts": { 22 | "test": "mocha-phantomjs test/index.html" 23 | }, 24 | "main": "./rquery", 25 | "engines": { 26 | "node": ">= 0.4.0" 27 | }, 28 | "dependencies": { 29 | "lodash": "^3.4.0" 30 | }, 31 | "devDependencies": { 32 | "chai": "1", 33 | "chai-react": "3.0.0-rc1", 34 | "es5-shim": "^4.0.0", 35 | "mocha": "1", 36 | "mocha-phantomjs": "3", 37 | "phantomjs": "1.9.7-15", 38 | "react": "^15.0", 39 | "react-addons-test-utils": "^0.14.0", 40 | "react-dom": "^0.14.0", 41 | "sinon": "^1.12.2", 42 | "sinon-chai": "^2.6.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rquery.js: -------------------------------------------------------------------------------- 1 | (function (rquery) { 2 | // Module systems magic dance. 3 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { 4 | // NodeJS 5 | module.exports = rquery; 6 | } else if (typeof define === "function" && define.amd) { 7 | // AMD 8 | define(['lodash', 'react', 'react-dom', 'react-addons-test-utils'], function (_, React, ReactDOM, TestUtils) { 9 | return rquery(_, React, ReactDOM, TestUtils); 10 | }); 11 | } else { 12 | // Other environment (usually 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /test/rquery.spec.js: -------------------------------------------------------------------------------- 1 | var TestUtils = React.addons.TestUtils; 2 | 3 | describe('#findComponent', function () { 4 | before(function () { 5 | this.reactClass = React.createClass({ 6 | render: function () { 7 | return React.createElement('p', this.props); 8 | } 9 | }); 10 | 11 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 12 | this.$r = $R(this.component).findComponent(this.reactClass); 13 | }); 14 | 15 | it('finds one component', function () { 16 | expect(this.$r).to.have.length(1); 17 | }); 18 | 19 | it('component is instance of MyComponent', function () { 20 | expect(this.$r[0]).to.be.componentOfType(this.reactClass); 21 | }); 22 | 23 | describe('when the rquery is shallow rendered', function () { 24 | before(function () { 25 | this.renderer = TestUtils.createRenderer(); 26 | this.element = React.createElement(this.reactClass, {}, React.createElement(this.reactClass)) 27 | this.renderer.render(this.element); 28 | 29 | this.$r = $R(this.renderer.getRenderOutput()); 30 | }); 31 | 32 | it('finds the component', function () { 33 | expect(this.$r.findComponent(this.reactClass)).to.have.length(1); 34 | }); 35 | 36 | it('the component is the correct type', function () { 37 | expect(this.$r.findComponent(this.reactClass)[0].type).to.equal(this.reactClass); 38 | }); 39 | }); 40 | }); 41 | 42 | describe('#get', function () { 43 | before(function () { 44 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { 'data-something': 123 })); 45 | this.$r = $R(this.component); 46 | }); 47 | 48 | describe('when accessing a valid index', function () { 49 | it('returns the component directly', function () { 50 | expect(this.$r.get(0)).to.equal(this.component); 51 | }); 52 | }); 53 | 54 | describe('when accessing an invalid index', function () { 55 | it('returns undefined', function () { 56 | expect(this.$r.get(1)).to.be.undefined(); 57 | }); 58 | }); 59 | }); 60 | 61 | describe('#at', function () { 62 | before(function () { 63 | this.reactClass = React.createClass({ 64 | render: function () { 65 | return React.createElement('div', {}, 66 | React.createElement('p'), 67 | React.createElement('p') 68 | ); 69 | } 70 | }); 71 | 72 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 73 | this.$r = $R(this.component, 'p'); 74 | }); 75 | 76 | describe('when accessing a valid index', function () { 77 | before(function () { 78 | this.value = this.$r.at(0); 79 | }); 80 | 81 | it('returns a new rquery object', function () { 82 | expect($R.isRQuery(this.value)).to.equal(true); 83 | }); 84 | 85 | it('returns only one item', function () { 86 | expect(this.value.length).to.equal(1); 87 | }); 88 | 89 | it('returns the item requested', function () { 90 | expect(this.value[0]).to.be.instanceOf(Element); 91 | }); 92 | }); 93 | 94 | describe('when accessing an invalid index', function () { 95 | before(function () { 96 | this.value = this.$r.at(2); 97 | }); 98 | 99 | it('returns an rquery object', function () { 100 | expect($R.isRQuery(this.value)).to.equal(true); 101 | }); 102 | 103 | it('returns an empty rquery object', function () { 104 | expect(this.value.length).to.be.equal(0); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('#prop', function () { 110 | before(function () { 111 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { 'data-something': 123 })); 112 | this.$r = $R(this.component); 113 | }); 114 | 115 | it('returns the prop value when defined', function() { 116 | expect(this.$r.prop('data-something')).to.eq('123'); 117 | }); 118 | 119 | it('returns undefined when no prop is defined', function() { 120 | expect(this.$r.prop('abc')).to.be.undefined; 121 | }); 122 | 123 | it('throws an error when no component is in the scope', function() { 124 | var $r = this.$r; 125 | expect(function () { 126 | $r.find('p').prop('a'); 127 | }).to.throw('$R#prop requires at least one component. No components in current scope.'); 128 | }); 129 | }); 130 | 131 | describe('#state', function () { 132 | before(function () { 133 | this.reactClass = React.createClass({ 134 | render: function () { 135 | return React.createElement('div'); 136 | } 137 | }); 138 | 139 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 140 | this.component.setState({ 141 | a: 123 142 | }); 143 | this.$r = $R(this.component); 144 | }); 145 | 146 | it('returns the state value when defined', function() { 147 | expect(this.$r.state('a')).to.eq(123); 148 | }); 149 | 150 | it('returns undefined when no state is defined', function() { 151 | expect(this.$r.state('abc')).to.be.undefined; 152 | }); 153 | 154 | it('throws an error when no component is in the scope', function() { 155 | var $r = this.$r; 156 | expect(function () { 157 | $r.find('p').state('a'); 158 | }).to.throw('$R#state requires at least one component. No components in current scope.'); 159 | }); 160 | }); 161 | 162 | describe('#nodes', function () { 163 | before(function () { 164 | this.reactClass = React.createClass({ 165 | render: function () { 166 | var p1 = React.createElement('p', null, 'Te'), 167 | p2 = React.createElement('p', null, 'xt'); 168 | 169 | return React.createElement('div', null, p1, p2); 170 | } 171 | }); 172 | 173 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 174 | this.$r = $R(this.component); 175 | }); 176 | 177 | context('when called on a single component', function() { 178 | it('returns the top-level node', function() { 179 | var nodes = this.$r.nodes(); 180 | 181 | expect(nodes).to.have.length(1); 182 | expect(nodes[0].tagName.toUpperCase()).to.eq('DIV'); 183 | }); 184 | }); 185 | 186 | context('when called on multiple components', function() { 187 | it('returns each node', function() { 188 | var nodes = this.$r.find('p').nodes(); 189 | 190 | expect(nodes).to.have.length(2); 191 | expect(nodes[0].tagName.toUpperCase()).to.eq('P'); 192 | expect(nodes[1].tagName.toUpperCase()).to.eq('P'); 193 | }); 194 | }); 195 | }); 196 | 197 | describe('#text', function () { 198 | before(function () { 199 | this.reactClass = React.createClass({ 200 | render: function () { 201 | var p1 = React.createElement('p', null, 'Te'), 202 | p2 = React.createElement('p', null, 'xt'); 203 | 204 | return React.createElement('div', null, p1, p2); 205 | } 206 | }); 207 | 208 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 209 | this.$r = $R(this.component).findComponent(this.reactClass); 210 | }); 211 | 212 | context('when called on multiple components', function() { 213 | it('returns the inner text of the selected components', function() { 214 | expect(this.$r.text()).to.eq('Text'); 215 | }); 216 | }); 217 | 218 | context('when called on single component', function() { 219 | it('returns the inner text of the selected component', function() { 220 | expect(this.$r.find('p').at(0).text()).to.eq('Te'); 221 | }); 222 | }); 223 | }); 224 | 225 | describe('#html', function () { 226 | before(function () { 227 | this.reactClass = React.createClass({ 228 | render: function () { 229 | var p1 = React.createElement('p', null, 'Te'), 230 | p2 = React.createElement('p', null, [ 231 | React.createElement('strong', { key: 1 }, 'xt') 232 | ]); 233 | 234 | return React.createElement('div', null, p1, p2); 235 | } 236 | }); 237 | 238 | this.component = TestUtils.renderIntoDocument(React.createElement(this.reactClass)); 239 | this.$r = $R(this.component).findComponent(this.reactClass); 240 | }); 241 | 242 | context('when called on multiple components', function() { 243 | it('returns the inner text of the selected components', function() { 244 | expect(this.$r.html()).to.eq('

Te

xt

'); 245 | }); 246 | }); 247 | 248 | context('when called on single component', function() { 249 | it('returns the inner text of the selected component', function() { 250 | expect(this.$r.find('p').at(1).html()).to.eq('xt'); 251 | }); 252 | }); 253 | }); 254 | 255 | describe('#val', function () { 256 | before(function () { 257 | this.spy = sinon.spy($R.rquery.prototype, 'change'); 258 | }); 259 | 260 | after(function () { 261 | this.spy.restore(); 262 | }); 263 | 264 | describe('when called on an input', function () { 265 | before(function () { 266 | this.component = TestUtils.renderIntoDocument(React.createElement('input', { defaultValue: 'hello' })); 267 | this.$r = $R(this.component); 268 | }); 269 | 270 | it('returns the current value when no value passed in', function () { 271 | expect(this.$r.val()).to.equal('hello'); 272 | expect(this.spy).to.not.have.beenCalled; 273 | }); 274 | 275 | it('changes the value of the input', function () { 276 | this.$r.val('world'); 277 | expect(this.$r.val()).to.equal('world'); 278 | expect(this.spy).to.have.beenCalled; 279 | }); 280 | }); 281 | 282 | describe('when called on a non-input', function () { 283 | before(function () { 284 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { value: 'hello' })); 285 | this.$r = $R(this.component); 286 | }); 287 | 288 | it('returns the attribute value', function () { 289 | expect(this.$r.val()).to.eq('hello'); 290 | expect(this.spy).to.not.have.beenCalled; 291 | }); 292 | 293 | it('does not change the value', function () { 294 | this.$r.val('world'); 295 | expect(this.spy).to.not.have.beenCalled; 296 | }); 297 | }); 298 | }); 299 | 300 | describe('#disabled', function () { 301 | describe('when called on a disabled input', function () { 302 | before(function () { 303 | this.component = TestUtils.renderIntoDocument(React.createElement('input', { disabled: true })); 304 | this.$r = $R(this.component); 305 | }); 306 | 307 | it('returns true', function () { 308 | expect(this.$r.disabled()).to.equal(true); 309 | }); 310 | }); 311 | 312 | describe('when called on an enabled input', function () { 313 | before(function () { 314 | this.component = TestUtils.renderIntoDocument(React.createElement('input', { disabled: false })); 315 | this.$r = $R(this.component); 316 | }); 317 | 318 | it('returns false', function () { 319 | expect(this.$r.disabled()).to.equal(false); 320 | }); 321 | }); 322 | 323 | describe('when called on a non-input', function () { 324 | before(function () { 325 | this.component = TestUtils.renderIntoDocument(React.createElement('div')); 326 | this.$r = $R(this.component); 327 | }); 328 | 329 | it('returns undefined', function () { 330 | expect(this.$r.disabled()).to.be.undefined; 331 | }); 332 | }); 333 | }); 334 | 335 | describe('#checked', function () { 336 | before(function () { 337 | this.spy = sinon.spy($R.rquery.prototype, 'change'); 338 | }); 339 | 340 | after(function () { 341 | this.spy.restore(); 342 | }); 343 | 344 | describe('when called on an input', function () { 345 | before(function () { 346 | this.component = TestUtils.renderIntoDocument(React.createElement('input', { defaultChecked: true })); 347 | this.$r = $R(this.component); 348 | }); 349 | 350 | it('returns the current checked property value when no checked value passed in', function () { 351 | expect(this.$r.checked()).to.equal(true); 352 | expect(this.spy).to.not.have.beenCalled; 353 | }); 354 | 355 | it('changes the checked property value of the input', function () { 356 | this.$r.checked(false); 357 | expect(this.$r.checked()).to.equal(false); 358 | expect(this.spy).to.have.beenCalled; 359 | }); 360 | }); 361 | 362 | describe('when called on a non-input', function () { 363 | before(function () { 364 | this.component = TestUtils.renderIntoDocument(React.createElement('div')); 365 | this.$r = $R(this.component); 366 | }); 367 | 368 | it('returns undefined when no value passed in', function () { 369 | expect(this.$r.checked()).to.be.undefined; 370 | expect(this.spy).to.not.have.beenCalled; 371 | }); 372 | 373 | it('no changes when a value is passed in', function () { 374 | this.$r.checked(true); 375 | expect(this.spy).to.not.have.beenCalled; 376 | }); 377 | }); 378 | }); 379 | 380 | describe('.isRQuery', function () { 381 | before(function () { 382 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { 'data-something': 123 })); 383 | }); 384 | 385 | it('it returns false for non rquery objects', function() { 386 | expect($R.isRQuery('abc')).to.be.false; 387 | expect($R.isRQuery(123)).to.be.false; 388 | expect($R.isRQuery({})).to.be.false; 389 | }); 390 | 391 | it('it returns true for rquery objects', function() { 392 | expect($R.isRQuery($R(this.component))).to.be.true; 393 | expect($R.isRQuery($R(this.component).at(0))).to.be.true; 394 | }); 395 | }); 396 | 397 | describe('.extend', function () { 398 | before(function () { 399 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { 'data-something': 123 })); 400 | 401 | this._builtInFind = $R(this.component).find; 402 | 403 | $R.extend({ 404 | find: function () {}, // should not allow overriding a built-in method 405 | customMethod: function () { 406 | return 123; 407 | } 408 | }); 409 | 410 | this.$r = $R(this.component); 411 | }); 412 | 413 | it('does not allow overriding built-in methods', function() { 414 | expect(this._builtInFind).to.eq(this.$r.find); 415 | }); 416 | 417 | it('it allows execution of custom methods', function() { 418 | expect(this.$r.customMethod()).to.eq(123); 419 | }); 420 | }); 421 | -------------------------------------------------------------------------------- /test/selectors.spec.js: -------------------------------------------------------------------------------- 1 | function runSelectors (shallow) { 2 | var MyComponent = React.createClass({ 3 | displayName: "MyComponent", 4 | 5 | render: function () { 6 | return ( 7 | React.createElement('div', { id: 'my-component', className: 'my-class some-other-class' }, 8 | React.createElement('p', {}, 'Hello, world!'), 9 | React.createElement('p', {}, React.createElement('span', {}, 'not descendant')), 10 | React.createElement('a', { className: 'button', target: '_blank', 'data-something': 'hello ' }, 'Click me!'), 11 | React.createElement('button', { className: 'button button-default' }, 'Save'), 12 | React.createElement('span', {}, 'descendant'), 13 | React.createElement(ChildComponent), 14 | React.createElement(NullComponent), 15 | React.createElement('div', {}, React.createElement('span', {}, 'not a child of p')) 16 | ) 17 | ); 18 | } 19 | }); 20 | 21 | var ChildComponent = React.createClass({ 22 | displayName: 'ChildComponent', 23 | 24 | render: function () { 25 | return ( 26 | React.createElement('button', { className: 'child-component' }, 'my child component') 27 | ); 28 | } 29 | }); 30 | 31 | var NullComponent = React.createClass({ 32 | render: function () { 33 | return null; 34 | } 35 | }); 36 | 37 | var TestUtils = React.addons.TestUtils; 38 | 39 | function getProp (component, prop) { 40 | if (TestUtils.isDOMComponent(component)) { 41 | if (prop === 'className') { 42 | prop = 'class'; 43 | } 44 | 45 | if (component.hasAttribute(prop)) { 46 | return component.getAttribute(prop); 47 | } 48 | } else { 49 | return component.props[prop]; 50 | } 51 | } 52 | 53 | function expectType (component, type) { 54 | if (shallow) { 55 | expect(component.type).to.equal(type); 56 | } else { 57 | if (typeof type === 'string') { 58 | expect(component).to.be.componentWithTag(type); 59 | } else { 60 | expect(component).to.be.componentOfType(type); 61 | } 62 | } 63 | } 64 | 65 | function expectAttribute (component, attribute) { 66 | var hasAttribute; 67 | 68 | if (shallow) { 69 | hasAttribute = attribute in component.props; 70 | } else { 71 | hasAttribute = component.hasAttribute(attribute); 72 | } 73 | 74 | expect(hasAttribute).to.be.true; 75 | } 76 | 77 | function className (component) { 78 | return getProp(component, 'className'); 79 | } 80 | 81 | function tagName (component) { 82 | if (shallow) { 83 | if (typeof component.type === 'string') { 84 | return component.type.toUpperCase(); 85 | } 86 | } else { 87 | return component.tagName; 88 | } 89 | } 90 | 91 | function run (selector, element) { 92 | var component, renderer; 93 | 94 | if (!element) { 95 | element = React.createElement(MyComponent) 96 | } 97 | 98 | if (shallow) { 99 | renderer = TestUtils.createRenderer(); 100 | renderer.render(element); 101 | return $R(renderer.getRenderOutput(), selector); 102 | } else { 103 | component = TestUtils.renderIntoDocument(element); 104 | return $R(component, selector); 105 | } 106 | }; 107 | 108 | describe('text description of component', function () { 109 | before(function () { 110 | this.$r = run('ChildComponent'); 111 | }); 112 | 113 | it('finds one component', function () { 114 | expect(this.$r).to.have.length(1); 115 | }); 116 | 117 | it('component is instance of ChildComponent', function () { 118 | expectType(this.$r[0], ChildComponent); 119 | }); 120 | }); 121 | 122 | describe('text description of connected component', function () { 123 | before(function () { 124 | this.originalChildComponentDisplayName = ChildComponent.displayName; 125 | ChildComponent.displayName = 'Connect(ChildComponent)'; 126 | this.$r = run('ChildComponent'); 127 | }); 128 | 129 | after(function () { 130 | ChildComponent.displayName = this.originalChildComponentDisplayName; 131 | }); 132 | 133 | it('finds one component', function () { 134 | expect(this.$r).to.have.length(1); 135 | }); 136 | 137 | it('component is instance of ChildComponent', function () { 138 | expectType(this.$r[0], ChildComponent); 139 | }); 140 | }); 141 | 142 | describe('text description of DOM component', function () { 143 | before(function () { 144 | this.$r = run('a'); 145 | }); 146 | 147 | it('finds one component', function () { 148 | expect(this.$r).to.have.length(1); 149 | }); 150 | 151 | it('component is instance of "a" tag', function () { 152 | expectType(this.$r[0], 'a'); 153 | }); 154 | }); 155 | 156 | describe('chained calls to find', function () { 157 | it('correctly matches composite components', function () { 158 | this.$r = run('div').find('ChildComponent'); 159 | expect(this.$r).to.have.length(1); 160 | }); 161 | 162 | it('matches composite components chained from a DOM component', function () { 163 | var TestComponent = React.createClass({ 164 | render: function () { 165 | return ( 166 | React.createElement('div', {}, 167 | React.createElement('div', { className: 'my-class' }, 168 | React.createElement(ChildComponent) 169 | ) 170 | ) 171 | ); 172 | } 173 | }); 174 | 175 | this.$r = run('', React.createElement(TestComponent)); 176 | 177 | expect(this.$r.find('.my-class').find('ChildComponent')).to.have.length(1); 178 | }); 179 | }); 180 | 181 | describe('union selector', function () { 182 | before(function () { 183 | this.$r = run('a, p'); 184 | }); 185 | 186 | it('finds all a & p components', function () { 187 | expect(this.$r).to.have.length(3); 188 | }); 189 | 190 | it('components are found in union order, then document order', function () { 191 | expectType(this.$r[0], 'a'); 192 | expectType(this.$r[1], 'p'); 193 | expectType(this.$r[2], 'p'); 194 | }); 195 | }); 196 | 197 | describe('descendant selector', function () { 198 | it('finds all span components', function () { 199 | this.$r = run('div span'); 200 | expect(this.$r).to.have.length(3); 201 | }); 202 | 203 | it('finds all p components', function () { 204 | this.$r = run('div p'); 205 | expect(this.$r).to.have.length(2); 206 | }); 207 | 208 | if (!shallow) { 209 | it('finds composite components that are descendants of composite components', function () { 210 | this.$r = run('MyComponent ChildComponent'); 211 | expect(this.$r).to.have.length(1); 212 | }); 213 | } 214 | 215 | it('finds composite components that are descendants of DOM components', function () { 216 | this.$r = run('div ChildComponent'); 217 | expect(this.$r).to.have.length(1); 218 | }); 219 | }); 220 | 221 | describe('child selector', function () { 222 | before(function () { 223 | this.$r = run('div > span'); 224 | }); 225 | 226 | it('finds all child span components', function () { 227 | expect(this.$r).to.have.length(2); 228 | }); 229 | 230 | describe('when descendant is of same depth, but not a child', function () { 231 | before(function () { 232 | this.$r = run('p > span') 233 | }); 234 | 235 | it('does not match the cousin', function () { 236 | expect(this.$r).to.have.length(1); 237 | }); 238 | }); 239 | 240 | if (!shallow) { 241 | describe('DOM components that are children of DOM components', function () { 242 | before(function () { 243 | this.$r = run('div > button'); 244 | }); 245 | 246 | it('finds the button components', function () { 247 | expect(this.$r).to.have.length(2); 248 | }); 249 | 250 | it('only matches DOM components', function () { 251 | expect(this.$r.components.map(tagName)).to.be.eql(['BUTTON', 'BUTTON']); 252 | }); 253 | }); 254 | 255 | describe('composite components that are children of DOM components', function () { 256 | before(function () { 257 | this.$r = run('div > ChildComponent'); 258 | }); 259 | 260 | it('finds one component', function () { 261 | expect(this.$r).to.have.length(1); 262 | }); 263 | 264 | it('finds the composite component', function () { 265 | expect(TestUtils.isCompositeComponentWithType(this.$r[0], ChildComponent)).to.be.true; 266 | }); 267 | }); 268 | 269 | it('composite components that are children of composite components', function () { 270 | this.$r = run('MyComponent > ChildComponent'); 271 | expect(this.$r).to.have.length(1); 272 | }); 273 | } 274 | 275 | it('finds composite components that are children of DOM components', function () { 276 | this.$r = run('div > ChildComponent'); 277 | expect(this.$r).to.have.length(1); 278 | }); 279 | }); 280 | 281 | if (!shallow) { 282 | describe('composite selector with child selector', function () { 283 | before(function () { 284 | this.$r = run('MyComponent > span'); 285 | }); 286 | 287 | it('finds all child span components', function () { 288 | expect(this.$r).to.have.length(1); 289 | }); 290 | 291 | describe('composite component\'s DOM component', function () { 292 | before(function () { 293 | this.$r = run('MyComponent > div'); 294 | }); 295 | 296 | it('returns the composite component\'s DOM component', function () { 297 | expect(this.$r).to.have.length(1); 298 | }); 299 | }); 300 | }); 301 | } 302 | 303 | describe('DOM selector with multiple matches', function () { 304 | before(function () { 305 | this.$r = run('p'); 306 | }); 307 | 308 | it('finds two components', function () { 309 | expect(this.$r).to.have.length(2); 310 | }); 311 | 312 | it('first component is instance of "p" tag', function () { 313 | expectType(this.$r[0], 'p'); 314 | }); 315 | 316 | it('second component is instance of "p" tag', function () { 317 | expectType(this.$r[1], 'p'); 318 | }); 319 | }); 320 | 321 | describe('DOM class selector', function () { 322 | before(function () { 323 | this.$r = run('.button'); 324 | }); 325 | 326 | it('finds two components', function () { 327 | expect(this.$r).to.have.length(2); 328 | }); 329 | 330 | it('first component is instance of "a" tag', function () { 331 | expectType(this.$r[0], 'a'); 332 | }); 333 | 334 | it('second component is instance of "button" tag', function () { 335 | expectType(this.$r[1], 'button'); 336 | }); 337 | }); 338 | 339 | describe('DOM class selector with dash', function () { 340 | before(function () { 341 | this.$r = run('.my-class'); 342 | }); 343 | 344 | it('find one component', function () { 345 | expect(this.$r).to.have.length(1); 346 | }); 347 | 348 | it('first component is instance of "div" tag', function () { 349 | expectType(this.$r[0], 'div'); 350 | }); 351 | }); 352 | 353 | describe('chained DOM class selector', function () { 354 | before(function () { 355 | this.$r = run('.my-class.some-other-class'); 356 | }); 357 | 358 | it('find one component', function () { 359 | expect(this.$r).to.have.length(1); 360 | }); 361 | 362 | it('has the correct class names', function () { 363 | expect(className(this.$r[0])).to.equal('my-class some-other-class'); 364 | }); 365 | }); 366 | 367 | describe('index selector', function () { 368 | it('matches the indexed element', function () { 369 | this.$r = run('button[0]'); 370 | 371 | expect(this.$r).to.have.length(1); 372 | expect(tagName(this.$r[0])).to.eql('BUTTON'); 373 | expect(className(this.$r[0])).to.eql('button button-default'); 374 | }); 375 | 376 | it('matches nothing if index out of range', function () { 377 | this.$r = run('div[3]'); 378 | 379 | expect(this.$r).to.have.length(0); 380 | }); 381 | }); 382 | 383 | describe('attribute selector', function () { 384 | before(function () { 385 | this.$r = run('[target]'); 386 | }); 387 | 388 | it('finds one component', function () { 389 | expect(this.$r).to.have.length(1); 390 | }); 391 | 392 | it('component is instance of "a" tag', function () { 393 | expectType(this.$r[0], 'a'); 394 | }); 395 | 396 | it('component has target property', function () { 397 | expectAttribute(this.$r[0], 'target'); 398 | }); 399 | }); 400 | 401 | describe('chained attribute selectors', function () { 402 | before(function () { 403 | this.$r = run('div[id="my-component"][class~="my-class"]'); 404 | }); 405 | 406 | it('finds the correct component', function () { 407 | expect(this.$r).to.have.length(1); 408 | expect(getProp(this.$r[0], 'id')).to.equal('my-component'); 409 | }); 410 | }); 411 | 412 | describe('attribute selector with dash in name', function () { 413 | before(function () { 414 | this.$r = run('[data-something]'); 415 | }); 416 | 417 | it('finds one component', function () { 418 | expect(this.$r).to.have.length(1); 419 | }); 420 | 421 | it('component is instance of "a" tag', function () { 422 | expectType(this.$r[0], 'a'); 423 | }); 424 | 425 | it('component has data-something property', function () { 426 | expectAttribute(this.$r[0], 'data-something'); 427 | }); 428 | }); 429 | 430 | describe('attribute value selector', function () { 431 | before(function () { 432 | this.$r = run('[target="_blank"]'); 433 | }); 434 | 435 | it('finds one component', function () { 436 | expect(this.$r).to.have.length(1); 437 | }); 438 | 439 | it('component is instance of "a" tag', function () { 440 | expectType(this.$r[0], 'a'); 441 | }); 442 | 443 | it('component has target property', function () { 444 | expectAttribute(this.$r[0], 'target'); 445 | }); 446 | }); 447 | 448 | describe('attribute ~= selector', function () { 449 | before(function () { 450 | this.$r = run('[class~="my-class"]'); 451 | }); 452 | 453 | it('finds one component', function () { 454 | expect(this.$r).to.have.length(1); 455 | }); 456 | 457 | it('component is instance of "div" tag', function () { 458 | expectType(this.$r[0], 'div'); 459 | }); 460 | 461 | it('component has target property', function () { 462 | expect(className(this.$r[0])).to.equal('my-class some-other-class'); 463 | }); 464 | }); 465 | 466 | describe('attribute |= selector', function () { 467 | before(function () { 468 | this.$r = run('[class|="my"]'); 469 | }); 470 | 471 | it('finds one component', function () { 472 | expect(this.$r).to.have.length(1); 473 | }); 474 | 475 | it('component is instance of "div" tag', function () { 476 | expectType(this.$r[0], 'div'); 477 | }); 478 | 479 | it('component has target property', function () { 480 | expect(className(this.$r[0])).to.equal('my-class some-other-class'); 481 | }); 482 | }); 483 | 484 | describe('attribute ^= selector', function () { 485 | before(function () { 486 | this.$r = run('[data-something^="he"]'); 487 | }); 488 | 489 | it('finds one component', function () { 490 | expect(this.$r).to.have.length(1); 491 | }); 492 | 493 | it('component is instance of "a" tag', function () { 494 | expectType(this.$r[0], 'a'); 495 | }); 496 | 497 | it('component has target property', function () { 498 | expectAttribute(this.$r[0], 'data-something'); 499 | }); 500 | }); 501 | 502 | describe('attribute $= selector', function () { 503 | before(function () { 504 | this.$r = run('[data-something$="lo "]'); 505 | }); 506 | 507 | it('finds one component', function () { 508 | expect(this.$r).to.have.length(1); 509 | }); 510 | 511 | it('component is instance of "a" tag', function () { 512 | expectType(this.$r[0], 'a'); 513 | }); 514 | 515 | it('component has target property', function () { 516 | expectAttribute(this.$r[0], 'data-something'); 517 | }); 518 | }); 519 | 520 | describe('attribute *= selector', function () { 521 | before(function () { 522 | this.$r = run('[data-something*="ell"]'); 523 | }); 524 | 525 | it('finds one component', function () { 526 | expect(this.$r).to.have.length(1); 527 | }); 528 | 529 | it('component is instance of "a" tag', function () { 530 | expectType(this.$r[0], 'a'); 531 | }); 532 | 533 | it('component has target property', function () { 534 | expectAttribute(this.$r[0], 'data-something'); 535 | }); 536 | }); 537 | 538 | describe(':contains() selector', function () { 539 | describe('when not scoped to a specific element', function () { 540 | before(function () { 541 | this.$r = run(':contains(descendant)'); 542 | }); 543 | 544 | it('finds all components that contain the text', function () { 545 | expect(this.$r).to.have.length(shallow ? 4 : 5); 546 | }); 547 | }); 548 | 549 | describe('when scoped to a specific element', function () { 550 | before(function () { 551 | this.$r = run('div :contains(descendant)'); 552 | }); 553 | 554 | it('finds all scoped components that contain the text', function () { 555 | expect(this.$r).to.have.length(3); 556 | }); 557 | }); 558 | }); 559 | 560 | describe(':not() selector', function () { 561 | describe('when scoped to descendants', function () { 562 | before(function () { 563 | this.$r = run('div :not(p)'); 564 | }); 565 | 566 | it('finds all the descendants that do not match the given selector', function () { 567 | expect(this.$r).to.have.length(shallow ? 8 : 8); 568 | }); 569 | 570 | it('the matched descendants have the expected tag names', function () { 571 | var expected; 572 | 573 | if (shallow) { 574 | expected = [ 575 | 'SPAN', 576 | 'SPAN', 577 | 'A', 578 | 'BUTTON', 579 | 'SPAN', 580 | undefined, 581 | undefined, 582 | 'DIV' 583 | ]; 584 | } else { 585 | expected = [ 586 | 'SPAN', 587 | 'A', 588 | 'BUTTON', 589 | 'SPAN', 590 | undefined, 591 | 'BUTTON', 592 | 'DIV', 593 | 'SPAN' 594 | ]; 595 | } 596 | 597 | expect(this.$r.components.map(tagName)).to.eql(expected); 598 | }); 599 | 600 | it('the matched descendants have the expected class names', function () { 601 | var expected; 602 | 603 | if (shallow) { 604 | expected = [ 605 | undefined, 606 | undefined, 607 | 'button', 608 | 'button button-default', 609 | undefined, 610 | undefined, 611 | undefined, 612 | undefined 613 | ]; 614 | } else { 615 | expected = [ 616 | undefined, 617 | 'button', 618 | 'button button-default', 619 | undefined, 620 | undefined, 621 | 'child-component', 622 | undefined, 623 | undefined 624 | ]; 625 | } 626 | 627 | expect(this.$r.components.map(className)).to.eql(expected); 628 | }); 629 | 630 | it('the composite component is matched', function () { 631 | expectType(this.$r[shallow ? 5 : 4], ChildComponent); 632 | }); 633 | }); 634 | 635 | describe('when scoped to children', function () { 636 | before(function () { 637 | this.$r = run('div > :not(p)'); 638 | }); 639 | 640 | it('finds all the children that do not match the given selector', function () { 641 | expect(this.$r).to.have.length(shallow ? 7 : 7); 642 | }); 643 | 644 | it('the matched children have the expected tag names', function () { 645 | var expected; 646 | 647 | if (shallow) { 648 | expected = [ 649 | 'SPAN', 650 | 'A', 651 | 'BUTTON', 652 | 'SPAN', 653 | undefined, 654 | undefined, 655 | 'DIV' 656 | ]; 657 | } else { 658 | expected = [ 659 | 'A', 660 | 'BUTTON', 661 | 'SPAN', 662 | undefined, 663 | 'BUTTON', 664 | 'DIV', 665 | 'SPAN' 666 | ]; 667 | } 668 | 669 | expect(this.$r.components.map(tagName)).to.eql(expected); 670 | }); 671 | 672 | it('the matched children have the expected class names', function () { 673 | var expected; 674 | 675 | if (shallow) { 676 | expected = [ 677 | undefined, 678 | 'button', 679 | 'button button-default', 680 | undefined, 681 | undefined, 682 | undefined, 683 | undefined 684 | ]; 685 | } else { 686 | expected = [ 687 | 'button', 688 | 'button button-default', 689 | undefined, 690 | undefined, 691 | 'child-component', 692 | undefined, 693 | undefined 694 | ]; 695 | } 696 | 697 | expect(this.$r.components.map(className)).to.eql(expected); 698 | }); 699 | }); 700 | 701 | describe('when using union selector', function () { 702 | before(function () { 703 | this.$r = run('div :not(p, button, span)'); 704 | }); 705 | 706 | it('finds all the descendants that do not match any of the union expressions', function () { 707 | expect(this.$r).to.have.length(shallow ? 4 : 3); 708 | }); 709 | 710 | it('the matched children have the expected tag names', function () { 711 | var expected; 712 | 713 | if (shallow) { 714 | expected = [ 715 | 'A', 716 | undefined, 717 | undefined, 718 | 'DIV' 719 | ]; 720 | } else { 721 | expected = [ 722 | 'A', 723 | undefined, 724 | 'DIV' 725 | ]; 726 | } 727 | 728 | expect(this.$r.components.map(tagName)).to.eql(expected); 729 | }); 730 | 731 | it('the matched children have the expected class names', function () { 732 | var expected; 733 | 734 | if (shallow) { 735 | expected = [ 736 | 'button', 737 | undefined, 738 | undefined, 739 | undefined 740 | ]; 741 | } else { 742 | expected = [ 743 | 'button', 744 | undefined, 745 | undefined 746 | ]; 747 | } 748 | 749 | expect(this.$r.components.map(className)).to.eql(expected); 750 | }); 751 | }); 752 | 753 | describe('when match negates self', function () { 754 | before(function () { 755 | this.$r = run('div.my-class:not(.my-class)'); 756 | }); 757 | 758 | it('matches nothing', function () { 759 | expect(this.$r).to.have.length(0); 760 | }); 761 | }); 762 | 763 | it('throws an error on missing )', function () { 764 | expect(function () { 765 | run(':not('); 766 | }).to.throw('Syntax error, unclosed )'); 767 | }); 768 | 769 | it('throws an error on un-matched )', function () { 770 | expect(function () { 771 | run(')'); 772 | }).to.throw('Syntax error, unmatched )'); 773 | }); 774 | }); 775 | 776 | describe('#style', function () { 777 | before(function () { 778 | this.component = TestUtils.renderIntoDocument(React.createElement('div', { style: { height: 123, display: 'none' } })); 779 | this.$r = $R(this.component); 780 | }); 781 | 782 | it('returns a px value for height', function() { 783 | expect(this.$r.style('height')).to.eq('123px'); 784 | }); 785 | 786 | it('returns the string value for display property', function() { 787 | expect(this.$r.style('display')).to.eq('none'); 788 | }); 789 | 790 | it('returns undefined when no style is defined', function() { 791 | expect(this.$r.style('abc')).to.be.undefined; 792 | }); 793 | 794 | it('throws an error when no component is in the scope', function() { 795 | var $r = this.$r; 796 | expect(function () { 797 | $r.find('p').style('a'); 798 | }).to.throw('$R#style requires at least one component. No components in current scope.'); 799 | }); 800 | }); 801 | } 802 | 803 | describe('Normal Selectors', function () { 804 | runSelectors(false); 805 | }); 806 | 807 | describe('Shallow Selectors', function () { 808 | runSelectors(true); 809 | }); 810 | --------------------------------------------------------------------------------