diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index ffe291b847..ba15449f2f 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -173,11 +173,24 @@ class ObjectField extends Component { } handleAddClick = schema => () => { - const type = schema.additionalProperties.type; + let type = schema.additionalProperties.type; const newFormData = { ...this.props.formData }; + + if (schema.additionalProperties.hasOwnProperty("$ref")) { + const { registry = getDefaultRegistry() } = this.props; + const refSchema = retrieveSchema( + { $ref: schema.additionalProperties["$ref"] }, + registry.definitions, + this.props.formData + ); + + type = refSchema.type; + } + newFormData[ this.getAvailableKey("newKey", newFormData) ] = this.getDefaultValue(type); + this.props.onChange(newFormData); }; diff --git a/src/utils.js b/src/utils.js index 44ef93834b..3b982c93ef 100644 --- a/src/utils.js +++ b/src/utils.js @@ -537,21 +537,32 @@ export function stubExistingAdditionalProperties( ...schema, properties: { ...schema.properties }, }; + Object.keys(formData).forEach(key => { if (schema.properties.hasOwnProperty(key)) { // No need to stub, our schema already has the property return; } - const additionalProperties = schema.additionalProperties.hasOwnProperty( - "type" - ) - ? { ...schema.additionalProperties } - : { type: guessType(formData[key]) }; + + let additionalProperties; + if (schema.additionalProperties.hasOwnProperty("$ref")) { + additionalProperties = retrieveSchema( + { $ref: schema.additionalProperties["$ref"] }, + definitions, + formData + ); + } else if (schema.additionalProperties.hasOwnProperty("type")) { + additionalProperties = { ...schema.additionalProperties }; + } else { + additionalProperties = { type: guessType(formData[key]) }; + } + // The type of our new key should match the additionalProperties value; schema.properties[key] = additionalProperties; // Set our additional property flag so we know it was dynamically added schema.properties[key][ADDITIONAL_PROPERTY_FLAG] = true; }); + return schema; } diff --git a/test/Form_test.js b/test/Form_test.js index 2903aef49a..0984be0052 100644 --- a/test/Form_test.js +++ b/test/Form_test.js @@ -516,6 +516,45 @@ describe("Form", () => { expect(node.querySelector("input[type=text]").value).eql("hello"); }); + it("should propagate referenced definition defaults in objects with additionalProperties", () => { + const schema = { + definitions: { + testdef: { type: "string" }, + }, + type: "object", + additionalProperties: { + $ref: "#/definitions/testdef", + }, + }; + + const { node } = createFormComponent({ schema }); + + Simulate.click(node.querySelector(".btn-add")); + + expect(node.querySelector("input[type=text]").value).eql("newKey"); + }); + + it("should propagate referenced definition defaults in objects with additionalProperties that have a type present", () => { + // Though `additionalProperties` has a `type` present here, it also has a `$ref` so that + // referenced schema should override it. + const schema = { + definitions: { + testdef: { type: "number" }, + }, + type: "object", + additionalProperties: { + type: "string", + $ref: "#/definitions/testdef", + }, + }; + + const { node } = createFormComponent({ schema }); + + Simulate.click(node.querySelector(".btn-add")); + + expect(node.querySelector("input[type=number]").value).eql("0"); + }); + it("should recursively handle referenced definitions", () => { const schema = { $ref: "#/definitions/node", diff --git a/test/utils_test.js b/test/utils_test.js index d9210af1d8..afef8c140e 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -2,6 +2,7 @@ import { expect } from "chai"; import React from "react"; import { + ADDITIONAL_PROPERTY_FLAG, asNumber, orderProperties, dataURItoBlob, @@ -1071,6 +1072,64 @@ describe("utils", () => { expect(retrieveSchema(schema, definitions)).eql(address); }); + it("should 'resolve' and stub out a schema which contains an `additionalProperties` with a definition", () => { + const schema = { + type: "object", + additionalProperties: { + $ref: "#/definitions/components/schemas/address", + }, + }; + + const address = { + type: "object", + properties: { + street_address: { type: "string" }, + city: { type: "string" }, + state: { type: "string" }, + }, + required: ["street_address", "city", "state"], + }; + + const definitions = { components: { schemas: { address } } }; + const formData = { newKey: {} }; + + expect(retrieveSchema(schema, definitions, formData)).eql({ + ...schema, + properties: { + newKey: { + ...address, + [ADDITIONAL_PROPERTY_FLAG]: true, + }, + }, + }); + }); + + it.only("should 'resolve' and stub out a schema which contains an `additionalProperties` with a type and definition", () => { + const schema = { + type: "string", + additionalProperties: { + $ref: "#/definitions/number", + }, + }; + + const number = { + type: "number", + }; + + const definitions = { number }; + const formData = { newKey: {} }; + + expect(retrieveSchema(schema, definitions, formData)).eql({ + ...schema, + properties: { + newKey: { + ...number, + [ADDITIONAL_PROPERTY_FLAG]: true, + }, + }, + }); + }); + it("should priorize local definitions over foreign ones", () => { const schema = { $ref: "#/definitions/address",