diff --git a/playground/samples/simple.js b/playground/samples/simple.js index f30e104305..223d93a069 100644 --- a/playground/samples/simple.js +++ b/playground/samples/simple.js @@ -8,6 +8,7 @@ module.exports = { firstName: { type: "string", title: "First name", + default: "Chuck", }, lastName: { type: "string", @@ -60,7 +61,6 @@ module.exports = { }, }, formData: { - firstName: "Chuck", lastName: "Norris", age: 75, bio: "Roundhouse kicking asses since 1940", diff --git a/src/components/Form.js b/src/components/Form.js index 88d92f096f..b233621b1c 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -9,6 +9,7 @@ import { toIdSchema, setState, getDefaultRegistry, + deepEquals, } from "../utils"; import validateFormData, { toErrorList } from "../validate"; @@ -25,10 +26,23 @@ export default class Form extends Component { constructor(props) { super(props); this.state = this.getStateFromProps(props); + if ( + this.props.onChange && + !deepEquals(this.state.formData, this.props.formData) + ) { + this.props.onChange(this.state); + } } componentWillReceiveProps(nextProps) { - this.setState(this.getStateFromProps(nextProps)); + const nextState = this.getStateFromProps(nextProps); + this.setState(nextState); + if ( + !deepEquals(nextState.formData, nextProps.formData) && + this.props.onChange + ) { + this.props.onChange(nextState); + } } getStateFromProps(props) { diff --git a/test/Form_test.js b/test/Form_test.js index 08a24e67c2..d90de7aedd 100644 --- a/test/Form_test.js +++ b/test/Form_test.js @@ -44,6 +44,64 @@ describe("Form", () => { }); }); + describe("on component creation", () => { + let comp; + let onChangeProp; + let formData; + let schema; + + function createComponent() { + comp = renderIntoDocument( +
+ + +
+ ); + } + + beforeEach(() => { + onChangeProp = sinon.spy(); + schema = { + type: "object", + title: "root object", + required: ["count"], + properties: { + count: { + type: "number", + default: 789, + }, + }, + }; + }); + + describe("when props.formData does not equal the default values", () => { + beforeEach(() => { + formData = { + foo: 123, + }; + createComponent(); + }); + + it("should call props.onChange with current state", () => { + sinon.assert.calledOnce(onChangeProp); + sinon.assert.calledWith(onChangeProp, comp.state); + }); + }); + + describe("when props.formData equals the default values", () => { + beforeEach(() => { + formData = { + count: 789, + }; + createComponent(); + }); + + it("should not call props.onChange", () => { + sinon.assert.notCalled(onChangeProp); + }); + }); + }); + describe("Option idPrefix", function() { it("should change the rendered ids", function() { const schema = { @@ -767,6 +825,88 @@ describe("Form", () => { }); }); + describe("Schema and external formData updates", () => { + let comp; + let onChangeProp; + + beforeEach(() => { + onChangeProp = sinon.spy(); + const formProps = { + schema: { + type: "string", + default: "foobar", + }, + formData: "some value", + onChange: onChangeProp, + }; + comp = createFormComponent(formProps).comp; + }); + + describe("when the form data is set to null", () => { + beforeEach(() => comp.componentWillReceiveProps({ formData: null })); + + it("should call onChange", () => { + sinon.assert.calledOnce(onChangeProp); + sinon.assert.calledWith(onChangeProp, comp.state); + expect(comp.state.formData).eql("foobar"); + }); + }); + + describe("when the schema default is changed but formData is not changed", () => { + const newSchema = { + type: "string", + default: "the new default", + }; + + beforeEach(() => + comp.componentWillReceiveProps({ + schema: newSchema, + formData: "some value", + })); + + it("should not call onChange", () => { + sinon.assert.notCalled(onChangeProp); + expect(comp.state.formData).eql("some value"); + expect(comp.state.schema).deep.eql(newSchema); + }); + }); + + describe("when the schema default is changed and formData is changed", () => { + const newSchema = { + type: "string", + default: "the new default", + }; + + beforeEach(() => + comp.componentWillReceiveProps({ + schema: newSchema, + formData: "something else", + })); + + it("should not call onChange", () => { + sinon.assert.notCalled(onChangeProp); + expect(comp.state.formData).eql("something else"); + expect(comp.state.schema).deep.eql(newSchema); + }); + }); + + describe("when the schema default is changed and formData is nulled", () => { + const newSchema = { + type: "string", + default: "the new default", + }; + + beforeEach(() => + comp.componentWillReceiveProps({ schema: newSchema, formData: null })); + + it("should call onChange", () => { + sinon.assert.calledOnce(onChangeProp); + sinon.assert.calledWith(onChangeProp, comp.state); + expect(comp.state.formData).eql("the new default"); + }); + }); + }); + describe("External formData updates", () => { describe("root level", () => { const formProps = {