From 3341c61b2697495e0604ab0db07ef16c15895a36 Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Thu, 30 May 2019 17:33:39 -0700 Subject: [PATCH 01/10] fix: refactor out getMatchingOption --- src/components/fields/MultiSchemaField.js | 62 ++------------------- src/utils.js | 66 ++++++++++++++++++++++- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/src/components/fields/MultiSchemaField.js b/src/components/fields/MultiSchemaField.js index 44401dc06a..1c0276308c 100644 --- a/src/components/fields/MultiSchemaField.js +++ b/src/components/fields/MultiSchemaField.js @@ -6,8 +6,8 @@ import { getWidget, guessType, retrieveSchema, + getMatchingOption, } from "../../utils"; -import { isValid } from "../../validate"; class AnyOfField extends Component { constructor(props) { @@ -35,65 +35,11 @@ class AnyOfField extends Component { getMatchingOption(formData, options) { const { definitions } = this.props.registry; - for (let i = 0; i < options.length; i++) { - // Assign the definitions to the option, otherwise the match can fail if - // the new option uses a $ref - const option = Object.assign( - { - definitions, - }, - options[i] - ); - - // If the schema describes an object then we need to add slightly more - // strict matching to the schema, because unless the schema uses the - // "requires" keyword, an object will match the schema as long as it - // doesn't have matching keys with a conflicting type. To do this we use an - // "anyOf" with an array of requires. This augmentation expresses that the - // schema should match if any of the keys in the schema are present on the - // object and pass validation. - if (option.properties) { - // Create an "anyOf" schema that requires at least one of the keys in the - // "properties" object - const requiresAnyOf = { - anyOf: Object.keys(option.properties).map(key => ({ - required: [key], - })), - }; - - let augmentedSchema; - - // If the "anyOf" keyword already exists, wrap the augmentation in an "allOf" - if (option.anyOf) { - // Create a shallow clone of the option - const { ...shallowClone } = option; - - if (!shallowClone.allOf) { - shallowClone.allOf = []; - } else { - // If "allOf" already exists, shallow clone the array - shallowClone.allOf = shallowClone.allOf.slice(); - } - - shallowClone.allOf.push(requiresAnyOf); - - augmentedSchema = shallowClone; - } else { - augmentedSchema = Object.assign({}, option, requiresAnyOf); - } - // Remove the "required" field as it's likely that not all fields have - // been filled in yet, which will mean that the schema is not valid - delete augmentedSchema.required; - - if (isValid(augmentedSchema, formData)) { - return i; - } - } else if (isValid(options[i], formData)) { - return i; - } + let option = getMatchingOption(formData, options, definitions); + if (option !== 0) { + return option; } - // If the form data matches none of the options, use the currently selected // option, assuming it's available; otherwise use the first option return this && this.state ? this.state.selectedOption : 0; diff --git a/src/utils.js b/src/utils.js index 22f08e1863..6bd7c4fc99 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ import React from "react"; -import validateFormData from "./validate"; +import validateFormData, { isValid } from "./validate"; import fill from "core-js/library/fn/array/fill"; export const ADDITIONAL_PROPERTY_FLAG = "__additional_property"; @@ -139,7 +139,7 @@ export function hasWidget(schema, widget, registeredWidgets = {}) { } } -function computeDefaults(schema, parentDefaults, definitions = {}) { +export function computeDefaults(schema, parentDefaults, definitions = {}) { // Compute the defaults recursively: give highest priority to deepest nodes. let defaults = parentDefaults; if (isObject(defaults) && isObject(schema.default)) { @@ -870,3 +870,65 @@ export function rangeSpec(schema) { } return spec; } + +export function getMatchingOption(formData, options, definitions) { + for (let i = 0; i < options.length; i++) { + // Assign the definitions to the option, otherwise the match can fail if + // the new option uses a $ref + const option = Object.assign( + { + definitions, + }, + options[i] + ); + + // If the schema describes an object then we need to add slightly more + // strict matching to the schema, because unless the schema uses the + // "requires" keyword, an object will match the schema as long as it + // doesn't have matching keys with a conflicting type. To do this we use an + // "anyOf" with an array of requires. This augmentation expresses that the + // schema should match if any of the keys in the schema are present on the + // object and pass validation. + if (option.properties) { + // Create an "anyOf" schema that requires at least one of the keys in the + // "properties" object + const requiresAnyOf = { + anyOf: Object.keys(option.properties).map(key => ({ + required: [key], + })), + }; + + let augmentedSchema; + + // If the "anyOf" keyword already exists, wrap the augmentation in an "allOf" + if (option.anyOf) { + // Create a shallow clone of the option + const { ...shallowClone } = option; + + if (!shallowClone.allOf) { + shallowClone.allOf = []; + } else { + // If "allOf" already exists, shallow clone the array + shallowClone.allOf = shallowClone.allOf.slice(); + } + + shallowClone.allOf.push(requiresAnyOf); + + augmentedSchema = shallowClone; + } else { + augmentedSchema = Object.assign({}, option, requiresAnyOf); + } + + // Remove the "required" field as it's likely that not all fields have + // been filled in yet, which will mean that the schema is not valid + delete augmentedSchema.required; + + if (isValid(augmentedSchema, formData)) { + return i; + } + } else if (isValid(options[i], formData)) { + return i; + } + } + return 0; +} From cbd7efc0b07511ff3705081ea01f221e69127e70 Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Thu, 30 May 2019 18:43:11 -0700 Subject: [PATCH 02/10] fix: populate defaults for oneOf/anyOf --- src/utils.js | 10 ++++++ test/anyOf_test.js | 23 ++++++++++++ test/oneOf_test.js | 23 ++++++++++++ test/utils_test.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) diff --git a/src/utils.js b/src/utils.js index 6bd7c4fc99..03682ec77b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -157,6 +157,16 @@ export function computeDefaults(schema, parentDefaults, definitions = {}) { defaults = schema.items.map(itemSchema => computeDefaults(itemSchema, undefined, definitions) ); + } else if ("oneOf" in schema) { + schema = + schema["oneOf"][ + getMatchingOption(undefined, schema["oneOf"], definitions) + ]; + } else if ("anyOf" in schema) { + schema = + schema["anyOf"][ + getMatchingOption(undefined, schema["anyOf"], definitions) + ]; } // Not defaults defined for this node, fallback to generic typed ones. if (typeof defaults === "undefined") { diff --git a/test/anyOf_test.js b/test/anyOf_test.js index 9f7cbf2b06..4fd3dac78b 100644 --- a/test/anyOf_test.js +++ b/test/anyOf_test.js @@ -54,6 +54,29 @@ describe("anyOf", () => { expect(node.querySelectorAll("select")).to.have.length.of(1); }); + it("should assign a default value", () => { + const { comp } = createFormComponent({ + schema: { + anyOf: [ + { + type: "object", + properties: { + foo: { type: "string", default: "defaultfoo" }, + }, + }, + { + type: "object", + properties: { + bar: { type: "string" }, + }, + }, + ], + }, + }); + + expect(comp.state.formData).eql({ foo: "defaultfoo" }); + }); + it("should render a custom widget", () => { const schema = { type: "object", diff --git a/test/oneOf_test.js b/test/oneOf_test.js index 3fa952faf2..12904c41fa 100644 --- a/test/oneOf_test.js +++ b/test/oneOf_test.js @@ -54,6 +54,29 @@ describe("oneOf", () => { expect(node.querySelectorAll("select")).to.have.length.of(1); }); + it("should assign a default value", () => { + const { comp } = createFormComponent({ + schema: { + oneOf: [ + { + type: "object", + properties: { + foo: { type: "string", default: "defaultfoo" }, + }, + }, + { + type: "object", + properties: { + bar: { type: "string" }, + }, + }, + ], + }, + }); + + expect(comp.state.formData).eql({ foo: "defaultfoo" }); + }); + it("should render a custom widget", () => { const schema = { type: "object", diff --git a/test/utils_test.js b/test/utils_test.js index 5acfcc229d..00a4a36b4f 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -314,6 +314,96 @@ describe("utils", () => { array: ["foo"], }); }); + + describe("getDefaultFormState with oneOf", () => { + it("should populate defaults for oneOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + oneOf: [ + { type: "string", default: "a" }, + { type: "string", default: "b" }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: "a", + }); + }); + + it("should populate nested default values for oneOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "object", + oneOf: [ + { + type: "object", + properties: { + first: { type: "string", default: "First Name" }, + }, + }, + { type: "string", default: "b" }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: { + first: "First Name", + }, + }); + }); + }); + + describe("getDefaultFormState with anyOf", () => { + it("should populate defaults for anyOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + anyOf: [ + { type: "string", default: "a" }, + { type: "string", default: "b" }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: "a", + }); + }); + + it("should populate nested default values for anyOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "object", + anyOf: [ + { + type: "object", + properties: { + first: { type: "string", default: "First Name" }, + }, + }, + { type: "string", default: "b" }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: { + first: "First Name", + }, + }); + }); + }); }); }); From ab042acbb929ebb96b223e94c00ab1656bafcf7c Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Thu, 30 May 2019 19:08:51 -0700 Subject: [PATCH 03/10] fix: oneOf dependencies issue #1293 --- src/utils.js | 11 ++++++++ test/utils_test.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/utils.js b/src/utils.js index 03682ec77b..4e9f9065f1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -549,6 +549,17 @@ export function retrieveSchema(schema, definitions = {}, formData = {}) { function resolveDependencies(schema, definitions, formData) { // Drop the dependencies from the source schema. let { dependencies = {}, ...resolvedSchema } = schema; + if ("oneOf" in resolvedSchema) { + resolvedSchema = + resolvedSchema["oneOf"][ + getMatchingOption(formData, resolvedSchema["oneOf"], definitions) + ]; + } else if ("anyOf" in resolvedSchema) { + resolvedSchema = + resolvedSchema["anyOf"][ + getMatchingOption(formData, resolvedSchema["anyOf"], definitions) + ]; + } // Process dependencies updating the local schema properties as appropriate. for (const dependencyKey in dependencies) { // Skip this dependency if its trigger property is not present. diff --git a/test/utils_test.js b/test/utils_test.js index 00a4a36b4f..79acc3071c 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -358,6 +358,41 @@ describe("utils", () => { }, }); }); + + it("should populate defaults for oneOf + dependencies", () => { + const schema = { + oneOf: [ + { + type: "object", + properties: { + name: { + type: "string", + }, + }, + }, + ], + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + default: "A", + }, + }, + }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, { name: "Name" })).eql({ + name: "Name", + grade: "A", + }); + }); }); describe("getDefaultFormState with anyOf", () => { @@ -403,6 +438,41 @@ describe("utils", () => { }, }); }); + + it("should populate defaults for anyOf + dependencies", () => { + const schema = { + anyOf: [ + { + type: "object", + properties: { + name: { + type: "string", + }, + }, + }, + ], + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + default: "A", + }, + }, + }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, { name: "Name" })).eql({ + name: "Name", + grade: "A", + }); + }); }); }); }); From b12ccf7c9ab86f751008d8978bfb857649ed4200 Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Thu, 30 May 2019 19:28:38 -0700 Subject: [PATCH 04/10] fix: set defaults on oneof/anyof change --- src/components/fields/MultiSchemaField.js | 12 +++++++----- test/anyOf_test.js | 14 +++++++++++--- test/oneOf_test.js | 14 +++++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/components/fields/MultiSchemaField.js b/src/components/fields/MultiSchemaField.js index 1c0276308c..21968ae729 100644 --- a/src/components/fields/MultiSchemaField.js +++ b/src/components/fields/MultiSchemaField.js @@ -6,6 +6,7 @@ import { getWidget, guessType, retrieveSchema, + getDefaultFormState, getMatchingOption, } from "../../utils"; @@ -57,11 +58,12 @@ class AnyOfField extends Component { // If the new option is of type object and the current data is an object, // discard properties added using the old option. + let newFormData = undefined; if ( guessType(formData) === "object" && (newOption.type === "object" || newOption.properties) ) { - const newFormData = Object.assign({}, formData); + newFormData = Object.assign({}, formData); const optionsToDiscard = options.slice(); optionsToDiscard.splice(selectedOption, 1); @@ -76,11 +78,11 @@ class AnyOfField extends Component { } } } - - onChange(newFormData); - } else { - onChange(undefined); } + // Call getDefaultFormState to make sure defaults are populated on change. + onChange( + getDefaultFormState(options[selectedOption], newFormData, definitions) + ); this.setState({ selectedOption: parseInt(option, 10), diff --git a/test/anyOf_test.js b/test/anyOf_test.js index 4fd3dac78b..b5aba8d839 100644 --- a/test/anyOf_test.js +++ b/test/anyOf_test.js @@ -54,8 +54,8 @@ describe("anyOf", () => { expect(node.querySelectorAll("select")).to.have.length.of(1); }); - it("should assign a default value", () => { - const { comp } = createFormComponent({ + it("should assign a default value and set defaults on option change", () => { + const { comp, node } = createFormComponent({ schema: { anyOf: [ { @@ -67,7 +67,7 @@ describe("anyOf", () => { { type: "object", properties: { - bar: { type: "string" }, + foo: { type: "string", default: "defaultbar" }, }, }, ], @@ -75,6 +75,14 @@ describe("anyOf", () => { }); expect(comp.state.formData).eql({ foo: "defaultfoo" }); + + const $select = node.querySelector("select"); + + Simulate.change($select, { + target: { value: $select.options[1].value }, + }); + + expect(comp.state.formData).eql({ foo: "defaultbar" }); }); it("should render a custom widget", () => { diff --git a/test/oneOf_test.js b/test/oneOf_test.js index 12904c41fa..386ae59b10 100644 --- a/test/oneOf_test.js +++ b/test/oneOf_test.js @@ -54,8 +54,8 @@ describe("oneOf", () => { expect(node.querySelectorAll("select")).to.have.length.of(1); }); - it("should assign a default value", () => { - const { comp } = createFormComponent({ + it("should assign a default value and set defaults on option change", () => { + const { comp, node } = createFormComponent({ schema: { oneOf: [ { @@ -67,7 +67,7 @@ describe("oneOf", () => { { type: "object", properties: { - bar: { type: "string" }, + foo: { type: "string", default: "defaultbar" }, }, }, ], @@ -75,6 +75,14 @@ describe("oneOf", () => { }); expect(comp.state.formData).eql({ foo: "defaultfoo" }); + + const $select = node.querySelector("select"); + + Simulate.change($select, { + target: { value: $select.options[1].value }, + }); + + expect(comp.state.formData).eql({ foo: "defaultbar" }); }); it("should render a custom widget", () => { From a59d73a00bc29296932dbfa79d7219a7be8cfaad Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Thu, 30 May 2019 19:31:36 -0700 Subject: [PATCH 05/10] fix: remove unneeded export --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 4e9f9065f1..ff7fc5e078 100644 --- a/src/utils.js +++ b/src/utils.js @@ -139,7 +139,7 @@ export function hasWidget(schema, widget, registeredWidgets = {}) { } } -export function computeDefaults(schema, parentDefaults, definitions = {}) { +function computeDefaults(schema, parentDefaults, definitions = {}) { // Compute the defaults recursively: give highest priority to deepest nodes. let defaults = parentDefaults; if (isObject(defaults) && isObject(schema.default)) { From 79a6b6eccf8b8dc3862e946cb0604359138ba752 Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Sat, 1 Jun 2019 17:32:18 -0700 Subject: [PATCH 06/10] test: reorg tests --- test/utils_test.js | 261 +++++++++++++++++++++++---------------------- 1 file changed, 131 insertions(+), 130 deletions(-) diff --git a/test/utils_test.js b/test/utils_test.js index 79acc3071c..dce83f8d15 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -314,164 +314,165 @@ describe("utils", () => { array: ["foo"], }); }); + }); - describe("getDefaultFormState with oneOf", () => { - it("should populate defaults for oneOf", () => { - const schema = { - type: "object", - properties: { - name: { - type: "string", - oneOf: [ - { type: "string", default: "a" }, - { type: "string", default: "b" }, - ], - }, + describe("defaults with oneOf", () => { + it("should populate defaults for oneOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + oneOf: [ + { type: "string", default: "a" }, + { type: "string", default: "b" }, + ], }, - }; - expect(getDefaultFormState(schema, {})).eql({ - name: "a", - }); + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: "a", }); + }); - it("should populate nested default values for oneOf", () => { - const schema = { - type: "object", - properties: { - name: { - type: "object", - oneOf: [ - { - type: "object", - properties: { - first: { type: "string", default: "First Name" }, - }, - }, - { type: "string", default: "b" }, - ], - }, - }, - }; - expect(getDefaultFormState(schema, {})).eql({ + it("should populate nested default values for oneOf", () => { + const schema = { + type: "object", + properties: { name: { - first: "First Name", + type: "object", + oneOf: [ + { + type: "object", + properties: { + first: { type: "string", default: "First Name" }, + }, + }, + { type: "string", default: "b" }, + ], }, - }); + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: { + first: "First Name", + }, }); + }); - it("should populate defaults for oneOf + dependencies", () => { - const schema = { - oneOf: [ - { - type: "object", - properties: { - name: { - type: "string", - }, + it("should populate defaults for oneOf + dependencies", () => { + const schema = { + oneOf: [ + { + type: "object", + properties: { + name: { + type: "string", }, }, - ], - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: "string", - }, - grade: { - default: "A", - }, + }, + ], + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + default: "A", }, }, - ], - }, + }, + ], }, - }; - expect(getDefaultFormState(schema, { name: "Name" })).eql({ - name: "Name", - grade: "A", - }); + }, + }; + expect(getDefaultFormState(schema, { name: "Name" })).eql({ + name: "Name", + grade: "A", }); }); + }); - describe("getDefaultFormState with anyOf", () => { - it("should populate defaults for anyOf", () => { - const schema = { - type: "object", - properties: { - name: { - type: "string", - anyOf: [ - { type: "string", default: "a" }, - { type: "string", default: "b" }, - ], - }, + describe("defaults with anyOf", () => { + it("should populate defaults for anyOf", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + anyOf: [ + { type: "string", default: "a" }, + { type: "string", default: "b" }, + ], }, - }; - expect(getDefaultFormState(schema, {})).eql({ - name: "a", - }); + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: "a", }); + }); - it("should populate nested default values for anyOf", () => { - const schema = { - type: "object", - properties: { - name: { - type: "object", - anyOf: [ - { - type: "object", - properties: { - first: { type: "string", default: "First Name" }, - }, - }, - { type: "string", default: "b" }, - ], - }, - }, - }; - expect(getDefaultFormState(schema, {})).eql({ + it("should populate nested default values for anyOf", () => { + const schema = { + type: "object", + properties: { name: { - first: "First Name", + type: "object", + anyOf: [ + { + type: "object", + properties: { + first: { type: "string", default: "First Name" }, + }, + }, + { type: "string", default: "b" }, + ], }, - }); + }, + }; + expect(getDefaultFormState(schema, {})).eql({ + name: { + first: "First Name", + }, }); + }); - it("should populate defaults for anyOf + dependencies", () => { - const schema = { - anyOf: [ - { - type: "object", - properties: { - name: { - type: "string", - }, + it("should populate defaults for anyOf + dependencies", () => { + const schema = { + anyOf: [ + { + type: "object", + properties: { + name: { + type: "string", }, }, - ], - dependencies: { - name: { - oneOf: [ - { - properties: { - name: { - type: "string", - }, - grade: { - default: "A", - }, + }, + ], + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + type: "string", + default: "A", }, }, - ], - }, + }, + ], }, - }; - expect(getDefaultFormState(schema, { name: "Name" })).eql({ - name: "Name", - grade: "A", - }); + }, + }; + expect(getDefaultFormState(schema, { name: "Name" })).eql({ + name: "Name", + grade: "A", }); }); }); From ed53a8f404404e622ac0f57b5efc10884e0b965a Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Sat, 1 Jun 2019 18:01:47 -0700 Subject: [PATCH 07/10] fix: populate defaults for nested dependencies --- src/utils.js | 20 ++++++-- test/utils_test.js | 116 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/utils.js b/src/utils.js index ff7fc5e078..efeb2df5f8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -139,7 +139,7 @@ export function hasWidget(schema, widget, registeredWidgets = {}) { } } -function computeDefaults(schema, parentDefaults, definitions = {}) { +function computeDefaults(schema, parentDefaults, definitions, formData) { // Compute the defaults recursively: give highest priority to deepest nodes. let defaults = parentDefaults; if (isObject(defaults) && isObject(schema.default)) { @@ -152,10 +152,13 @@ function computeDefaults(schema, parentDefaults, definitions = {}) { } else if ("$ref" in schema) { // Use referenced schema defaults for this node. const refSchema = findSchemaDefinition(schema.$ref, definitions); - return computeDefaults(refSchema, defaults, definitions); + return computeDefaults(refSchema, defaults, definitions, formData); + } else if ("dependencies" in schema) { + const resolvedSchema = resolveDependencies(schema, definitions, formData); + return computeDefaults(resolvedSchema, defaults, definitions, formData); } else if (isFixedItems(schema)) { defaults = schema.items.map(itemSchema => - computeDefaults(itemSchema, undefined, definitions) + computeDefaults(itemSchema, undefined, definitions, formData) ); } else if ("oneOf" in schema) { schema = @@ -168,6 +171,7 @@ function computeDefaults(schema, parentDefaults, definitions = {}) { getMatchingOption(undefined, schema["anyOf"], definitions) ]; } + // Not defaults defined for this node, fallback to generic typed ones. if (typeof defaults === "undefined") { defaults = schema.default; @@ -182,7 +186,8 @@ function computeDefaults(schema, parentDefaults, definitions = {}) { acc[key] = computeDefaults( schema.properties[key], (defaults || {})[key], - definitions + definitions, + (formData || {})[key] ); return acc; }, {}); @@ -218,7 +223,12 @@ export function getDefaultFormState(_schema, formData, definitions = {}) { throw new Error("Invalid schema: " + _schema); } const schema = retrieveSchema(_schema, definitions, formData); - const defaults = computeDefaults(schema, _schema.default, definitions); + const defaults = computeDefaults( + schema, + _schema.default, + definitions, + formData + ); if (typeof formData === "undefined") { // No form data? Use schema defaults. return defaults; diff --git a/test/utils_test.js b/test/utils_test.js index dce83f8d15..3a814f0269 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -476,6 +476,122 @@ describe("utils", () => { }); }); }); + + describe("with dependencies", () => { + it("should populate defaults for dependencies", () => { + const schema = { + type: "object", + properties: { + name: { + type: "string", + }, + }, + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + type: "string", + default: "A", + }, + }, + }, + ], + }, + }, + }; + expect(getDefaultFormState(schema, { name: "Name" })).eql({ + name: "Name", + grade: "A", + }); + }); + + it("should populate defaults for nested dependencies", () => { + const schema = { + type: "object", + properties: { + foo: { + type: "object", + properties: { + name: { + type: "string", + }, + }, + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + type: "string", + default: "A", + }, + }, + }, + ], + }, + }, + }, + }, + }; + expect(getDefaultFormState(schema, { foo: { name: "Name" } })).eql({ + foo: { + name: "Name", + grade: "A", + }, + }); + }); + + it("should populate defaults for nested oneOf + dependencies", () => { + const schema = { + type: "object", + properties: { + foo: { + oneOf: [ + { + type: "object", + properties: { + name: { + type: "string", + }, + }, + }, + ], + dependencies: { + name: { + oneOf: [ + { + properties: { + name: { + type: "string", + }, + grade: { + type: "string", + default: "A", + }, + }, + }, + ], + }, + }, + }, + }, + }; + expect(getDefaultFormState(schema, { foo: { name: "Name" } })).eql({ + foo: { + name: "Name", + grade: "A", + }, + }); + }); + }); }); describe("asNumber()", () => { From 6c09360a6a37467b47fb2785b95e8c50eaa284cc Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Sat, 1 Jun 2019 18:03:35 -0700 Subject: [PATCH 08/10] fix: fix NIT by @lucianbuzzo --- src/utils.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/utils.js b/src/utils.js index efeb2df5f8..3778ac4281 100644 --- a/src/utils.js +++ b/src/utils.js @@ -162,14 +162,10 @@ function computeDefaults(schema, parentDefaults, definitions, formData) { ); } else if ("oneOf" in schema) { schema = - schema["oneOf"][ - getMatchingOption(undefined, schema["oneOf"], definitions) - ]; + schema.oneOf[getMatchingOption(undefined, schema.oneOf, definitions)]; } else if ("anyOf" in schema) { schema = - schema["anyOf"][ - getMatchingOption(undefined, schema["anyOf"], definitions) - ]; + schema.anyOf[getMatchingOption(undefined, schema.anyOf, definitions)]; } // Not defaults defined for this node, fallback to generic typed ones. @@ -561,13 +557,13 @@ function resolveDependencies(schema, definitions, formData) { let { dependencies = {}, ...resolvedSchema } = schema; if ("oneOf" in resolvedSchema) { resolvedSchema = - resolvedSchema["oneOf"][ - getMatchingOption(formData, resolvedSchema["oneOf"], definitions) + resolvedSchema.oneOf[ + getMatchingOption(formData, resolvedSchema.oneOf, definitions) ]; } else if ("anyOf" in resolvedSchema) { resolvedSchema = - resolvedSchema["anyOf"][ - getMatchingOption(formData, resolvedSchema["anyOf"], definitions) + resolvedSchema.oneOf[ + getMatchingOption(formData, resolvedSchema.anyOf, definitions) ]; } // Process dependencies updating the local schema properties as appropriate. From 4ae2729a439614a19542ddbbc9e83bce3501f4d5 Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Sat, 1 Jun 2019 18:51:17 -0700 Subject: [PATCH 09/10] fix: fix typo --- src/utils.js | 2 +- test/utils_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.js b/src/utils.js index 3778ac4281..86f89c8829 100644 --- a/src/utils.js +++ b/src/utils.js @@ -562,7 +562,7 @@ function resolveDependencies(schema, definitions, formData) { ]; } else if ("anyOf" in resolvedSchema) { resolvedSchema = - resolvedSchema.oneOf[ + resolvedSchema.anyOf[ getMatchingOption(formData, resolvedSchema.anyOf, definitions) ]; } diff --git a/test/utils_test.js b/test/utils_test.js index 3a814f0269..62fb303aca 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -440,7 +440,7 @@ describe("utils", () => { }); }); - it("should populate defaults for anyOf + dependencies", () => { + it.only("should populate defaults for anyOf + dependencies", () => { const schema = { anyOf: [ { From 3c6cd1c2eb05fad0b85b1b70ad09611debf72faf Mon Sep 17 00:00:00 2001 From: Ashwin Ramaswami Date: Sun, 2 Jun 2019 08:16:06 -0700 Subject: [PATCH 10/10] Update test/utils_test.js --- test/utils_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils_test.js b/test/utils_test.js index 62fb303aca..3a814f0269 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -440,7 +440,7 @@ describe("utils", () => { }); }); - it.only("should populate defaults for anyOf + dependencies", () => { + it("should populate defaults for anyOf + dependencies", () => { const schema = { anyOf: [ {