├── .babelrc
├── .npmignore
├── CHANGELOG.md
├── README.md
├── build.sh
├── demo
├── dist
│ ├── demo.html
│ ├── index.js
│ └── index.js.map
├── fixtures
│ ├── questions.json
│ └── user.json
├── package.json
└── src
│ ├── actions
│ └── actions.js
│ ├── components
│ ├── Answer.js
│ ├── App.js
│ ├── Byline.js
│ ├── Keywords.js
│ ├── Question.js
│ ├── QuestionDetail.js
│ ├── QuestionList.js
│ ├── SubmitAnswer.js
│ └── SubmitQuestion.js
│ ├── index.js
│ ├── reducers
│ └── reducers.js
│ └── styles
│ └── styles.js
├── dist
├── index.js
└── index.js.map
├── gulpfile.js
├── package.json
├── scripts
└── make-package-json.js
├── src
├── components
│ ├── Button.js
│ ├── ComponentList.js
│ ├── Console.js
│ ├── Controls.js
│ ├── DataIconCell.js
│ ├── LifeCycle.js
│ ├── Method.js
│ ├── PopoutWindow.js
│ └── Utf8Char.js
├── index.js
├── root.js
├── settingsManager.js
├── store
│ ├── actions.js
│ ├── lifecycleConfig.js
│ └── reducers.js
├── styles
│ └── styles.js
└── vendor
│ ├── bootstrap.css
│ ├── bootstrap.min.css
│ ├── bootstrapStyles.js
│ ├── bootstrapTableStyles.js
│ └── react-bootstrap-table-all.min.css
├── test
├── ConfigureSpec.js
├── MonitorSpec.js
└── fixtures
│ └── entries.json
└── webpack.config-test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0",
5 | "react"
6 | ],
7 | "plugins": [
8 | "add-module-exports"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .gitignore
3 | .idea/
4 | *.tgz
5 | .babelrc
6 | build.sh
7 | gulpfile.js
8 | npm-debug.log
9 | node_modules/
10 | demo
11 | src
12 | test
13 | scripts
14 | webpack.config-test.js
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### v0.0.8 (2016-10-18)
2 |
3 | #### New Features
4 |
5 | - You can disable Continuous Refresh of the Monitor and manually refresh instead for better performance
6 | - Monitor shows whether setState has been called in a component
7 | - Better styling in Monitor for showing presence of overridden lifecycle methods in wrapped component
8 | - The Component List is sortable by column
9 | - You can filter the Component List
10 | - Component List is responsive
11 | - Faster performance
12 |
13 | #### Bug Fixes
14 |
15 | - Presence of overridden lifecycle methods in wrapped component now correctly detected
16 | - States/props now correctly flagged as different if one is null
17 | - Component names now displayed properly in React debugger
18 | - Scrollbar now works correctly in Component List
19 |
20 |
21 | ### v0.0.5 (2016-10-10)
22 |
23 | #### New Features
24 |
25 | - Global configuration can be set from webpack build
26 | - Component-level configuration can be set through arguments passed to Visible wrapper function
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Visible React
2 |
3 | See a visual representation of the component lifecycle events as they occur in your React application. Analyze and correct performance lags caused by unnecessary component rerenders.
4 |
5 | [Try out the demo](https://rawgit.com/rkendall/visible-react/master/demo/dist/demo.html)
6 | (Your popup blocker must be disabled)
7 |
8 | [This tool has not yet been tested on versions of React prior to 15 or on browsers other than Chrome and Firefox.]
9 |
10 | ## What Is Visible React?
11 |
12 | Include **Visible React** in your React project, and a Monitor Window will open when your project launches in a development environment. (You must disable your browser's popup blocker to enable the window.) The Monitor provides a visualization of the lifecycle events in every component mounted in your app, along with views of the data passing through them. It also warns you about components that are rerendering unnecessarily, potentially slowing performance. In a production environment, it optionally provides a means for avoiding these unnecessary rerenders.
13 |
14 | ## Why Visible React?
15 |
16 | #### Learning
17 |
18 | Creating full-featured, efficient components in React requires a thorough understanding of the component rendering lifecycle and the methods associated with lifecycle events. The React lifecycle can be confusing even to experienced React users, however, because of its complexity. There are different events for the initial render and subsequent rerenders. You have to know which of the available versions of props and state to read at any given point in the lifecycle. You can set the component's state inside some lifecycle methods but not others, and optimal performance depends upon setting the state in the correct places. Performance further depends upon preventing unnecessary rerenders by checking for changes in data. **Visible React** clarifies these issues by means of detailed visualizations.
19 |
20 | #### Debugging
21 |
22 | In the **Visible React** Monitor, trace the flow of props and states through the different components of your app, view and diff props/state values, see which lifecycle events have been triggered, and see whenever setState has been called. In the browser console, view a log of all the lifecycle events triggered by your components.
23 |
24 | #### Performance Analysis
25 | Your app can be slowed by unnecessary rerenders that are normally very difficult to identify. **Visible React** finds these performance bottlenecks for you.
26 |
27 | #### Performance Enhancement
28 |
29 | **Visible React** provides a global solution for preventing many unnecessary rerenders by overriding the `shouldComponentUpdate` method.
30 |
31 | ## Adding It to Your Project
32 |
33 | Install from NPM.
34 |
35 | `npm install visible-react`
36 |
37 | Import **Visible React** into each component you want to monitor.
38 |
39 | `import Visible from 'visible-react';`
40 |
41 | or
42 |
43 | `var Visible = require('visible-react');`
44 |
45 | Wrap the component's export in the `Visible` function.
46 |
47 | `export default Visible()(MyWidget);`
48 |
49 | If the component has other wrappers, wrap it with Visible first.
50 |
51 | `export default Radium(Visible()(MyWidget));`
52 |
53 |
54 | ## The Visible React Monitor
55 |
56 | The Component Pane on the left side of the Monitor presents a list of all the components in your app that are currently or were formerly mounted. Colored dots appear in the leftmost column if the component's props or state changed during the last render (blue dots for props, green for state). The **Rendered** column shows how many times the component has been rendered since mounting. (The value is reset upon each unmounting.) The **Warnings** column shows the number of times that **Visible React** has prevented a component from unnecessarily rerendering when its props and state have not changed. To filter the component list, type filtering text into the **Filter** field. To sort the list, click on the header of the column you wish to sort by.
57 |
58 | The Lifecycle Pane on the right side of the monitor provides a visualization of the selected component's lifecycle events. The three columns of cards represent the three possible types of lifecycle activity: initial mounting, updating, and unmounting. Each card represents a lifecycle method, and a glow effect shows which methods were called as part of the component's last lifecycle activity. Each Method Card provides the following information.
59 |
60 | * A yellow box around the method name indicates that the method is called by the underlying wrapped component.
61 | * The **Times called** field shows the number of times the method has been called since mounting.
62 | * The card lists all the props and state variables available in the method and shows the values they had the last time that method was called. Click on a value to see its complete JSON.
63 | * If a new props or state value is different from the old one (eg., `nextProps` compared to `this.props`), both values are shown. If they are the same, only a single value is shown for both variables. The popup JSON view shows a line-by-line diff of any differences between old and new values.
64 | * Colored text (blue for props, green for state) shows where in the lifecycle any data changes have been introduced.
65 | * A gray box indicates whether the state can be set within the method.
66 | * A green box around `setState` indicates that `setState` was called by the method.
67 | * At the bottom of the **shouldComponentUpdate** card a warning will appear if **Visible React** has prevented a component from unnecessarily rerendering when its props and state have not changed.
68 | * At the bottom of the **componentDidMount** and **componentDidUpdate** cards, a warning will appear if `setState` was called within these methods, indicating that the extra rerender this causes may have been unnecessary.
69 |
70 | If your app is complex or uses large data sets, the **Visible React** Monitor Window may slow its performance. If this is the case, you can eliminate all or most of this performance lag by unselecting the Continuous Refresh checkbox at the left. Then click the Refresh button every time you want to refresh the contents of the Monitor Window.
71 |
72 | ## Managing Rerenders with Life Insurance
73 |
74 | By default, React rerenders a component every time the props or state are updated, even if the actual data hasn’t changed. These unnecessary rerenders can slow performance if the component is large or has a lot of children (which will also rerender), or if multiple rerenders occur in quick succession.
75 |
76 | React provides a preventive measure for this _rerenderitis_ in the form of the `shouldComponentUpdate` method. You can use it to evaluate the component’s state and props, and if nothing has changed, you can return false from the method to prevent a rerender. There are complications, though. If the data structure is deeply nested you’ll have to do a deep comparison to avoid a false negative, unless you’re using immutable data structures. A deep compare on a very large data structure could potentially take longer than the rerender it is preventing.
77 |
78 | This is where **Visible React** comes in. It provides a feature called Life Insurance, which by default performs the following functions in a development environment.
79 |
80 | 1. Overrides `shouldComponentUpdate` in every wrapped component
81 | 1. Intercepts the returned value
82 | 1. If an existing `shouldComponentUpdate` method returns false, Life Insurance returns false
83 | 1. Otherwise it deep compares the new and old props and states
84 | 1. Returns false if there are no changes (preventing a rerender)
85 | 1. Provides warnings in the Visible React Monitor if unnecessary rerenders have been prevented
86 |
87 | Life Insurance is disabled by default in production environments, but you can enable it to prevent unnecessary rerenders. See below for instructions on configuring **Visible React** and Life Insurance.
88 |
89 | ## Configuring Visible React
90 |
91 | ### Quick Start
92 |
93 | To specify a development or production environment for **Visible React**, you must include a task in your build process to replace `process.env.NODE_ENV` in your build with the appropriate value. (This is also the recommended method for specifying the environment to React.) If no environment is specified, **Visible React** defaults to 'development.' The following example sets the environment to 'production' in a Gulp build. (Requires `gulp-replace`.)
94 |
95 | ```javascript
96 | gulp.task('set-vr', function() {
97 | gulp.src('build/bundle.js')
98 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('production')))
99 | .pipe(gulp.dest('dist'));
100 | });
101 | ```
102 |
103 | To activate preventive comparison in a 'production' environment, make the following addition. This will trigger a shallow comparison on the data going into each component and prevent any unnecessary rerenders. This is the same functionality as that provided by React's pureRenderMixin, except that Life Insurance doesn't override a false value that is returned from an existing `shouldComponentUpdate` method. See below for more configuration details.
104 |
105 | ```javascript
106 | gulp.task('set-vr', function() {
107 | gulp.src('build/bundle.js')
108 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('production')))
109 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify('all')))
110 | .pipe(gulp.dest('dist'));
111 | });
112 | ```
113 |
114 | ### Global Configuration
115 |
116 | The following variables can be set with a build tool, as shown above. Each variable name must be prefixed with either `VR_DEV_` or `VR_PROD_` to determine which environment (development or production) it will take effect in. For example, `VR_DEV_ENABLED` determines whether **Visible React** is enabled in a development environment. The first three variables listed below all accept the same three values: _'all'_ enables the feature in all components; *'none'* disables it in all components; *'selected'* enables it only where specified by a component configuration setting. (See below for setting component configuration options.)
117 |
118 | **ENABLED**
119 | *'all'|'selected'|'none'*
120 | Whether **Visible React** is enabled. If set to *'none'*, the features controlled by the following variables will all be disabled and the `Visible` function will noninvasively pass values through to the wrapped component without overriding any methods. Defaults to _'all'_ in development and _'none'_ in production.
121 | **MONITOR**
122 | *'all'|'selected'|'none'*
123 | Whether or not to display the Monitor Window. Defaults to _'all'_ in development and _'none'_ in production.
124 | **LOGGING**
125 | *'all'|'selected'|'none'*
126 | Whether to log messages to the browser console for each lifecycle event and setState call. Defaults to _'selected'_ in development and _'none'_ in production.
127 | **CONTROL**
128 | *'all'|'none'*
129 | Whether to prevent component rerenders if props or state have not changed. 'selected' is not an option for this variable. Defaults to _'all'_ in both environments.
130 | **COMPARE**
131 | *'none'|'shallow'|'deep'*
132 | The type of comparison to perform. If CONTROL is false, no comparison will be performed. Defaults to _'deep'_ in development and _'shallow'_ in production.
133 |
134 | #### Setting Variables in a Build
135 |
136 | Normally you will want to have one configuration for development that enables the dev tools and one for production that disables them. It's easy to accomplish a conditional configuration with a Gulp task, and you can set the relevant configuration options either in environment variables or directly in Gulp. The following Gulp task will configure **Visible React** using the values found in any existing Node environment variables. If any variable is undefined, **Visible React** will use its default value. The following example will set the environment to 'production,' enable logging for all components in 'development,' and enable **Visible React** in 'production.'
137 |
138 | ```javascript
139 | var replace = require('gulp-replace');
140 |
141 | process.env.NODE_ENV = 'production';
142 | process.env.VR_DEV_LOGGING = 'all';
143 | process.env.VR_PROD_ENABLED = 'all';
144 |
145 | gulp.task('set-vr', function() {
146 |
147 | gulp.src('build/bundle.js')
148 |
149 | .pipe(replace('process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV) || null))
150 |
151 | .pipe(replace('process.env.VR_DEV_ENABLED', JSON.stringify(process.env.VR_DEV_ENABLED) || null))
152 | .pipe(replace('process.env.VR_DEV_MONITOR', JSON.stringify(process.env.VR_DEV_MONITOR) || null))
153 | .pipe(replace('process.env.VR_DEV_LOGGING', JSON.stringify(process.env.VR_DEV_LOGGING) || null))
154 | .pipe(replace('process.env.VR_DEV_CONTROL', JSON.stringify(process.env.VR_DEV_CONTROL) || null))
155 | .pipe(replace('process.env.VR_DEV_COMPARE', JSON.stringify(process.env.VR_DEV_COMPARE) || null))
156 |
157 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify(process.env.VR_PROD_ENABLED) || null))
158 | .pipe(replace('process.env.VR_PROD_MONITOR', JSON.stringify(process.env.VR_PROD_MONITOR) || null))
159 | .pipe(replace('process.env.VR_PROD_LOGGING', JSON.stringify(process.env.VR_PROD_LOGGING) || null))
160 | .pipe(replace('process.env.VR_PROD_CONTROL', JSON.stringify(process.env.VR_PROD_CONTROL) || null))
161 | .pipe(replace('process.env.VR_PROD_COMPARE', JSON.stringify(process.env.VR_PROD_COMPARE) || null))
162 |
163 | .pipe(gulp.dest('dist'));
164 |
165 | });
166 | ```
167 |
168 | If you don't wish to set environment variables, you can accomplish the same configuration as above with the following more-concise syntax.
169 |
170 | ```javascript
171 | gulp.task('set-vr', function() {
172 | gulp.src('build/bundle.js')
173 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('production')))
174 | .pipe(replace('process.env.VR_DEV_LOGGING', JSON.stringify('all')))
175 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify('all'))
176 | .pipe(gulp.dest('dist'));
177 | });
178 | ```
179 |
180 | ### Component Configuration
181 |
182 | For more fine-grained control, you can pass arguments to the `Visible` wrapper function to control how **Visible React** handles individual components. You can configure a component by passing a configuration object as an argument to the `Visible` function. The supported variables correspond to the global configuration variables, but most of them accept Boolean values, since they apply only to a single component. All values default to false, except for `compare`, which defaults to null.
183 |
184 | **enabled**
185 | *true|false*
186 | Whether **Visible React** is enabled for the component.
187 | **monitor:**
188 | *true|false*
189 | Whether or not to include the component in the Monitor Window display.
190 | **logging:**
191 | *true|false*
192 | Whether to log messages to the browser console for the component.
193 | **compare**
194 | *'none'|'shallow'|'deep'*
195 | Type of comparison to perform for the component. This value will override any `compare` value specified in a build variable.
196 |
197 | Component configuration lets you optimize performance by specifying which type of preventive comparison to perform for each component. Shallow compares can return false negatives. Deep compares, while always accurate, are not always cost effective. Logging can be quite verbose, so it can be useful to log calls for only selected components. Enabling **Visible React** only for selected components can be useful if you find that the Monitor Window is degrading the performance of your app. Below is an example of a component configuration.
198 |
199 | ```javascript
200 | const options = {
201 | enabled: true,
202 | monitor: true,
203 | logging: true,
204 | compare: 'shallow'
205 | }
206 | export default Visible(options)(MyComponent);
207 | ```
208 |
209 | ### More Configuration Examples
210 |
211 | Sets environment to 'development' and enables logging and monitoring only for MyComponent in that environment.
212 |
213 | ```javascript
214 | // In gulpfile.js
215 | gulp.task('set-vr', function() {
216 | gulp.src('build/bundle.js')
217 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('development')))
218 | .pipe(replace('process.env.VR_DEV_MONITOR', JSON.stringify('selected'))
219 | .pipe(replace('process.env.VR_DEV_LOGGING', JSON.stringify('selected')))
220 | .pipe(gulp.dest('dist'));
221 | });
222 |
223 | // In MyComponent.js
224 | const options = {
225 | monitor: true,
226 | logging: true
227 | }
228 | export default Visible(options)(MyComponent);
229 | ```
230 |
231 | Sets environment to 'production' and enables **Visible React** in production to do a only preventive shallow compare (the default) for every component except MyComponent, which undergoes a deep compare.
232 |
233 | ```javascript
234 | // In gulpfile.js
235 | gulp.task('set-vr', function() {
236 | gulp.src('build/bundle.js')
237 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('production')))
238 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify('all'))
239 | .pipe(gulp.dest('dist'));
240 | });
241 |
242 | // In MyComponent.js
243 | const options = {
244 | compare: 'deep'
245 | }
246 | export default Visible(options)(MyComponent);
247 | ```
248 |
249 | Sets environment to 'production' and prevents comparison in production in every component except MyComponent, which undergoes a shallow compare.
250 |
251 | ```javascript
252 | // In gulpfile.js
253 | gulp.task('set-vr', function() {
254 | gulp.src('build/bundle.js')
255 | .pipe(replace('process.env.NODE_ENV', JSON.stringify('production')))
256 | .pipe(replace('process.env.VR_PROD_COMPARE', JSON.stringify('none')))
257 | .pipe(gulp.dest('dist'));
258 | });
259 |
260 | // In MyComponent.js
261 | const options = {
262 | compare: 'shallow'
263 | }
264 | export default Visible(options)(MyComponent);
265 | ```
266 |
267 | ### License
268 |
269 | [MIT](LICENSE)
270 | Copyright (c) 2016 [Robert Kendall](http://robertkendall.com)
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | npm install
4 | cd demo
5 | npm install
6 | cd ../
7 | npm run gulp
--------------------------------------------------------------------------------
/demo/dist/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Questions and Answers
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/fixtures/questions.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1",
4 | "title": "What is the quickest way to double my money?",
5 | "description": "I need expert financial advice on this matter.",
6 | "user": "Jane Doe",
7 | "date": 1450016214776,
8 | "keywords": ["money", "finance", "investment"],
9 | "answers": [
10 | {
11 | "text": "The experts are all in agreement: Just fold it in half.",
12 | "user": "Joe Blow",
13 | "date": 1470716314876
14 | }
15 | ]
16 | },
17 | {
18 | "id": "2",
19 | "title": "What language do they speak in Cuba?",
20 | "description": "I am planning a vacation there and want to learn some of the vocabulary in preparation.",
21 | "user": "John Smith",
22 | "date": 1470716315776,
23 | "keywords": ["language", "Cuba", "travel"],
24 | "answers": [
25 | {
26 | "text": "Everyone knows that they speak Cubic in Cuba.",
27 | "user": "Juan",
28 | "date": 1470716316776
29 | }
30 | ]
31 | },
32 | {
33 | "id": "3",
34 | "title": "What's black and white and red all over?",
35 | "description": "I've searched the forum and couldn't find an answer.",
36 | "user": "May Brown",
37 | "date": 1470716319776,
38 | "keywords": ["colors", "riddles"],
39 | "answers": [
40 | {
41 | "text": "A newspaper.",
42 | "user": "Rupert Murdoch",
43 | "date": 1472742254565
44 | },
45 | {
46 | "text": "What's a newspaper?",
47 | "user": "Mark Zuckerberg",
48 | "date": 1472742254565
49 | },
50 | {
51 | "text": "A sunburned zebra.",
52 | "user": "May",
53 | "date": 1472771242555
54 | },
55 | {
56 | "text": "An old picture of Lenin.",
57 | "user": "Vladimir",
58 | "date": 1472772252565
59 | }
60 | ]
61 | }
62 |
63 | ]
--------------------------------------------------------------------------------
/demo/fixtures/user.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "me"
3 | }
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "visible-react-demo",
3 | "version": "0.0.2",
4 | "description": "Demo of Visible React",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "mocha --compilers js:babel-register --recursive"
8 | },
9 | "author": "Robert Kendall (http://robertkendall.com)",
10 | "license": "MIT",
11 | "dependencies": {
12 | "color": "^0.11.3",
13 | "deepcopy": "^0.6.3",
14 | "json-loader": "^0.5.4",
15 | "material-ui": "^0.15.3",
16 | "moment": "^2.14.1",
17 | "node-uuid": "^1.4.7",
18 | "radium": "^0.18.1",
19 | "react": "^15.3.0",
20 | "react-dom": "^15.3.0",
21 | "react-redux": "^4.4.5",
22 | "react-router": "^2.6.1",
23 | "react-tap-event-plugin": "^1.0.0",
24 | "react-time": "^4.2.0",
25 | "redux": "^3.5.2",
26 | "script-loader": "^0.7.0",
27 | "visible-react": "^0.0.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/demo/src/actions/actions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const addQuestion = (question) => {
4 | return {
5 | type: 'ADD_QUESTION',
6 | question
7 | };
8 | };
9 |
10 | export const addAnswer = (questionId, answer) => {
11 | return {
12 | type: 'ADD_ANSWER',
13 | questionId,
14 | answer
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/demo/src/components/Answer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Visible from 'visible-react';
5 |
6 | import Byline from './Byline';
7 | import styles from '../styles/styles';
8 |
9 | class Answer extends Component {
10 |
11 | static propTypes = {
12 | id: PropTypes.string,
13 | user: PropTypes.string.isRequired,
14 | date: PropTypes.number.isRequired,
15 | text: PropTypes.string.isRequired
16 | };
17 |
18 | static defaultProps = {
19 | id: ''
20 | };
21 |
22 | styles = {
23 | text: {
24 | paddingTop: '10px',
25 | borderTop: '1px solid lightgray',
26 | fontSize: '14px'
27 | }
28 | };
29 |
30 | componentWillReceiveProps() {
31 | this.setState({
32 | value: 'State is set here just for fun'
33 | });
34 | }
35 |
36 | render() {
37 | return (
38 |
39 |
40 |
{this.props.text}
41 |
42 | );
43 | }
44 |
45 | }
46 |
47 | export default Visible()(Answer);
--------------------------------------------------------------------------------
/demo/src/components/App.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component} from 'react';
4 | import {connect} from 'react-redux';
5 | import {bindActionCreators} from 'redux';
6 | import Radium from 'radium';
7 | import Visible from 'visible-react';
8 |
9 | import * as questionActions from '../actions/actions';
10 |
11 | class App extends Component {
12 |
13 | styles = {
14 | app: {
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center',
18 | fontFamily: 'Helvetica, Arial, sans-serif'
19 | },
20 | container: {
21 | '@media (min-width: 800px)': {
22 | width: '800px'
23 | },
24 | '@media (max-width: 799px)': {
25 | width: '100%'
26 | }
27 | },
28 | heading: {
29 | display: 'flex',
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | width: '100%',
33 | height: '50px',
34 | marginBottom: '20px',
35 | color: 'white',
36 | backgroundColor: '#00bcd4',
37 | fontSize: '26px',
38 | fontWeight: 'bold'
39 | }
40 | };
41 |
42 | render() {
43 | return (
44 |
45 |
The Big Questions
46 |
47 | {this.props.children}
48 |
49 |
50 | );
51 | }
52 |
53 | }
54 |
55 | function mapStateToProps(state) {
56 | return {
57 | questions: state.questions
58 | };
59 | }
60 |
61 | const visibleSettings = {
62 | enabled: true,
63 | monitor: true
64 | };
65 |
66 | export default connect(
67 | mapStateToProps
68 | )(Radium(Visible(visibleSettings)(App)));
--------------------------------------------------------------------------------
/demo/src/components/Byline.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Time from 'react-time'
5 | import Visible from 'visible-react';
6 |
7 | const Byline = React.createClass({
8 |
9 | propTypes: {
10 | user: PropTypes.string.isRequired,
11 | date: PropTypes.number.isRequired,
12 | answerCount: PropTypes.number
13 | },
14 |
15 | styles: {
16 | container: {
17 | display: 'flex',
18 | marginBottom: '10px',
19 | fontSize: '12px'
20 | },
21 | name: {
22 | color: '#ff4081'
23 | },
24 | answerCount: {
25 | marginLeft: '30px',
26 | fontWeight: 'bold'
27 | }
28 | },
29 |
30 | getDefaultProps: function() {
31 | return {
32 | answerCount: null
33 | }
34 | },
35 |
36 | getInitialState: function() {
37 | return {
38 | user: this.props.user
39 | };
40 | },
41 |
42 | componentWillReceiveProps() {
43 | this.setState({
44 | propsReceived: true
45 | });
46 | },
47 |
48 | componentWillMount() {
49 | console.log('Byline will mount');
50 | },
51 |
52 | componentWillUnmount() {
53 | console.log('Byline mounted');
54 | },
55 |
56 | getAnswerCount: function() {
57 | if (this.props.answerCount === null) {
58 | return false;
59 | }
60 | let label = 'answer';
61 | if (this.props.answerCount !== 1) {
62 | label += 's';
63 | }
64 | return (
65 |
66 | {this.props.answerCount} {label}
67 |
68 | )
69 | },
70 |
71 | getName: function() {
72 | return (
73 |
74 | {this.props.user}
75 |
76 | )
77 | },
78 |
79 | render: function() {
80 | return (
81 |
82 |
83 | Posted by {this.getName()},
84 |
85 | {this.getAnswerCount()}
86 |
87 | );
88 | }
89 |
90 | });
91 |
92 | export default Visible()(Byline);
--------------------------------------------------------------------------------
/demo/src/components/Keywords.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Visible from 'visible-react';
5 |
6 | class Keywords extends Component {
7 |
8 | static propTypes = {
9 | keywords: PropTypes.array.isRequired
10 | };
11 |
12 | styles = {
13 | keywords: {
14 | display: 'flex',
15 | justifyContent: 'flex-begin',
16 | marginBottom: '5px',
17 | fontSize: '11px'
18 | },
19 | keyword: {
20 | marginRight: '10px',
21 | padding: '2px 4px',
22 | border: '1px solid lightgray',
23 | backgroundColor: 'white'
24 | }
25 | };
26 |
27 |
28 | formatKeywords = (keywords) => {
29 | return keywords.map((keyword, ind) => {
30 | return (
31 |
32 | {keyword}
33 |
34 | );
35 | });
36 | };
37 |
38 | render() {
39 | if (!this.props.keywords.length) {
40 | return false;
41 | }
42 | return (
43 |
44 | {this.formatKeywords(this.props.keywords)}
45 |
46 | );
47 | }
48 |
49 | }
50 |
51 | export default Visible()(Keywords);
--------------------------------------------------------------------------------
/demo/src/components/Question.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import {hashHistory} from 'react-router';
5 | import Radium from 'radium';
6 | import Visible from 'visible-react';
7 |
8 | import Keywords from './Keywords';
9 | import Byline from './Byline';
10 | import styles from '../styles/styles';
11 |
12 | class Question extends Component {
13 |
14 | static propTypes = {
15 | id: PropTypes.string.isRequired,
16 | title: PropTypes.string.isRequired,
17 | user: PropTypes.string.isRequired,
18 | date: PropTypes.number.isRequired,
19 | keywords: PropTypes.array.isRequired,
20 | answers: PropTypes.array.isRequired
21 | };
22 |
23 | styles = {
24 | title: {
25 | marginBottom: '5px',
26 | color: '#04aec3',
27 | fontSize: '16px',
28 | cursor: 'pointer',
29 | ':hover': {
30 | opacity: '.5'
31 | }
32 | }
33 | };
34 |
35 | componentWillMount() {
36 |
37 | }
38 |
39 | handleQuestionClicked = () => {
40 | hashHistory.push('question/' + this.props.id);
41 | };
42 |
43 | render() {
44 | const id = 'question-' + this.props.id;
45 | return (
46 |
47 |
48 | {this.props.title}
49 |
50 |
51 |
57 |
58 | );
59 | }
60 |
61 | }
62 |
63 | export default Radium(Visible()(Question));
--------------------------------------------------------------------------------
/demo/src/components/QuestionDetail.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import {connect} from 'react-redux';
5 | import {hashHistory} from 'react-router';
6 | import deepcopy from 'deepcopy';
7 | import RaisedButton from 'material-ui/RaisedButton';
8 | import Visible from 'visible-react';
9 |
10 | import Keywords from './Keywords';
11 | import Byline from './Byline';
12 | import Answer from './Answer';
13 | import SubmitAnswer from './SubmitAnswer';
14 |
15 | import styles from '../styles/styles';
16 |
17 | // Export unconnected component for testing
18 | export class QuestionDetail extends Component {
19 |
20 | styles = {
21 | title: {
22 | margin: '10px 0 5px 0',
23 | fontSize: '18px',
24 | fontWeight: 'bold'
25 | },
26 | description: {
27 | paddingTop: '10px',
28 | borderTop: '1px solid lightgray',
29 | fontSize: '14px'
30 | }
31 | };
32 |
33 | componentWillReceiveProps() {
34 |
35 | }
36 |
37 | componentWillMount() {
38 | // If the question ID in the URL is invalid, go to main page
39 | if (!this.getSelectedQuestion()) {
40 | hashHistory.push('/');
41 | }
42 | }
43 |
44 | getSelectedQuestion = () => {
45 | return this.props.questions.find((question) => {
46 | return question.id === this.props.params.id;
47 | });
48 | };
49 |
50 | getAnswerHeading = (answerCount) => {
51 | let label = 'Answer';
52 | if (answerCount !== 1) {
53 | label += 's';
54 | }
55 | return (
56 |
57 | {answerCount} {label}
58 |
59 | )
60 | };
61 |
62 | getAnswers = (answers) => {
63 | return answers.map((answer, ind) => {
64 | const id = 'answer-' + ind;
65 | return ();
66 | });
67 | };
68 |
69 | handleBackButtonClick = () => {
70 | hashHistory.push('/');
71 | };
72 |
73 | render() {
74 | const question = this.getSelectedQuestion();
75 | if (!question) {
76 | return false;
77 | }
78 | return (
79 |
80 |
86 |
Question
87 |
88 |
89 | {question.title}
90 |
91 |
92 |
93 |
{question.description}
94 |
95 | {this.getAnswerHeading(question.answers.length)}
96 | {this.getAnswers(question.answers)}
97 |
98 |
99 | )
100 | }
101 |
102 | }
103 |
104 | export default connect(
105 | (state) => deepcopy(state)
106 | )(Visible()(QuestionDetail));
--------------------------------------------------------------------------------
/demo/src/components/QuestionList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component} from 'react';
4 | import {connect} from 'react-redux';
5 | import {hashHistory} from 'react-router';
6 | import deepcopy from 'deepcopy';
7 | import RaisedButton from 'material-ui/RaisedButton';
8 | import Visible from 'visible-react';
9 |
10 | import Question from './Question';
11 | import styles from '../styles/styles';
12 |
13 | // Export unconnected component for testing
14 | export class QuestionList extends Component {
15 |
16 | getQuestionList = () => {
17 | return this.props.questions.map((question, ind) => {
18 | return (
19 |
23 | );
24 | });
25 | };
26 |
27 | showSubmitQuestion = () => {
28 | hashHistory.push('new-question');
29 | };
30 |
31 | render() {
32 | return (
33 |
34 |
35 |
41 |
42 |
Questions
43 |
{this.getQuestionList()}
44 |
45 | );
46 | }
47 |
48 | }
49 |
50 | export default connect(
51 | (state) => deepcopy(state)
52 | )(Visible({disabled: true, id: 'questionlist1'})(QuestionList));
--------------------------------------------------------------------------------
/demo/src/components/SubmitAnswer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import {connect} from 'react-redux';
5 | import {hashHistory} from 'react-router';
6 | import RaisedButton from 'material-ui/RaisedButton';
7 | import Visible from 'visible-react';
8 |
9 | import {addAnswer} from '../actions/actions';
10 | import styles from '../styles/styles';
11 |
12 | class SubmitAnswer extends Component {
13 |
14 | static propTypes = {
15 | questionId: PropTypes.string.isRequired
16 | };
17 |
18 | styles = {
19 | container: {
20 | display: 'flex',
21 | flexDirection: 'column'
22 | },
23 | textArea: {
24 | resize: 'vertical',
25 | marginBottom: '20px',
26 | border: '1px solid lightgray',
27 | outline: 'none'
28 | },
29 | submitButton: {
30 | marginTop: '20px',
31 | fontSize: '12px',
32 | cursor: 'pointer'
33 | }
34 | };
35 |
36 | constructor() {
37 | super();
38 | this.state = {
39 | answerText: ''
40 | };
41 | }
42 |
43 | componentDidMount() {
44 | console.log('CDM called in submit');
45 | this.setState({
46 | mounted: true
47 | });
48 | }
49 |
50 | componentWillReceiveProps() {
51 |
52 | };
53 |
54 | componentDidUpate() {
55 |
56 | };
57 |
58 | updateEntry = (event) => {
59 | this.setState({
60 | answerText: event.target.value
61 | });
62 | };
63 |
64 | handleSubmitClicked = () => {
65 | this.props.dispatch(addAnswer(this.props.questionId, this.state.answerText));
66 | this.setState({
67 | answerText: ''
68 | });
69 | };
70 |
71 | render() {
72 | const mounted = this.state.mounted;
73 | return (
74 |
75 |
Post an Answer
76 |
83 |
84 |
91 |
92 |
93 | );
94 | }
95 |
96 | }
97 |
98 | export default connect()(Visible()(SubmitAnswer));
--------------------------------------------------------------------------------
/demo/src/components/SubmitQuestion.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component} from 'react';
4 | import {connect} from 'react-redux';
5 | import {hashHistory} from 'react-router';
6 | import RaisedButton from 'material-ui/RaisedButton';
7 | import Visible from 'visible-react';
8 |
9 | import {addQuestion} from '../actions/actions';
10 | import styles from '../styles/styles';
11 |
12 | // Export unconnected component for testing
13 | export class SubmitQuestion extends Component {
14 |
15 | styles = {
16 | container: {
17 | display: 'flex',
18 | flexDirection: 'column'
19 | },
20 | textArea: {
21 | resize: 'vertical',
22 | marginBottom: '20px',
23 | border: '1px solid lightgray',
24 | outline: 'none'
25 | },
26 | input: {
27 | height: '20px',
28 | marginBottom: '20px',
29 | outline: 'none'
30 | },
31 | button: {
32 | marginRight: '10px'
33 | }
34 | };
35 |
36 | constructor() {
37 | super();
38 | this.state = {
39 | title: '',
40 | description: '',
41 | keywords: ''
42 | };
43 | }
44 |
45 | onChange = (contentType, event) => {
46 | this.setState({
47 | [contentType]: event.target.value
48 | });
49 | };
50 |
51 | handleSubmitClicked = () => {
52 | this.props.dispatch(addQuestion(this.state));
53 | this.setState({
54 | title: '',
55 | description: '',
56 | keywords: ''
57 | });
58 | hashHistory.push('/');
59 | };
60 |
61 | handleCancelClicked = () => {
62 | hashHistory.push('/');
63 | };
64 |
65 | render() {
66 | return (
67 |
68 |
69 | Ask a Question
70 |
71 |
79 |
86 |
93 |
94 |
102 |
106 |
107 |
108 | );
109 | }
110 |
111 | }
112 |
113 | export default connect()(Visible()(SubmitQuestion));
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component} from 'react';
4 | import {render} from 'react-dom';
5 | import {Provider} from 'react-redux';
6 | import {createStore} from 'redux';
7 | import {Router, Route, hashHistory} from 'react-router'
8 | import injectTapEventPlugin from 'react-tap-event-plugin';
9 | injectTapEventPlugin();
10 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
11 | import {StyleRoot} from 'radium';
12 | import Visible from 'visible-react';
13 |
14 | import questions from './reducers/reducers.js';
15 | import App from './components/App.js';
16 | import QuestionList from './components/QuestionList.js';
17 | import QuestionDetail from './components/QuestionDetail.js';
18 | import SubmitQuestion from './components/SubmitQuestion.js';
19 |
20 | import questionData from 'json!../fixtures/questions.json';
21 | import userData from 'json!../fixtures/user.json';
22 |
23 | // import Perf from 'react-addons-perf';
24 | // window.Perf = Perf;
25 |
26 | const initialStore = {
27 | questions: questionData,
28 | user: userData
29 | };
30 | let store = createStore(questions, initialStore);
31 |
32 | render((
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ), document.getElementById('root'));
48 |
--------------------------------------------------------------------------------
/demo/src/reducers/reducers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import deepcopy from 'deepcopy';
4 | import uuid from 'node-uuid';
5 |
6 | const questions = (state = {}, action) => {
7 |
8 | let newState = deepcopy(state);
9 |
10 | if (action.type === 'ADD_QUESTION') {
11 | let newQuestion = {
12 | ...action.question,
13 | keywords: action.question.keywords !== '' ? action.question.keywords.trim().split(/\s+/) : [],
14 | user: newState.user.name,
15 | date: Date.now(),
16 | id: uuid.v1(),
17 | answers: []
18 | };
19 | newState.questions.unshift(newQuestion);
20 | return newState;
21 | } else if (action.type === 'ADD_ANSWER') {
22 | const answer = {
23 | text: action.answer,
24 | user: newState.user.name,
25 | date: Date.now()
26 | };
27 | let questionToUpdate = newState.questions.find((question) => {
28 | return question.id === action.questionId;
29 | });
30 | questionToUpdate.answers.push(answer);
31 | return newState;
32 | } else {
33 | return state;
34 | }
35 |
36 | };
37 |
38 | export default questions;
--------------------------------------------------------------------------------
/demo/src/styles/styles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const item = {
4 | margin: '15px 0',
5 | padding: '10px',
6 | boxShadow: '1px 1px 6px lightgray'
7 | };
8 |
9 | const styles = {
10 | heading: {
11 | margin: '20px 0',
12 | fontSize: '16px',
13 | fontWeight: 'bold'
14 | },
15 | question: {
16 | display: 'flex',
17 | flexDirection: 'column',
18 | alignItems: 'flex-start',
19 | ...item,
20 | backgroundColor: '#fff8dc'
21 | },
22 | answer: {
23 | ...item,
24 | border: '1px solid #eaeaea'
25 | }
26 | };
27 |
28 | export default styles;
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var babel = require('gulp-babel');
6 | var gutil = require('gulp-util');
7 | var replace = require('gulp-replace');
8 | var webpack = require('webpack');
9 | var runSequence = require('run-sequence');
10 |
11 | var visibleSrc = 'src';
12 | var demoSrc = 'demo/src';
13 |
14 | // Transpiled Visible React
15 | var visibleDist = 'dist';
16 | var vrNodeModule = 'demo/node_modules/visible-react';
17 | var vrNodeModuleDist = path.join(vrNodeModule, 'dist');
18 |
19 | // Transpiled demo build without converted env variables
20 | var demoBuild = 'demo/build';
21 | // Converted env variables
22 | var demoDist = 'demo/dist';
23 |
24 | gulp.task('default', [
25 | 'build'
26 | ], function() {
27 | gulp.watch([path.join(visibleSrc, '**/*'), path.join(demoSrc, '**/*')], ['build']);
28 | });
29 |
30 | gulp.task('build', function(done) {
31 | runSequence(
32 | 'package-json',
33 | 'visible',
34 | 'copy-index',
35 | 'demo',
36 | 'vr-settings',
37 | done)
38 | });
39 |
40 | gulp.task('package-json', function() {
41 | return gulp
42 | .src('package.json')
43 | .pipe(gulp.dest(vrNodeModule))
44 | .on('error', gutil.log);
45 | });
46 |
47 | gulp.task('visible', function(callback) {
48 | var webpackConfig = {
49 | entry: path.join(__dirname, visibleSrc, 'index'),
50 | output: {
51 | path: path.join(__dirname, visibleDist),
52 | filename: 'index.js',
53 | library: 'visible-react',
54 | libraryTarget: 'umd'
55 | },
56 | externals: [
57 | {
58 | react: {
59 | root: 'React',
60 | commonjs2: 'react',
61 | commonjs: 'react',
62 | amd: 'react'
63 | }
64 | },
65 | {
66 | 'react-dom': {
67 | root: 'ReactDOM',
68 | commonjs2: 'react-dom',
69 | commonjs: 'react-dom',
70 | amd: 'react-dom'
71 | }
72 | }
73 | ],
74 | devtool: 'source-map',
75 | module: {
76 | loaders: [
77 | {
78 | test: /\.js$/,
79 | loader: 'babel-loader',
80 | include: [
81 | path.join(__dirname, visibleSrc)
82 | ]
83 | }
84 | ]
85 | },
86 | plugins: [
87 | new webpack.optimize.DedupePlugin()
88 |
89 | ]
90 | };
91 |
92 | // run webpack
93 | webpack(webpackConfig, function(err, stats) {
94 | if (err) throw new gutil.PluginError('webpack:build', err);
95 | if (stats.compilation.errors.toString()) {
96 | gutil.log("[webpack:errors]", stats.compilation.errors.toString({
97 | colors: true
98 | }));
99 | }
100 | if (stats.compilation.warnings.toString()) {
101 | gutil.log("[webpack:warnings]", stats.compilation.warnings.toString({
102 | colors: true
103 | }));
104 | }
105 | callback();
106 | });
107 | });
108 |
109 | gulp.task('copy-index', function() {
110 | return gulp
111 | .src(path.join(visibleDist, 'index.js'))
112 | .pipe(gulp.dest(vrNodeModuleDist))
113 | .on('error', gutil.log);
114 | });
115 |
116 | gulp.task('demo', function(callback) {
117 | var webpackConfig = {
118 | entry: path.join(__dirname, demoSrc, 'index'),
119 | output: {
120 | path: path.join(__dirname, demoBuild),
121 | filename: 'index.js'
122 | },
123 | devtool: 'source-map',
124 | module: {
125 | loaders: [
126 | {
127 | test: /\.js$/,
128 | loader: 'babel-loader',
129 | include: [
130 | path.join(__dirname, demoSrc),
131 | path.join(__dirname, visibleDist)
132 | ]
133 | }
134 | ]
135 | },
136 | plugins: [
137 | new webpack.optimize.DedupePlugin(),
138 | ]
139 | };
140 |
141 | // run webpack
142 | webpack(webpackConfig, function(err, stats) {
143 | if (err) throw new gutil.PluginError('webpack:build', err);
144 | if (stats.compilation.errors.toString()) {
145 | gutil.log("[webpack:errors]", stats.compilation.errors.toString({
146 | colors: true
147 | }));
148 | }
149 | if (stats.compilation.warnings.toString()) {
150 | gutil.log("[webpack:warnings]", stats.compilation.warnings.toString({
151 | colors: true
152 | }));
153 | }
154 | callback();
155 | });
156 | });
157 |
158 | gulp.task('vr-settings', function() {
159 |
160 | gulp.src(path.join(demoBuild, 'index.js'))
161 |
162 | .pipe(replace('process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV) || null))
163 |
164 | .pipe(replace('process.env.VR_DEV_ENABLED', JSON.stringify(process.env.VR_DEV_ENABLED) || null))
165 | .pipe(replace('process.env.VR_DEV_MONITOR', JSON.stringify(process.env.VR_DEV_MONITOR) || null))
166 | .pipe(replace('process.env.VR_DEV_LOGGING', JSON.stringify(process.env.VR_DEV_LOGGING) || null))
167 | .pipe(replace('process.env.VR_DEV_CONTROL', JSON.stringify(process.env.VR_DEV_CONTROL) || null))
168 | .pipe(replace('process.env.VR_DEV_COMPARE', JSON.stringify(process.env.VR_DEV_COMPARE) || null))
169 |
170 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify(process.env.VR_PROD_ENABLED) || null))
171 | .pipe(replace('process.env.VR_PROD_MONITOR', JSON.stringify(process.env.VR_PROD_MONITOR) || null))
172 | .pipe(replace('process.env.VR_PROD_LOGGING', JSON.stringify(process.env.VR_PROD_LOGGING) || null))
173 | .pipe(replace('process.env.VR_PROD_CONTROL', JSON.stringify(process.env.VR_PROD_CONTROL) || null))
174 | .pipe(replace('process.env.VR_PROD_COMPARE', JSON.stringify(process.env.VR_PROD_COMPARE) || null))
175 |
176 | .pipe(gulp.dest(demoDist));
177 |
178 | gulp.src(path.join(demoBuild, 'index.js.map'))
179 | .pipe(gulp.dest(demoDist));
180 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "visible-react",
3 | "version": "0.0.8",
4 | "description": "Debugging and optimization tool for React lifecycle events",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/rkendall/visible-react.git"
8 | },
9 | "main": "dist/index.js",
10 | "scripts": {
11 | "build": "./build.sh",
12 | "gulp": "gulp default",
13 | "test": "mocha --compilers js:babel-register --recursive",
14 | "test-config": "mocha test/ConfigureSpec.js --compilers js:babel-register --recursive",
15 | "test-all": "mocha test/ConfigureSpec.js --compilers js:babel-register --recursive && mocha-webpack --webpack-config webpack.config-test.js \"test/MonitorSpec.js\""
16 | },
17 | "author": "Robert Kendall (http://robertkendall.com)",
18 | "license": "MIT",
19 | "bugs": {
20 | "email": "kendall@wordcircuits.com"
21 | },
22 | "dependencies": {
23 | "color": "^0.11.3",
24 | "colors": "^1.1.2",
25 | "css-loader": "^0.25.0",
26 | "deep-equal": "^1.0.1",
27 | "deepcopy": "^0.6.3",
28 | "diff": "^3.0.0",
29 | "immutable": "^3.8.1",
30 | "radium": "^0.18.1",
31 | "radium-loader": "^1.0.10",
32 | "react-bootstrap-table": "^2.5.5",
33 | "react-draggable": "^2.2.1",
34 | "react-redux": "^4.4.5",
35 | "react-router": "^2.6.1",
36 | "redux": "^3.5.2",
37 | "shallowequal": "^0.2.2"
38 | },
39 | "peerDependencies": {
40 | "react": "^15.3.0",
41 | "react-dom": "^15.3.0"
42 | },
43 | "devDependencies": {
44 | "babel-core": "^6.13.2",
45 | "babel-loader": "^6.2.4",
46 | "babel-plugin-add-module-exports": "^0.2.1",
47 | "babel-polyfill": "^6.13.0",
48 | "babel-preset-es2015": "^6.13.2",
49 | "babel-preset-react": "^6.11.1",
50 | "babel-preset-stage-0": "^6.5.0",
51 | "babel-register": "^6.11.6",
52 | "computed-style": "^0.3.0",
53 | "copy-webpack-plugin": "^3.0.1",
54 | "css-to-radium": "^1.0.3",
55 | "del": "^2.2.2",
56 | "enzyme": "^2.4.1",
57 | "expect": "^1.20.2",
58 | "gulp": "^3.9.1",
59 | "gulp-babel": "^6.1.2",
60 | "gulp-replace": "^0.5.4",
61 | "gulp-util": "^3.0.7",
62 | "html-webpack-plugin": "^2.22.0",
63 | "jsdom": "9.5.0",
64 | "json-loader": "^0.5.4",
65 | "karma": "^1.1.2",
66 | "karma-cli": "^1.0.1",
67 | "karma-mocha": "^1.1.1",
68 | "karma-webpack": "^1.8.0",
69 | "mocha": "^3.0.2",
70 | "mocha-webpack": "^0.7.0",
71 | "radium-loader": "^1.0.10",
72 | "react": "^15.3.0",
73 | "react-addons-perf": "^15.3.1",
74 | "react-addons-test-utils": "^15.3.0",
75 | "react-dom": "^15.3.0",
76 | "redux-devtools": "^3.3.1",
77 | "run-sequence": "^1.2.2",
78 | "sinon": "^1.17.5",
79 | "webpack": "^1.13.2",
80 | "webpack-dev-server": "^1.15.1",
81 | "webpack-node-externals": "^1.5.4"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/scripts/make-package-json.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | let packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf8'));
4 |
5 | packageJson.dependencies = {};
6 | packageJson.devDependencies = {};
7 |
8 | fs.writeFileSync('../package.json', JSON.stringify(packageJson, null, ' ') + '\n', 'utf8');
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Radium from 'radium';
5 | import color from 'color';
6 |
7 | class Button extends Component {
8 |
9 | static propTypes = {
10 | label: PropTypes.string.isRequired,
11 | onClick: PropTypes.func.isRequired,
12 | disabled: PropTypes.bool
13 | };
14 |
15 | static defaultProps = {
16 | disabled: false
17 | };
18 |
19 | styles = {
20 | button: {
21 | marginLeft: '10px',
22 | backgroundColor: color('lightblue').lighten(.1).hexString(),
23 | borderRadius: '0',
24 | boxShadow: '2px 2px 5px rgba(0, 0, 0, 0.5)',
25 | border: 'lightgray',
26 | outline: 'none',
27 | cursor: 'pointer',
28 | ':hover': {
29 | backgroundColor: 'lightblue'
30 | }
31 | },
32 | active: {
33 | boxShadow: '0 0 2px rgba(0, 0, 0, 0.5)',
34 | transform: 'translateX(1px) translateY(1px)'
35 | },
36 | disabled: {
37 | cursor: 'default',
38 | color: 'gray',
39 | backgroundColor: color('lightgray').lighten(.1).hexString(),
40 | ':hover': {
41 | backgroundColor: 'lightgray'
42 | }
43 | }
44 | };
45 |
46 | constructor() {
47 | super();
48 | this.state = {
49 | active: false
50 | };
51 | }
52 |
53 | handleMouseDown = () => {
54 | this.setState({
55 | active: true
56 | });
57 | };
58 |
59 | handleMouseUp = () => {
60 | this.setState({
61 | active: false
62 | });
63 | };
64 |
65 | render() {
66 |
67 | let buttonStyle = [this.styles.button];
68 | if (this.props.disabled) {
69 | buttonStyle.push(this.styles.disabled);
70 | } else if (this.state.active) {
71 | buttonStyle.push(this.styles.active);
72 | }
73 |
74 | return (
75 |
82 | {this.props.label}
83 |
84 | );
85 |
86 | }
87 |
88 | }
89 |
90 | export default Radium(Button);
--------------------------------------------------------------------------------
/src/components/ComponentList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import {findDOMNode} from 'react-dom';
5 | import Radium, {Style} from 'radium';
6 | import color from 'color';
7 | import {BootstrapTable, TableHeaderColumn} from 'react-bootstrap-table';
8 | // import tableStyles from 'radium!css!../vendor/react-bootstrap-table-all.min.css';
9 | // import bootstrapStyles from 'radium!css!../vendor/bootstrap.css';
10 | import tableStyles from '../vendor/bootstrapTableStyles';
11 | import bootstrapStyles from '../vendor/bootstrapStyles';
12 | import shallowEqual from 'shallowequal';
13 | import Immutable from 'immutable';
14 |
15 | import DataIconCell from './DataIconCell';
16 |
17 | class ComponentList extends Component {
18 |
19 | static propTypes = {
20 | entries: PropTypes.instanceOf(Immutable.Map).isRequired,
21 | onChange: PropTypes.func.isRequired
22 | };
23 |
24 | rowHeight = 28;
25 | headerHeight = 28;
26 |
27 | styles = {
28 | heading: {
29 | marginBottom: '20px',
30 | fontSize: '14px',
31 | fontWeight: 'bold'
32 | },
33 | name: {
34 | cursor: 'pointer',
35 | opacity: '1',
36 | animation: 'x 1s ease-out',
37 | animationName: Radium.keyframes({
38 | '0%': {opacity: '0'},
39 | '60%': {opacity: '0.7'},
40 | '100%': {opacity: '1'}
41 | }),
42 | overflow: 'hidden',
43 | whiteSpace: 'nowrap',
44 | textOverflow: 'ellipsis',
45 | fontWeight: 'bold'
46 | },
47 | unmountedComponent: {
48 | fontWeight: 'normal',
49 | color: 'gray'
50 | },
51 | methodName: {
52 | overflow: 'hidden',
53 | whiteSpace: 'nowrap',
54 | textOverflow: 'ellipsis'
55 | },
56 | renderCount: {
57 | justifyContent: 'flex-end',
58 | width: '100%',
59 | textAlign: 'right'
60 | },
61 | warning: {
62 | color: 'red',
63 | fontWeight: 'bold'
64 | }
65 | };
66 |
67 | constructor(props) {
68 | super(props);
69 | const columnWidths = {
70 | changed: 20,
71 | name: 175,
72 | renderCount: 90,
73 | warningCount: 90,
74 | scrollbarPadding: 20
75 | };
76 | let tableWidth = 0;
77 | for (let name in columnWidths) {
78 | tableWidth += columnWidths[name];
79 | }
80 | const componentTableData = this.getComponentTableData(props.entries);
81 | const tableHeight = this.getTableHeight(componentTableData);
82 | this.state = {
83 | selectedComponentId: props.entries.first().get('id'),
84 | componentTableData,
85 | tableWidth,
86 | tableBottom: 0,
87 | tableHeight,
88 | columnWidths,
89 | tableOffsetTop: 0
90 | };
91 | }
92 |
93 | componentDidMount() {
94 | const tableElement = findDOMNode(this.refs.table);
95 | this.setState({
96 | tableOffsetTop: tableElement.offsetParent.offsetTop
97 | });
98 | }
99 |
100 | componentWillReceiveProps(nextProps) {
101 | const componentTableData = this.getComponentTableData(nextProps.entries);
102 | const tableHeight = this.getTableHeight(componentTableData);
103 | this.setState({
104 | componentTableData,
105 | tableHeight
106 | });
107 | }
108 |
109 | shouldComponentUpdate(nextProps, nextState) {
110 | return !shallowEqual(this.props, nextProps)
111 | || !shallowEqual(this.state, nextState);
112 | }
113 |
114 | getComponentTableData = (immutableEntries) => {
115 | // TODO Keep these as immutables for better performance?
116 | const entries = immutableEntries.toJS();
117 | const sortedComponentIds = this.getSortedComponentIds(entries);
118 | return sortedComponentIds.map((id) => {
119 | const entry = entries[id];
120 | return {
121 | isChanged: entry.isChanged,
122 | name: entry.displayName,
123 | renderCount: entry.renderCount,
124 | warningCount: entry.unnecessaryUpdatesPrevented,
125 | id
126 | };
127 | });
128 | };
129 |
130 | getTableHeight = (componentTableData) => {
131 | const rowsCount = componentTableData.length;
132 | const paddingOffset = 5;
133 | const tableHeight = (this.rowHeight * rowsCount) + paddingOffset;
134 | return tableHeight;
135 | };
136 |
137 | mergeStyles = (stylesArray) => {
138 | stylesArray.unshift({});
139 | return Object.assign(...stylesArray);
140 | };
141 |
142 | getChangedCell = (cell, row) => {
143 |
144 | const isChanged = this.props.entries.getIn([row.id, 'isChanged']);
145 | const key = 'is-changed-cell-' + row.id;
146 |
147 | return (
148 |
153 | );
154 |
155 | };
156 |
157 | getComponentNameCell = (cell, row) => {
158 | let componentStyle = [this.styles.name];
159 | const isMounted = this.props.entries.getIn([row.id, 'isMounted']);
160 | if (!isMounted) {
161 | componentStyle.push(this.styles.unmountedComponent);
162 | }
163 | // Need to manually merge styles because Radium can't access these elements
164 | // inside a custom component
165 | let childStyle = this.mergeStyles([this.styles.methodName, {width: this.state.columnWidths.name - 8}]);
166 | return (
167 |
172 | );
173 | };
174 |
175 | getWarningCountCell = (cell) => {
176 | const warningCount = cell || '';
177 | const tooltip = warningCount ? warningCount + ' unnecessary rerenders prevented' : '';
178 | return (
179 | {warningCount}
180 | )
181 | };
182 |
183 | makeTable = () => {
184 | const selectRowSettings = {
185 | clickToSelect: true,
186 | mode: 'radio',
187 | hideSelectColumn: true,
188 | bgColor: color('lightblue').lighten(.1).hexString(),
189 | onSelect: this.handleComponentSelected,
190 | selected: [this.state.selectedComponentId]
191 | };
192 | return (
193 |
206 |
210 |
211 |
218 |
219 |
226 | Name
227 |
228 |
236 | Rendered
237 |
238 |
248 | Warnings
249 |
250 |
255 |
256 |
257 | );
258 | };
259 |
260 | getSortedComponentIds = (components) => {
261 | return Object.keys(components).sort((a, b) => {
262 | const nameA = components[a].displayName.toLowerCase();
263 | const nameB = components[b].displayName.toLowerCase();
264 | if (nameA < nameB) {
265 | return -1;
266 | }
267 | if (nameA > nameB) {
268 | return 1
269 | }
270 | return 0;
271 | });
272 | };
273 |
274 | handleComponentSelected = (row) => {
275 | this.setState({
276 | selectedComponentId: row.id
277 | });
278 | const option = {selectedComponentId: row.id};
279 | this.props.onChange(option);
280 | };
281 |
282 | getTableStyles = () => {
283 |
284 | const heightOffset = 30;
285 |
286 | // TODO Assign these styles using the table API (e.g., 'containerStyle={}')
287 | return {
288 | '.react-bs-table-container': {
289 | height: this.state.tableHeight + this.headerHeight + this.state.tableOffsetTop + 'px',
290 | maxHeight: `calc(100% - ${heightOffset + 10}px)`
291 | },
292 | '.react-bs-table': {
293 | height: this.state.tableHeight + this.headerHeight + 'px',
294 | maxHeight: `calc(100% - ${heightOffset + 5}px)`,
295 | margin: '0',
296 | borderTop: '1px lightgray solid',
297 | borderRight: '1px lightgray solid'
298 | },
299 | '.react-bs-container-body': {
300 | height: this.state.tableHeight + 'px',
301 | maxHeight: `calc(100% - ${heightOffset}px)`,
302 | overflowY: 'auto',
303 | overflowX: 'hidden'
304 | },
305 | '.react-bs-container-header, .react-bs-container-header .table': {
306 | height: this.headerHeight + 'px'
307 | },
308 | '.data-table-header': {
309 | fontSize: '12px',
310 | textAlign: 'left !important',
311 | background: 'linear-gradient(to bottom, ' + color('#e6e6e6').lighten(0.7).hexString() + ' 0%, #e6e6e6 100%)'
312 | },
313 | '.data-table-row': {
314 | fontSize: '12px',
315 | cursor: 'pointer',
316 | height: this.rowHeight + 'px'
317 | },
318 | '.react-bs-table-tool-bar': {
319 | width: '200px',
320 | marginBottom: '10px'
321 | },
322 | '.form-group-sm input.form-control': {
323 | height: '25px'
324 | }
325 | };
326 |
327 | };
328 |
329 | render() {
330 |
331 | const stylesheets = Object.assign({}, tableStyles, bootstrapStyles, this.getTableStyles());
332 | const scrollbarOffset = 20;
333 | const tableWidth = this.state.tableWidth + scrollbarOffset;
334 | const containerStyle = [this.styles.container, {
335 | width: tableWidth + 'px',
336 | height: this.state.tableHeight + this.headerHeight + this.state.tableOffsetTop + 40 + 'px',
337 | maxHeight: `calc(100% - ${this.state.tableOffsetTop}px)`,
338 | position: 'relative',
339 | boxSizing: 'border-box',
340 | padding: '10px',
341 | background: 'white',
342 | boxShadow: '2px 2px 5px 1px rgba(0, 0, 0, 0.5)'
343 | }];
344 |
345 | return (
346 |
347 |
351 |
Available Components
352 | {this.makeTable()}
353 |
354 | )
355 |
356 | }
357 |
358 | }
359 |
360 | export default Radium(ComponentList);
--------------------------------------------------------------------------------
/src/components/Console.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import deepEqual from 'deep-equal';
5 | import Draggable from 'react-draggable';
6 | import {diffJson} from 'diff';
7 | import Radium from 'radium';
8 | import color from 'color';
9 | import Immutable from 'immutable';
10 |
11 | import ComponentList from './ComponentList';
12 | import LifeCycle from './LifeCycle';
13 | import Controls from './Controls';
14 | import styles from '../styles/styles';
15 |
16 | class Console extends Component {
17 |
18 | static propTypes = {
19 | entries: PropTypes.instanceOf(Immutable.Map).isRequired,
20 | windowWidth: PropTypes.number.isRequired
21 | };
22 |
23 | styles = {
24 | container: {
25 | display: 'flex',
26 | justifyContent: 'space-between',
27 | height: '100%',
28 | overflow: 'hidden',
29 | background: '#f2fcff',
30 | ...styles.base
31 | },
32 | leftPanel: {
33 | position: 'relative',
34 | height: '100%',
35 | padding: '10px'
36 | },
37 | lifeCycle: {
38 | height: '100%',
39 | overflowX: 'hidden',
40 | overflowY: 'auto'
41 | },
42 | draggableWindowHidden: {
43 | display: 'none'
44 | },
45 | draggableWindow: {
46 | display: 'flex',
47 | flexDirection: 'column',
48 | position: 'absolute',
49 | top: '10px',
50 | left: '10px',
51 | backgroundColor: 'white',
52 | border: '1px solid gray',
53 | boxShadow: '5px 5px 6px rgba(0, 0, 0, .4)',
54 | zIndex: '100'
55 | },
56 | // draggableWindowOpen: {
57 | // display: 'block',
58 | // opacity: '0',
59 | // animation: 'x .4s ease-in',
60 | // animationName: Radium.keyframes({
61 | // '0%': {opacity: '0'},
62 | // '50%': {opacity: '1'},
63 | // '100%': {opacity: '1'}
64 | // }),
65 | // zIndex: '100'
66 | // },
67 | // draggableWindowClosed: {
68 | // opacity: '1',
69 | // animation: 'x .4s ease-in',
70 | // animationName: Radium.keyframes({
71 | // '0%': {opacity: '1'},
72 | // '50%': {opacity: '0'},
73 | // '100%': {opacity: '0'}
74 | // })
75 | // },
76 | handle: {
77 | display: 'flex',
78 | justifyContent: 'flex-end',
79 | alignItems: 'center',
80 | height: '30px',
81 | backgroundColor: 'lightgray',
82 | cursor: 'move'
83 | },
84 | windowContent: {
85 | minWidth: '800px',
86 | maxWidth: (this.props.windowWidth - 50) + 'px',
87 | height: '600px',
88 | margin: '0',
89 | padding: '10px',
90 | overflow: 'auto',
91 | resize: 'both'
92 | },
93 | close: {
94 | marginRight: '5px',
95 | cursor: 'pointer'
96 | },
97 | added: {
98 | backgroundColor: color('#4caf50').lighten(.5).hexString()
99 | },
100 | removed: {
101 | backgroundColor: color('red').lighten(.5).hexString()
102 | }
103 | };
104 |
105 | constructor(props) {
106 | super(props);
107 |
108 | const initialSelectedComponentId = props.entries ? props.entries.first().get('id') : '';
109 | this.state = {
110 | initialSelectedComponentId,
111 | selectedComponentId: initialSelectedComponentId,
112 | showFullText: false
113 | }
114 | }
115 |
116 | shouldComponentUpdate(nextProps, nextState) {
117 | return (
118 | nextState !== this.state
119 | || nextProps !== this.props
120 | || !deepEqual(nextState, this.state, {strict: true})
121 | || !deepEqual(nextProps, this.props, {strict: true})
122 | );
123 | }
124 |
125 | handleConfigChange = (option) => {
126 | this.setState(option);
127 | };
128 |
129 | openDraggableWindow = (values) => {
130 | let fullText = '';
131 | const value1 = values[0] || null;
132 | if (values.length === 2) {
133 | const value2 = values[1] || null;
134 | fullText = this.diffText(
135 | JSON.stringify(value1, null, 2),
136 | JSON.stringify(value2, null, 2)
137 | );
138 | } else {
139 | fullText = JSON.stringify(value1, null, 2);
140 | }
141 | this.setState({
142 | showFullText: true,
143 | fullText
144 | });
145 | };
146 |
147 | diffText = (before, after) => {
148 | const self = this;
149 | const diff = diffJson(before, after);
150 | let html = [];
151 | diff.forEach(function(part, ind) {
152 | // green for additions, red for deletions
153 | // black for common parts
154 | const color = part.added ? 'added' :
155 | part.removed ? 'removed' : 'unchanged';
156 | html.push(
157 | {part.value}
158 | )
159 | });
160 | return html;
161 | };
162 |
163 | closeDraggableWindow = () => {
164 | this.setState({
165 | showFullText: false
166 | });
167 | };
168 |
169 | // TODO Replace this animation with CSS
170 | render() {
171 | const draggableWindowStyle = this.state.showFullText
172 | ? this.styles.draggableWindow
173 | : this.styles.draggableWindowHidden;
174 | const entry = this.props.entries.get(this.state.selectedComponentId);
175 | return (
176 |
177 |
178 |
179 |
180 |
184 |
185 |
186 |
190 |
191 |
195 |
196 |
199 |
{this.state.fullText}
200 |
201 |
202 |
203 |
204 | )
205 | }
206 |
207 | }
208 |
209 | export default Radium(Console);
--------------------------------------------------------------------------------
/src/components/Controls.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component} from 'react';
4 | import Radium from 'radium';
5 |
6 | import root from '../root.js';
7 | import Button from './Button';
8 |
9 | class Controls extends Component {
10 |
11 | styles = {
12 | container: {
13 | display: 'flex',
14 | marginBottom: '10px',
15 | padding: '10px',
16 | background: 'white',
17 | boxShadow: '2px 2px 5px 1px rgba(0, 0, 0, 0.5)'
18 | },
19 | checkbox: {
20 | marginRight: '5px',
21 | cursor: 'pointer',
22 | fontWeight: 'normal'
23 | }
24 | };
25 |
26 | constructor() {
27 | super();
28 | this.state = {
29 | autoRefresh: true
30 | };
31 | }
32 |
33 | handleRefreshStatusCheckbox = () => {
34 | const value = !this.state.autoRefresh;
35 | this.setState({
36 | autoRefresh: value
37 | });
38 | root.autoRefresh = value;
39 | };
40 |
41 | refresh = () => {
42 | root.updateWindow(true)
43 | };
44 |
45 | render() {
46 |
47 | const tooltip = 'Update the monitor automatically every time the monitored app changes (instead of manually refreshing with the "Refresh" button).';
48 |
49 | return (
50 |
51 |
52 |
54 | Continuous Refresh
55 |
56 |
57 |
58 | );
59 |
60 | }
61 |
62 | }
63 |
64 | export default Radium(Controls);
--------------------------------------------------------------------------------
/src/components/DataIconCell.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Radium from 'radium';
5 | import shallowEqual from 'shallowequal';
6 |
7 | import Utf8Char from './Utf8Char';
8 | import styles from '../styles/styles';
9 |
10 | class DataIconCell extends Component {
11 |
12 | static propTypes = {
13 | isChanged: PropTypes.object.isRequired
14 | };
15 |
16 | styles = {
17 | icons: {
18 | display: 'flex'
19 | },
20 | container: {
21 | display: 'flex',
22 | justifyContent: 'center',
23 | alignItems: 'center',
24 | margin: '-6px',
25 | fontSize: '20px'
26 | }
27 | };
28 |
29 | shouldComponentUpdate(nextProps) {
30 | return !shallowEqual(this.props, nextProps);
31 | }
32 |
33 | render() {
34 | const dot = ( );
35 | const {isChanged, ...props} = this.props;
36 | const propsIcon = isChanged.get('props')
37 | ? ({dot}
)
38 | : '';
39 | const stateIcon = isChanged.get('state')
40 | ? ({dot}
)
41 | : '';
42 | let data = (
43 |
44 | {propsIcon}
45 | {stateIcon}
46 |
47 | );
48 | return (
49 | {data}
50 | );
51 |
52 | }
53 |
54 | }
55 |
56 | export default Radium(DataIconCell);
--------------------------------------------------------------------------------
/src/components/LifeCycle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Radium from 'radium';
5 |
6 | import Method from './Method';
7 | import Utf8Char from './Utf8Char';
8 | import styles from '../styles/styles';
9 |
10 | class LifeCycle extends Component {
11 |
12 | styles = {
13 | container: {
14 | width: '900px',
15 | minWidth: '900px',
16 | padding: '10px',
17 | backgroundColor: 'lightblue',
18 | boxSizing: 'border-box'
19 | },
20 | title: {
21 | display: 'flex',
22 | justifyContent: 'space-around',
23 | marginBottom: '15px',
24 | fontSize: '18px',
25 | fontWeight: 'bold'
26 | },
27 | heading: {
28 | display: 'flex',
29 | justifyContent: 'space-around',
30 | marginBottom: '0',
31 | fontSize: '13px',
32 | fontWeight: 'bold'
33 | },
34 | toggleContainer: {
35 | position: 'absolute',
36 | float: 'left',
37 | cursor: 'pointer'
38 | },
39 | toggle: {
40 | cursor: 'pointer'
41 | },
42 | text: {
43 | textIndent: '-10px'
44 | },
45 | left: {
46 | display: 'flex',
47 | justifyContent: 'flex-start',
48 | boxSizing: 'border-box'
49 | },
50 | right: {
51 | display: 'flex',
52 | justifyContent: 'flex-end',
53 | boxSizing: 'border-box'
54 | },
55 | both: {
56 | display: 'flex',
57 | justifyContent: 'space-between',
58 | boxSizing: 'border-box'
59 | },
60 | center: {
61 | display: 'flex',
62 | justifyContent: 'center',
63 | boxSizing: 'border-box'
64 | },
65 | arrows: {
66 | display: 'flex',
67 | justifyContent: 'space-around',
68 | margin: '0 0 10px 0',
69 | fontSize: '16px',
70 | fontWeight: 'bold'
71 | }
72 | };
73 |
74 | getArrows = () => {
75 | const arrow = ( );
76 | if (!this.props.isCompactView) {
77 | return (
78 |
79 |
{arrow}
80 |
{arrow}
81 |
{arrow}
82 |
83 | );
84 | } else {
85 | return false;
86 | }
87 | };
88 |
89 | handleSetCompactView = (event) => {
90 | const value = event.target.checked;
91 | this.props.dispatch(setCompactView(value));
92 | };
93 |
94 | makeProps = (methodName) => {
95 | return {
96 | methodObj: this.props.entry.getIn(['methods', methodName]),
97 | isChanged: this.props.entry.get('isChanged'),
98 | // TODO rename function to make it clear it's a callback
99 | showFullText: this.props.showFullText,
100 | key: methodName + '-method-box'
101 | };
102 | };
103 |
104 | render() {
105 |
106 | const entry = this.props.entry;
107 | if (!entry) {
108 | return false;
109 | }
110 | const methods = entry.get('methods');
111 | const isChanged = entry.get('isChanged');
112 |
113 | // TODO refactor repetitive code
114 | return (
115 |
116 | {/*
117 |
Compact View
118 |
Lifecycle Methods for Child Component
119 |
*/}
120 |
121 |
Called on Initial Render
122 |
Called on Each Rerender
123 |
Called on Removal from DOM
124 |
125 | {this.getArrows()}
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | )
149 |
150 | }
151 |
152 | }
153 |
154 | export default Radium(LifeCycle);
--------------------------------------------------------------------------------
/src/components/Method.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import Radium from 'radium';
5 | import color from 'color';
6 | import Immutable from 'immutable';
7 |
8 | import Utf8Char from './Utf8Char';
9 | import styles from '../styles/styles';
10 |
11 | class Method extends Component {
12 |
13 | static propTypes = {
14 | methodObj: PropTypes.instanceOf(Immutable.Map).isRequired,
15 | isChanged: PropTypes.object.isRequired,
16 | showFullText: PropTypes.func.isRequired
17 | };
18 |
19 | // TODO need to install react-immutable-proptypes to do this
20 | // static propTypes = {
21 | // methodObj: PropTypes.shape({
22 | // name: PropTypes.string,
23 | // called: PropTypes.bool,
24 | // count: PropTypes.number,
25 | // props: PropTypes.object,
26 | // state: PropTypes.object,
27 | // args: PropTypes.array
28 | // }).isRequired
29 | // };
30 |
31 |
32 | styles = {
33 | section: {
34 | width: '275px',
35 | margin: '0 10px',
36 | padding: '10px 10px 5px 10px',
37 | backgroundColor: 'white',
38 | boxSizing: 'border-box'
39 | },
40 | active: {
41 | boxShadow: '0 0 10px 6px #2d7188',
42 | fontWeight: 'bold',
43 | animation: 'x .7s ease-out',
44 | animationName: Radium.keyframes({
45 | '0%': {boxShadow: 'none'},
46 | '40%': {boxShadow: '0 0 14px 9px ' + color('#2d7188').lighten(0.5).hexString()},
47 | '100%': {boxShadow: '0 0 10px 6px #2d7188'}
48 | })
49 | },
50 | inactive: {
51 | fontWeight: 'normal',
52 | boxShadow: 'none'
53 | },
54 | methodName: {
55 | display: 'flex',
56 | alignItems: 'center',
57 | flex: '1',
58 | position: 'relative',
59 | fontWeight: 'bold',
60 | wordBreak: 'break-word'
61 | },
62 | overriddenName: {
63 | display: 'flex',
64 | alignItems: 'center',
65 | margin: '-5px',
66 | padding: '5px',
67 | background: '#fdfdb8',
68 | boxShadow: '1px 1px 3px 1px rgba(0,0,0,.3)'
69 | },
70 | propsAndState: {
71 | display: 'flex',
72 | alignItems: 'center'
73 | },
74 | propsAndStateChanged: {
75 | fontWeight: 'bold',
76 | animation: 'x .7s ease-out',
77 | animationName: Radium.keyframes({
78 | '0%': {backgroundColor: 'white'},
79 | '40%': {backgroundColor: 'yellow'},
80 | '100%': {backgroundColor: 'white'}
81 | })
82 | },
83 | propsAndStateUnchanged: {
84 | fontWeight: 'normal',
85 | color: 'gray'
86 | },
87 | line: {
88 | display: 'flex'
89 | },
90 | valueContainer: {
91 | overflow: 'hidden'
92 | },
93 | value: {
94 | marginLeft: '5px',
95 | fontWeight: 'normal',
96 | cursor: 'pointer',
97 | overflow: 'hidden',
98 | textOverflow: 'ellipsis',
99 | whiteSpace: 'nowrap'
100 | },
101 | times: {
102 | display: 'flex',
103 | justifyContent: 'space-between',
104 | marginTop: '10px'
105 | },
106 | warning: {
107 | color: 'red'
108 | },
109 | message: {
110 | marginTop: '5px',
111 | color: 'red'
112 | },
113 | setState: {
114 | display: 'flex',
115 | alignItems: 'center',
116 | margin: '5px -5px 0 -5px',
117 | padding: '5px',
118 | backgroundColor: 'rgba(0, 0, 0, .1)'
119 | },
120 | setStateCalled: {
121 | backgroundColor: 'rgba(76, 175, 80, .5)',
122 | boxShadow: '1px 1px 3px 1px rgba(0,0,0,.4)'
123 | },
124 | setStateValue: {
125 | marginTop: '5px'
126 | },
127 | arrow: {
128 | display: 'flex',
129 | justifyContent: 'space-around',
130 | alignItems: 'center',
131 | margin: '5px 0 7px 0',
132 | fontSize: '16px',
133 | fontWeight: 'bold'
134 | },
135 | hidden: {
136 | opacity: '0'
137 | },
138 | input: {
139 | width: '100%',
140 | marginLeft: '5px'
141 | }
142 | };
143 |
144 | constructor() {
145 | super();
146 | this.state = {
147 | showFullText: false
148 | }
149 | }
150 |
151 | shouldComponentUpdate(nextProps, nextState) {
152 | return (
153 | nextProps !== this.props
154 | || nextState.showFullText !== this.state.showFullText
155 | );
156 | }
157 |
158 | getMethodName = (methodObj) => {
159 | const args = this.getArgNames(methodObj).str;
160 | const key = this.makeKey([methodObj.name, 'name']);
161 | const name = methodObj.name === 'constructorMethod'
162 | ? `constructor(${args}) or getInitialState()`
163 | : `${methodObj.name}(${args})`;
164 | let message = '';
165 | let nameStyle = [this.styles.methodName];
166 | if (methodObj.isMethodOverridden) {
167 | message = 'This method exists in the wrapped component';
168 | nameStyle.push(this.styles.overriddenName);
169 | }
170 | return (
171 |
176 | );
177 | };
178 |
179 | getArgNames = (methodObj) => {
180 | const args = methodObj.args;
181 | return {
182 | str: args.join(', '),
183 | arr: args
184 | }
185 | };
186 |
187 | getActivityStyle = (methodObj) => {
188 | const isActive = methodObj.called;
189 | let style = isActive ? this.styles.active : this.styles.inactive;
190 | if (methodObj.name === 'render') {
191 | style.width = '570px'
192 | }
193 | return Object.assign({}, this.styles.section, style);
194 | };
195 |
196 | getTimesCalled = (methodObj) => {
197 | const key = this.makeKey([methodObj.name, 'times', 'called']);
198 | const loop = methodObj.isInfiniteLoop ? (
199 | (infinite loop terminated)
) : false;
200 | return (
201 |
202 |
Times called: {methodObj.count}
203 | {loop}
204 |
205 | );
206 | };
207 |
208 | // TODO Refactor for clarity
209 | getPropsAndStates = (methodObj) => {
210 | let types = ['props', 'state'];
211 | return types.map((type) => {
212 | const propsOrState = methodObj[type];
213 | const values = propsOrState.values;
214 | const nameComponents = propsOrState.names.map((name, ind) => {
215 | return this.getPropAndStateName(name, type, ind);
216 | });
217 | // Display both values only if one has changed
218 | let valueComponents = false;
219 | if (values.length) {
220 | const isParallelItemDifferent = propsOrState.arePartnersDifferent;
221 | const valuesToDisplay = !isParallelItemDifferent && values.length === 2
222 | ? values.slice(0, 1)
223 | : values;
224 | valueComponents = valuesToDisplay.map((item, ind) => {
225 | return this.getPropAndStateValues(valuesToDisplay, ind, type, methodObj.name, propsOrState.isChanged);
226 | });
227 | }
228 |
229 | return this.getPropAndStateComponents({
230 | name:methodObj.name,
231 | type,
232 | nameComponents,
233 | valueComponents
234 | });
235 | });
236 | };
237 |
238 | getPropAndStateName = (name, type, ind) => {
239 | const isSecond = ind > 0;
240 | const arrow = isSecond ? ( ) : '';
241 | const key = this.makeKey([name, type, 'name', ind]);
242 | return (
243 |
244 |
{arrow}{name}:
245 |
246 | );
247 | };
248 |
249 | getPropAndStateValues = (valuesToDisplay, ind, type, name, isChanged) => {
250 | const value = valuesToDisplay[ind];
251 | const key = this.makeKey([name, type, 'value', ind]);
252 | const isChangedStyle = isChanged && ((valuesToDisplay.length === 1 && ind === 0) || ind === 1)
253 | ? this.styles.propsAndStateChanged
254 | : this.styles.propsAndStateUnchanged;
255 | return (
256 |
262 | {value ? JSON.stringify(value).substr(0, 300) : 'null'}
263 |
264 | );
265 | };
266 |
267 | handleShowFullText = (items) => {
268 | this.props.showFullText(items);
269 | };
270 |
271 | getUnnecessaryUpdatePrevented = (methodObj) => {
272 | return methodObj.isUnnecessaryUpdatePrevented
273 | ? (Unnecessary rerender prevented (props and state values have not
274 | changed)
)
275 | : false;
276 | };
277 |
278 | getDescription = (methodObj) => {
279 | const description = methodObj.description;
280 | if (description) {
281 | return (
282 | {description}
283 | );
284 | } else {
285 | return false;
286 | }
287 | };
288 |
289 | getSetState = (methodObj) => {
290 | const validMethods = [
291 | 'constructorMethod',
292 | 'componentWillMount',
293 | 'componentDidMount',
294 | 'componentWillReceiveProps',
295 | 'componentDidUpdate'
296 | ];
297 | const methodsWhereStateShouldNotBeSet = [
298 | 'componentDidMount',
299 | 'componentDidUpdate'
300 | ];
301 | const name = methodObj.name;
302 | const key = this.makeKey([name, 'set', 'state']);
303 | if (validMethods.indexOf(name) === -1) {
304 | return false;
305 | }
306 | let label = methodObj.name === 'constructorMethod' ? 'this.state can be set here' : 'this.setState';
307 | let stateToDisplay = false;
308 | let description = false;
309 | let setStateStyles = [this.styles.setState];
310 | if (methodObj.isSetStateCalled) {
311 | setStateStyles.push(this.styles.setStateCalled);
312 | label += ' called';
313 | if (methodsWhereStateShouldNotBeSet.includes(name)) {
314 | stateToDisplay = this.getSetStateNameAndValues(methodObj);
315 | description = this.getDescription(methodObj);
316 | }
317 | } else if (methodObj.name !== 'constructorMethod') {
318 | label += ' can be called here';
319 | }
320 | return (
321 |
322 |
323 | {label}
324 |
325 |
326 | {stateToDisplay}
327 | {description}
328 |
329 |
330 | );
331 | };
332 |
333 | getSetStateNameAndValues = (methodObj) => {
334 | const name = methodObj.setState.names[0];
335 | const ind = 0;
336 | const nameComponents = this.getPropAndStateName(name, 'state', ind);
337 | const valueToDisplay = methodObj.setState.values;
338 | const valueComponents = this.getPropAndStateValues(valueToDisplay, ind, 'state', name, methodObj.setState.isChanged);
339 | return this.getPropAndStateComponents({
340 | name,
341 | type: 'state',
342 | nameComponents,
343 | valueComponents
344 | });
345 | };
346 |
347 | getPropAndStateComponents = (dataObj) => {
348 |
349 | const baseKey = this.makeKey([dataObj.name, dataObj.type]);
350 | return (
351 |
352 |
{dataObj.nameComponents}
353 |
{dataObj.valueComponents}
354 |
355 | );
356 |
357 | };
358 |
359 | getArrow = (methodObj) => {
360 | const arrow = ( );
361 | const key = this.makeKey([methodObj.name, 'arrows']);
362 | if (this.props.isCompactView) {
363 | return (
);
364 | } else if (methodObj.name === 'render') {
365 | return (
366 |
367 |
{arrow}
368 |
{arrow}
369 |
370 | )
371 | } else if (!methodObj.terminal) {
372 | return ({arrow}
);
373 | } else {
374 | return ({arrow}
);
375 | }
376 | };
377 |
378 | makeKey = (names) => {
379 | return names.map((name) => {
380 | if (typeof name !== 'string') {
381 | return name;
382 | }
383 | return name.replace(/[A-Z]/g, function(uppercaseLetter) {
384 | return '-' + uppercaseLetter.toLowerCase();
385 | });
386 | }).join('-');
387 | };
388 |
389 | handleInputChange = (event) => {
390 | this.setState({
391 | value: event.target.value
392 | });
393 | };
394 |
395 | render() {
396 |
397 | const methodObj = this.props.methodObj.toJS();
398 | return (
399 |
400 |
401 | {this.getMethodName(methodObj)}
402 | {this.getTimesCalled(methodObj)}
403 | {this.getPropsAndStates(methodObj)}
404 | {this.getUnnecessaryUpdatePrevented(methodObj)}
405 | {this.getSetState(methodObj)}
406 |
407 | {this.getArrow(methodObj)}
408 |
409 | )
410 |
411 | }
412 |
413 | }
414 |
415 | export default Radium(Method);
--------------------------------------------------------------------------------
/src/components/PopoutWindow.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 | import {render} from 'react-dom';
5 | import Immutable from 'immutable';
6 | import Radium, {Style} from 'radium';
7 |
8 | import root from '../root';
9 | import Console from './Console';
10 |
11 | class PopoutWindow extends Component {
12 |
13 | static propTypes = {
14 | entries: PropTypes.instanceOf(Immutable.Map).isRequired
15 | };
16 |
17 | styles = {
18 | global: {
19 | body: {
20 | margin: '0'
21 | }
22 | }
23 | };
24 |
25 | shouldComponentUpdate(nextProps) {
26 | return this.props !== nextProps;
27 | }
28 |
29 | render() {
30 | const windowWidth = root.getWindow().innerWidth;
31 |
32 | return (
33 |
34 |
35 |
36 |
37 | );
38 |
39 | }
40 |
41 | }
42 |
43 | export default Radium(PopoutWindow);
--------------------------------------------------------------------------------
/src/components/Utf8Char.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React, {Component, PropTypes} from 'react';
4 |
5 | // Using dangerouslySetInnerHTML appears to be the only way to get UTF-8 characters to display if the client app
6 | // doesn't specify the UTF-8 charset in its header
7 | class Utf8Char extends Component {
8 |
9 | static propTypes = {
10 | char: PropTypes.string.isRequired
11 | };
12 |
13 | chars = {
14 | downArrow: '8595',
15 | rightArrow: '8627',
16 | dot: '8226'
17 | };
18 |
19 | render() {
20 |
21 | return (
22 |
23 | );
24 |
25 | }
26 |
27 | }
28 |
29 | export default Utf8Char;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import deepEqual from 'deep-equal';
5 | import shallowEqual from 'shallowequal';
6 | import deepCopy from 'deepcopy';
7 | import Immutable from 'immutable';
8 |
9 | import root from './root.js';
10 | import lifecycleConfig from './store/lifecycleConfig';
11 | import settingsManager from './settingsManager';
12 |
13 |
14 | function Visible(wrapperParams) {
15 |
16 | settingsManager.set(wrapperParams);
17 | const settings = settingsManager.get();
18 |
19 | return function HOCFactory(WrappedComponent) {
20 |
21 | if (!settings.enabled || (!settings.monitor && !settings.logging && settings.compare === 'none')) {
22 |
23 | // Do nothing
24 | return class DisabledComponentWrapper extends WrappedComponent {
25 | static displayName = getComponentName(WrappedComponent);
26 | }
27 |
28 | }
29 |
30 | if (!settings.monitor && !settings.logging && settings.compare !== 'none') {
31 |
32 |
33 | // Only prevent unnecessary rerenders
34 | return class MinimalComponentWrapper extends WrappedComponent {
35 |
36 | static displayName = getComponentName(WrappedComponent);
37 |
38 | shouldComponentUpdate(nextProps, nextState) {
39 |
40 | if (super.shouldComponentUpdate) {
41 | const isSuperAllowingUpdate = super.shouldComponentUpdate(nextProps, nextState);
42 | if (!isSuperAllowingUpdate) {
43 | return false;
44 | }
45 | }
46 | let isChanged;
47 | if (settings.compare === 'shallow') {
48 | isChanged = !shallowEqual(nextState, this.state)
49 | || !shallowEqual(nextProps, this.props);
50 | } else if (settings.compare == 'deep') {
51 | isChanged = !deepEqual(nextState, this.state, {strict: true})
52 | || !deepEqual(nextProps, this.props, {strict: true});
53 | }
54 | return isChanged;
55 |
56 | }
57 |
58 | };
59 |
60 | }
61 |
62 | // Full set of features
63 | return class FullComponentWrapper extends WrappedComponent {
64 |
65 | static displayName = getComponentName(WrappedComponent);
66 |
67 | logEntryId = null;
68 | autoRenderCount = 0;
69 | isInfiniteLoop = false;
70 | isRenderingComplete = true;
71 | consoleWindow = null;
72 | lifecycleLocation = '';
73 | componentName = '';
74 |
75 | constructor(props) {
76 | super(...arguments);
77 | // if (!this.key) {
78 | // this.key = uuid.v1();
79 | // }
80 | this.componentName = getComponentName(WrappedComponent);
81 | this.setStateSpy = this.spy(this, 'setState');
82 | if (settings.monitor) {
83 | this.logEntryId = root.add({
84 | type: 'ADD_ENTRY',
85 | key: props.id,
86 | name: this.componentName
87 | });
88 | this.isRenderingComplete = false;
89 | this.lifecycleLocation = 'mounting';
90 | this.clearCalled();
91 | }
92 | this.handleLifecycleEvent({
93 | name: 'constructorMethod',
94 | props: [props],
95 | state: []
96 | });
97 | if (!settings.monitor) {
98 | return;
99 | }
100 | let methodObj = {};
101 | lifecycleConfig.methodNames.forEach((name) => {
102 | const methodName = name === 'constructorMethod' ? 'constructor' : name;
103 | methodObj[name] = {
104 | isMethodOverridden: Boolean(super[methodName])
105 | }
106 | });
107 | root.updateStore({
108 | type: 'UPDATE_METHODS',
109 | key: this.logEntryId,
110 | value: methodObj
111 | });
112 | }
113 |
114 | componentWillMount() {
115 | let isSetStateCalled = false;
116 | let setStateValue = null;
117 | if (super.componentWillMount) {
118 | ({isSetStateCalled, setStateValue} = this.getSetState(super.componentWillMount.bind(this)));
119 | }
120 | this.handleLifecycleEvent({
121 | name: 'componentWillMount',
122 | props: [this.props],
123 | state: [this.state],
124 | setState: [setStateValue],
125 | isSetStateCalled
126 | });
127 | }
128 |
129 | componentDidMount() {
130 | if (settings.monitor) {
131 | this.consoleWindow = root.getWindow();
132 | }
133 | let isSetStateCalled = false;
134 | let setStateValue = null;
135 | if (super.componentDidMount) {
136 | ({isSetStateCalled, setStateValue} = this.getSetState(super.componentDidMount.bind(this)));
137 | }
138 | const lifecycleData = {
139 | name: 'componentDidMount',
140 | props: [this.props],
141 | state: [this.state],
142 | setState: [setStateValue],
143 | isSetStateCalled
144 | };
145 | this.handleLifecycleEvent(lifecycleData);
146 | if (!settings.monitor) {
147 | return;
148 | }
149 | this.isRenderingComplete = !isSetStateCalled;
150 | this.setIsMounted(true);
151 | root.updateWindow();
152 | }
153 |
154 | componentWillReceiveProps(nextProps) {
155 | let isSetStateCalled = false;
156 | let setStateValue = null;
157 | if (super.componentWillReceiveProps) {
158 | ({isSetStateCalled, setStateValue} = this.getSetState(super.componentWillReceiveProps.bind(this, nextProps)));
159 | }
160 | this.lifecycleLocation = 'updating';
161 | this.clearCalled();
162 | this.handleLifecycleEvent({
163 | name: 'componentWillReceiveProps',
164 | props: [this.props, nextProps],
165 | state: [this.state],
166 | setState: [setStateValue],
167 | isSetStateCalled
168 | });
169 | this.isRenderingComplete = false;
170 | }
171 |
172 | shouldComponentUpdate(nextProps, nextState) {
173 |
174 | this.lifecycleLocation = 'updating';
175 | this.isInfiniteLoop = this.autoRenderCount >= 10;
176 |
177 | let isSuperAllowingUpdate = true;
178 | // TODO Show warning if super is preventing update when value has changed
179 | if (super.shouldComponentUpdate) {
180 | // TODO If this is false (updated prevented by super) show message
181 | isSuperAllowingUpdate = super.shouldComponentUpdate(nextProps, nextState);
182 | }
183 | let isChanged = null;
184 | if (settings.compare === 'shallow') {
185 | isChanged = !shallowEqual(nextState, this.state)
186 | || !shallowEqual(nextProps, this.props);
187 | } else if (settings.compare == 'deep') {
188 | isChanged = !deepEqual(nextState, this.state, {strict: true})
189 | || !deepEqual(nextProps, this.props, {strict: true});
190 | }
191 | const isUnnecessaryUpdatePrevented = isSuperAllowingUpdate
192 | && isChanged === false;
193 | if (isUnnecessaryUpdatePrevented) {
194 | this.incrementUnnecessaryUpdatesPrevented();
195 | }
196 | if (this.isRenderingComplete) {
197 | this.clearCalled();
198 | }
199 | this.handleLifecycleEvent({
200 | name: 'shouldComponentUpdate',
201 | props: [this.props, nextProps],
202 | state: [this.state, nextState],
203 | isUnnecessaryUpdatePrevented
204 | });
205 | let willUpdate = isSuperAllowingUpdate && isChanged !== false;
206 | // TODO Will there be cases where desired behavior sets isInfiniteLoop to true?
207 | if (this.isInfiniteLoop) {
208 | willUpdate = false;
209 | }
210 | if (willUpdate) {
211 | this.isRenderingComplete = false;
212 | return true;
213 | } else {
214 | this.isRenderingComplete = true;
215 | if (settings.monitor) {
216 | root.updateWindow();
217 | }
218 | return false;
219 | }
220 |
221 | }
222 |
223 | componentWillUpdate(nextProps, nextState) {
224 | if (super.componentWillUpdate) {
225 | super.componentWillUpdate(nextProps, nextState);
226 | }
227 | this.handleLifecycleEvent({
228 | name: 'componentWillUpdate',
229 | props: [this.props, nextProps],
230 | state: [this.state, nextState]
231 | });
232 | }
233 |
234 | componentDidUpdate(prevProps, prevState) {
235 | let isSetStateCalled = false;
236 | let setStateValue = null;
237 | if (super.componentDidUpdate) {
238 | ({isSetStateCalled, setStateValue} = this.getSetState(super.componentDidUpdate.bind(this, prevProps, prevState)));
239 | }
240 | this.isRenderingComplete = true;
241 | this.handleLifecycleEvent({
242 | name: 'componentDidUpdate',
243 | props: [prevProps, this.props],
244 | state: [prevState, this.state],
245 | setState: [setStateValue],
246 | isSetStateCalled
247 | });
248 | if (!settings.monitor) {
249 | return;
250 | }
251 | root.updateWindow();
252 | }
253 |
254 | componentWillUnmount() {
255 | if (super.componentWillUnmount) {
256 | super.componentWillUnmount();
257 | }
258 | this.lifecycleLocation = 'unmounting';
259 | this.isRenderingComplete = true;
260 | this.setIsMounted(false);
261 | this.clearCalled();
262 | this.handleLifecycleEvent({
263 | name: 'componentWillUnmount',
264 | props: [this.props],
265 | state: [this.state]
266 | });
267 | if (!settings.monitor) {
268 | return;
269 | }
270 | root.updateWindow();
271 | }
272 |
273 | spy(obj, method) {
274 | const spy = {
275 | calls: [],
276 | callCount: 0,
277 | lastCall: null
278 | };
279 | let original = obj[method];
280 | obj[method] = function() {
281 | spy.calls.push(arguments[0]);
282 | spy.callCount++;
283 | spy.lastCall = arguments[0];
284 | if (settings.logging) {
285 | console.log(`%csetState %ccalled in component %c${this.componentName}`, 'color: green; font-weight: bold;', 'color: black; font-weight: normal;', 'color: blue; font-weight: bold;');
286 | }
287 | return original.apply(obj, [...arguments]);
288 | };
289 | return spy;
290 | };
291 |
292 | getSetState = (superMethod) => {
293 |
294 | let isSetStateCalled = false;
295 | let setStateValue = null;
296 | let setStateCallCount1 = this.setStateSpy.callCount;
297 | superMethod();
298 | let setStateCallCount2 = this.setStateSpy.callCount;
299 | isSetStateCalled = setStateCallCount2 - setStateCallCount1 === 1;
300 | if (isSetStateCalled) {
301 | setStateValue = deepCopy(this.setStateSpy.lastCall);
302 | }
303 | return {
304 | isSetStateCalled,
305 | setStateValue
306 | }
307 |
308 | };
309 |
310 | handleLifecycleEvent = (lifecycleData) => {
311 | if (settings.logging) {
312 | console.log(`%c${lifecycleData.name} %ccalled in component %c${this.componentName}`, 'color: green; font-weight: bold;', 'color: black; font-weight: normal;', 'color: blue; font-weight: bold;');
313 | }
314 | if (!settings.monitor) {
315 | return;
316 | }
317 | let count = root.getFromStore([
318 | 'entries',
319 | this.logEntryId,
320 | 'methods',
321 | lifecycleData.name,
322 | 'count'
323 | ]);
324 | const cleanedProps = lifecycleData.props.map((propsValue) => {
325 | return this.removePropsChildren(propsValue);
326 | });
327 | const method = Immutable.Map({
328 | name: lifecycleData.name,
329 | called: true,
330 | count: count + 1,
331 | props: Immutable.fromJS(cleanedProps || []),
332 | state: Immutable.fromJS(lifecycleData.state || []),
333 | setState: Immutable.fromJS(lifecycleData.setState || []),
334 | isSetStateCalled: lifecycleData.isSetStateCalled || false,
335 | isInfiniteLoop: this.isInfiniteLoop,
336 | isUnnecessaryUpdatePrevented: lifecycleData.isUnnecessaryUpdatePrevented,
337 | lifecycleLocation: this.lifecycleLocation
338 | });
339 | root.updateStore({
340 | type: 'UPDATE_METHOD',
341 | entryId: this.logEntryId,
342 | methodName: lifecycleData.name,
343 | value: method
344 | });
345 | };
346 |
347 | // Remove props.children because they can contain
348 | // circular references, which cause problems
349 | // with cloning and stringifying;
350 | // This prop value is meant to be opaque
351 | // and used only internally
352 | removePropsChildren = (props) => {
353 | if (!props) {
354 | return [];
355 | }
356 | let newProps = Object.assign({}, props);
357 | if (newProps.hasOwnProperty('children')) {
358 | delete newProps.children;
359 | }
360 | return newProps;
361 | };
362 |
363 | clearCalled = () => {
364 | if (!settings.monitor) {
365 | return;
366 | }
367 | this.autoRenderCount = 0;
368 | let methodObj = {};
369 | lifecycleConfig.methodNames.forEach((name) => {
370 | methodObj[name] = {
371 | called: false
372 | };
373 | });
374 | root.updateStore({
375 | type: 'UPDATE_METHODS',
376 | key: this.logEntryId,
377 | value: methodObj
378 | });
379 | };
380 |
381 | incrementRenderCount = () => {
382 | if (!settings.monitor) {
383 | return;
384 | }
385 | this.autoRenderCount++;
386 | root.updateStore({
387 | type: 'INCREMENT_VALUE',
388 | keyPath: [this.logEntryId, 'renderCount']
389 | })
390 | };
391 |
392 | setIsMounted = (isMounted) => {
393 | if (!settings.monitor) {
394 | return;
395 | }
396 | root.updateStore({
397 | type: 'UPDATE_VALUE',
398 | keyPath: [this.logEntryId, 'isMounted'],
399 | value: isMounted
400 | });
401 | };
402 |
403 | incrementUnnecessaryUpdatesPrevented = () => {
404 | if (!settings.monitor) {
405 | return;
406 | }
407 | root.updateStore({
408 | type: 'INCREMENT_VALUE',
409 | keyPath: [this.logEntryId, 'unnecessaryUpdatesPrevented']
410 | });
411 | };
412 |
413 | render() {
414 |
415 | this.incrementRenderCount();
416 | this.handleLifecycleEvent({
417 | name: 'render',
418 | props: [this.props],
419 | state: [this.state]
420 | });
421 | return super.render();
422 |
423 | }
424 | };
425 |
426 | }
427 |
428 | }
429 |
430 | const getComponentName = (component) => {
431 | return component ? component.displayName
432 | || component.name
433 | || 'Component'
434 | : ''
435 | };
436 |
437 | // Visible.setup = function(settings) {
438 | // root.setSettings(settings);
439 | // };
440 |
441 | export default Visible;
--------------------------------------------------------------------------------
/src/root.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import {Provider} from 'react-redux';
6 | import {createStore} from 'redux';
7 | import {StyleRoot} from 'radium';
8 | import Immutable from 'immutable';
9 |
10 | import PopoutWindow from './components/PopoutWindow.js';
11 | import entries from './store/reducers';
12 | import lifecycleConfig from './store/lifecycleConfig';
13 |
14 | let consoleWindow = null;
15 |
16 | const initialStore = Immutable.fromJS({entries: {}}).toMap();
17 | let store = createStore(entries, initialStore);
18 |
19 | const root = {
20 |
21 | isTimerOn: false,
22 | isWindowInitialized: false,
23 | _autoRefresh: true,
24 |
25 | add(action) {
26 | const {key, name} = action;
27 | const id = this.makeId(key, name);
28 | store.dispatch({
29 | type: 'ADD',
30 | key,
31 | name,
32 | id,
33 | displayName: this.makeDisplayName(key, name)
34 | });
35 | return id;
36 | },
37 |
38 | updateStore(action) {
39 | store.dispatch(action);
40 | },
41 |
42 | getFromStore(keyPath) {
43 | return store.getState().getIn(keyPath);
44 | },
45 |
46 | makeId(key, name) {
47 | return key ? `${name}-${key}` : name;
48 | },
49 |
50 | makeDisplayName(key, name) {
51 | const formattedName = this.removeComponentWrapperNames(name);
52 | return key ? `${formattedName} (${key})` : formattedName;
53 | },
54 |
55 | removeComponentWrapperNames(name) {
56 | const wrapperPattern = /[^\(]+\(([^\)]+)\)/;
57 | while (wrapperPattern.test(name)) {
58 | name = name.replace(/[^\(]+\(([^\)]+)\)/, '$1')
59 | }
60 | return name;
61 | },
62 |
63 | addCalculatedValues() {
64 | const entries = store.getState().get('entries');
65 | return lifecycleConfig.addRemainingPropertiesToAllEntries(entries);
66 | },
67 |
68 | set autoRefresh(value) {
69 | this._autoRefresh = value;
70 | },
71 |
72 | get autoRefresh() {
73 | return this._autoRefresh;
74 | },
75 |
76 | // The timer returns control to the wrapped application while
77 | // Visible React processes a batch of updates and does it's own rendering.
78 | // This is necessary for performance.
79 | updateWindow(isManualRefresh = false) {
80 | if (this.isWindowInitialized && !this.autoRefresh && !isManualRefresh) {
81 | return;
82 | }
83 | if (this.isTimerOn || consoleWindow === null || consoleWindow.closed) {
84 | return;
85 | }
86 | this.isTimerOn = true;
87 | setTimeout(() => {
88 | const entries = this.addCalculatedValues();
89 | const container = consoleWindow.document.getElementById('visible-react');
90 | if (!this.isWindowInitialized) {
91 | this.isWindowInitialized = true;
92 | }
93 | ReactDOM.render((
94 |
95 |
96 |
99 |
100 |
101 | ), container);
102 | this.isTimerOn = false;
103 | }, 0);
104 | },
105 |
106 | getWindow() {
107 |
108 | if ((window && consoleWindow === null) || consoleWindow.closed) {
109 |
110 | consoleWindow = window.open(
111 | '',
112 | 'console',
113 | "width=1350,height=900,resizable,scrollbars=yes,status=1"
114 | );
115 | if (!consoleWindow) {
116 | alert('You must disable your popup blocker to use the Visible React Console.');
117 | }
118 |
119 | consoleWindow.document.title = 'Visible React';
120 | const container = consoleWindow.document.createElement('div');
121 | container.id = 'visible-react';
122 | consoleWindow.document.body.appendChild(container);
123 |
124 | consoleWindow.focus();
125 | window.onbeforeunload = () => {
126 | consoleWindow.close();
127 | };
128 | }
129 | return consoleWindow;
130 | }
131 |
132 | };
133 |
134 | export default root;
--------------------------------------------------------------------------------
/src/settingsManager.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const settingsManager = {
4 |
5 | settings: {},
6 |
7 | set(wrapperParams = {}) {
8 |
9 | let process = process ? process : {};
10 | process.env = process.env ? process.env : {};
11 | let buildVars = null;
12 |
13 | try {
14 | buildVars = {
15 | mode: process.env.NODE_ENV,
16 | development: {
17 | enabled: process.env.VR_DEV_ENABLED,
18 | monitor: process.env.VR_DEV_MONITOR,
19 | logging: process.env.VR_DEV_LOGGING,
20 | control: process.env.VR_DEV_CONTROL,
21 | compare: process.env.VR_DEV_COMPARE
22 | },
23 | production: {
24 | enabled: process.env.VR_PROD_ENABLED,
25 | monitor: process.env.VR_PROD_MONITOR,
26 | logging: process.env.VR_PROD_LOGGING,
27 | control: process.env.VR_PROD_CONTROL,
28 | compare: process.env.VR_PROD_COMPARE
29 | }
30 | };
31 | } catch(error) {
32 | console.error('Error setting environment variables from build -- ' + error);
33 | }
34 |
35 | const isBuildVarsValid = Boolean(buildVars);
36 |
37 | let defaultValues = {
38 | development: {
39 | enabled: 'all',
40 | monitor: 'all',
41 | logging: 'selected',
42 | control: 'all',
43 | compare: 'deep'
44 | },
45 | production: {
46 | enabled: 'none',
47 | monitor: 'none',
48 | logging: 'none',
49 | control: 'all',
50 | compare: 'shallow'
51 | }
52 | };
53 |
54 | let envVars = {};
55 | if (isBuildVarsValid) {
56 | envVars = this.getValueFromBuildVar(buildVars, defaultValues);
57 | } else {
58 | envVars = defaultValues;
59 | }
60 | envVars.mode = (isBuildVarsValid && buildVars.mode === 'production') ? 'production' : 'development';
61 |
62 | let localVars = {
63 | enabled: wrapperParams.enabled !== undefined ? wrapperParams.enabled : false,
64 | monitor: wrapperParams.monitor !== undefined ? wrapperParams.monitor : false,
65 | logging: wrapperParams.logging !== undefined ? wrapperParams.logging : false,
66 | compare: wrapperParams.compare !== undefined ? wrapperParams.compare : null
67 | };
68 |
69 | let settingValues = {};
70 |
71 | const mode = envVars.mode;
72 |
73 | const envProperties = [
74 | 'enabled',
75 | 'monitor',
76 | 'logging',
77 | 'control'
78 | ];
79 |
80 | envProperties.forEach((prop) => {
81 | let value = envVars[mode][prop];
82 | if (value === 'all') {
83 | settingValues[prop] = true;
84 | } else if (value === 'selected') {
85 | const localValue = localVars[prop];
86 | if ([true, false].includes(localValue)) {
87 | settingValues[prop] = localValue;
88 | } else {
89 | settingValues[prop] = false;
90 | console.error(`Illegal Visible React configuration value "${localValue}" for "${prop}" passed to Visible function`);
91 | }
92 | } else if (value === 'none') {
93 | settingValues[prop] = false;
94 | }
95 | });
96 |
97 | // compare
98 | // If control is false, no comparison will be done
99 | if (!settingValues.control) {
100 | settingValues.compare = 'none';
101 | // Local compare settings will always override global compare settings
102 | } else if (localVars.compare !== null && this.isValueValid('compare', localVars.compare)) {
103 | settingValues.compare = localVars.compare;
104 | } else {
105 | settingValues.compare = envVars[mode].compare;
106 | }
107 |
108 | this.settings = {
109 | enabled: settingValues.enabled,
110 | monitor: settingValues.monitor,
111 | logging: settingValues.logging,
112 | compare: settingValues.compare
113 | };
114 |
115 | },
116 |
117 | getValueFromBuildVar(buildVars, defaultValues) {
118 | let envVars = {};
119 | for (let category in defaultValues) {
120 | envVars[category] = {};
121 | for (let prop in defaultValues[category]) {
122 | let value = buildVars[category][prop];
123 | if (value === null || value === undefined || !this.isValueValid(prop, value)) {
124 | value = defaultValues[category][prop];
125 | }
126 | envVars[category][prop] = value;
127 | }
128 | }
129 | return envVars;
130 | },
131 |
132 | isValueValid(prop, value) {
133 | let isValid = false;
134 | if (['enabled', 'monitor', 'logging'].includes(prop)) {
135 | isValid = ['all', 'selected', 'none'].includes(value);
136 | } else if (prop === 'control') {
137 | isValid = ['all', 'none'].includes(value);
138 | } else if (prop === 'compare') {
139 | isValid = ['none', 'shallow', 'deep'].includes(value);
140 | }
141 | if (!isValid) {
142 | console.error(`Illegal Visible React configuration value "${value}" for "${prop}"`);
143 | return false;
144 | } else {
145 | return true;
146 | }
147 | },
148 |
149 | get() {
150 | return Object.assign({}, this.settings);
151 | }
152 |
153 | };
154 |
155 | export default settingsManager;
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export const add = (key, name) => {
4 | return {
5 | type: 'ADD',
6 | key,
7 | name
8 | };
9 | };
10 |
11 | export const updateValue = (key, value) => {
12 | return {
13 | type: 'UPDATE_VALUE',
14 | keyPath,
15 | value
16 | };
17 | };
18 |
19 | export const updateMethod = (entryId, methodName, value) => {
20 | return {
21 | type: 'UPDATE_METHOD',
22 | entryId,
23 | methodName,
24 | value
25 | };
26 | };
27 |
28 | export const updateMethods = (entryId, value) => {
29 | return {
30 | type: 'UPDATE_METHODS',
31 | entryId,
32 | value
33 | };
34 | };
--------------------------------------------------------------------------------
/src/store/lifecycleConfig.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import deepEqual from 'deep-equal';
4 | import Immutable from 'immutable';
5 |
6 | const lifecycleConfig = {
7 |
8 | isPartnerChanged: {
9 | props: null,
10 | state: null
11 | },
12 |
13 | setStateWarningMessage: 'setState was called to change the state after the component rendered. Avoid calling setState here because it triggers an extra render. If possible, call it earlier in the lifecycle.',
14 |
15 | addRemainingPropertiesToAllEntries(entries) {
16 | return entries.map((entry) => {
17 | return this.addRemainingPropertiesToEntry(entry);
18 | });
19 | },
20 |
21 | addRemainingPropertiesToEntry(entry) {
22 |
23 | this.isPartnerChanged = {
24 | props: null,
25 | state: null
26 | };
27 | let isChanged = {
28 | props: false,
29 | state: false
30 | };
31 |
32 | const remainingPropertiesForEntry = this.properties;
33 | const newMethods = entry.get('methods').map((method, methodName) => {
34 | // TODO refactor repeated code
35 | const remainingPropertiesForMethod = remainingPropertiesForEntry[methodName];
36 |
37 | const propValues = method.get('props');
38 | remainingPropertiesForMethod.props.values = propValues;
39 | if (remainingPropertiesForMethod.props.hasOwnProperty('arePartnersDifferent')) {
40 | remainingPropertiesForMethod.props.arePartnersDifferent = this.arePartnerPropsOrStatesDifferent(propValues, 'props');
41 | }
42 | if (remainingPropertiesForMethod.props.hasOwnProperty('isChanged')) {
43 | const isPropsChanged = this.isPropsOrStateChanged(propValues, methodName, 'props', entry);
44 | remainingPropertiesForMethod.props.isChanged = isPropsChanged;
45 | if (isPropsChanged && !isChanged.props) {
46 | isChanged.props = true;
47 | }
48 | }
49 |
50 | const stateValues = method.get('state');
51 | remainingPropertiesForMethod.state.values = stateValues;
52 | if (remainingPropertiesForMethod.state.hasOwnProperty('arePartnersDifferent')) {
53 | remainingPropertiesForMethod.state.arePartnersDifferent = this.arePartnerPropsOrStatesDifferent(stateValues, 'state');
54 | }
55 | if (remainingPropertiesForMethod.state.hasOwnProperty('isChanged')) {
56 | const isStateChanged = this.isPropsOrStateChanged(stateValues, methodName, 'state', entry);
57 | remainingPropertiesForMethod.state.isChanged = isStateChanged;
58 | if (isStateChanged && !isChanged.state) {
59 | isChanged.state = true;
60 | }
61 | }
62 |
63 | if (methodName === 'componentDidMount' || methodName == 'componentDidUpdate') {
64 | remainingPropertiesForMethod.setState.values = method.get('setState');
65 | remainingPropertiesForMethod.setState.isChanged = method.get('isSetStateCalled');
66 | }
67 |
68 | return method.merge(Immutable.fromJS(remainingPropertiesForMethod));
69 |
70 | });
71 |
72 | const newEntry = entry.set('methods', newMethods).set('isChanged', Immutable.fromJS(isChanged));
73 | return newEntry;
74 |
75 | },
76 |
77 | arePartnerPropsOrStatesDifferent(immutableItems, type) {
78 | if (immutableItems.size < 2) {
79 | return false;
80 | }
81 | // Immutability doesn't help with comparison here because the two source objects
82 | // were different; convert to JS for faster comparison
83 | const items = immutableItems.toJS();
84 | if (this.isPartnerChanged[type] === null) {
85 | this.isPartnerChanged[type] = !deepEqual(items[0], items[1], {strict: true});
86 | }
87 | return this.isPartnerChanged[type];
88 | },
89 |
90 | isPropsOrStateChanged(values, methodName, type, entry) {
91 | const method = entry.getIn(['methods', methodName]);
92 | const lifecycleLocation = method.get('lifecycleLocation');
93 | const isCalled = method.get('called');
94 | if (!isCalled) {
95 | return false;
96 | }
97 | if (lifecycleLocation === 'mounting' && !values.first()) {
98 | return false;
99 | }
100 | if (methodName === 'constructorMethod' || methodName === 'componentWillMount') {
101 | return true;
102 | }
103 | let precedingValue;
104 | if (methodName === 'render') {
105 | if (lifecycleLocation === 'mounting') {
106 | precedingValue = entry.getIn(['methods', 'componentWillMount', type, '0']);
107 | } else {
108 | return false;
109 | }
110 | } else if (methodName === 'componentWillReceiveProps' && type === 'props'
111 | || methodName === 'shouldComponentUpdate' && type === 'state'
112 | ) {
113 | precedingValue = values.first();
114 | } else {
115 | return false;
116 | }
117 | // Only the second of two parallel values will be marked changed;
118 | // for example, nextProps
119 | const value1ToCompare = values.last() ? values.last().toJS() : '';
120 | const value2ToCompare = precedingValue ? precedingValue.toJS() : '';
121 | return !deepEqual(value1ToCompare, value2ToCompare, {strict: true})
122 | },
123 |
124 | get properties() {
125 | return {
126 | constructorMethod: {
127 | args: ['props'],
128 | props: {
129 | names: ['props'],
130 | values: [],
131 | isChanged: false
132 | },
133 | state: {
134 | names: [],
135 | values: []
136 | },
137 | terminal: false
138 | },
139 | componentWillMount: {
140 | args: [],
141 | props: {
142 | names: ['this.props'],
143 | values: []
144 | },
145 | state: {
146 | names: ['this.state'],
147 | values: [],
148 | isChanged: false
149 | },
150 | terminal: false
151 | },
152 | render: {
153 | args: [],
154 | props: {
155 | names: ['this.props'],
156 | values: []
157 | },
158 | state: {
159 | names: ['this.state'],
160 | values: [],
161 | isChanged: false
162 | },
163 | terminal: false
164 | },
165 | componentDidMount: {
166 | args: [],
167 | props: {
168 | names: ['this.props'],
169 | values: []
170 | },
171 | state: {
172 | names: ['this.state'],
173 | values: []
174 | },
175 | setState: {
176 | names: ['setState value'],
177 | values: [],
178 | isChanged: false
179 | },
180 | terminal: true,
181 | description: this.setStateWarningMessage
182 | },
183 | componentWillReceiveProps: {
184 | args: ['nextProps'],
185 | props: {
186 | names: ['this.props', 'nextProps'],
187 | values: [],
188 | arePartnersDifferent: false,
189 | isChanged: false
190 | },
191 | state: {
192 | names: ['this.state'],
193 | values: []
194 | },
195 | terminal: false
196 | },
197 | shouldComponentUpdate: {
198 | args: ['nextProps', 'nextState'],
199 | props: {
200 | names: ['this.props', 'nextProps'],
201 | values: [],
202 | arePartnersDifferent: false
203 | },
204 | state: {
205 | names: ['this.state', 'nextState'],
206 | values: [],
207 | isChanged: false,
208 | arePartnersDifferent: false
209 | },
210 | terminal: false
211 | },
212 | componentWillUpdate: {
213 | args: ['nextProps', 'nextState'],
214 | props: {
215 | names: ['this.props', 'nextProps'],
216 | values: [],
217 | arePartnersDifferent: false
218 | },
219 | state: {
220 | names: ['this.state', 'nextState'],
221 | values: [],
222 | arePartnersDifferent: false
223 | },
224 | terminal: false
225 | },
226 | componentDidUpdate: {
227 | args: ['prevProps', 'prevState'],
228 | props: {
229 | names: ['prevProps', 'this.props'],
230 | values: [],
231 | arePartnersDifferent: false
232 | },
233 | state: {
234 | names: ['prevState', 'this.state'],
235 | values: [],
236 | arePartnersDifferent: false
237 | },
238 | setState: {
239 | names: ['setState value'],
240 | values: [],
241 | isChanged: false
242 | },
243 | terminal: true,
244 | description: this.setStateWarningMessage
245 | },
246 | componentWillUnmount: {
247 | args: [],
248 | props: {
249 | names: ['this.props'],
250 | values: []
251 | },
252 | state: {
253 | names: ['this.state'],
254 | values: []
255 | },
256 | terminal: true
257 | }
258 | };
259 | },
260 |
261 | getDisplayNames(methodName, type) {
262 | return this.properties[methodName][type];
263 | },
264 |
265 | get methodNames() {
266 | return Object.keys(this.properties);
267 | }
268 |
269 | };
270 |
271 | export default lifecycleConfig;
--------------------------------------------------------------------------------
/src/store/reducers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import root from '../root.js';
4 | import Immutable from 'immutable';
5 |
6 | const entries = (state, action) => {
7 |
8 | switch (action.type) {
9 | case 'ADD':
10 | const {key, id, name, displayName} = action;
11 | const entry = Immutable.Map({
12 | id,
13 | displayName,
14 | name,
15 | key,
16 | renderCount: 0,
17 | unnecessaryUpdatesPrevented: 0,
18 | isMounted: false,
19 | isChanged: {
20 | props: false,
21 | state: false
22 | },
23 | lifecycleLocation: '',
24 | methods: null
25 | });
26 | const newEntry = entry.set('methods', initMethods());
27 | return state.setIn(['entries', id], newEntry);
28 | case 'UPDATE_METHOD':
29 | return state.updateIn(['entries', action.entryId, 'methods', action.methodName], (method) => {
30 | return method.merge(action.value);
31 | });
32 | case 'UPDATE_VALUE':
33 | return state.setIn(['entries', ...action.keyPath], action.value);
34 | case 'INCREMENT_VALUE':
35 | return state.updateIn(['entries', ...action.keyPath], (oldValue) => {
36 | return oldValue + 1;
37 | });
38 | case 'UPDATE_METHODS':
39 | // TODO Convert this to Immutable data
40 | const newState = state.updateIn(['entries', action.key, 'methods'], (methods) => {
41 | return methods.mergeDeep(Immutable.fromJS(action.value));
42 | });
43 | return newState;
44 | default:
45 | return state;
46 | }
47 |
48 | };
49 |
50 | export default entries;
51 |
52 | const initMethods = () => {
53 | const logMap = Immutable.Map({
54 | constructorMethod: null,
55 | componentWillMount: null,
56 | componentDidMount: null,
57 | componentWillReceiveProps: null,
58 | shouldComponentUpdate: null,
59 | componentWillUpdate: null,
60 | render: null,
61 | componentDidUpdate: null,
62 | componentWillUnmount: null
63 | });
64 | const newLogMap = logMap.mapEntries((item) => {
65 | const name = item[0];
66 | return [name, Immutable.Map({
67 | name,
68 | isMethodOverridden: false,
69 | called: false,
70 | count: 0,
71 | isInfiniteLoop: false,
72 | isSetStateCalled: false,
73 | props: Immutable.List(),
74 | state: Immutable.List(),
75 | setState: Immutable.List()
76 | })];
77 | });
78 | return newLogMap;
79 | };
--------------------------------------------------------------------------------
/src/styles/styles.js:
--------------------------------------------------------------------------------
1 | import color from 'color';
2 |
3 | const hoverColor = color('lightblue').darken(0.1).hexString();
4 | const activeColor = color('lightblue').darken(0.2).hexString();
5 |
6 | const styles = {
7 | base: {
8 | fontFamily: 'Arial, Helvetica, sans-serif',
9 | fontSize: '12px'
10 | },
11 | box: {
12 | padding: '10px',
13 | border: '1px solid lightgray',
14 | boxShadow: '4px 4px 8px rgba(0, 0, 0, .5)'
15 | },
16 | input: {
17 | width: '200px',
18 | margin: '5px 5px 0 0'
19 | },
20 | button: {
21 | height: '20px',
22 | width: '85px',
23 | padding: '0',
24 | backgroundColor: 'lightblue',
25 | border: '1px solid lightblue',
26 | borderRadius: 'none',
27 | boxShadow: '2px 2px 5px rgba(0, 0, 0, .5)',
28 | outline: 'none',
29 | cursor: 'pointer',
30 | ':hover': {
31 | backgroundColor: hoverColor,
32 | border: '1px solid ' + hoverColor
33 | },
34 | ':active': {
35 | backgroundColor: activeColor,
36 | border: '1px solid ' + activeColor
37 | }
38 | },
39 | description: {
40 | marginTop: '5px',
41 | color: 'red',
42 | fontWeight: 'normal'
43 | },
44 | props: {
45 | color: 'blue'
46 | },
47 | state: {
48 | color: '#4caf50'
49 | }
50 | };
51 |
52 | export default styles;
--------------------------------------------------------------------------------
/src/vendor/bootstrapTableStyles.js:
--------------------------------------------------------------------------------
1 | const bootstrapTableStyles = {
2 | ".react-bs-table-container .react-bs-table-search-form": {"marginBottom": 0},
3 | ".react-bs-table table": {"marginBottom": 0, "tableLayout": "fixed"},
4 | ".react-bs-table table td,.react-bs-table table th": {
5 | "overflow": "hidden",
6 | "whiteSpace": "nowrap",
7 | "textOverflow": "ellipsis"
8 | },
9 | ".react-bs-table": {"border": "1px solid #ddd", "borderRadius": 5, "margin": "5px 10px"},
10 | ".react-bs-table-pagination": {"margin": 10},
11 | ".react-bs-table-tool-bar": {"margin": "10px 10px 0"},
12 | ".react-bs-container-header": {"overflow": "hidden", "width": "100%"},
13 | ".react-bs-container-body": {"overflow": "auto", "width": "100%"},
14 | ".react-bs-table .table-bordered": {"border": 0},
15 | ".react-bs-table .table-bordered>thead>tr>td,.react-bs-table .table-bordered>thead>tr>th": {"borderBottomWidth": 2},
16 | ".react-bs-table .table-bordered>tfoot>tr>td,.react-bs-table .table-bordered>tfoot>tr>th": {
17 | "borderTopWidth": 2,
18 | "borderBottomWidth": 0
19 | },
20 | ".react-bs-table .table-bordered>tbody>tr>td:first-child,.react-bs-table .table-bordered>tbody>tr>th:first-child,.react-bs-table .table-bordered>tfoot>tr>td:first-child,.react-bs-table .table-bordered>tfoot>tr>th:first-child,.react-bs-table .table-bordered>thead>tr>td:first-child,.react-bs-table .table-bordered>thead>tr>th:first-child": {"borderLeftWidth": 0},
21 | ".react-bs-table .table-bordered>tbody>tr>td:last-child,.react-bs-table .table-bordered>tbody>tr>th:last-child,.react-bs-table .table-bordered>tfoot>tr>td:last-child,.react-bs-table .table-bordered>tfoot>tr>th:last-child,.react-bs-table .table-bordered>thead>tr>td:last-child,.react-bs-table .table-bordered>thead>tr>th:last-child": {"borderRightWidth": 0},
22 | ".react-bs-table .table-bordered>thead>tr:first-child>td,.react-bs-table .table-bordered>thead>tr:first-child>th": {"borderTopWidth": 0},
23 | ".react-bs-table .table-bordered>tfoot>tr:last-child>td,.react-bs-table .table-bordered>tfoot>tr:last-child>th": {"borderBottomWidth": 0},
24 | ".react-bs-table .react-bs-container-header>table>thead>tr>th": {"verticalAlign": "middle"},
25 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .filter": {"fontWeight": 400},
26 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .filter::-webkit-input-placeholder,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-input::-webkit-input-placeholder,.react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter option[value=''],.react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter.placeholder-selected": {
27 | "color": "#d3d3d3",
28 | "fontStyle": "italic"
29 | },
30 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter.placeholder-selected option:not([value=''])": {
31 | "color": "initial",
32 | "fontStyle": "initial"
33 | },
34 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter": {"display": "flex"},
35 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter-input,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-input": {
36 | "marginLeft": 5,
37 | "float": "left",
38 | "width": "calc(100% - 67px - 5px)"
39 | },
40 | ".react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter-comparator,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-comparator": {
41 | "width": 67,
42 | "float": "left"
43 | },
44 | ".react-bs-table .react-bs-container-header .sort-column": {"cursor": "pointer"},
45 | ".react-bs-container .textarea-save-btn": {"position": "absolute", "zIndex": 100, "right": 0, "top": -21},
46 | ".react-bs-table-no-data": {"textAlign": "center"},
47 | ".animated": {"animationFillMode": "both"},
48 | ".animated.bounceIn,.animated.bounceOut": {"animationDuration": ".75s"},
49 | ".animated.shake": {"animationDuration": ".3s"},
50 | ".shake": {"animationName": "shake"},
51 | ".bounceIn": {"animationName": "bounceIn"},
52 | ".bounceOut": {"animationName": "bounceOut"},
53 | ".toast-title": {"fontWeight": 700},
54 | ".toast-message": {"msWordWrap": "break-word", "wordWrap": "break-word"},
55 | ".toast-message a,.toast-message label": {"color": "#fff"},
56 | ".toast-message a:hover": {"color": "#ccc", "textDecoration": "none"},
57 | ".toast-close-button": {
58 | "position": "relative",
59 | "right": "-.3em",
60 | "top": "-.3em",
61 | "float": "right",
62 | "fontSize": 20,
63 | "fontWeight": 700,
64 | "color": "#fff",
65 | "WebkitTextShadow": "0 1px 0 #fff",
66 | "textShadow": "0 1px 0 #fff",
67 | "opacity": 0.8,
68 | "msFilter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)",
69 | "filter": "alpha(opacity=80)"
70 | },
71 | ".toast-top-center,.toast-top-full-width": {"top": 0, "right": 0, "width": "100%"},
72 | ".toast-close-button:focus,.toast-close-button:hover": {
73 | "color": "#000",
74 | "textDecoration": "none",
75 | "cursor": "pointer",
76 | "opacity": 0.4,
77 | "msFilter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)",
78 | "filter": "alpha(opacity=40)"
79 | },
80 | "button.toast-close-button": {
81 | "padding": 0,
82 | "cursor": "pointer",
83 | "background": "0 0",
84 | "border": 0,
85 | "WebkitAppearance": "none"
86 | },
87 | ".toast-bottom-center": {"bottom": 0, "right": 0, "width": "100%"},
88 | ".toast-bottom-full-width": {"bottom": 0, "right": 0, "width": "100%"},
89 | ".toast-top-left": {"top": 12, "left": 12},
90 | ".toast-top-right": {"top": 12, "right": 12},
91 | ".toast-bottom-right": {"right": 12, "bottom": 12},
92 | ".toast-bottom-left": {"bottom": 12, "left": 12},
93 | "#toast-container": {"position": "fixed", "zIndex": 999999},
94 | "#toast-container *": {"MozBoxSizing": "border-box", "WebkitBoxSizing": "border-box", "boxSizing": "border-box"},
95 | "#toast-container>div": {
96 | "position": "relative",
97 | "overflow": "hidden",
98 | "margin": "0 0 6px",
99 | "padding": "15px 15px 15px 50px",
100 | "width": 300,
101 | "MozBorderRadius": 3,
102 | "WebkitBorderRadius": 3,
103 | "borderRadius": 3,
104 | "backgroundPosition": "15px center",
105 | "backgroundRepeat": "no-repeat",
106 | "MozBoxShadow": "0 0 12px #999",
107 | "WebkitBoxShadow": "0 0 12px #999",
108 | "boxShadow": "0 0 12px #999",
109 | "color": "#fff",
110 | "opacity": 0.8,
111 | "msFilter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)",
112 | "filter": "alpha(opacity=80)"
113 | },
114 | "#toast-container>:hover": {
115 | "MozBoxShadow": "0 0 12px #000",
116 | "WebkitBoxShadow": "0 0 12px #000",
117 | "boxShadow": "0 0 12px #000",
118 | "opacity": 1,
119 | "msFilter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",
120 | "filter": "alpha(opacity=100)",
121 | "cursor": "pointer"
122 | },
123 | "#toast-container>.toast-info": {"backgroundImage": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)"},
124 | "#toast-container>.toast-error": {"backgroundImage": null},
125 | "#toast-container>.toast-success": {"backgroundImage": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)"},
126 | "#toast-container>.toast-warning": {"backgroundImage": "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)"},
127 | "#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div": {"width": 300, "margin": "auto"},
128 | "#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div": {
129 | "width": "96%",
130 | "margin": "auto"
131 | },
132 | ".toast": {"backgroundColor": "#030303"},
133 | ".toast-success": {"backgroundColor": "#51a351"},
134 | ".toast-error": {"backgroundColor": "#bd362f"},
135 | ".toast-info": {"backgroundColor": "#2f96b4"},
136 | ".toast-warning": {"backgroundColor": "#f89406"},
137 | ".toast-progress": {
138 | "position": "absolute",
139 | "left": 0,
140 | "bottom": 0,
141 | "height": 4,
142 | "backgroundColor": "#000",
143 | "opacity": 0.4,
144 | "msFilter": "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)",
145 | "filter": "alpha(opacity=40)"
146 | },
147 | "mediaQueries": {
148 | "all and (max-width:240px)": {
149 | "#toast-container>div": {
150 | "padding": "8px 8px 8px 50px",
151 | "width": "11em"
152 | }, "#toast-container .toast-close-button": {"right": "-.2em", "top": "-.2em"}
153 | },
154 | "all and (min-width:241px) and (max-width:480px)": {
155 | "#toast-container>div": {
156 | "padding": "8px 8px 8px 50px",
157 | "width": "18em"
158 | }, "#toast-container .toast-close-button": {"right": "-.2em", "top": "-.2em"}
159 | },
160 | "all and (min-width:481px) and (max-width:768px)": {
161 | "#toast-container>div": {
162 | "padding": "15px 15px 15px 50px",
163 | "width": "25em"
164 | }
165 | }
166 | }
167 | };
168 |
169 | export default bootstrapTableStyles;
--------------------------------------------------------------------------------
/src/vendor/react-bootstrap-table-all.min.css:
--------------------------------------------------------------------------------
1 | .react-bs-table-container .react-bs-table-search-form{margin-bottom:0}.react-bs-table table{margin-bottom:0;table-layout:fixed}.react-bs-table table td,.react-bs-table table th{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.react-bs-table{border:1px solid #ddd;border-radius:5px;margin:5px 10px}.react-bs-table-pagination{margin:10px}.react-bs-table-tool-bar{margin:10px 10px 0}.react-bs-container-header{overflow:hidden;width:100%}.react-bs-container-body{overflow:auto;width:100%}.react-bs-table .table-bordered{border:0}.react-bs-table .table-bordered>thead>tr>td,.react-bs-table .table-bordered>thead>tr>th{border-bottom-width:2px}.react-bs-table .table-bordered>tfoot>tr>td,.react-bs-table .table-bordered>tfoot>tr>th{border-top-width:2px;border-bottom-width:0}.react-bs-table .table-bordered>tbody>tr>td:first-child,.react-bs-table .table-bordered>tbody>tr>th:first-child,.react-bs-table .table-bordered>tfoot>tr>td:first-child,.react-bs-table .table-bordered>tfoot>tr>th:first-child,.react-bs-table .table-bordered>thead>tr>td:first-child,.react-bs-table .table-bordered>thead>tr>th:first-child{border-left-width:0}.react-bs-table .table-bordered>tbody>tr>td:last-child,.react-bs-table .table-bordered>tbody>tr>th:last-child,.react-bs-table .table-bordered>tfoot>tr>td:last-child,.react-bs-table .table-bordered>tfoot>tr>th:last-child,.react-bs-table .table-bordered>thead>tr>td:last-child,.react-bs-table .table-bordered>thead>tr>th:last-child{border-right-width:0}.react-bs-table .table-bordered>thead>tr:first-child>td,.react-bs-table .table-bordered>thead>tr:first-child>th{border-top-width:0}.react-bs-table .table-bordered>tfoot>tr:last-child>td,.react-bs-table .table-bordered>tfoot>tr:last-child>th{border-bottom-width:0}.react-bs-table .react-bs-container-header>table>thead>tr>th{vertical-align:middle}.react-bs-table .react-bs-container-header>table>thead>tr>th .filter{font-weight:400}.react-bs-table .react-bs-container-header>table>thead>tr>th .filter::-webkit-input-placeholder,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-input::-webkit-input-placeholder,.react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter option[value=''],.react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter.placeholder-selected{color:#d3d3d3;font-style:italic}.react-bs-table .react-bs-container-header>table>thead>tr>th .select-filter.placeholder-selected option:not([value='']){color:initial;font-style:initial}.react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter{display:flex}.react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter-input,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-input{margin-left:5px;float:left;width:calc(100% - 67px - 5px)}.react-bs-table .react-bs-container-header>table>thead>tr>th .date-filter-comparator,.react-bs-table .react-bs-container-header>table>thead>tr>th .number-filter-comparator{width:67px;float:left}.react-bs-table .react-bs-container-header .sort-column{cursor:pointer}.react-bs-container .textarea-save-btn{position:absolute;z-index:100;right:0;top:-21px}.react-bs-table-no-data{text-align:center}.animated{animation-fill-mode:both}.animated.bounceIn,.animated.bounceOut{animation-duration:.75s}.animated.shake{animation-duration:.3s}@keyframes shake{from,to{transform:translate3d(0,0,0)}10%,50%,90%{transform:translate3d(-10px,0,0)}30%,70%{transform:translate3d(10px,0,0)}}.shake{animation-name:shake}@keyframes bounceIn{20%,40%,60%,80%,from,to{animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:scale3d(.3,.3,.3)}20%{transform:scale3d(1.1,1.1,1.1)}40%{transform:scale3d(.9,.9,.9)}60%{opacity:1;transform:scale3d(1.03,1.03,1.03)}80%{transform:scale3d(.97,.97,.97)}to{opacity:1;transform:scale3d(1,1,1)}}.bounceIn{animation-name:bounceIn}@keyframes bounceOut{20%{transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;transform:scale3d(1.1,1.1,1.1)}to{opacity:0;transform:scale3d(.3,.3,.3)}}.bounceOut{animation-name:bounceOut}.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}.toast-top-center,.toast-top-full-width{top:0;right:0;width:100%}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}}
--------------------------------------------------------------------------------
/test/ConfigureSpec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import expect from 'expect';
4 | import gulp from 'gulp';
5 | import replace from 'gulp-replace';
6 | import gutil from 'gulp-util';
7 | import path from 'path';
8 | import del from 'del';
9 |
10 | const runGulp = (dir, callback) => {
11 | gulp.task('vr-settings', function() {
12 | gulp.src('src/settingsManager.js')
13 | .pipe(replace('process.env.NODE_ENV', JSON.stringify(process.env.NODE_ENV) || null))
14 | .pipe(replace('process.env.VR_DEV_ENABLED', JSON.stringify(process.env.VR_DEV_ENABLED) || null))
15 | .pipe(replace('process.env.VR_DEV_MONITOR', JSON.stringify(process.env.VR_DEV_MONITOR) || null))
16 | .pipe(replace('process.env.VR_DEV_LOGGING', JSON.stringify(process.env.VR_DEV_LOGGING) || null))
17 | .pipe(replace('process.env.VR_DEV_CONTROL', JSON.stringify(process.env.VR_DEV_CONTROL) || null))
18 | .pipe(replace('process.env.VR_DEV_COMPARE', JSON.stringify(process.env.VR_DEV_COMPARE) || null))
19 | .pipe(replace('process.env.VR_PROD_ENABLED', JSON.stringify(process.env.VR_PROD_ENABLED) || null))
20 | .pipe(replace('process.env.VR_PROD_MONITOR', JSON.stringify(process.env.VR_PROD_MONITOR) || null))
21 | .pipe(replace('process.env.VR_PROD_LOGGING', JSON.stringify(process.env.VR_PROD_LOGGING) || null))
22 | .pipe(replace('process.env.VR_PROD_CONTROL', JSON.stringify(process.env.VR_PROD_CONTROL) || null))
23 | .pipe(replace('process.env.VR_PROD_COMPARE', JSON.stringify(process.env.VR_PROD_COMPARE) || null))
24 | .pipe(gulp.dest(path.join('test/temp', dir)))
25 | .on('finish', function() {
26 | callback();
27 | gutil.log('Gulp done');
28 | });
29 | });
30 | gulp.start('vr-settings');
31 | };
32 |
33 | describe('Configuration', function() {
34 | after(function() {
35 | del(['test/temp/**']);
36 | });
37 | describe('Settings Manager', function() {
38 | it('Should use default settings if no variables passed in', function() {
39 | const settingsManager = require('../src/settingsManager');
40 | settingsManager.set();
41 | const settings = settingsManager.get();
42 | expect(settings.enabled).toEqual(true);
43 | expect(settings.monitor).toEqual(true);
44 | expect(settings.logging).toEqual(false);
45 | expect(settings.compare).toEqual('deep');
46 | });
47 | });
48 | describe('Settings Manager Production Settings', function() {
49 | const dir = 'production';
50 | before(function(done) {
51 | process.env.NODE_ENV = 'production';
52 | runGulp(dir, done);
53 | });
54 | it('Should use default production settings if env variable is set to production', function() {
55 | const settingsManager = require('./temp/' + dir + '/settingsManager');
56 | settingsManager.set();
57 | const settings = settingsManager.get();
58 | expect(settings.enabled).toEqual(false);
59 | expect(settings.monitor).toEqual(false);
60 | expect(settings.logging).toEqual(false);
61 | expect(settings.compare).toEqual('shallow');
62 | });
63 | });
64 | describe('Settings Manager Custom Dev', function() {
65 | const dir = 'custom-dev';
66 | before(function(done) {
67 | process.env.NODE_ENV = 'development';
68 | process.env.VR_DEV_MONITOR = 'none';
69 | process.env.VR_DEV_LOGGING = 'all';
70 | process.env.VR_DEV_CONTROL = 'none';
71 | runGulp(dir, done);
72 | });
73 | it('Should enable logging and disable monitoring and comparison', function() {
74 | const settingsManager = require('./temp/' + dir + '/settingsManager');
75 | settingsManager.set();
76 | const settings = settingsManager.get();
77 | expect(settings.enabled).toEqual(true);
78 | expect(settings.monitor).toEqual(false);
79 | expect(settings.logging).toEqual(true);
80 | expect(settings.compare).toEqual('none');
81 | });
82 | });
83 | describe('Settings Manager Custom Prod', function() {
84 | const dir = 'custom-prod';
85 | before(function(done) {
86 | process.env.NODE_ENV = 'production';
87 | process.env.VR_PROD_ENABLED = 'all';
88 | process.env.VR_PROD_MONITOR = 'all';
89 | process.env.VR_PROD_LOGGING = 'all';
90 | process.env.VR_PROD_CONTROL = 'all';
91 | process.env.VR_PROD_COMPARE = 'deep';
92 | process.env.VR_DEV_ENABLED = 'none';
93 | process.env.VR_DEV_MONITOR = 'none';
94 | process.env.VR_DEV_LOGGING = 'none';
95 | process.env.VR_DEV_CONTROL = 'none';
96 | runGulp(dir, done);
97 | });
98 | it('Should enable all features in prod', function() {
99 | const settingsManager = require('./temp/' + dir + '/settingsManager');
100 | settingsManager.set();
101 | const settings = settingsManager.get();
102 | expect(settings.enabled).toEqual(true);
103 | expect(settings.monitor).toEqual(true);
104 | expect(settings.logging).toEqual(true);
105 | expect(settings.compare).toEqual('deep');
106 | });
107 | });
108 | describe('Settings Manager Invalid Settings', function() {
109 | const dir = 'invalid';
110 | before(function(done) {
111 | process.env.NODE_ENV = 'development';
112 | process.env.VR_DEV_FOO = 'all';
113 | process.env.VR_DEV_MONITOR = 'some';
114 | process.env.VR_DEV_LOGGING = true;
115 | process.env.VR_DEV_CONTROL = false;
116 | process.env.VR_DEV_ENABLED = 'all';
117 | process.env.VR_DEV_COMPARE = 'deep';
118 | runGulp(dir, done);
119 | });
120 | it('Should ignore invalid settings', function() {
121 | const settingsManager = require('./temp/' + dir + '/settingsManager');
122 | settingsManager.set();
123 | const settings = settingsManager.get();
124 | console.log(settings);
125 | expect(settings.enabled).toEqual(true);
126 | expect(settings.monitor).toEqual(true);
127 | expect(settings.logging).toEqual(false);
128 | expect(settings.compare).toEqual('deep');
129 | });
130 | });
131 | describe('Settings Manager Local Prod', function() {
132 | const dir = 'local-prod';
133 | before(function(done) {
134 | process.env.NODE_ENV = 'production';
135 | process.env.VR_PROD_ENABLED = 'selected';
136 | process.env.VR_PROD_MONITOR = 'selected';
137 | process.env.VR_PROD_LOGGING = 'none';
138 | process.env.VR_PROD_CONTROL = 'none';
139 | runGulp(dir, done);
140 | });
141 | it('Should use local settings for prod', function() {
142 | const settingsManager = require('./temp/' + dir + '/settingsManager');
143 | const localSettings = {
144 | enabled: true,
145 | monitor: true,
146 | logging: true
147 | };
148 | settingsManager.set(localSettings);
149 | const settings = settingsManager.get();
150 | expect(settings.enabled).toEqual(true);
151 | expect(settings.monitor).toEqual(true);
152 | expect(settings.logging).toEqual(false);
153 | expect(settings.compare).toEqual('none');
154 | });
155 | });
156 | describe('Settings Manager Local Dev', function() {
157 | const dir = 'local-dev';
158 | before(function(done) {
159 | process.env.NODE_ENV = 'development';
160 | process.env.VR_DEV_ENABLED = 'selected';
161 | process.env.VR_DEV_MONITOR = 'none';
162 | process.env.VR_DEV_LOGGING = 'none';
163 | process.env.VR_DEV_CONTROL = 'selected';
164 | process.env.VR_DEV_COMPARE = 'none';
165 | runGulp(dir, done);
166 | });
167 | it('Should use local settings for dev', function() {
168 | const settingsManager = require('./temp/' + dir + '/settingsManager');
169 | const localSettings = {
170 | enabled: true,
171 | compare: 'shallow'
172 | };
173 | settingsManager.set(localSettings);
174 | const settings = settingsManager.get();
175 | expect(settings.enabled).toEqual(true);
176 | expect(settings.monitor).toEqual(false);
177 | expect(settings.logging).toEqual(false);
178 | expect(settings.compare).toEqual('shallow');
179 | });
180 | });
181 | describe('Settings Manager Local Invalide', function() {
182 | const dir = 'local-invalid';
183 | before(function(done) {
184 | process.env.NODE_ENV = 'development';
185 | process.env.VR_DEV_ENABLED = 'all';
186 | process.env.VR_DEV_MONITOR = 'none';
187 | process.env.VR_DEV_LOGGING = 'selected';
188 | // 'selected' is invalid here
189 | process.env.VR_DEV_CONTROL = 'selected';
190 | process.env.VR_DEV_COMPARE = 'shallow';
191 | runGulp(dir, done);
192 | });
193 | it('Should use local settings for dev', function() {
194 | const settingsManager = require('./temp/' + dir + '/settingsManager');
195 | const localSettings = {
196 | logging: 'all',
197 | compare: 'foo'
198 | };
199 | settingsManager.set(localSettings);
200 | const settings = settingsManager.get();
201 | expect(settings.enabled).toEqual(true);
202 | expect(settings.monitor).toEqual(false);
203 | expect(settings.logging).toEqual(false);
204 | expect(settings.compare).toEqual('shallow');
205 | });
206 | });
207 | });
208 |
--------------------------------------------------------------------------------
/test/MonitorSpec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import expect from 'expect';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import {shallow, mount} from 'enzyme';
7 | import Immutable from 'immutable';
8 | import jsdom from 'jsdom';
9 | import {StyleRoot} from 'radium';
10 | import getComputedStyle from 'computed-style';
11 |
12 | // Mocks
13 | const doc = jsdom.jsdom(' ');
14 | global.document = doc;
15 | global.window = doc.defaultView;
16 | global.window.document = doc;
17 | global.window.open = function() {
18 | let consoleWindow = jsdom.jsdom('Test ');
19 | consoleWindow.document = {
20 | title: '',
21 | body: {
22 | appendChild: function() {
23 | }
24 | },
25 | createElement: function() {
26 | return {id: ''}
27 | }
28 | };
29 | consoleWindow.close = function() {
30 | };
31 | consoleWindow.focus = function() {
32 | };
33 | return consoleWindow;
34 | };
35 | global.alert = function() {
36 | };
37 | global.getComputedStyle = function() {
38 | return {
39 | width: '',
40 | paddingLeftWidth: '',
41 | paddingRightWidth: '',
42 | borderRightWidth: '',
43 | borderLeftWidth: ''
44 | }
45 | };
46 | ReactDOM.findDOMNode = function() {
47 | return {
48 | offsetParent: {
49 | offsetTop: 100
50 | }
51 | }
52 | };
53 |
54 | // Fixture for initial load of detail of first question
55 | import entries from './fixtures/entries.json';
56 | import Console from '../src/components/Console';
57 |
58 | describe('Console', function() {
59 |
60 | let consoleWrapper;
61 |
62 | beforeEach(function() {
63 | const props = {
64 | entries: Immutable.fromJS(entries),
65 | windowWidth: 1200
66 | };
67 | consoleWrapper = mount( );
68 | });
69 |
70 | describe('ComponentList', function() {
71 | it('Should render correct numbers of rows and cells', function() {
72 | expect(consoleWrapper.find('BootstrapTable').length).toBe(1);
73 | expect(consoleWrapper.find('TableRow').length).toBe(4);
74 | expect(consoleWrapper.find('TableRow').at(0).find('TableColumn').length).toBe(6);
75 | });
76 | it('Should render correct values in row 1', function() {
77 | const row1Columns = consoleWrapper.find('TableRow').at(0).find('TableColumn');
78 | expect(row1Columns.at(0).text()).toBe('Component1');
79 | expect(row1Columns.at(1).text()).toBe('••');
80 | expect(row1Columns.at(2).text()).toBe('Component1');
81 | expect(row1Columns.at(3).text()).toBe('1');
82 | expect(row1Columns.at(4).text()).toBe('');
83 | });
84 | it('Should render correct values in row 2', function() {
85 | const row2Columns = consoleWrapper.find('TableRow').at(1).find('TableColumn');
86 | expect(row2Columns.at(1).text()).toBe('•');
87 | expect(row2Columns.at(1).find('.propsIcon').length).toBe(1);
88 | expect(row2Columns.at(1).find('.stateIcon').length).toBe(0);
89 | expect(row2Columns.at(4).text()).toBe('2');
90 | });
91 | it('Should render correct values in row 3', function() {
92 | const row3Columns = consoleWrapper.find('TableRow').at(2).find('TableColumn');
93 | expect(row3Columns.at(1).text()).toBe('•');
94 | expect(row3Columns.at(1).find('.propsIcon').length).toBe(0);
95 | expect(row3Columns.at(1).find('.stateIcon').length).toBe(1);
96 | expect(row3Columns.at(2).text()).toBe('Component3');
97 | });
98 | });
99 |
100 | describe('LifeCyle Pane', function() {
101 | it('Should render correct number of method cards', function() {
102 | expect(consoleWrapper.find('Method').length).toBe(9);
103 | });
104 |
105 | it('Should render correct content for first method card', function() {
106 |
107 | expect(consoleWrapper.find('#constructor-method-name').text()).toBe('constructor(props) or getInitialState()');
108 | expect(consoleWrapper.find('#constructor-method-times-called').text()).toBe('Times called: 1');
109 |
110 | const propsElement = consoleWrapper.find('#component-will-mount-props-value-0');
111 | const props = JSON.parse(propsElement.text());
112 | expect(props.propValueForComponent1).toBe('Component1 props');
113 | console.log(propsElement.get(0).getAttribute('style'));
114 | expect(propsElement.get(0).getAttribute('style')).toContain('color: gray');
115 |
116 | const stateElement = consoleWrapper.find('#component-will-mount-state-value-0');
117 | const state = JSON.parse(stateElement.text());
118 | expect(state.stateValueForComponent1).toBe('Component1 state');
119 | expect(stateElement.get(0).getAttribute('style')).toContain('color: rgb(76, 175, 80)');
120 |
121 | });
122 |
123 | it('Should correctly indicate existing methods', function() {
124 |
125 | const bgColorIfExists = 'background: rgb(253, 253, 184)';
126 | const title = 'This method exists in the wrapped component';
127 | const shadow = /box\-shadow: \dpx \dpx/;
128 |
129 | let element = consoleWrapper.find('#constructor-method-name > div').get(0);
130 | expect(element.getAttribute('title')).toBe(title);
131 | // Style if method exists in wrapped component
132 | expect(element.getAttribute('style')).toContain(bgColorIfExists);
133 | expect(element.getAttribute('style')).toMatch(shadow);
134 |
135 | element = consoleWrapper.find('#component-will-mount-name > div').get(0);
136 | expect(element.getAttribute('title').length).toBe(0);
137 | // Style if method exists in wrapped component
138 | expect(element.getAttribute('style')).toNotContain(bgColorIfExists);
139 | expect(element.getAttribute('style')).toNotMatch(shadow);
140 |
141 | element = consoleWrapper.find('#component-did-mount-name > div').get(0);
142 | expect(element.getAttribute('title').length).toBe(0);
143 | // Style if method exists in wrapped component
144 | expect(element.getAttribute('style')).toNotContain(bgColorIfExists);
145 | expect(element.getAttribute('style')).toNotMatch(shadow);
146 |
147 | element = consoleWrapper.find('#component-will-update-name > div').get(0);
148 | expect(element.getAttribute('title')).toBe(title);
149 | // Style if method exists in wrapped component
150 | expect(element.getAttribute('style')).toContain(bgColorIfExists);
151 | expect(element.getAttribute('style')).toMatch(shadow);
152 |
153 | element = consoleWrapper.find('#render-name > div').get(0);
154 | expect(element.getAttribute('title')).toBe(title);
155 | // Style if method exists in wrapped component
156 | expect(element.getAttribute('style')).toContain(bgColorIfExists);
157 | expect(element.getAttribute('style')).toMatch(shadow);
158 |
159 | });
160 | });
161 |
162 | });
163 |
164 |
--------------------------------------------------------------------------------
/test/fixtures/entries.json:
--------------------------------------------------------------------------------
1 | {
2 | "Component1": {
3 | "unnecessaryUpdatesPrevented": 0,
4 | "isChanged": {
5 | "props": true,
6 | "state": true
7 | },
8 | "name": "Component1",
9 | "displayName": "Component1",
10 | "isMounted": true,
11 | "methods": {
12 | "componentWillMount": {
13 | "isInfiniteLoop": false,
14 | "name": "componentWillMount",
15 | "count": 1,
16 | "props": {
17 | "names": [
18 | "this.props"
19 | ],
20 | "values": [
21 | {
22 | "propValueForComponent1": "Component1 props"
23 | }
24 | ]
25 | },
26 | "state": {
27 | "names": [
28 | "this.state"
29 | ],
30 | "values": [
31 | {
32 | "stateValueForComponent1": "Component1 state"
33 | }
34 | ],
35 | "isChanged": true
36 | },
37 | "terminal": false,
38 | "args": [],
39 | "called": true,
40 | "lifecycleLocation": "mounting",
41 | "isMethodOverridden": false
42 | },
43 | "componentDidUpdate": {
44 | "isInfiniteLoop": false,
45 | "name": "componentDidUpdate",
46 | "count": 0,
47 | "props": {
48 | "names": [
49 | "prevProps",
50 | "this.props"
51 | ],
52 | "values": [],
53 | "arePartnersDifferent": false
54 | },
55 | "state": {
56 | "names": [
57 | "prevState",
58 | "this.state"
59 | ],
60 | "values": [],
61 | "arePartnersDifferent": false
62 | },
63 | "terminal": true,
64 | "description": "Avoid calling setState here because it will trigger an extra render.",
65 | "args": [
66 | "prevProps",
67 | "prevState"
68 | ],
69 | "called": false,
70 | "isMethodOverridden": false
71 | },
72 | "componentWillUnmount": {
73 | "isInfiniteLoop": false,
74 | "name": "componentWillUnmount",
75 | "count": 0,
76 | "props": {
77 | "names": [
78 | "this.props"
79 | ],
80 | "values": []
81 | },
82 | "state": {
83 | "names": [
84 | "this.state"
85 | ],
86 | "values": []
87 | },
88 | "terminal": true,
89 | "args": [],
90 | "called": false,
91 | "isMethodOverridden": false
92 | },
93 | "shouldComponentUpdate": {
94 | "isInfiniteLoop": false,
95 | "name": "shouldComponentUpdate",
96 | "count": 0,
97 | "props": {
98 | "names": [
99 | "this.props",
100 | "nextProps"
101 | ],
102 | "values": [],
103 | "arePartnersDifferent": false
104 | },
105 | "state": {
106 | "names": [
107 | "this.state",
108 | "nextState"
109 | ],
110 | "values": [],
111 | "isChanged": false,
112 | "arePartnersDifferent": false
113 | },
114 | "terminal": false,
115 | "args": [
116 | "nextProps",
117 | "nextState"
118 | ],
119 | "called": false,
120 | "isMethodOverridden": false
121 | },
122 | "render": {
123 | "isInfiniteLoop": false,
124 | "name": "render",
125 | "count": 1,
126 | "props": {
127 | "names": [
128 | "this.props"
129 | ],
130 | "values": [
131 | {
132 | "propValueForComponent1": "Component1 props"
133 | }
134 | ]
135 | },
136 | "state": {
137 | "names": [
138 | "this.state"
139 | ],
140 | "values": [
141 | {
142 | "stateValueForComponent1": "Component1 state"
143 | }
144 | ],
145 | "isChanged": false
146 | },
147 | "terminal": false,
148 | "args": [],
149 | "called": true,
150 | "lifecycleLocation": "mounting",
151 | "isMethodOverridden": true
152 | },
153 | "componentDidMount": {
154 | "isInfiniteLoop": false,
155 | "name": "componentDidMount",
156 | "count": 1,
157 | "props": {
158 | "names": [
159 | "this.props"
160 | ],
161 | "values": [
162 | {
163 | "stateValueForComponent1": "Component1 state"
164 | }
165 | ]
166 | },
167 | "state": {
168 | "names": [
169 | "this.state"
170 | ],
171 | "values": [
172 | {
173 | "stateValueForComponent1": "Component1 state"
174 | }
175 | ]
176 | },
177 | "terminal": true,
178 | "description": "Avoid calling setState here because it will trigger an extra render.",
179 | "args": [],
180 | "called": true,
181 | "lifecycleLocation": "mounting",
182 | "isMethodOverridden": false
183 | },
184 | "componentWillUpdate": {
185 | "isInfiniteLoop": false,
186 | "name": "componentWillUpdate",
187 | "count": 0,
188 | "props": {
189 | "names": [
190 | "this.props",
191 | "nextProps"
192 | ],
193 | "values": [],
194 | "arePartnersDifferent": false
195 | },
196 | "state": {
197 | "names": [
198 | "this.state",
199 | "nextState"
200 | ],
201 | "values": [],
202 | "arePartnersDifferent": false
203 | },
204 | "terminal": false,
205 | "args": [
206 | "nextProps",
207 | "nextState"
208 | ],
209 | "called": false,
210 | "isMethodOverridden": true
211 | },
212 | "constructorMethod": {
213 | "isInfiniteLoop": false,
214 | "name": "constructorMethod",
215 | "count": 1,
216 | "props": {
217 | "names": [
218 | "props"
219 | ],
220 | "values": [
221 | {
222 | "propValueForComponent1": "Component1 props"
223 | }
224 | ],
225 | "isChanged": true
226 | },
227 | "state": {
228 | "names": [],
229 | "values": []
230 | },
231 | "terminal": false,
232 | "args": [
233 | "props"
234 | ],
235 | "called": true,
236 | "lifecycleLocation": "mounting",
237 | "isMethodOverridden": true
238 | },
239 | "componentWillReceiveProps": {
240 | "isInfiniteLoop": false,
241 | "name": "componentWillReceiveProps",
242 | "count": 0,
243 | "props": {
244 | "names": [
245 | "this.props",
246 | "nextProps"
247 | ],
248 | "values": [],
249 | "arePartnersDifferent": false,
250 | "isChanged": false
251 | },
252 | "state": {
253 | "names": [
254 | "this.state"
255 | ],
256 | "values": []
257 | },
258 | "terminal": false,
259 | "args": [
260 | "nextProps"
261 | ],
262 | "called": false,
263 | "isMethodOverridden": false
264 | }
265 | },
266 | "renderCount": 1,
267 | "id": "Component1",
268 | "lifecycleLocation": ""
269 | },
270 | "Component3": {
271 | "unnecessaryUpdatesPrevented": 0,
272 | "isChanged": {
273 | "props": false,
274 | "state": true
275 | },
276 | "name": "Component3",
277 | "displayName": "Component3",
278 | "isMounted": true,
279 | "methods": {
280 | "componentWillMount": {
281 | "isInfiniteLoop": false,
282 | "name": "componentWillMount",
283 | "count": 1,
284 | "props": {
285 | "names": [
286 | "this.props"
287 | ],
288 | "values": [
289 | {
290 | "propValueForComponent3": "Component3 props"
291 | }
292 | ]
293 | },
294 | "state": {
295 | "names": [
296 | "this.state"
297 | ],
298 | "values": [
299 | {
300 | "stateValueForComponent3": "Component3 state"
301 | }
302 | ],
303 | "isChanged": true
304 | },
305 | "terminal": false,
306 | "args": [],
307 | "called": true,
308 | "lifecycleLocation": "mounting",
309 | "isMethodOverridden": false
310 | },
311 | "componentDidUpdate": {
312 | "isInfiniteLoop": false,
313 | "name": "componentDidUpdate",
314 | "count": 0,
315 | "props": {
316 | "names": [
317 | "prevProps",
318 | "this.props"
319 | ],
320 | "values": [],
321 | "arePartnersDifferent": false
322 | },
323 | "state": {
324 | "names": [
325 | "prevState",
326 | "this.state"
327 | ],
328 | "values": [],
329 | "arePartnersDifferent": false
330 | },
331 | "terminal": true,
332 | "description": "Avoid calling setState here because it will trigger an extra render.",
333 | "args": [
334 | "prevProps",
335 | "prevState"
336 | ],
337 | "called": false,
338 | "isMethodOverridden": false
339 | },
340 | "componentWillUnmount": {
341 | "isInfiniteLoop": false,
342 | "name": "componentWillUnmount",
343 | "count": 0,
344 | "props": {
345 | "names": [
346 | "this.props"
347 | ],
348 | "values": []
349 | },
350 | "state": {
351 | "names": [
352 | "this.state"
353 | ],
354 | "values": []
355 | },
356 | "terminal": true,
357 | "args": [],
358 | "called": false,
359 | "isMethodOverridden": false
360 | },
361 | "shouldComponentUpdate": {
362 | "isInfiniteLoop": false,
363 | "name": "shouldComponentUpdate",
364 | "count": 0,
365 | "props": {
366 | "names": [
367 | "this.props",
368 | "nextProps"
369 | ],
370 | "values": [],
371 | "arePartnersDifferent": false
372 | },
373 | "state": {
374 | "names": [
375 | "this.state",
376 | "nextState"
377 | ],
378 | "values": [],
379 | "isChanged": false,
380 | "arePartnersDifferent": false
381 | },
382 | "terminal": false,
383 | "args": [
384 | "nextProps",
385 | "nextState"
386 | ],
387 | "called": false,
388 | "isMethodOverridden": false
389 | },
390 | "render": {
391 | "isInfiniteLoop": false,
392 | "name": "render",
393 | "count": 1,
394 | "props": {
395 | "names": [
396 | "this.props"
397 | ],
398 | "values": [
399 | {
400 | "propValueForComponent3": "Component3 props"
401 | }
402 | ]
403 | },
404 | "state": {
405 | "names": [
406 | "this.state"
407 | ],
408 | "values": [
409 | {
410 | "stateValueForComponent3": "Component3 state"
411 | }
412 | ],
413 | "isChanged": false
414 | },
415 | "terminal": false,
416 | "args": [],
417 | "called": true,
418 | "lifecycleLocation": "mounting",
419 | "isMethodOverridden": true
420 | },
421 | "componentDidMount": {
422 | "isInfiniteLoop": false,
423 | "name": "componentDidMount",
424 | "count": 1,
425 | "props": {
426 | "names": [
427 | "this.props"
428 | ],
429 | "values": [
430 | {
431 | "stateValueForComponent3": "Component3 state"
432 | }
433 | ]
434 | },
435 | "state": {
436 | "names": [
437 | "this.state"
438 | ],
439 | "values": [
440 | {
441 | "stateValueForComponent3": "Component3 state"
442 | }
443 | ]
444 | },
445 | "terminal": true,
446 | "description": "Avoid calling setState here because it will trigger an extra render.",
447 | "args": [],
448 | "called": true,
449 | "lifecycleLocation": "mounting",
450 | "isMethodOverridden": false
451 | },
452 | "componentWillUpdate": {
453 | "isInfiniteLoop": false,
454 | "name": "componentWillUpdate",
455 | "count": 0,
456 | "props": {
457 | "names": [
458 | "this.props",
459 | "nextProps"
460 | ],
461 | "values": [],
462 | "arePartnersDifferent": false
463 | },
464 | "state": {
465 | "names": [
466 | "this.state",
467 | "nextState"
468 | ],
469 | "values": [],
470 | "arePartnersDifferent": false
471 | },
472 | "terminal": false,
473 | "args": [
474 | "nextProps",
475 | "nextState"
476 | ],
477 | "called": false,
478 | "isMethodOverridden": false
479 | },
480 | "constructorMethod": {
481 | "isInfiniteLoop": false,
482 | "name": "constructorMethod",
483 | "count": 1,
484 | "props": {
485 | "names": [
486 | "props"
487 | ],
488 | "values": [
489 | {
490 | "propValueForComponent3": "Component3 props"
491 | }
492 | ],
493 | "isChanged": true
494 | },
495 | "state": {
496 | "names": [],
497 | "values": []
498 | },
499 | "terminal": false,
500 | "args": [
501 | "props"
502 | ],
503 | "called": true,
504 | "lifecycleLocation": "mounting",
505 | "isMethodOverridden": true
506 | },
507 | "componentWillReceiveProps": {
508 | "isInfiniteLoop": false,
509 | "name": "componentWillReceiveProps",
510 | "count": 0,
511 | "props": {
512 | "names": [
513 | "this.props",
514 | "nextProps"
515 | ],
516 | "values": [],
517 | "arePartnersDifferent": false,
518 | "isChanged": false
519 | },
520 | "state": {
521 | "names": [
522 | "this.state"
523 | ],
524 | "values": []
525 | },
526 | "terminal": false,
527 | "args": [
528 | "nextProps"
529 | ],
530 | "called": false,
531 | "isMethodOverridden": false
532 | }
533 | },
534 | "renderCount": 1,
535 | "id": "Component3",
536 | "lifecycleLocation": ""
537 | },
538 | "Component2": {
539 | "unnecessaryUpdatesPrevented": 2,
540 | "isChanged": {
541 | "props": true,
542 | "state": false
543 | },
544 | "name": "Component2",
545 | "displayName": "Component2",
546 | "isMounted": true,
547 | "methods": {
548 | "componentWillMount": {
549 | "isInfiniteLoop": false,
550 | "name": "componentWillMount",
551 | "count": 1,
552 | "props": {
553 | "names": [
554 | "this.props"
555 | ],
556 | "values": [
557 | {
558 | "propValueForComponent2": "Component2 props"
559 | }
560 | ]
561 | },
562 | "state": {
563 | "names": [
564 | "this.state"
565 | ],
566 | "values": [
567 | {
568 | "stateValueForComponent2": "Component2 state"
569 | }
570 | ],
571 | "isChanged": false
572 | },
573 | "terminal": false,
574 | "args": [],
575 | "called": true,
576 | "lifecycleLocation": "mounting",
577 | "isMethodOverridden": false
578 | },
579 | "componentDidUpdate": {
580 | "isInfiniteLoop": false,
581 | "name": "componentDidUpdate",
582 | "count": 0,
583 | "props": {
584 | "names": [
585 | "prevProps",
586 | "this.props"
587 | ],
588 | "values": [],
589 | "arePartnersDifferent": false
590 | },
591 | "state": {
592 | "names": [
593 | "prevState",
594 | "this.state"
595 | ],
596 | "values": [],
597 | "arePartnersDifferent": false
598 | },
599 | "terminal": true,
600 | "description": "Avoid calling setState here because it will trigger an extra render.",
601 | "args": [
602 | "prevProps",
603 | "prevState"
604 | ],
605 | "called": false,
606 | "isMethodOverridden": false
607 | },
608 | "componentWillUnmount": {
609 | "isInfiniteLoop": false,
610 | "name": "componentWillUnmount",
611 | "count": 0,
612 | "props": {
613 | "names": [
614 | "this.props"
615 | ],
616 | "values": []
617 | },
618 | "state": {
619 | "names": [
620 | "this.state"
621 | ],
622 | "values": []
623 | },
624 | "terminal": true,
625 | "args": [],
626 | "called": false,
627 | "isMethodOverridden": false
628 | },
629 | "shouldComponentUpdate": {
630 | "isInfiniteLoop": false,
631 | "name": "shouldComponentUpdate",
632 | "count": 0,
633 | "props": {
634 | "names": [
635 | "this.props",
636 | "nextProps"
637 | ],
638 | "values": [],
639 | "arePartnersDifferent": false
640 | },
641 | "state": {
642 | "names": [
643 | "this.state",
644 | "nextState"
645 | ],
646 | "values": [],
647 | "isChanged": false,
648 | "arePartnersDifferent": false
649 | },
650 | "terminal": false,
651 | "args": [
652 | "nextProps",
653 | "nextState"
654 | ],
655 | "called": false,
656 | "isMethodOverridden": false
657 | },
658 | "render": {
659 | "isInfiniteLoop": false,
660 | "name": "render",
661 | "count": 1,
662 | "props": {
663 | "names": [
664 | "this.props"
665 | ],
666 | "values": [
667 | {
668 | "propValueForComponent2": "Component2 props"
669 | }
670 | ]
671 | },
672 | "state": {
673 | "names": [
674 | "this.state"
675 | ],
676 | "values": [
677 | {
678 | "stateValueForComponent2": "Component2 state"
679 | }
680 | ],
681 | "isChanged": false
682 | },
683 | "terminal": false,
684 | "args": [],
685 | "called": true,
686 | "lifecycleLocation": "mounting",
687 | "isMethodOverridden": true
688 | },
689 | "componentDidMount": {
690 | "isInfiniteLoop": false,
691 | "name": "componentDidMount",
692 | "count": 1,
693 | "props": {
694 | "names": [
695 | "this.props"
696 | ],
697 | "values": [
698 | {
699 | "stateValueForComponent2": "Component2 state"
700 | }
701 | ]
702 | },
703 | "state": {
704 | "names": [
705 | "this.state"
706 | ],
707 | "values": [
708 | {
709 | "stateValueForComponent2": "Component2 state"
710 | }
711 | ]
712 | },
713 | "terminal": true,
714 | "description": "Avoid calling setState here because it will trigger an extra render.",
715 | "args": [],
716 | "called": true,
717 | "lifecycleLocation": "mounting",
718 | "isMethodOverridden": false
719 | },
720 | "componentWillUpdate": {
721 | "isInfiniteLoop": false,
722 | "name": "componentWillUpdate",
723 | "count": 0,
724 | "props": {
725 | "names": [
726 | "this.props",
727 | "nextProps"
728 | ],
729 | "values": [],
730 | "arePartnersDifferent": false
731 | },
732 | "state": {
733 | "names": [
734 | "this.state",
735 | "nextState"
736 | ],
737 | "values": [],
738 | "arePartnersDifferent": false
739 | },
740 | "terminal": false,
741 | "args": [
742 | "nextProps",
743 | "nextState"
744 | ],
745 | "called": false,
746 | "isMethodOverridden": false
747 | },
748 | "constructorMethod": {
749 | "isInfiniteLoop": false,
750 | "name": "constructorMethod",
751 | "count": 1,
752 | "props": {
753 | "names": [
754 | "props"
755 | ],
756 | "values": [
757 | {
758 | "propValueForComponent2": "Component2 props"
759 | }
760 | ],
761 | "isChanged": true
762 | },
763 | "state": {
764 | "names": [],
765 | "values": []
766 | },
767 | "terminal": false,
768 | "args": [
769 | "props"
770 | ],
771 | "called": true,
772 | "lifecycleLocation": "mounting",
773 | "isMethodOverridden": true
774 | },
775 | "componentWillReceiveProps": {
776 | "isInfiniteLoop": false,
777 | "name": "componentWillReceiveProps",
778 | "count": 0,
779 | "props": {
780 | "names": [
781 | "this.props",
782 | "nextProps"
783 | ],
784 | "values": [],
785 | "arePartnersDifferent": false,
786 | "isChanged": false
787 | },
788 | "state": {
789 | "names": [
790 | "this.state"
791 | ],
792 | "values": []
793 | },
794 | "terminal": false,
795 | "args": [
796 | "nextProps"
797 | ],
798 | "called": false,
799 | "isMethodOverridden": false
800 | }
801 | },
802 | "renderCount": 1,
803 | "id": "Component2",
804 | "lifecycleLocation": ""
805 | },
806 | "Component4": {
807 | "unnecessaryUpdatesPrevented": 0,
808 | "isChanged": {
809 | "props": true,
810 | "state": true
811 | },
812 | "name": "Component4",
813 | "displayName": "Component4",
814 | "isMounted": true,
815 | "methods": {
816 | "componentWillMount": {
817 | "isInfiniteLoop": false,
818 | "name": "componentWillMount",
819 | "count": 1,
820 | "props": {
821 | "names": [
822 | "this.props"
823 | ],
824 | "values": [
825 | {
826 | "propValueForComponent4": "Component4 props"
827 | }
828 | ]
829 | },
830 | "state": {
831 | "names": [
832 | "this.state"
833 | ],
834 | "values": [
835 | {
836 | "stateValueForComponent4": "Component4 state"
837 | }
838 | ],
839 | "isChanged": true
840 | },
841 | "terminal": false,
842 | "args": [],
843 | "called": true,
844 | "lifecycleLocation": "mounting",
845 | "isMethodOverridden": false
846 | },
847 | "componentDidUpdate": {
848 | "isInfiniteLoop": false,
849 | "name": "componentDidUpdate",
850 | "count": 0,
851 | "props": {
852 | "names": [
853 | "prevProps",
854 | "this.props"
855 | ],
856 | "values": [],
857 | "arePartnersDifferent": false
858 | },
859 | "state": {
860 | "names": [
861 | "prevState",
862 | "this.state"
863 | ],
864 | "values": [],
865 | "arePartnersDifferent": false
866 | },
867 | "terminal": true,
868 | "description": "Avoid calling setState here because it will trigger an extra render.",
869 | "args": [
870 | "prevProps",
871 | "prevState"
872 | ],
873 | "called": false,
874 | "isMethodOverridden": false
875 | },
876 | "componentWillUnmount": {
877 | "isInfiniteLoop": false,
878 | "name": "componentWillUnmount",
879 | "count": 0,
880 | "props": {
881 | "names": [
882 | "this.props"
883 | ],
884 | "values": []
885 | },
886 | "state": {
887 | "names": [
888 | "this.state"
889 | ],
890 | "values": []
891 | },
892 | "terminal": true,
893 | "args": [],
894 | "called": false,
895 | "isMethodOverridden": false
896 | },
897 | "shouldComponentUpdate": {
898 | "isInfiniteLoop": false,
899 | "name": "shouldComponentUpdate",
900 | "count": 0,
901 | "props": {
902 | "names": [
903 | "this.props",
904 | "nextProps"
905 | ],
906 | "values": [],
907 | "arePartnersDifferent": false
908 | },
909 | "state": {
910 | "names": [
911 | "this.state",
912 | "nextState"
913 | ],
914 | "values": [],
915 | "isChanged": false,
916 | "arePartnersDifferent": false
917 | },
918 | "terminal": false,
919 | "args": [
920 | "nextProps",
921 | "nextState"
922 | ],
923 | "called": false,
924 | "isMethodOverridden": false
925 | },
926 | "render": {
927 | "isInfiniteLoop": false,
928 | "name": "render",
929 | "count": 1,
930 | "props": {
931 | "names": [
932 | "this.props"
933 | ],
934 | "values": [
935 | {
936 | "propValueForComponent4": "Component4 props"
937 | }
938 | ]
939 | },
940 | "state": {
941 | "names": [
942 | "this.state"
943 | ],
944 | "values": [
945 | {
946 | "stateValueForComponent4": "Component4 state"
947 | }
948 | ],
949 | "isChanged": false
950 | },
951 | "terminal": false,
952 | "args": [],
953 | "called": true,
954 | "lifecycleLocation": "mounting",
955 | "isMethodOverridden": true
956 | },
957 | "componentDidMount": {
958 | "isInfiniteLoop": false,
959 | "name": "componentDidMount",
960 | "count": 1,
961 | "props": {
962 | "names": [
963 | "this.props"
964 | ],
965 | "values": [
966 | {
967 | "stateValueForComponent4": "Component4 state"
968 | }
969 | ]
970 | },
971 | "state": {
972 | "names": [
973 | "this.state"
974 | ],
975 | "values": [
976 | {
977 | "stateValueForComponent4": "Component4 state"
978 | }
979 | ]
980 | },
981 | "terminal": true,
982 | "description": "Avoid calling setState here because it will trigger an extra render.",
983 | "args": [],
984 | "called": true,
985 | "lifecycleLocation": "mounting",
986 | "isMethodOverridden": false
987 | },
988 | "componentWillUpdate": {
989 | "isInfiniteLoop": false,
990 | "name": "componentWillUpdate",
991 | "count": 0,
992 | "props": {
993 | "names": [
994 | "this.props",
995 | "nextProps"
996 | ],
997 | "values": [],
998 | "arePartnersDifferent": false
999 | },
1000 | "state": {
1001 | "names": [
1002 | "this.state",
1003 | "nextState"
1004 | ],
1005 | "values": [],
1006 | "arePartnersDifferent": false
1007 | },
1008 | "terminal": false,
1009 | "args": [
1010 | "nextProps",
1011 | "nextState"
1012 | ],
1013 | "called": false,
1014 | "isMethodOverridden": false
1015 | },
1016 | "constructorMethod": {
1017 | "isInfiniteLoop": false,
1018 | "name": "constructorMethod",
1019 | "count": 1,
1020 | "props": {
1021 | "names": [
1022 | "props"
1023 | ],
1024 | "values": [
1025 | {
1026 | "propValueForComponent4": "Component4 props"
1027 | }
1028 | ],
1029 | "isChanged": true
1030 | },
1031 | "state": {
1032 | "names": [],
1033 | "values": []
1034 | },
1035 | "terminal": false,
1036 | "args": [
1037 | "props"
1038 | ],
1039 | "called": true,
1040 | "lifecycleLocation": "mounting",
1041 | "isMethodOverridden": true
1042 | },
1043 | "componentWillReceiveProps": {
1044 | "isInfiniteLoop": false,
1045 | "name": "componentWillReceiveProps",
1046 | "count": 0,
1047 | "props": {
1048 | "names": [
1049 | "this.props",
1050 | "nextProps"
1051 | ],
1052 | "values": [],
1053 | "arePartnersDifferent": false,
1054 | "isChanged": false
1055 | },
1056 | "state": {
1057 | "names": [
1058 | "this.state"
1059 | ],
1060 | "values": []
1061 | },
1062 | "terminal": false,
1063 | "args": [
1064 | "nextProps"
1065 | ],
1066 | "called": false,
1067 | "isMethodOverridden": false
1068 | }
1069 | },
1070 | "renderCount": 1,
1071 | "id": "Component4",
1072 | "lifecycleLocation": ""
1073 | }
1074 | }
--------------------------------------------------------------------------------
/webpack.config-test.js:
--------------------------------------------------------------------------------
1 | // Not currently used
2 | // For using with mocha-webpack, which precompiles each test with Webpack
3 | // before it's run; this enables support for radium-loader and css-loader
4 | // but makes the tests run very slowly so not using it anymore
5 |
6 | var nodeExternals = require('webpack-node-externals');
7 |
8 | module.exports = {
9 | target: 'node',
10 | externals: [nodeExternals()],
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.js$/,
15 | loader: "babel-loader"
16 | }
17 | ]
18 | }
19 | };
--------------------------------------------------------------------------------