├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── packages ├── platforms ├── release └── versions ├── README.md ├── both ├── actions │ └── CounterActions.jsx ├── components │ ├── Counter.jsx │ └── EvenOdd.jsx └── domains │ └── CounterDomain.jsx ├── client ├── main.html ├── main.jsx └── styles.css └── tests └── jasmine └── client └── unit ├── components ├── Counter_spec.js └── EvenOdd_spec.js └── spec_helper.js /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1q3uh68w7l5r3r9v0j5 8 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | react 11 | sanjo:jasmine 12 | velocity:html-reporter 13 | reactive-dict 14 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.2 2 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | amplify@1.0.0 2 | autopublish@1.0.3 3 | autoupdate@1.2.1 4 | babel-compiler@5.7.3 5 | babel-runtime@0.1.2 6 | base64@1.0.3 7 | binary-heap@1.0.3 8 | blaze@2.1.2 9 | blaze-tools@1.0.3 10 | boilerplate-generator@1.0.3 11 | callback-hook@1.0.3 12 | check@1.0.5 13 | coffeescript@1.0.6 14 | cosmos:browserify@0.4.0 15 | ddp@1.1.0 16 | deps@1.0.7 17 | ejson@1.0.6 18 | fastclick@1.0.3 19 | geojson-utils@1.0.3 20 | html-tools@1.0.4 21 | htmljs@1.0.4 22 | http@1.1.0 23 | id-map@1.0.3 24 | insecure@1.0.3 25 | jquery@1.11.3_2 26 | json@1.0.3 27 | jsx@0.1.3 28 | launch-screen@1.0.2 29 | less@1.0.14 30 | livedata@1.0.13 31 | logging@1.0.7 32 | meteor@1.1.6 33 | meteor-platform@1.2.2 34 | minifiers@1.1.5 35 | minimongo@1.0.8 36 | mobile-status-bar@1.0.3 37 | mongo@1.1.0 38 | observe-sequence@1.0.6 39 | ordered-dict@1.0.3 40 | package-version-parser@3.0.3 41 | practicalmeteor:chai@1.9.2_3 42 | practicalmeteor:loglevel@1.1.0_3 43 | random@1.0.3 44 | react@0.1.3 45 | react-meteor-data@0.1.1 46 | react-runtime@0.13.3_2 47 | react-runtime-dev@0.13.3_2 48 | react-runtime-prod@0.13.3_1 49 | reactive-dict@1.1.0 50 | reactive-var@1.0.5 51 | reload@1.1.3 52 | retry@1.0.3 53 | routepolicy@1.0.5 54 | sanjo:jasmine@0.14.0 55 | sanjo:karma@1.5.1 56 | sanjo:long-running-child-process@1.0.3 57 | sanjo:meteor-files-helpers@1.1.0_6 58 | sanjo:meteor-version@1.0.0 59 | session@1.1.0 60 | spacebars@1.0.6 61 | spacebars-compiler@1.0.6 62 | templating@1.1.1 63 | tracker@1.0.7 64 | ui@1.0.6 65 | underscore@1.0.3 66 | url@1.0.4 67 | velocity:chokidar@1.0.3_1 68 | velocity:core@0.7.1 69 | velocity:html-reporter@0.7.0 70 | velocity:meteor-internals@1.1.0_7 71 | velocity:meteor-stubs@1.1.0 72 | velocity:shim@0.1.0 73 | webapp@1.2.0 74 | webapp-hashing@1.0.3 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Meteor Tests 2 | ### React Unit Tests in Meteor 3 | 4 | - Jasmine Unit Tests 5 | - jQuery based spec helpers 6 | 7 | 8 | #### TODO 9 | - Shallow rendering (without DOM) 10 | - Test Domains (data fetching object) 11 | - Test Actions 12 | - Integration tests 13 | - Mocha 14 | - Jest 15 | - external Karma runner (not in Velocity) 16 | 17 | #### Usage 18 | To run just cd into this repo and run `meteor` 19 | 20 | 21 | This repo shows how to test a very basic React app. Though the app is small it's structured in a way that suits 22 | larger apps. However this makes it easy to test! If you like this layout structure/architecture, keep an eye on [Meteor Generate](https://github.com/AdamBrodzinski/meteor-generate) for a CLI scaffolding tool to *create components with an initial passing spec!* 23 | 24 | ![code](https://s3.amazonaws.com/f.cl.ly/items/0q1c3v381r2I2O320l1V/Screen%20Shot%202015-07-19%20at%2012.10.52%20AM.png) 25 | 26 | ![tests](https://s3.amazonaws.com/f.cl.ly/items/1G2q3x0u3N2l2p2R2x0V/Screen%20Shot%202015-07-19%20at%2012.10.11%20AM.png) 27 | -------------------------------------------------------------------------------- /both/actions/CounterActions.jsx: -------------------------------------------------------------------------------- 1 | /*global CounterActions:true */ 2 | 3 | CounterActions = { 4 | incrementCount() { 5 | CounterDomain.handleIncrementCount(1); 6 | console.log('[Actions] incrementCount'); 7 | }, 8 | 9 | decrementCount() { 10 | CounterDomain.handleDecrementCount(1); 11 | console.log('[Actions] decrementCount'); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /both/components/Counter.jsx: -------------------------------------------------------------------------------- 1 | /* jshint maxlen: false */ 2 | 3 | Counter = React.createClass({ 4 | mixins: [ReactMeteorData], 5 | 6 | getDefaultProps() { 7 | return { foo: true }; 8 | }, 9 | 10 | getInitialState() { 11 | return { isHidden: false }; 12 | }, 13 | 14 | getMeteorData() { 15 | return { 16 | count: CounterDomain.getCount() 17 | }; 18 | }, 19 | 20 | toggleHidden() { 21 | var toggled = !this.state.isHidden; 22 | this.setState({isHidden: toggled}); 23 | }, 24 | 25 | sumTwoNumbers(a, b) { 26 | return a + b; 27 | }, 28 | 29 | handleInc() { 30 | CounterActions.incrementCount(); 31 | }, 32 | 33 | handleDec() { 34 | CounterActions.decrementCount(); 35 | }, 36 | 37 | render() { 38 | return ( 39 |
40 |

Meteor Counter

41 | 42 |
Checkout the tests in the tests directory!
43 | 44 | 45 | 46 | 47 |

The count is: { this.data.count }

48 | 49 |
50 | ); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /both/components/EvenOdd.jsx: -------------------------------------------------------------------------------- 1 | /* jshint maxlen: false */ 2 | 3 | EvenOdd = React.createClass({ 4 | // notice no Meteor mixin is required since we don't have reactive data 5 | propTypes: { 6 | countNumber: React.PropTypes.number.isRequired 7 | }, 8 | 9 | isEven(num) { 10 | return num % 2 === 0; 11 | }, 12 | 13 | render() { 14 | var count = this.props.countNumber; 15 | var suffix = (this.isEven(count)) ? 'is Even' : 'is Odd'; 16 | return ( 17 |
18 | {this.props.countNumber} {suffix} 19 |
20 | ); 21 | } 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /both/domains/CounterDomain.jsx: -------------------------------------------------------------------------------- 1 | var state = new ReactiveDict('CounterDomain'); 2 | state.set('count', 0); 3 | 4 | CounterDomain = { 5 | getCount() { 6 | return state.get('count'); 7 | }, 8 | 9 | handleIncrementCount(amount) { 10 | state.set('count', this.getCount() + amount); 11 | }, 12 | 13 | handleDecrementCount(amount) { 14 | state.set('count', this.getCount() - amount); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /client/main.html: -------------------------------------------------------------------------------- 1 | 2 | Meteor Counter 3 | 4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /client/main.jsx: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | Meteor.startup(function() { 3 | var container = document.getElementById('app-container'); 4 | React.render(, container); 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | /* CSS declarations go here */ 2 | html { 3 | font-family: sans-serif; 4 | } 5 | 6 | button { 7 | height: 25px; 8 | margin: 7px 10px; 9 | display: block; 10 | } 11 | -------------------------------------------------------------------------------- /tests/jasmine/client/unit/components/Counter_spec.js: -------------------------------------------------------------------------------- 1 | /*global Counter, renderComponent */ 2 | 3 | // note, see ../spec_helper.js for renderWithProps 4 | 5 | describe("Counter Component", function() { 6 | var defProps, renderWithProps, component, el, $el; 7 | 8 | beforeEach(function() { 9 | defProps = { 10 | label: 'Check me', 11 | }; 12 | 13 | renderWithProps = function(props) { 14 | component = renderComponent(Counter, props); 15 | el = React.findDOMNode(component); 16 | $el = $(el); 17 | }; 18 | }); 19 | 20 | it("should be mounted in DOM", function() { 21 | renderWithProps(defProps); 22 | expect($el.length).toEqual(1); 23 | }); 24 | 25 | it("should have default foo prop", function() { 26 | renderWithProps({}); 27 | expect(component.props.foo).toBe(true); 28 | }); 29 | 30 | it("should have default hidden state", function() { 31 | renderWithProps({}); 32 | expect(component.state.isHidden).toBe(false); 33 | }); 34 | 35 | it("should toggle hidden state", function() { 36 | renderWithProps({}); 37 | component.toggleHidden(); 38 | expect(component.state.isHidden).toBe(true); 39 | }); 40 | 41 | it("should show count markup", function() { 42 | spyOn(CounterDomain, 'getCount').and.returnValue(5); 43 | renderWithProps({}); // re-render after stubbing 44 | expect($el.find('p').text()).toBe('The count is: 5'); 45 | }); 46 | 47 | it("inc button should call action when clicked", function() { 48 | spyOn(CounterActions, 'incrementCount'); 49 | renderWithProps({}); 50 | simulateClickOn($el.find('#inc')); 51 | expect(CounterActions.incrementCount).toHaveBeenCalled(); 52 | }); 53 | 54 | it("inc button should call action when clicked", function() { 55 | spyOn(CounterActions, 'decrementCount'); 56 | renderWithProps({}); 57 | simulateClickOn($el.find('#dec')); 58 | expect(CounterActions.decrementCount).toHaveBeenCalled(); 59 | }); 60 | 61 | it("should add two numbers", function() { 62 | renderWithProps({}); 63 | var result = component.sumTwoNumbers(2, 3); 64 | expect(result).toBe(5); 65 | }); 66 | 67 | it("should have proper heading", function() { 68 | expect($el.text()).toContain('Meteor Counter'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/jasmine/client/unit/components/EvenOdd_spec.js: -------------------------------------------------------------------------------- 1 | /*global EvenOdd, renderComponent */ 2 | 3 | // note, see ../spec_helper.js for renderWithProps 4 | 5 | describe("EvenOdd Component", function() { 6 | var defProps, renderWithProps, component, el, $el; 7 | 8 | beforeEach(function() { 9 | renderWithProps = function(props) { 10 | component = renderComponent(EvenOdd, props); 11 | el = React.findDOMNode(component); 12 | $el = $(el); 13 | }; 14 | }); 15 | 16 | it("should determine if number is odd or even", function() { 17 | renderWithProps({}); 18 | expect(component.isEven(0)).toBe(true); 19 | expect(component.isEven(4)).toBe(true); 20 | expect(component.isEven(5)).toBe(false); 21 | }); 22 | 23 | it("should print out odd", function() { 24 | renderWithProps({countNumber: 5}); 25 | expect($el.text()).toBe("5 is Odd"); 26 | }); 27 | 28 | it("should print out even", function() { 29 | renderWithProps({countNumber: 4}); 30 | expect($el.text()).toBe("4 is Even"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/jasmine/client/unit/spec_helper.js: -------------------------------------------------------------------------------- 1 | TestUtils = React.addons.TestUtils; 2 | Simulate = TestUtils.Simulate; 3 | 4 | renderComponent = function (comp, props) { 5 | return TestUtils.renderIntoDocument( 6 | React.createElement(comp, props) 7 | ); 8 | }; 9 | 10 | simulateClickOn = function($el) { 11 | React.addons.TestUtils.Simulate.click($el[0]); 12 | }; 13 | --------------------------------------------------------------------------------