Repository: mozilla-services/react-jsonschema-form (js)

Issue:
Boolean checkbox can't send false
### Prerequisites
- [ x] I have read the [documentation](https://github.com/mozilla-services/react-jsonschema-form/blob/master/README.md#readme).
### Description

Boolean checkbox's require you to tick the box, you cannot send a false.  
### Steps to Reproduce

Create a  form where a boolean field is required
#### Expected behavior

Either a unticked / ticked state will be accepted
#### Actual behavior

Only a ticked box is accepted.  

I'm using a jsonschema-form to send a  json to an api that uses https://github.com/geraintluff/tv4 as a validator using the same schema.  tv4's interpretation of a required boolean is it can be true or false as long as it is present.  Which implementation is right?
### Version

^0.38.0


Pull request description:
Limit cases where "required" attribute is used on checkboxes
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 <lucian.buzzo@gmail.com>

### Reasons for making this change

[Please describe them here]

If this is related to existing tickets, include links to them as well.

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've [checked the rendering](https://react-jsonschema-form.readthedocs.io/en/latest/#contributing) of the Markdown text I've added
* [x] **I'm adding or updating code**
  - [x] I've added and/or updated tests
  - [ ] I've updated [docs](https://react-jsonschema-form.readthedocs.io/) if needed
  - [x] I've run `npm run cs-format` on my branch to conform my code to [prettier](https://github.com/prettier/prettier) coding style
* [ ] **I'm adding a new feature**
  - [ ] I've updated the playground with an example use of the feature


Patch:
diff --git a/src/components/widgets/CheckboxWidget.js b/src/components/widgets/CheckboxWidget.js
index f583f5e7a..632ab4b42 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 (
     <div className={`checkbox ${disabled || readonly ? "disabled" : ""}`}>
       {schema.description && (


Test patch:
diff --git a/test/BooleanField_test.js b/test/BooleanField_test.js
index ea9d43b7a..640dc46f5 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: {