Skip to content

Commit f9d4c63

Browse files
LucianBuzzoepicfaace
authored andcommittedFeb 21, 2019
Fix multiple bugs related to switching between anyOf/oneOf options (#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
1 parent bece2a5 commit f9d4c63

File tree

6 files changed

+273
-3
lines changed

6 files changed

+273
-3
lines changed
 

‎src/components/fields/MultiSchemaField.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class AnyOfField extends Component {
6969
augmentedSchema = Object.assign({}, option, requiresAnyOf);
7070
}
7171

72+
// Remove the "required" field as it's likely that not all fields have
73+
// been filled in yet, which will mean that the schema is not valid
74+
delete augmentedSchema.required;
75+
7276
if (isValid(augmentedSchema, formData)) {
7377
return i;
7478
}
@@ -85,7 +89,14 @@ class AnyOfField extends Component {
8589
const selectedOption = parseInt(event.target.value, 10);
8690
const { formData, onChange, options } = this.props;
8791

88-
if (guessType(formData) === "object") {
92+
const newOption = options[selectedOption];
93+
94+
// If the new option is of type object and the current data is an object,
95+
// discard properties added using the old option.
96+
if (
97+
guessType(formData) === "object" &&
98+
(newOption.type === "object" || newOption.properties)
99+
) {
89100
const newFormData = Object.assign({}, formData);
90101

91102
const optionsToDiscard = options.slice();

‎src/components/fields/ObjectField.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ class ObjectField extends Component {
237237
errorSchema={errorSchema[name]}
238238
idSchema={idSchema[name]}
239239
idPrefix={idPrefix}
240-
formData={formData[name]}
240+
formData={(formData || {})[name]}
241241
onKeyChange={this.onKeyChange(name)}
242242
onChange={this.onPropertyChange(
243243
name,

‎src/utils.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,9 @@ export function toIdSchema(
732732
field,
733733
fieldId,
734734
definitions,
735-
formData[name],
735+
// It's possible that formData is not an object -- this can happen if an
736+
// array item has just been added, but not populated with data yet
737+
(formData || {})[name],
736738
idPrefix
737739
);
738740
}

‎test/anyOf_test.js

+46
Original file line numberDiff line numberDiff line change
@@ -571,5 +571,51 @@ describe("anyOf", () => {
571571

572572
expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
573573
});
574+
575+
it("should correctly render mixed types for anyOf inside array items", () => {
576+
const schema = {
577+
type: "object",
578+
properties: {
579+
items: {
580+
type: "array",
581+
items: {
582+
anyOf: [
583+
{
584+
type: "string",
585+
},
586+
{
587+
type: "object",
588+
properties: {
589+
foo: {
590+
type: "integer",
591+
},
592+
bar: {
593+
type: "string",
594+
},
595+
},
596+
},
597+
],
598+
},
599+
},
600+
},
601+
};
602+
603+
const { node } = createFormComponent({
604+
schema,
605+
});
606+
607+
expect(node.querySelector(".array-item-add button")).not.eql(null);
608+
609+
Simulate.click(node.querySelector(".array-item-add button"));
610+
611+
const $select = node.querySelector("select");
612+
expect($select).not.eql(null);
613+
Simulate.change($select, {
614+
target: { value: $select.options[1].value },
615+
});
616+
617+
expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
618+
expect(node.querySelectorAll("input#root_bar")).to.have.length.of(1);
619+
});
574620
});
575621
});

‎test/oneOf_test.js

+135
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,139 @@ describe("oneOf", () => {
299299

300300
expect(node.querySelector("select").value).eql("1");
301301
});
302+
303+
it("should not change the selected option when entering values on a subschema with multiple required options", () => {
304+
const schema = {
305+
type: "object",
306+
properties: {
307+
items: {
308+
oneOf: [
309+
{
310+
type: "string",
311+
},
312+
{
313+
type: "object",
314+
properties: {
315+
foo: {
316+
type: "integer",
317+
},
318+
bar: {
319+
type: "string",
320+
},
321+
},
322+
required: ["foo", "bar"],
323+
},
324+
],
325+
},
326+
},
327+
};
328+
329+
const { node } = createFormComponent({
330+
schema,
331+
});
332+
333+
const $select = node.querySelector("select");
334+
335+
expect($select.value).eql("0");
336+
337+
Simulate.change($select, {
338+
target: { value: $select.options[1].value },
339+
});
340+
341+
expect($select.value).eql("1");
342+
343+
Simulate.change(node.querySelector("input#root_bar"), {
344+
target: { value: "Lorem ipsum dolor sit amet" },
345+
});
346+
347+
expect($select.value).eql("1");
348+
});
349+
350+
it("should empty the form data when switching from an option of type 'object'", () => {
351+
const schema = {
352+
oneOf: [
353+
{
354+
type: "object",
355+
properties: {
356+
foo: {
357+
type: "integer",
358+
},
359+
bar: {
360+
type: "string",
361+
},
362+
},
363+
required: ["foo", "bar"],
364+
},
365+
{
366+
type: "string",
367+
},
368+
],
369+
};
370+
371+
const { node } = createFormComponent({
372+
schema,
373+
formData: {
374+
foo: 1,
375+
bar: "abc",
376+
},
377+
});
378+
379+
const $select = node.querySelector("select");
380+
381+
Simulate.change($select, {
382+
target: { value: $select.options[1].value },
383+
});
384+
385+
expect($select.value).eql("1");
386+
387+
expect(node.querySelector("input#root").value).eql("");
388+
});
389+
390+
describe("Arrays", () => {
391+
it("should correctly render mixed types for oneOf inside array items", () => {
392+
const schema = {
393+
type: "object",
394+
properties: {
395+
items: {
396+
type: "array",
397+
items: {
398+
oneOf: [
399+
{
400+
type: "string",
401+
},
402+
{
403+
type: "object",
404+
properties: {
405+
foo: {
406+
type: "integer",
407+
},
408+
bar: {
409+
type: "string",
410+
},
411+
},
412+
},
413+
],
414+
},
415+
},
416+
},
417+
};
418+
419+
const { node } = createFormComponent({
420+
schema,
421+
});
422+
423+
expect(node.querySelector(".array-item-add button")).not.eql(null);
424+
425+
Simulate.click(node.querySelector(".array-item-add button"));
426+
427+
const $select = node.querySelector("select");
428+
expect($select).not.eql(null);
429+
Simulate.change($select, {
430+
target: { value: $select.options[1].value },
431+
});
432+
433+
expect(node.querySelectorAll("input#root_foo")).to.have.length.of(1);
434+
expect(node.querySelectorAll("input#root_bar")).to.have.length.of(1);
435+
});
436+
});
302437
});

‎test/utils_test.js

+76
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,64 @@ describe("utils", () => {
11831183
});
11841184
});
11851185

1186+
it("should return an idSchema for nested property dependencies", () => {
1187+
const schema = {
1188+
type: "object",
1189+
properties: {
1190+
obj: {
1191+
type: "object",
1192+
properties: {
1193+
foo: { type: "string" },
1194+
},
1195+
dependencies: {
1196+
foo: {
1197+
properties: {
1198+
bar: { type: "string" },
1199+
},
1200+
},
1201+
},
1202+
},
1203+
},
1204+
};
1205+
const formData = {
1206+
obj: {
1207+
foo: "test",
1208+
},
1209+
};
1210+
1211+
expect(toIdSchema(schema, undefined, schema.definitions, formData)).eql({
1212+
$id: "root",
1213+
obj: {
1214+
$id: "root_obj",
1215+
foo: { $id: "root_obj_foo" },
1216+
bar: { $id: "root_obj_bar" },
1217+
},
1218+
});
1219+
});
1220+
1221+
it("should return an idSchema for unmet property dependencies", () => {
1222+
const schema = {
1223+
type: "object",
1224+
properties: {
1225+
foo: { type: "string" },
1226+
},
1227+
dependencies: {
1228+
foo: {
1229+
properties: {
1230+
bar: { type: "string" },
1231+
},
1232+
},
1233+
},
1234+
};
1235+
1236+
const formData = {};
1237+
1238+
expect(toIdSchema(schema, undefined, schema.definitions, formData)).eql({
1239+
$id: "root",
1240+
foo: { $id: "root_foo" },
1241+
});
1242+
});
1243+
11861244
it("should handle idPrefix parameter", () => {
11871245
const schema = {
11881246
definitions: {
@@ -1205,6 +1263,24 @@ describe("utils", () => {
12051263
}
12061264
);
12071265
});
1266+
1267+
it("should handle null form data for object schemas", () => {
1268+
const schema = {
1269+
type: "object",
1270+
properties: {
1271+
foo: { type: "string" },
1272+
bar: { type: "string" },
1273+
},
1274+
};
1275+
const formData = null;
1276+
const result = toIdSchema(schema, null, {}, formData, "rjsf");
1277+
1278+
expect(result).eql({
1279+
$id: "rjsf",
1280+
foo: { $id: "rjsf_foo" },
1281+
bar: { $id: "rjsf_bar" },
1282+
});
1283+
});
12081284
});
12091285

12101286
describe("parseDateString()", () => {

0 commit comments

Comments
 (0)
Please sign in to comment.