Skip to content

Commit

Permalink
Fix multiple bugs related to switching between anyOf/oneOf options (#…
Browse files Browse the repository at this point in the history
…1169)

* Fix multiple bugs related to switching between anyOf/oneOf options

Fixes #1168

- Fixed a bug that would prevent input fields from rendering when
switching between a non-object type option and an object type option
- Fixed a bug where options would incorrectly change when entering
values if a subschema with multiple required fields is used
- Fixed a bug where switching from an object tpye option to a non-object
type option would result in an input field containing the value [Object object]

Change-type: patch
Signed-off-by: Lucian <lucian.buzzo@gmail.com>

* Update src/utils.js

* Update src/utils.js
  • Loading branch information
LucianBuzzo authored and epicfaace committed Feb 21, 2019
1 parent bece2a5 commit f9d4c63
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 3 deletions.
13 changes: 12 additions & 1 deletion src/components/fields/MultiSchemaField.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ class AnyOfField extends Component {
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;
}
Expand All @@ -85,7 +89,14 @@ class AnyOfField extends Component {
const selectedOption = parseInt(event.target.value, 10);
const { formData, onChange, options } = this.props;

if (guessType(formData) === "object") {
const newOption = options[selectedOption];

// If the new option is of type object and the current data is an object,
// discard properties added using the old option.
if (
guessType(formData) === "object" &&
(newOption.type === "object" || newOption.properties)
) {
const newFormData = Object.assign({}, formData);

const optionsToDiscard = options.slice();
Expand Down
2 changes: 1 addition & 1 deletion src/components/fields/ObjectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ class ObjectField extends Component {
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
idPrefix={idPrefix}
formData={formData[name]}
formData={(formData || {})[name]}
onKeyChange={this.onKeyChange(name)}
onChange={this.onPropertyChange(
name,
Expand Down
4 changes: 3 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,9 @@ export function toIdSchema(
field,
fieldId,
definitions,
formData[name],
// It's possible that formData is not an object -- this can happen if an
// array item has just been added, but not populated with data yet
(formData || {})[name],
idPrefix
);
}
Expand Down
46 changes: 46 additions & 0 deletions test/anyOf_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,5 +571,51 @@ describe("anyOf", () => {

expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
});

it("should correctly render mixed types for anyOf inside array items", () => {
const schema = {
type: "object",
properties: {
items: {
type: "array",
items: {
anyOf: [
{
type: "string",
},
{
type: "object",
properties: {
foo: {
type: "integer",
},
bar: {
type: "string",
},
},
},
],
},
},
},
};

const { node } = createFormComponent({
schema,
});

expect(node.querySelector(".array-item-add button")).not.eql(null);

Simulate.click(node.querySelector(".array-item-add button"));

const $select = node.querySelector("select");
expect($select).not.eql(null);
Simulate.change($select, {
target: { value: $select.options[1].value },
});

expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
expect(node.querySelectorAll("input#root_bar")).to.have.length.of(1);
});
});
});
135 changes: 135 additions & 0 deletions test/oneOf_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,139 @@ describe("oneOf", () => {

expect(node.querySelector("select").value).eql("1");
});

it("should not change the selected option when entering values on a subschema with multiple required options", () => {
const schema = {
type: "object",
properties: {
items: {
oneOf: [
{
type: "string",
},
{
type: "object",
properties: {
foo: {
type: "integer",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
],
},
},
};

const { node } = createFormComponent({
schema,
});

const $select = node.querySelector("select");

expect($select.value).eql("0");

Simulate.change($select, {
target: { value: $select.options[1].value },
});

expect($select.value).eql("1");

Simulate.change(node.querySelector("input#root_bar"), {
target: { value: "Lorem ipsum dolor sit amet" },
});

expect($select.value).eql("1");
});

it("should empty the form data when switching from an option of type 'object'", () => {
const schema = {
oneOf: [
{
type: "object",
properties: {
foo: {
type: "integer",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
{
type: "string",
},
],
};

const { node } = createFormComponent({
schema,
formData: {
foo: 1,
bar: "abc",
},
});

const $select = node.querySelector("select");

Simulate.change($select, {
target: { value: $select.options[1].value },
});

expect($select.value).eql("1");

expect(node.querySelector("input#root").value).eql("");
});

describe("Arrays", () => {
it("should correctly render mixed types for oneOf inside array items", () => {
const schema = {
type: "object",
properties: {
items: {
type: "array",
items: {
oneOf: [
{
type: "string",
},
{
type: "object",
properties: {
foo: {
type: "integer",
},
bar: {
type: "string",
},
},
},
],
},
},
},
};

const { node } = createFormComponent({
schema,
});

expect(node.querySelector(".array-item-add button")).not.eql(null);

Simulate.click(node.querySelector(".array-item-add button"));

const $select = node.querySelector("select");
expect($select).not.eql(null);
Simulate.change($select, {
target: { value: $select.options[1].value },
});

expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
expect(node.querySelectorAll("input#root_bar")).to.have.length.of(1);
});
});
});
76 changes: 76 additions & 0 deletions test/utils_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,64 @@ describe("utils", () => {
});
});

it("should return an idSchema for nested property dependencies", () => {
const schema = {
type: "object",
properties: {
obj: {
type: "object",
properties: {
foo: { type: "string" },
},
dependencies: {
foo: {
properties: {
bar: { type: "string" },
},
},
},
},
},
};
const formData = {
obj: {
foo: "test",
},
};

expect(toIdSchema(schema, undefined, schema.definitions, formData)).eql({
$id: "root",
obj: {
$id: "root_obj",
foo: { $id: "root_obj_foo" },
bar: { $id: "root_obj_bar" },
},
});
});

it("should return an idSchema for unmet property dependencies", () => {
const schema = {
type: "object",
properties: {
foo: { type: "string" },
},
dependencies: {
foo: {
properties: {
bar: { type: "string" },
},
},
},
};

const formData = {};

expect(toIdSchema(schema, undefined, schema.definitions, formData)).eql({
$id: "root",
foo: { $id: "root_foo" },
});
});

it("should handle idPrefix parameter", () => {
const schema = {
definitions: {
Expand All @@ -1205,6 +1263,24 @@ describe("utils", () => {
}
);
});

it("should handle null form data for object schemas", () => {
const schema = {
type: "object",
properties: {
foo: { type: "string" },
bar: { type: "string" },
},
};
const formData = null;
const result = toIdSchema(schema, null, {}, formData, "rjsf");

expect(result).eql({
$id: "rjsf",
foo: { $id: "rjsf_foo" },
bar: { $id: "rjsf_bar" },
});
});
});

describe("parseDateString()", () => {
Expand Down

0 comments on commit f9d4c63

Please sign in to comment.