├── .gitignore ├── .npmignore ├── LICENSE.MD ├── README.md ├── examples ├── instareact-app │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── feed.css │ │ ├── index.html │ │ ├── instalogo.png │ │ ├── jquery-3.2.1.min.js │ │ └── profile.jpg │ └── src │ │ ├── index.css │ │ ├── index.js │ │ └── jquery-3.2.1.min.js └── tictactoe │ ├── README.md │ ├── client │ └── bundle.js │ ├── index.js │ ├── package.json │ ├── scss │ ├── _game.scss │ ├── _variables.scss │ └── application.scss │ ├── server │ ├── db │ │ ├── games.dev.json │ │ ├── games.js │ │ └── games.test.json │ └── util.js │ ├── src │ ├── components │ │ ├── App.js │ │ ├── GameList.js │ │ ├── Input.js │ │ ├── Row.js │ │ ├── Square.js │ │ └── newWindow.html │ └── index.js │ ├── test.js │ ├── test │ ├── index.js │ └── js │ │ ├── enzyme.js │ │ ├── enzymeTest.js │ │ ├── supertest.js │ │ ├── unit.js │ │ └── zombie.js │ ├── views │ └── index.ejs │ └── webpack.config.js ├── package.json ├── src ├── BlockScreenshot.png ├── assert.js ├── demo.gif ├── dom-parse.js ├── inject.js └── nodeStore.js └── test └── assert.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bundle.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /examples 2 | /cli -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | #MIT License 2 | 3 | ###Copyright (c) 2017 Ian Ramos, Ian Rustandi, Brian Schmalz 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React VT 2 | 3 | React VT presents a live view of the React component structure of your app, along with current state and props. 4 | 5 | Users can define assertions and test them in real time while interacting with their application. Once the user is satisfied with their defined tests, they can export their assertions into an Enzyme file. 6 | 7 | ![Image of demo](https://github.com/brIAN-3/react-vt/blob/master/src/demo.gif) 8 | 9 | 10 | ## Set Up 11 | **Please make sure that your application is running React and React-DOM version 15.x** 12 | 13 | 1. Run npm install for React VT in your root project folder 14 | 15 | ``` 16 | npm install --save-dev react-vt 17 | ``` 18 | 19 | 2. Import React VT in your top-level component and use your imported React class and 'this' as arguments to React VT in the componentWillMount lifecycle. 20 | ```javascript 21 | import React, { Component } from 'react'; 22 | // Add import statement 23 | import reactVT from 'react-vt'; 24 | 25 | class App extends Component { 26 | // Add your own componentWillMount if there is not an existing one 27 | componentWillMount() { 28 | // Add this line in componentWillMount 29 | reactVT(React, this); 30 | } 31 | } 32 | ``` 33 | 34 | 3. Install the React VT Chrome Developer Tool from the [Chrome Store](https://chrome.google.com/webstore/detail/react-vt/aphjepidficfgphkbggojoemgpmianhi?hl=en). 35 | 36 | 37 | 4. Run your React application in Chrome 38 | 39 | 5. Open Chrome Developer Tools -> React VT panel 40 | 41 | ## Usage 42 | 43 | ### Terminology 44 | * Assertion Block: An assertion block is a collection of actions and tests to define your assertion. This is analogous to *it* statements in Mocha tests. At least one test or action is required for an assertion block to be valid. 45 | * Action: Actions can be added to your assertion block to build your test case. Click, double click, right click, and enter are available as actions. **Enzyme exports are currently unsupported for Enter actions**. 46 | * Test: Equal, not equal, greater than and less than comparisons can be done on props, state, and text nodes. 47 | 48 | ### Creating an assertion block 49 | 1. Click on new assertion block and enter a name - e.g. 'should render a button'. This will be the statement that will appear on an exported Enzyme test: 50 | ```javascript 51 | it('should render a button', () => {...}); 52 | ``` 53 | 2. To create an action, click on a tree node in order to specify the component that the action will be done on and select an action type. 54 | 3. To create a test: 55 | 1. Select target 56 | * Node: Selected tree node, which refers to a component 57 | * Component: Specify a component to test the number of components (length) or a particular component (index) 58 | * ID: Select an element by ID to test text content 59 | * Class: Select elements by class to test the number of elements that match the className (length) or a particular element of the class (index) 60 | * Tag: Select a tag type to select elements that are not categorized as class or ID 61 | 2. Select source: state, props, or both will show up as selections if they exist. Only text is available as a source with ID, class, and tag targets. 62 | * Property: Select a property within the state or props 63 | * Modifier: If the property value is an array, a modifier is available to select either the length or an index to select an element from the array 64 | 3. Set Expectation 65 | * Select the data type for the expectation to compare to - string, number, boolean, undefined, and null are available 66 | * Select a comparator - equal, not equal, greater than, and less than are available 67 | * Set the expected value 68 | 69 | ### Exporting Tests into an Enzyme File 70 | Once you're satisfied with your tests, you can export them as an Enzyme file and use it as a basis to start writing your own Enzyme tests. Enzyme exports are generated based on the Mocha test framework and Chai assertion library. 71 | 72 | **Note that Enter actions are currently unsupported for this feature.** 73 | 74 | 1. Clicking the 'Export to Enzyme' button will initiate a download. Check your default downloads folder and look for the generated 'enzymeTest.js' file. 75 | 2. Make sure that you've installed Mocha, Chai, and Enzyme in your project. 76 | 3. Transfer your enzymeTest.js file into your test folder. 77 | 4. The generated test will resemble something similar to below. Replace the import path with the location of your top-level component. 78 | ```javascript 79 | const expect = require('chai').expect; 80 | import React from 'react'; 81 | import { mount } from 'enzyme'; 82 | import 'jsdom-global/register'; 83 | 84 | // MAKE SURE TO UPDATE THE PATH BELOW TO THE LOCATION OF YOUR TOP-LEVEL COMPONENT 85 | import App from 'fill this in with proper path'; 86 | 87 | describe('React VT Tests', () => { 88 | let wrapper; 89 | beforeEach(() => { 90 | wrapper = mount(); 91 | }); 92 | 93 | it('should equal X', () => { 94 | wrapper.find('#board').childAt(0).childAt(0).simulate('click'); 95 | expect(wrapper.find('Square').at(0).props().letter).to.equal('X'); 96 | }); 97 | 98 | }); 99 | ``` 100 | 5. Run your test suite. 101 | 102 | ### Other Notes 103 | 1. React-router and NextJS are currently unsupported. We are looking into supporting react-router in a future release. 104 | 2. Multiple actions and tests can be defined in one assertion block. Once you're ready to test, save the assertion block. Saved assertion blocks are stored in LocalStorage and are restored the next time React VT is opened. 105 | 3. To run through your actions and tests, execute them in your React application while React VT Developer Tool is open to view live test results (e.g. clicking the button that you've specified as an action in your assertion block). 106 | 4. Tests will not run until the actions that precede them are completed. 107 | 108 | ![Image of Block](https://github.com/brIAN-3/react-vt/blob/master/src/BlockScreenshot.png) 109 | 110 | E.g. Test to check the value of Square will not run until the click action on Square has occurred. 111 | 112 | ## Contributing 113 | 114 | Please submit issues/pull requests if you have feedback or would like to contribute. If you're interested in joining the React VT team as a contributor, feel free to message one of us directly. 115 | 116 | ## Authors 117 | 118 | Brian Schmalz (https://github.com/bschmalz) 119 | 120 | Ian Rustandi (https://github.com/icrustandi) 121 | 122 | Ian Ramos (https://github.com/ibramos) 123 | 124 | ## License 125 | 126 | This project is licensed under the MIT License - see the LICENSE.md file for details 127 | -------------------------------------------------------------------------------- /examples/instareact-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /examples/instareact-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^15.5.4", 7 | "react-dom": "^15.5.4" 8 | }, 9 | "devDependencies": { 10 | "react-scripts": "0.9.5" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } -------------------------------------------------------------------------------- /examples/instareact-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactVT/react-vt/3db86a856419ac9ed979cdbb7ad0ff9963a84691/examples/instareact-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/instareact-app/public/feed.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica; 3 | } 4 | 5 | #header { 6 | text-align: center; 7 | height: 50px; 8 | vertical-align: middle; 9 | display: flex; 10 | } 11 | 12 | #leftHeader { 13 | float: left; 14 | list-style: none; 15 | } 16 | 17 | #rightBorder { 18 | border-right: 1px solid #000000; 19 | } 20 | 21 | #leftHeader li { 22 | display: inline; 23 | padding: 0px; 24 | padding-bottom: 10px; 25 | padding-right: 20px; 26 | vertical-align: middle; 27 | margin: auto; 28 | } 29 | 30 | #leftHeader i { 31 | vertical-align: middle; 32 | } 33 | 34 | #leftHeader img { 35 | width: 120px; 36 | vertical-align: middle; 37 | } 38 | 39 | #leftHeader { 40 | margin-left: 100px; 41 | padding-bottom: 20px; 42 | height: 100%; 43 | } 44 | 45 | #rightHeader { 46 | float: right; 47 | list-style: none; 48 | margin-right: 200px; 49 | color: rgb(100, 100, 100); 50 | } 51 | 52 | #rightHeader li { 53 | display: inline; 54 | padding: 10px; 55 | } 56 | 57 | #search { 58 | margin: auto; 59 | margin-top: 20px; 60 | text-align: center; 61 | vertical-align: middle; 62 | font-size: 14px; 63 | width: 200px; 64 | padding: 5px 10px; 65 | } 66 | 67 | #search:focus { 68 | text-align: left; 69 | } 70 | 71 | .post { 72 | width: 600px; 73 | border: 1px solid rgb(230, 230, 230); 74 | margin: 50px auto; 75 | border-radius: 3px; 76 | 77 | } 78 | 79 | .postHeader { 80 | height: 70px; 81 | padding: 15px; 82 | padding-bottom: 0; 83 | } 84 | 85 | #feed { 86 | text-align: center; 87 | width: 100% 88 | margin: auto; 89 | } 90 | 91 | .logoPic { 92 | height: 75%; 93 | display: inline-block; 94 | float: left; 95 | border-radius: 50%; 96 | } 97 | 98 | .postPic { 99 | width: 100%; 100 | } 101 | 102 | .headerText { 103 | display: inline-block; 104 | float: left; 105 | padding-left: 20px; 106 | text-align: left; 107 | } 108 | 109 | .headerText p{ 110 | margin: 5px; 111 | padding: 0px; 112 | font-size: 15px; 113 | } 114 | 115 | .name { 116 | font-weight: 600; 117 | font-size: 16px; 118 | } 119 | 120 | .location { 121 | font-weight: 300; 122 | } 123 | 124 | .date { 125 | display: inline-block; 126 | float: right; 127 | } 128 | 129 | .likes { 130 | text-align: left; 131 | margin-left: 20px; 132 | font-weight: 700; 133 | } 134 | 135 | .comments { 136 | text-align: left; 137 | margin-left: 20px; 138 | } 139 | 140 | .comment { 141 | margin: 10px 0px; 142 | } 143 | .userLink { 144 | font-weight: 700; 145 | } 146 | 147 | .social { 148 | text-align: left; 149 | border-top: 1px solid rgb(230, 230, 230); 150 | margin: 20px; 151 | padding-top: 20px; 152 | margin-bottom: 15px; 153 | 154 | } 155 | 156 | .addComment { 157 | font-size: 16px; 158 | padding: 5px; 159 | width: 500px; 160 | vertical-align: middle; 161 | margin-top: -15px; 162 | margin-left: 10px; 163 | border: none; 164 | } 165 | 166 | .addComment:focus { 167 | outline: none; 168 | } 169 | 170 | .liked { 171 | color: #ED4956; 172 | } -------------------------------------------------------------------------------- /examples/instareact-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 | 21 | 22 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/instareact-app/public/instalogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactVT/react-vt/3db86a856419ac9ed979cdbb7ad0ff9963a84691/examples/instareact-app/public/instalogo.png -------------------------------------------------------------------------------- /examples/instareact-app/public/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactVT/react-vt/3db86a856419ac9ed979cdbb7ad0ff9963a84691/examples/instareact-app/public/profile.jpg -------------------------------------------------------------------------------- /examples/instareact-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica; 3 | } 4 | 5 | #header { 6 | text-align: center; 7 | height: 50px; 8 | vertical-align: middle; 9 | display: flex; 10 | } 11 | 12 | #leftHeader { 13 | float: left; 14 | list-style: none; 15 | } 16 | 17 | #rightBorder { 18 | border-right: 1px solid #000000; 19 | } 20 | 21 | #leftHeader li { 22 | display: inline; 23 | padding: 0px; 24 | padding-bottom: 10px; 25 | padding-right: 20px; 26 | vertical-align: middle; 27 | margin: auto; 28 | } 29 | 30 | #leftHeader i { 31 | vertical-align: middle; 32 | } 33 | 34 | #leftHeader img { 35 | width: 120px; 36 | vertical-align: middle; 37 | } 38 | 39 | #leftHeader { 40 | margin-left: 100px; 41 | padding-bottom: 20px; 42 | height: 100%; 43 | } 44 | 45 | #rightHeader { 46 | float: right; 47 | list-style: none; 48 | margin-right: 200px; 49 | color: rgb(100, 100, 100); 50 | } 51 | 52 | #rightHeader li { 53 | display: inline; 54 | padding: 10px; 55 | } 56 | 57 | #search { 58 | margin: auto; 59 | margin-top: 20px; 60 | text-align: center; 61 | vertical-align: middle; 62 | font-size: 14px; 63 | width: 200px; 64 | padding: 5px 10px; 65 | } 66 | 67 | #search:focus { 68 | text-align: left; 69 | } 70 | 71 | .post { 72 | width: 600px; 73 | border: 1px solid rgb(230, 230, 230); 74 | margin: 50px auto; 75 | border-radius: 3px; 76 | 77 | } 78 | 79 | .postHeader { 80 | height: 70px; 81 | padding: 15px; 82 | padding-bottom: 0; 83 | } 84 | 85 | #feed { 86 | text-align: center; 87 | width: 100%; 88 | margin: auto; 89 | } 90 | 91 | .logoPic { 92 | height: 75%; 93 | display: inline-block; 94 | float: left; 95 | border-radius: 50%; 96 | } 97 | 98 | .postPic { 99 | width: 100%; 100 | } 101 | 102 | .headerText { 103 | display: inline-block; 104 | float: left; 105 | padding-left: 20px; 106 | text-align: left; 107 | } 108 | 109 | .headerText p{ 110 | margin: 5px; 111 | padding: 0px; 112 | font-size: 15px; 113 | } 114 | 115 | .name { 116 | font-weight: 600; 117 | font-size: 16px; 118 | } 119 | 120 | .location { 121 | font-weight: 300; 122 | } 123 | 124 | .date { 125 | display: inline-block; 126 | float: right; 127 | } 128 | 129 | .likes { 130 | text-align: left; 131 | margin-left: 20px; 132 | font-weight: 700; 133 | } 134 | 135 | .comments { 136 | text-align: left; 137 | margin-left: 20px; 138 | } 139 | 140 | .comment { 141 | margin: 10px 0; 142 | text-align: left; 143 | } 144 | .userLink { 145 | font-weight: 700; 146 | margin-right: 5px; 147 | } 148 | 149 | .social { 150 | text-align: left; 151 | border-top: 1px solid rgb(230, 230, 230); 152 | margin: 20px; 153 | padding-top: 20px; 154 | margin-bottom: 15px; 155 | 156 | } 157 | 158 | .addComment { 159 | font-size: 16px; 160 | padding: 5px; 161 | width: 500px; 162 | vertical-align: middle; 163 | margin-top: -15px; 164 | margin-left: 10px; 165 | border: none; 166 | } 167 | 168 | .addComment:focus { 169 | outline: none; 170 | } 171 | 172 | .liked { 173 | color: #ED4956; 174 | } -------------------------------------------------------------------------------- /examples/instareact-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import injector from './../../../src/inject.js'; 5 | 6 | class List extends React.Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | posts: [], 11 | }; 12 | this.picSearch('https://codesmith-precourse.firebaseio.com/instagram/-JqL35o8u6t3dTQaFXSV.json'); 13 | } 14 | 15 | componentWillMount() { 16 | injector(React, this); 17 | } 18 | 19 | picSearch(term) { 20 | fetch(term).then((response) => { 21 | response.json().then((data) => { 22 | this.picValidate(data); 23 | }); 24 | }); 25 | } 26 | 27 | addPic(pic) { 28 | const newPosts = this.state.posts.slice(); 29 | if (!newPosts.includes(pic)) { 30 | newPosts.push(pic); 31 | } 32 | this.setState({ posts: newPosts }); 33 | } 34 | 35 | picValidate(data) { 36 | const self = this; 37 | let count = 0; 38 | data.forEach((pic) => { 39 | fetch(pic) 40 | .then((response) => { 41 | if (response.status !== 200) { 42 | // Error text could be added here if desired 43 | return; 44 | } 45 | count++; 46 | if (count < 5) self.addPic(pic); 47 | } 48 | ) 49 | .catch((err) => { 50 | }); 51 | // Error text could be added here if desired 52 | }); 53 | } 54 | 55 | render() { 56 | const posts = this.state.posts.map((url) => { 57 | return ( 58 | 59 | ); 60 | }); 61 | return ( 62 |
63 | {posts} 64 |
65 | ); 66 | } 67 | } 68 | 69 | class PostItem extends React.Component { 70 | constructor(props) { 71 | super(props); 72 | this.state = { 73 | url: props.url, 74 | likes: 7, 75 | liked: false 76 | }; 77 | } 78 | 79 | liked() { 80 | if(this.state.liked) { 81 | const likey = this.state.likes - 1; 82 | this.setState({ likes: likey, liked: false}); 83 | return; 84 | } 85 | const likey = this.state.likes + 1; 86 | this.setState({ likes: likey, liked: true}); 87 | } 88 | 89 | render() { 90 | const self = this; 91 | return ( 92 |
93 |
94 | huh 95 |
96 |

brischmalz

97 |

Altadena, CA

98 |
99 |

3d

100 |
101 | profile this.liked()}/> 102 | this.liked()}/> 104 |
105 | ); 106 | } 107 | } 108 | 109 | class Social extends React.Component { 110 | constructor(props) { 111 | super(props); 112 | this.likeClick = props.likeClick; 113 | this.state = { 114 | comments: [{ user: 'username', comment: 'Sup Yo', key: 0 }] 115 | }; 116 | } 117 | 118 | 119 | render() { 120 | const comments = this.state.comments.map(comment => 121 | 122 | ); 123 | 124 | return ( 125 |
126 |

{this.props.likes} likes

127 |
128 | {comments} 129 |
130 |
131 | this.likeClick()} liked={this.props.liked} /> 132 | { 134 | const add = { user: 'bschmalz', comment: input, key: getKey.getKey() }; 135 | const newComments = this.state.comments.slice(); 136 | newComments.push(add); 137 | this.setState({ comments: newComments }); 138 | }} 139 | /> 140 |
); 141 | } 142 | } 143 | 144 | const getKey = { 145 | key: 0, 146 | getKey: () => { 147 | getKey.key += 1; 148 | return getKey.key; 149 | } 150 | }; 151 | 152 | class InputComment extends React.Component { 153 | constructor(props) { 154 | super(props); 155 | this.onEnter = props.onEnter; 156 | this.state = { 157 | value: '' 158 | }; 159 | this.handleChange = this.handleChange.bind(this); 160 | } 161 | 162 | handleChange(event) { 163 | if (event.key === 'Enter') { 164 | this.onEnter(this.state.value); 165 | this.setState({ value: '' }); 166 | return; 167 | } 168 | const val = this.state.value + event.key; 169 | this.setState({ value: val }); 170 | } 171 | 172 | render() { 173 | return ( 174 | 181 | ); 182 | } 183 | } 184 | 185 | const Comment = props => 186 |

{props.user}{props.comment}

; 187 | 188 | const LikeButton = (props) => { 189 | const liked = props.liked; 190 | const str = liked ? 'fa fa-heart fa-2x likeButton liked' : 'fa fa-heart-o fa-2x likeButton'; 191 | return ( 192 |