From 2a6b83017dcb2941401f0d04c9a23ee7288f25fd Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Wed, 6 Mar 2019 17:37:59 +0000 Subject: [PATCH] Limit cases where "required" attribute is used on checkboxes (#1194) Fixes #338 If the "required" attribute is applied to a checkbox, then HTML5 validation will only pass if the checkbox is ticked. This is at odds with the JSON schema definition of "required" that simply requires that the key exists. This PR will make it so that the "required" HTML5 attribute will only be applied if the schema uses `{ const: true }` or `{ enum: [ true ] }`. Signed-off-by: Lucian --- src/components/widgets/CheckboxWidget.js | 36 +++++++- test/BooleanField_test.js | 110 +++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/components/widgets/CheckboxWidget.js b/src/components/widgets/CheckboxWidget.js index f583f5e7a4..632ab4b426 100644 --- a/src/components/widgets/CheckboxWidget.js +++ b/src/components/widgets/CheckboxWidget.js @@ -2,12 +2,40 @@ import React from "react"; import PropTypes from "prop-types"; import DescriptionField from "../fields/DescriptionField.js"; +// Check to see if a schema specifies that a value must be true +function schemaRequiresTrueValue(schema) { + // Check if const is a truthy value + if (schema.const) { + return true; + } + + // Check if an enum has a single value of true + if (schema.enum && schema.enum.length === 1 && schema.enum[0] === true) { + return true; + } + + // If anyOf has a single value, evaluate the subschema + if (schema.anyOf && schema.anyOf.length === 1) { + return schemaRequiresTrueValue(schema.anyOf[0]); + } + + // If oneOf has a single value, evaluate the subschema + if (schema.oneOf && schema.oneOf.length === 1) { + return schemaRequiresTrueValue(schema.oneOf[0]); + } + + // Evaluate each subschema in allOf, to see if one of them requires a true + // value + if (schema.allOf) { + return schema.allOf.some(schemaRequiresTrueValue); + } +} + function CheckboxWidget(props) { const { schema, id, value, - required, disabled, readonly, label, @@ -16,6 +44,12 @@ function CheckboxWidget(props) { onFocus, onChange, } = props; + + // Because an unchecked checkbox will cause html5 validation to fail, only add + // the "required" attribute if the field value must be "true", due to the + // "const" or "enum" keywords + const required = schemaRequiresTrueValue(schema); + return (
{schema.description && ( diff --git a/test/BooleanField_test.js b/test/BooleanField_test.js index ea9d43b7a6..640dc46f5f 100644 --- a/test/BooleanField_test.js +++ b/test/BooleanField_test.js @@ -51,6 +51,116 @@ describe("BooleanField", () => { expect(node.querySelector(".field label span").textContent).eql("foo"); }); + describe("HTML5 required attribute", () => { + it("should not render a required attribute for simple required fields", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + }, + }, + required: ["foo"], + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(false); + }); + + it("should add a required attribute if the schema uses const with a true value", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + const: true, + }, + }, + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(true); + }); + + it("should add a required attribute if the schema uses an enum with a single value of true", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + enum: [true], + }, + }, + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(true); + }); + + it("should add a required attribute if the schema uses an anyOf with a single value of true", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + anyOf: [ + { + const: true, + }, + ], + }, + }, + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(true); + }); + + it("should add a required attribute if the schema uses a oneOf with a single value of true", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + oneOf: [ + { + const: true, + }, + ], + }, + }, + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(true); + }); + + it("should add a required attribute if the schema uses an allOf with a value of true", () => { + const { node } = createFormComponent({ + schema: { + type: "object", + properties: { + foo: { + type: "boolean", + allOf: [ + { + const: true, + }, + ], + }, + }, + }, + }); + + expect(node.querySelector("input[type=checkbox]").required).eql(true); + }); + }); + it("should render a single label", () => { const { node } = createFormComponent({ schema: {