From f266bd1d7c0a9fbed613c7faa4091234a5378c88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?=
Date: Thu, 16 Apr 2015 19:57:42 +0300
Subject: [PATCH 1/6] injectState using new ComponentTree API #8
---
src/components/component-playground.jsx | 27 ++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/components/component-playground.jsx b/src/components/component-playground.jsx
index a5d358b..ea1abd7 100644
--- a/src/components/component-playground.jsx
+++ b/src/components/component-playground.jsx
@@ -89,7 +89,7 @@ module.exports = React.createClass({
key: JSON.stringify(this.state.fixtureContents)
};
- return _.merge(props, this.state.fixtureContents);
+ return _.merge(props, _.omit(this.state.fixtureContents, 'state'));
}
},
@@ -255,6 +255,12 @@ module.exports = React.createClass({
;
},
+ componentDidMount: function() {
+ if (this.refs.preview) {
+ this._injectPreviewChildState();
+ }
+ },
+
componentWillReceiveProps: function(nextProps) {
if (nextProps.selectedComponent !== this.props.selectedComponent ||
nextProps.selectedFixture !== this.props.selectedFixture) {
@@ -263,6 +269,17 @@ module.exports = React.createClass({
}
},
+ componentDidUpdate: function(prevProps, prevState) {
+ if (this.refs.preview && (
+ // Avoid deep comparing the fixture contents when component and/or
+ // fixture changed, because it's more expensive
+ this.props.selectedComponent !== prevProps.selectedComponent ||
+ this.props.selectedFixture !== prevProps.selectedFixture ||
+ !_.isEqual(this.state.fixtureContents, prevState.fixtureContents))) {
+ this._injectPreviewChildState();
+ }
+ },
+
onComponentClick: function(componentName, event) {
event.preventDefault();
@@ -324,5 +341,13 @@ module.exports = React.createClass({
fixtureName === this.props.selectedFixture;
return classNames(classes);
+ },
+
+ _injectPreviewChildState: function() {
+ var state = this.state.fixtureContents.state;
+
+ if (!_.isEmpty(state)) {
+ ComponentTree.injectState(this.refs.preview, _.cloneDeep(state));
+ }
}
});
From 3219fd7878f9df5cfc7960950fbd97b6210fde9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?=
Date: Thu, 16 Apr 2015 20:00:54 +0300
Subject: [PATCH 2/6] Test state injection #8
---
.../component-playground/children.js | 12 +++++++++++-
.../components/component-playground/state.js | 19 +++++++++++++++----
2 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/tests/components/component-playground/children.js b/tests/components/component-playground/children.js
index 3cdfca4..3febf06 100644
--- a/tests/components/component-playground/children.js
+++ b/tests/components/component-playground/children.js
@@ -64,7 +64,10 @@ describe('ComponentPlayground component', function() {
fixtureContents: {
component: 'MyComponent',
width: 200,
- height: 100
+ height: 100,
+ state: {
+ paused: true
+ }
}
}
});
@@ -79,6 +82,13 @@ describe('ComponentPlayground component', function() {
expect(childParams.height).to.equal(fixtureContents.height);
});
+ it('should not send state as prop to preview child', function() {
+ render();
+
+ var fixtureContents = component.state.fixtureContents;
+ expect(childParams.state).to.be.undefined;
+ });
+
it('should use fixture contents as key for preview child', function() {
render();
diff --git a/tests/components/component-playground/state.js b/tests/components/component-playground/state.js
index ac2ebb0..9c54643 100644
--- a/tests/components/component-playground/state.js
+++ b/tests/components/component-playground/state.js
@@ -18,14 +18,16 @@ describe('ComponentPlayground component', function() {
};
beforeEach(function() {
- // Don't render any children
- sinon.stub(ComponentTree.loadChild, 'loadChild');
+ sinon.stub(ComponentTree, 'injectState');
props = {
fixtures: {
FirstComponent: {
'blank state': {
- myProp: false
+ myProp: false,
+ state: {
+ somethingHappened: true
+ }
}
},
SecondComponent: {
@@ -41,7 +43,7 @@ describe('ComponentPlayground component', function() {
});
afterEach(function() {
- ComponentTree.loadChild.loadChild.restore();
+ ComponentTree.injectState.restore();
})
describe('state', function() {
@@ -84,6 +86,15 @@ describe('ComponentPlayground component', function() {
.to.equal(JSON.stringify(fixtureContents, null, 2));
});
+ // TODO: Test this on fixture transition as well
+ it('should inject state to preview child', function() {
+ render();
+
+ var args = ComponentTree.injectState.lastCall.args;
+ expect(args[0]).to.equal(component.refs.preview);
+ expect(args[1].somethingHappened).to.equal(true);
+ });
+
describe('on fixture transition', function() {
beforeEach(function() {
render({
From 3b1221c0ee4e9f4540b91a4fd97fea0459ee4a05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?=
Date: Thu, 16 Apr 2015 20:21:06 +0300
Subject: [PATCH 3/6] Test state injection after component transition as well
#8
---
tests/components/component-playground/state.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/tests/components/component-playground/state.js b/tests/components/component-playground/state.js
index 9c54643..b2aeeef 100644
--- a/tests/components/component-playground/state.js
+++ b/tests/components/component-playground/state.js
@@ -32,7 +32,10 @@ describe('ComponentPlayground component', function() {
},
SecondComponent: {
'simple state': {
- myProp: true
+ myProp: true,
+ state: {
+ somethingHappened: false
+ }
}
}
},
@@ -131,6 +134,12 @@ describe('ComponentPlayground component', function() {
it('should reset valid user input flag', function() {
expect(component.state.isFixtureUserInputValid).to.be.true;
});
+
+ it('should inject new state to preview child', function() {
+ var args = ComponentTree.injectState.lastCall.args;
+ expect(args[0]).to.equal(component.refs.preview);
+ expect(args[1].somethingHappened).to.equal(false);
+ });
});
});
});
From 1789d4724846f99b33e1793fa8c012d7e9cfeedb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?=
Date: Fri, 17 Apr 2015 19:37:44 +0300
Subject: [PATCH 4/6] Refactor fixtures format to include component class #8
---
src/components/component-playground.jsx | 85 ++++----
.../component-playground/children.js | 60 +++---
.../components/component-playground/events.js | 72 ++++---
.../components/component-playground/render.js | 203 +++++++++---------
.../components/component-playground/state.js | 38 ++--
5 files changed, 240 insertions(+), 218 deletions(-)
diff --git a/src/components/component-playground.jsx b/src/components/component-playground.jsx
index ea1abd7..87c37eb 100644
--- a/src/components/component-playground.jsx
+++ b/src/components/component-playground.jsx
@@ -17,7 +17,7 @@ module.exports = React.createClass({
mixins: [ComponentTree.Mixin],
propTypes: {
- fixtures: React.PropTypes.object.isRequired,
+ components: React.PropTypes.object.isRequired,
selectedComponent: React.PropTypes.string,
selectedFixture: React.PropTypes.string,
fixtureEditor: React.PropTypes.bool,
@@ -39,35 +39,36 @@ module.exports = React.createClass({
return props.selectedComponent && props.selectedFixture;
},
- getSelectedFixtureContents: function(props) {
- if (!this.isFixtureSelected(props)) {
- return {};
- }
-
- var fixture = props.fixtures[props.selectedComponent]
- [props.selectedFixture];
+ getSelectedComponentClass: function(props) {
+ return props.components[props.selectedComponent].class;
+ },
- return _.merge({
- component: props.selectedComponent
- }, fixture);
+ getSelectedFixtureContents: function(props) {
+ return props.components[props.selectedComponent]
+ .fixtures[props.selectedFixture];
},
getSelectedFixtureUserInput: function(props) {
- if (!this.isFixtureSelected(props)) {
- return '{}';
- }
-
return JSON.stringify(this.getSelectedFixtureContents(props), null, 2);
},
getFixtureState: function(props, expandedComponents) {
- return {
- expandedComponents: this.getExpandedComponents(props,
- expandedComponents),
- fixtureContents: this.getSelectedFixtureContents(props),
- fixtureUserInput: this.getSelectedFixtureUserInput(props),
+ var state = {
+ expandedComponents:
+ this.getExpandedComponents(props, expandedComponents),
+ fixtureContents: {},
+ fixtureUserInput: '{}',
isFixtureUserInputValid: true
};
+
+ if (this.isFixtureSelected(props)) {
+ _.assign(state, {
+ fixtureContents: this.getSelectedFixtureContents(props),
+ fixtureUserInput: this.getSelectedFixtureUserInput(props)
+ });
+ }
+
+ return state;
}
},
@@ -84,44 +85,42 @@ module.exports = React.createClass({
children: {
preview: function() {
- var props = {
+ var params = {
+ component: this.constructor.getSelectedComponentClass(this.props),
// Child should re-render whenever fixture changes
key: JSON.stringify(this.state.fixtureContents)
};
- return _.merge(props, _.omit(this.state.fixtureContents, 'state'));
+ return _.merge(params, _.omit(this.state.fixtureContents, 'state'));
}
},
render: function() {
+ var isFixtureSelected = this.constructor.isFixtureSelected(this.props);
+
var classes = classNames({
'component-playground': true,
'full-screen': this.props.fullScreen
});
- var homeUrlProps = {
- fixtureEditor: this.props.fixtureEditor
- };
-
return (
- {this._renderButtons()}
+ {isFixtureSelected ? this._renderButtons() : null}
React Component Playground
- {_.isEmpty(this.state.fixtureContents) ? this._renderCosmosPlug()
- : null}
+ {!isFixtureSelected ? this._renderCosmosPlug() : null}
{this._renderFixtures()}
- {this._renderContentFrame()}
+ {isFixtureSelected ? this._renderContentFrame() : null}
);
},
@@ -135,7 +134,7 @@ module.exports = React.createClass({
_renderFixtures: function() {
return
- {_.map(this.props.fixtures, function(fixtures, componentName) {
+ {_.map(this.props.components, function(component, componentName) {
var classes = classNames({
'component': true,
@@ -151,7 +150,7 @@ module.exports = React.createClass({
{componentName}
- {this._renderComponentFixtures(componentName, fixtures)}
+ {this._renderComponentFixtures(componentName, component.fixtures)}
;
}.bind(this))}
@@ -186,8 +185,7 @@ module.exports = React.createClass({
_renderContentFrame: function() {
return
- {!_.isEmpty(this.state.fixtureContents) ? this.loadChild('preview')
- : null}
+ {this.loadChild('preview')}
{this.props.fixtureEditor ? this._renderFixtureEditor() : null}
@@ -209,11 +207,9 @@ module.exports = React.createClass({
},
_renderButtons: function() {
- var isFixtureSelected = this.constructor.isFixtureSelected(this.props);
-
return
{this._renderFixtureEditorButton()}
- {isFixtureSelected ? this._renderFullScreenButton() : null}
+ {this._renderFullScreenButton()}
;
},
@@ -224,16 +220,11 @@ module.exports = React.createClass({
});
var fixtureEditorUrlProps = {
- fixtureEditor: !this.props.fixtureEditor
+ fixtureEditor: !this.props.fixtureEditor,
+ selectedComponent: this.props.selectedComponent,
+ selectedFixture: this.props.selectedFixture
};
- if (this.constructor.isFixtureSelected(this.props)) {
- _.extend(fixtureEditorUrlProps, {
- selectedComponent: this.props.selectedComponent,
- selectedFixture: this.props.selectedFixture
- });
- }
-
return
Date: Fri, 17 Apr 2015 19:38:21 +0300
Subject: [PATCH 5/6] Hello, reader!
---
README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
package.json | 2 +-
2 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 86e1152..cd0361a 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,47 @@
-# react-component-playground [](https://travis-ci.org/skidding/react-component-playground) [](https://coveralls.io/r/skidding/react-component-playground?branch=master)
+# React Component Playground [](https://travis-ci.org/skidding/react-component-playground) [](https://coveralls.io/r/skidding/react-component-playground?branch=master)
-Isolated loader for React components.
+ComponentPlayground provides a minimal frame for loading and testing React
+components in isolation.
+
+Working with ComponentPlayground improves the component design because it
+surfaces any implicit dependencies. It also forces us to define sane inputs for
+every component, no matter how small, making them more predictable and easier
+to debug down the road.
+
+Features include:
+
+- Rendering full-screen components or with the navigation pane on the side.
+- Injecting predefined state into components via [ComponentTree](https://github.com/skidding/react-component-tree)
+- Real-time editing of props and state with instant feedback
+
+Before diving deeper, you need to understand what a component _fixture_ looks
+like. It's the same thing as a snapshot in the ComponentTree utility. Read more
+on the project [README](https://github.com/skidding/react-component-tree#componenttreeserialize).
+
+`components` is by far the most important of the ComponentPlayground [props](https://github.com/skidding/react-component-playground/blob/master/src/components/component-playground.jsx#L19-L26).
+This is an example:
+
+```js
+{
+ ComponentOne: {
+ class: require('./components/ComponentOne.jsx'),
+ fixtures: {
+ normal: {
+ fooProp: 'bar'
+ },
+ paused: {
+ fooProp: 'bar',
+ state: {
+ paused: true
+ }
+ }
+ }
+ },
+ ComponentTwo: {
+ class: require('./components/ComponentTwo.jsx'),
+ fixtures: {
+ //...
+ }
+ }
+};
+```
diff --git a/package.json b/package.json
index 33bbaeb..64a80cf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-component-playground",
- "version": "0.1.1",
+ "version": "0.2.0",
"description": "Isolated loader for React components",
"main": "build/bundle.js",
"repository": {
From 62765adf44ca024d9dfe271dba50a91ba4aff214 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ovidiu=20Chereche=C8=99?=
Date: Fri, 17 Apr 2015 19:43:08 +0300
Subject: [PATCH 6/6] Tweak README #8
---
README.md | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index cd0361a..0176ed0 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
-# React Component Playground [](https://travis-ci.org/skidding/react-component-playground) [](https://coveralls.io/r/skidding/react-component-playground?branch=master)
+# React Component Playground
+[](https://travis-ci.org/skidding/react-component-playground) [](https://coveralls.io/r/skidding/react-component-playground?branch=master)
ComponentPlayground provides a minimal frame for loading and testing React
components in isolation.
Working with ComponentPlayground improves the component design because it
-surfaces any implicit dependencies. It also forces us to define sane inputs for
-every component, no matter how small, making them more predictable and easier
-to debug down the road.
+surfaces any implicit dependencies. It also forces you to define sane inputs
+for every component, making them more predictable and easier to debug down
+the road.
Features include: