Skip to content

Commit

Permalink
- Fix rjsf-team#2538 by fixing additionalProperties to deal with allO…
Browse files Browse the repository at this point in the history
…f/anyOf/oneOf
  • Loading branch information
heath-freenome committed Jan 22, 2023
1 parent df49d1e commit a5a2dc5
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ should change the heading of the (upcoming) version to include a major version b
- [#2069](https://github.com/rjsf-team/react-jsonschema-form/issues/2069)
- [#1661](https://github.com/rjsf-team/react-jsonschema-form/issues/1661)
- And probably others
- Updated `ObjectField` to deal with additionalProperties with anyOf/oneOf, fixing [#2538](https://github.com/rjsf-team/react-jsonschema-form/issues/2538)

# @rjsf/utils
- Added new `getClosestMatchingOption()`, `getFirstMatchingOption()` and `sanitizeDataForNewSchema()` schema-based utility functions
- Deprecated `getMatchingOption()` and updated all calls to it in other utility functions to use `getFirstMatchingOption()`
- Updated `stubExistingAdditionalProperties` to deal with additionalProperties with anyOf/oneOf, fixing [#2538](https://github.com/rjsf-team/react-jsonschema-form/issues/2538)

## Dev / docs / playground
- Updated the playground to `onFormDataEdited()` to only change the formData in the state if the `JSON.stringify()` of the old and new values are different, partially fixing [#3236](https://github.com/rjsf-team/react-jsonschema-form/issues/3236)
Expand Down
14 changes: 10 additions & 4 deletions packages/core/src/components/fields/ObjectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
ADDITIONAL_PROPERTY_FLAG,
PROPERTIES_KEY,
REF_KEY,
ANY_OF_KEY,
ONE_OF_KEY,
} from "@rjsf/utils";
import get from "lodash/get";
import has from "lodash/has";
Expand Down Expand Up @@ -203,13 +205,17 @@ class ObjectField<
let type: RJSFSchema["type"] = undefined;
if (isObject(schema.additionalProperties)) {
type = schema.additionalProperties.type;
if (REF_KEY in schema.additionalProperties) {
let apSchema = schema.additionalProperties;
if (REF_KEY in apSchema) {
const { schemaUtils } = registry;
const refSchema = schemaUtils.retrieveSchema(
{ $ref: schema.additionalProperties[REF_KEY] } as S,
apSchema = schemaUtils.retrieveSchema(
{ $ref: apSchema[REF_KEY] } as S,
formData
);
type = refSchema.type;
type = apSchema.type;
}
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
type = "object";
}
}

Expand Down
65 changes: 65 additions & 0 deletions packages/core/test/anyOf_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,71 @@ describe("anyOf", () => {
expect(options[1].firstChild.nodeValue).eql("Person");
});

it("should select anyOf in additionalProperties with anyOf", () => {
const schema = {
type: "object",
properties: {
testProperty: {
description: "Any key name, fixed set of possible values",
type: "object",
minProperties: 1,
additionalProperties: {
anyOf: [
{
title: "my choice 1",
type: "object",
properties: {
prop1: {
description: "prop1 description",
type: "string",
},
},
required: ["prop1"],
additionalProperties: false,
},
{
title: "my choice 2",
type: "object",
properties: {
prop2: {
description: "prop2 description",
type: "string",
},
},
required: ["prop2"],
additionalProperties: false,
},
],
},
},
},
required: ["testProperty"],
};

const { node, onChange } = createFormComponent({
schema,
formData: { testProperty: { newKey: { prop2: "foo" } } },
});

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

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

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

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

sinon.assert.calledWithMatch(onChange.lastCall, {
formData: {
testProperty: { newKey: { prop1: undefined, prop2: undefined } },
},
});
});

describe("Arrays", () => {
it("should correctly render form inputs for anyOf inside array items", () => {
const schema = {
Expand Down
65 changes: 65 additions & 0 deletions packages/core/test/oneOf_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,71 @@ describe("oneOf", () => {
);
});

it("should select oneOf in additionalProperties with oneOf", () => {
const schema = {
type: "object",
properties: {
testProperty: {
description: "Any key name, fixed set of possible values",
type: "object",
minProperties: 1,
additionalProperties: {
oneOf: [
{
title: "my choice 1",
type: "object",
properties: {
prop1: {
description: "prop1 description",
type: "string",
},
},
required: ["prop1"],
additionalProperties: false,
},
{
title: "my choice 2",
type: "object",
properties: {
prop2: {
description: "prop2 description",
type: "string",
},
},
required: ["prop2"],
additionalProperties: false,
},
],
},
},
},
required: ["testProperty"],
};

const { node, onChange } = createFormComponent({
schema,
formData: { testProperty: { newKey: { prop2: "foo" } } },
});

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

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

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

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

sinon.assert.calledWithMatch(onChange.lastCall, {
formData: {
testProperty: { newKey: { prop1: undefined, prop2: undefined } },
},
});
});

describe("Arrays", () => {
it("should correctly render mixed types for oneOf inside array items", () => {
const schema = {
Expand Down
10 changes: 10 additions & 0 deletions packages/utils/src/schema/retrieveSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
ADDITIONAL_PROPERTIES_KEY,
ADDITIONAL_PROPERTY_FLAG,
ALL_OF_KEY,
ANY_OF_KEY,
DEPENDENCIES_KEY,
ONE_OF_KEY,
REF_KEY,
} from "../constants";
import findSchemaDefinition, {
Expand Down Expand Up @@ -205,6 +207,14 @@ export function stubExistingAdditionalProperties<
);
} else if ("type" in schema.additionalProperties!) {
additionalProperties = { ...schema.additionalProperties };
} else if (
ANY_OF_KEY in schema.additionalProperties! ||
ONE_OF_KEY in schema.additionalProperties!
) {
additionalProperties = {
type: "object",
...schema.additionalProperties,
};
} else {
additionalProperties = { type: guessType(get(formData, [key])) };
}
Expand Down
58 changes: 58 additions & 0 deletions packages/utils/test/schema/retrieveSchemaTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,64 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) {
});
});

it("should `resolve` and stub out a schema which contains an `additionalProperties` with oneOf", () => {
const oneOf: RJSFSchema[] = [
{
type: "string",
},
{
type: "number",
},
];
const schema: RJSFSchema = {
additionalProperties: {
oneOf,
},
type: "object",
};

const formData = { newKey: {} };
expect(retrieveSchema(testValidator, schema, {}, formData)).toEqual({
...schema,
properties: {
newKey: {
type: "object",
oneOf,
[ADDITIONAL_PROPERTY_FLAG]: true,
},
},
});
});

it("should `resolve` and stub out a schema which contains an `additionalProperties` with anyOf", () => {
const anyOf: RJSFSchema[] = [
{
type: "string",
},
{
type: "number",
},
];
const schema: RJSFSchema = {
additionalProperties: {
anyOf,
},
type: "object",
};

const formData = { newKey: {} };
expect(retrieveSchema(testValidator, schema, {}, formData)).toEqual({
...schema,
properties: {
newKey: {
type: "object",
anyOf,
[ADDITIONAL_PROPERTY_FLAG]: true,
},
},
});
});

it("should handle null formData for schema which contains additionalProperties", () => {
const schema: RJSFSchema = {
additionalProperties: {
Expand Down

0 comments on commit a5a2dc5

Please sign in to comment.