Skip to content

Commit

Permalink
Add support for unserializable props in fixtures #15
Browse files Browse the repository at this point in the history
  • Loading branch information
ovidiuch committed Jun 7, 2015
1 parent 97e0401 commit d3ba172
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 21 deletions.
24 changes: 22 additions & 2 deletions fixtures/component-playground/default.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
var React = require('react');

class FirstComponent extends React.Component {
render() {
return React.DOM.div();
}
}

class SecondComponent extends React.Component {
render() {
return React.DOM.div();
}
}

module.exports = {
components: {
FirstComponent: {
class: 'FirstComponent',
class: FirstComponent,
fixtures: {
'default': {
myProp: false,
nested: {
foo: 'bar',
shouldBeCloned: {}
},
children: [
React.createElement('span', {
key: '1',
children: 'test child'
})
],
state: {
somethingHappened: false
}
Expand All @@ -17,7 +37,7 @@ module.exports = {
}
},
SecondComponent: {
class: 'SecondComponent',
class: SecondComponent,
fixtures: {
'index': {
myProp: true,
Expand Down
11 changes: 10 additions & 1 deletion fixtures/component-playground/selected-fixture.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var _ = require('lodash'),
var React = require('react'),
_ = require('lodash'),
defaultFixture = require('./default.js');

module.exports = _.merge({}, defaultFixture, {
Expand All @@ -17,6 +18,14 @@ module.exports = _.merge({}, defaultFixture, {
somethingHappened: false
}
},
fixtureUnserializableProps: {
children: [
React.createElement('span', {
key: '1',
children: 'test child'
})
]
},
fixtureChange: 10
}
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dependencies": {
"classnames": "^1.2.0",
"lodash": "^3.6.0",
"react-component-tree": "^0.2.2",
"react-component-tree": "^0.2.3",
"react-querystring-router": "^0.2.0"
},
"devDependencies": {
Expand Down
42 changes: 29 additions & 13 deletions src/components/component-playground.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ var _ = require('lodash'),
classNames = require('classnames'),
ComponentTree = require('react-component-tree'),
stringifyParams = require('react-querystring-router').uri.stringifyParams,
parseLocation = require('react-querystring-router').uri.parseLocation;
parseLocation = require('react-querystring-router').uri.parseLocation,
isSerializable = require('../lib/is-serializable.js').isSerializable;

module.exports = React.createClass({
/**
Expand Down Expand Up @@ -62,15 +63,31 @@ module.exports = React.createClass({
expandedComponents:
this.getExpandedComponents(props, expandedComponents),
fixtureContents: {},
fixtureUnserializableProps: {},
fixtureUserInput: '{}',
isFixtureUserInputValid: true
};

if (this.isFixtureSelected(props)) {
var fixtureContents = this.getSelectedFixtureContents(props);
var originalFixtureContents = this.getSelectedFixtureContents(props),
fixtureContents = {},
fixtureUnserializableProps = {};

// Unserializable props are stored separately from serializable ones
// because the serializable props can be overriden by the user using
// the editor, while the unserializable props are always attached
// behind the scenes
_.forEach(originalFixtureContents, function(value, key) {
if (isSerializable(value)) {
fixtureContents[key] = value;
} else {
fixtureUnserializableProps[key] = value;
}
});

_.assign(state, {
fixtureContents: fixtureContents,
fixtureUnserializableProps: fixtureUnserializableProps,
fixtureUserInput: this.getStringifiedFixtureContents(fixtureContents)
});
}
Expand Down Expand Up @@ -104,6 +121,9 @@ module.exports = React.createClass({
key: this._getPreviewComponentKey()
};

// Shallow apply unserializable props
_.assign(params, this.state.fixtureUnserializableProps);

return _.merge(params, _.omit(this.state.fixtureContents, 'state'));
}
},
Expand Down Expand Up @@ -357,10 +377,14 @@ module.exports = React.createClass({

var snapshot = ComponentTree.serialize(this.refs.preview);

// Continue to ignore unserializable props
var serializableSnapshot =
_.omit(snapshot, _.keys(this.state.fixtureUnserializableProps));

this.setState({
fixtureContents: snapshot,
fixtureContents: serializableSnapshot,
fixtureUserInput:
this.constructor.getStringifiedFixtureContents(snapshot),
this.constructor.getStringifiedFixtureContents(serializableSnapshot),
isFixtureUserInputValid: true
});
},
Expand All @@ -370,15 +394,7 @@ module.exports = React.createClass({
newState = {fixtureUserInput: userInput};

try {
var originalFixtureContents =
this.constructor.getSelectedFixtureContents(this.props);

// We only want to extend Function props because they can't be serialized
// and they are not part of the editor's contents
var fixtureContents =
_.pick(originalFixtureContents, function(value, key) {
return _.isFunction(value);
});
var fixtureContents = {};

if (userInput) {
_.merge(fixtureContents, JSON.parse(userInput));
Expand Down
25 changes: 25 additions & 0 deletions src/lib/is-serializable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var _ = require('lodash');

exports.isSerializable = function(obj) {
if (_.isUndefined(obj) ||
_.isNull(obj) ||
_.isBoolean(obj) ||
_.isNumber(obj) ||
_.isString(obj)) {
return true;
}

if (!_.isPlainObject(obj) &&
!_.isArray(obj)) {
return false;
}

for (var key in obj) {
if (!exports.isSerializable(obj[key])) {
return false;
}
}

return true;
};

Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ describe(`ComponentPlayground (${FIXTURE}) Events Handlers`, function() {
}
});

it('should ignore unserializable child state', function() {
for (var key in fixture.state.fixtureUnserializableProps) {
expect(stateSet.fixtureContents[key]).to.be.undefined;
}
});

it('should update stringified child snapshot state', function() {
expect(stateSet.fixtureUserInput)
.to.equal(JSON.stringify(fixture.state.fixtureContents, null, 2));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,13 @@ describe(`ComponentPlayground (${FIXTURE}) Events DOM`, function() {
});

it('should reset state', function() {
var fixtureContents =
fixture.components.FirstComponent.fixtures['default'];
var fixtureContents = _.omit(
fixture.components.FirstComponent.fixtures['default'],
_.keys(component.state.fixtureUnserializableProps));

expect(stateSet.expandedComponents.length).to.equal(1);
expect(stateSet.expandedComponents[0]).to.equal('FirstComponent');
expect(stateSet.fixtureContents).to.equal(fixtureContents);
expect(stateSet.fixtureContents).to.deep.equal(fixtureContents);
expect(stateSet.fixtureUserInput).to.equal(
JSON.stringify(fixtureContents, null, 2));
expect(stateSet.isFixtureUserInputValid).to.equal(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ describe(`ComponentPlayground (${FIXTURE}) Render Children`, function() {

it('should send fixture contents to preview child', function() {
var fixtureContents = fixture.state.fixtureContents;

for (var key in fixtureContents) {
if (key !== 'state') {
expect(childParams[key]).to.deep.equal(fixtureContents[key]);
}
}
});

it('should send unserializable props to preview child', function() {
var fixtureUnserializableContents =
fixture.state.fixtureUnserializableContents;

for (var key in fixtureUnserializableContents) {
expect(childParams[key]).to.equal(fixtureUnserializableContents[key]);
}
});

it('should not send state as prop to preview child', function() {
expect(childParams.state).to.be.undefined;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ describe(`ComponentPlayground (${FIXTURE}) Transitions Mount`, function() {
expect(expandedComponents[0]).to.equal('FirstComponent');
});

it('should populate state with fixture contents', function() {
it('should populate state with serializable fixture contents', function() {
expect(component.state.fixtureContents.myProp).to.equal(false);
});

it('should populate state with unserializable fixture props', function() {
expect(component.state.fixtureUnserializableProps.children).to.equal(
fixture.components[fixture.component]
.fixtures[fixture.fixture].children);
});

it('should populate stringified fixture contents as user input', function() {
expect(component.state.fixtureUserInput).to.equal(
JSON.stringify(component.state.fixtureContents, null, 2));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ describe(`ComponentPlayground (${FIXTURE}) Transitions Props`, function() {
expect(stateSet.fixtureContents.myProp).to.equal(true);
});

it('should reset unserializable fixture props', function() {
expect(stateSet.fixtureUnserializableProps).to.deep.equal({});
});

it('should replace fixture user input', function() {
expect(JSON.parse(stateSet.fixtureUserInput).myProp).to.equal(true);
});
Expand Down

0 comments on commit d3ba172

Please sign in to comment.