Repository: mozilla-services/react-jsonschema-form
Pull request #711 (2017-10-04 14:57:24)

Switch to ajv validator
### Reasons for making this change

Switching to the [ajv](https://github.com/epoberezkin/ajv) validator should help address validation problems outlined in these issues:

#384 
#702 

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've checked the rendering of the Markdown text I've added
  - [ ] If I'm adding a new section, I've updated the Table of Content
* [x ] **I'm adding or updating code**
  - [x ] I've added and/or updated tests
  - [ ] I've updated docs 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


sgentry: I agree that switching from `arguments` -> `params` could be a breaking change for anyone who may use it. I guess another option is to try and re-create `arguments` based on the ajv error list but I don't think I'd like to attempt that.

Patch:
diff --git a/README.md b/README.md
index e0c5c8fae..6f716ef30 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
         - [removable option](#removable-option)
      - [Custom CSS class names](#custom-css-class-names)
      - [Custom labels for enum fields](#custom-labels-for-enum-fields)
+     - [Disabled enum fields](#disabled-attribute-for-enum-fields)
      - [Multiple choices list](#multiple-choices-list)
      - [Autogenerated widget ids](#autogenerated-widget-ids)
      - [Form action buttons](#form-action-buttons)
@@ -196,7 +197,7 @@ render((
 
 #### Form error event handler
 
-To react to when submitted form data are invalid, pass an `onError` handler, which is passed the list of encoutered errors:
+To react to when submitted form data are invalid, pass an `onError` handler, which is passed the list of encountered errors:
 
 ```js
 const onError = (errors) => console.log("I have", errors.length, "errors to fix");
@@ -209,7 +210,7 @@ render((
 
 #### Form data changes
 
-If you plan on being notified everytime the form data are updated, you can pass an `onChange` handler, which will receive the same args as `onSubmit` any time a value is updated in the form.
+If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same args as `onSubmit` any time a value is updated in the form.
 
 #### Form field blur events
 
@@ -324,7 +325,7 @@ The built-in string field also supports the JSONSchema `format` property, and wi
 
 ![](http://i.imgur.com/xqu6Lcp.png)
 
-Please note that while standardized, `datetime-local` and `date` input elements are not yet supported by Firefox and IE. If you plan on targetting these platforms, two alternative widgets are available:
+Please note that while standardized, `datetime-local` and `date` input elements are not yet supported by Firefox and IE. If you plan on targeting these platforms, two alternative widgets are available:
 
 - `alt-datetime`: Six `select` elements are used to select the year, the month, the day, the hour, the minute and the second;
 - `alt-date`: Three `select` elements are used to select the year, month and the day.
@@ -444,7 +445,7 @@ render((
 ), document.getElementById("app"));
 ```
 
-If a guarenteed fixed order is only important for some fields, you can insert a wildcard `"*"` item in your `ui:order` definition. All fields that are not referenced explicitly anywhere in the list will be rendered at that point:
+If a guaranteed fixed order is only important for some fields, you can insert a wildcard `"*"` item in your `ui:order` definition. All fields that are not referenced explicitly anywhere in the list will be rendered at that point:
 
 ```js
 const uiSchema = {
@@ -544,6 +545,31 @@ This will be rendered using a select box that way:
 
 Note that string representations of numbers will be cast back and reflected as actual numbers into form state.
 
+### Disabled attribute for `enum` fields
+
+This library supports the 'disbaled' attribute for `enum` options.Enum disabled allows disabling options for 'enum' fields.This attribute can be added as a part of uiSchema.
+
+```js
+const schema = {
+  type: "string",
+  enum: ["one", "two", "three"],
+};
+
+const uiSchema={
+  "ui:enumDisabled": ['two'],
+}
+```
+
+This will be rendered using a select box that way:
+
+```html
+<select>
+  <option value="1">one</option>
+  <option value="2" disabled>two</option>
+  <option value="3">three</option>
+</select>
+```
+
 ### Multiple choices list
 
 The default behavior for array fields is a list of text inputs with add/remove buttons. Though there are two alternative simpler widgets for common situations like picking elements against a list of choices; typically this maps to a schema having:
@@ -796,7 +822,7 @@ If you want to handle the rendering of each element yourself, you can use the pr
 
 The following props are passed to a custom field template component:
 
-- `id`: The id of the field in the hierarchy. You can use it to render a label targetting the wrapped widget.
+- `id`: The id of the field in the hierarchy. You can use it to render a label targeting the wrapped widget.
 - `classNames`: A string containing the base bootstrap CSS classes merged with any [custom ones](#custom-css-class-names) defined in your uiSchema.
 - `label`: The computed label for this field, as a string.
 - `description`: A component instance rendering the field description, if any defined (this will use any [custom `DescriptionField`](#custom-descriptions) defined).
@@ -850,7 +876,7 @@ The following props are passed to each `ArrayFieldTemplate`:
 - `idSchema`: Object
 - `items`: An array of objects representing the items in the array. Each of the items represent a child with properties described below.
 - `onAddClick: (event) => (event) => void`: Returns a function that adds a new item to the array.
-- `readonly`: A boolean value stating if the array is readonly.
+- `readonly`: A boolean value stating if the array is read-only.
 - `required`: A boolean value stating if the array is required.
 - `schema`: The schema object for this array.
 - `uiSchema`: The uiSchema object for this array field.
@@ -870,7 +896,7 @@ The following props are part of each element in `items`:
 - `index`: A number stating the index the array item occurs in `items`.
 - `onDropIndexClick: (index) => (event) => void`: Returns a function that removes the item at `index`.
 - `onReorderClick: (index, newIndex) => (event) => void`: Returns a function that swaps the items at `index` with `newIndex`.
-- `readonly`: A boolean value stating if the array item is readonly.
+- `readonly`: A boolean value stating if the array item is read-only.
 
 ### Object Field Template
 
@@ -915,7 +941,7 @@ The following props are part of each element in `properties`:
 - `content`: The html for the property's content.
 - `name`: A string representing the property name.
 - `disabled`: A boolean value stating if the object property is disabled.
-- `readonly`: A boolean value stating if the property is readonly.
+- `readonly`: A boolean value stating if the property is read-only.
 
 ### Error List template
 
diff --git a/package.json b/package.json
index 5bb2fd77f..0b01f3643 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
     "react": "^15.0.0"
   },
   "dependencies": {
-    "jsonschema": "^1.2.0",
+    "ajv": "^5.2.3",
     "lodash.topath": "^4.5.2",
     "prop-types": "^15.5.8",
     "setimmediate": "^1.0.5"
@@ -62,6 +62,8 @@
     "cross-env": "^2.0.1",
     "css-loader": "^0.23.1",
     "eslint": "^3.15.0",
+    "eslint-config-react-app": "^2.0.1",
+    "eslint-plugin-import": "^2.7.0",
     "eslint-plugin-react": "^4.2.3",
     "estraverse": "^4.2.0",
     "estraverse-fb": "^1.3.1",
@@ -71,6 +73,7 @@
     "html": "0.0.10",
     "husky": "^0.13.2",
     "jsdom": "^8.3.0",
+    "json-loader": "^0.5.7",
     "lint-staged": "^3.3.1",
     "mocha": "^2.5.3",
     "prettier": "^1.7.2",
diff --git a/playground/samples/alternatives.js b/playground/samples/alternatives.js
index 3aebe1f64..cfdbc6e44 100644
--- a/playground/samples/alternatives.js
+++ b/playground/samples/alternatives.js
@@ -54,7 +54,11 @@ module.exports = {
       },
     },
   },
-  uiSchema: {},
+  uiSchema: {
+    blendMode: {
+      "ui:enumDisabled": ["multiply"],
+    },
+  },
   formData: {
     currentColor: "#00ff00",
     colorMask: ["#0000ff"],
diff --git a/src/components/widgets/SelectWidget.js b/src/components/widgets/SelectWidget.js
index c11c10b47..722440a0f 100644
--- a/src/components/widgets/SelectWidget.js
+++ b/src/components/widgets/SelectWidget.js
@@ -51,7 +51,7 @@ function SelectWidget(props) {
     onFocus,
     placeholder,
   } = props;
-  const { enumOptions } = options;
+  const { enumOptions, enumDisabled } = options;
   const emptyValue = multiple ? [] : "";
   return (
     <select
@@ -82,8 +82,9 @@ function SelectWidget(props) {
       }}>
       {!multiple && !schema.default && <option value="">{placeholder}</option>}
       {enumOptions.map(({ value, label }, i) => {
+        const disabled = enumDisabled && enumDisabled.indexOf(value) != -1;
         return (
-          <option key={i} value={value}>
+          <option key={i} value={value} disabled={disabled}>
             {label}
           </option>
         );
diff --git a/src/validate.js b/src/validate.js
index 0ac38670c..1f72b8dcd 100644
--- a/src/validate.js
+++ b/src/validate.js
@@ -1,14 +1,27 @@
 import toPath from "lodash.topath";
-import { validate as jsonValidate } from "jsonschema";
+import Ajv from "ajv";
+const ajv = new Ajv({
+  errorDataPath: "property",
+  allErrors: true,
+});
+// add custom formats
+ajv.addFormat(
+  "data-url",
+  /^data:([a-z]+\/[a-z0-9-+.]+)?;name=(.*);base64,(.*)$/
+);
+ajv.addFormat(
+  "color",
+  /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/
+);
 
 import { isObject, mergeObjects } from "./utils";
 
 function toErrorSchema(errors) {
-  // Transforms a jsonschema validation errors list:
+  // Transforms a ajv validation errors list:
   // [
-  //   {property: "instance.level1.level2[2].level3", message: "err a"},
-  //   {property: "instance.level1.level2[2].level3", message: "err b"},
-  //   {property: "instance.level1.level2[4].level3", message: "err b"},
+  //   {property: ".level1.level2[2].level3", message: "err a"},
+  //   {property: ".level1.level2[2].level3", message: "err b"},
+  //   {property: ".level1.level2[4].level3", message: "err b"},
   // ]
   // Into an error tree:
   // {
@@ -26,7 +39,14 @@ function toErrorSchema(errors) {
     const { property, message } = error;
     const path = toPath(property);
     let parent = errorSchema;
-    for (const segment of path.slice(1)) {
+
+    // If the property is at the root (.level1) then toPath creates
+    // an empty array element at the first index. Remove it.
+    if (path.length > 0 && path[0] === "") {
+      path.splice(0, 1);
+    }
+
+    for (const segment of path.slice(0)) {
       if (!(segment in parent)) {
         parent[segment] = {};
       }
@@ -98,6 +118,30 @@ function unwrapErrorHandler(errorHandler) {
   }, {});
 }
 
+/**
+ * Transforming the error output from ajv to format used by jsonschema.
+ * At some point, components should be updated to support ajv.
+ */
+function transformAjvErrors(errors = []) {
+  if (errors === null) {
+    return [];
+  }
+
+  return errors.map(e => {
+    const { dataPath, keyword, message, params } = e;
+    let property = `${dataPath}`;
+
+    // put data in expected format
+    return {
+      name: keyword,
+      property,
+      message,
+      params, // specific to ajv
+      stack: `${property} ${message}`.trim(),
+    };
+  });
+}
+
 /**
  * This function processes the formData with a user `validate` contributed
  * function, which receives the form data and an `errorHandler` object that
@@ -109,7 +153,10 @@ export default function validateFormData(
   customValidate,
   transformErrors
 ) {
-  let { errors } = jsonValidate(formData, schema);
+  ajv.validate(schema, formData);
+
+  let errors = transformAjvErrors(ajv.errors);
+
   if (typeof transformErrors === "function") {
     errors = transformErrors(errors);
   }
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index 0bd26893d..0657e6373 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -35,6 +35,15 @@ module.exports = {
           path.join(__dirname, "playground"),
           path.join(__dirname, "node_modules"),
         ],
+      },
+      {
+        test: /\.json$/,
+        loader:"json-loader",
+        include: [
+          path.join(__dirname, "css"),
+          path.join(__dirname, "playground"),
+          path.join(__dirname, "node_modules"),
+        ],
       }
     ]
   }
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index 731c2203f..d68b7b278 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -39,6 +39,15 @@ module.exports = {
           path.join(__dirname, "playground"),
           path.join(__dirname, "node_modules"),
         ],
+      },
+      {
+        test: /\.json$/,
+        loader:"json-loader",
+        include: [
+          path.join(__dirname, "css"),
+          path.join(__dirname, "playground"),
+          path.join(__dirname, "node_modules"),
+        ],
       }
     ]
   }


Test patch:
diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js
index 7480b1c94..9dc22e21c 100644
--- a/test/ArrayField_test.js
+++ b/test/ArrayField_test.js
@@ -517,7 +517,7 @@ describe("ArrayField", () => {
       expect(form.state.formData.multipleChoicesList).to.be.empty;
       expect(form.state.errors.length).to.equal(1);
       expect(form.state.errors[0].name).to.equal("minItems");
-      expect(form.state.errors[0].argument).to.equal(3);
+      expect(form.state.errors[0].params.limit).to.equal(3);
     });
 
     it("should honor given formData, even when it does not meet ths minItems-requirement", () => {
diff --git a/test/Form_test.js b/test/Form_test.js
index 68fb4746e..282134448 100644
--- a/test/Form_test.js
+++ b/test/Form_test.js
@@ -678,9 +678,7 @@ describe("Form", () => {
         });
 
         expect(comp.state.errors).to.have.length.of(1);
-        expect(comp.state.errors[0].stack).to.eql(
-          "instance is not of a type(s) number"
-        );
+        expect(comp.state.errors[0].stack).to.eql("should be number");
       });
     });
 
@@ -808,7 +806,7 @@ describe("Form", () => {
           });
 
           expect(comp.state.errorSchema).eql({
-            __errors: ["does not meet minimum length of 8"],
+            __errors: ["should NOT be shorter than 8 characters"],
           });
         });
 
@@ -822,7 +820,7 @@ describe("Form", () => {
           expect(node.querySelectorAll(".field-error")).to.have.length.of(1);
           expect(
             node.querySelector(".field-string .error-detail").textContent
-          ).eql("does not meet minimum length of 8");
+          ).eql("should NOT be shorter than 8 characters");
         });
       });
 
@@ -877,7 +875,7 @@ describe("Form", () => {
         Simulate.submit(node);
 
         expect(comp.state.errorSchema).eql({
-          __errors: ["does not meet minimum length of 8"],
+          __errors: ["should NOT be shorter than 8 characters"],
         });
       });
 
@@ -895,7 +893,7 @@ describe("Form", () => {
           sinon.match(value => {
             return (
               value.length === 1 &&
-              value[0].message === "does not meet minimum length of 8"
+              value[0].message === "should NOT be shorter than 8 characters"
             );
           })
         );
@@ -916,7 +914,7 @@ describe("Form", () => {
         const { comp } = createFormComponent(formProps);
 
         expect(comp.state.errorSchema).eql({
-          __errors: ["does not meet minimum length of 8"],
+          __errors: ["should NOT be shorter than 8 characters"],
         });
       });
 
@@ -926,7 +924,7 @@ describe("Form", () => {
         expect(node.querySelectorAll(".field-error")).to.have.length.of(1);
         expect(
           node.querySelector(".field-string .error-detail").textContent
-        ).eql("does not meet minimum length of 8");
+        ).eql("should NOT be shorter than 8 characters");
       });
     });
 
@@ -945,8 +943,8 @@ describe("Form", () => {
         const { comp } = createFormComponent(formProps);
         expect(comp.state.errorSchema).eql({
           __errors: [
-            "does not meet minimum length of 8",
-            'does not match pattern "d+"',
+            "should NOT be shorter than 8 characters",
+            'should match pattern "d+"',
           ],
         });
       });
@@ -958,8 +956,8 @@ describe("Form", () => {
         const errors = [].map.call(liNodes, li => li.textContent);
 
         expect(errors).eql([
-          "does not meet minimum length of 8",
-          'does not match pattern "d+"',
+          "should NOT be shorter than 8 characters",
+          'should match pattern "d+"',
         ]);
       });
     });
@@ -996,7 +994,7 @@ describe("Form", () => {
         expect(comp.state.errorSchema).eql({
           level1: {
             level2: {
-              __errors: ["does not meet minimum length of 8"],
+              __errors: ["should NOT be shorter than 8 characters"],
             },
           },
         });
@@ -1010,7 +1008,7 @@ describe("Form", () => {
 
         expect(node.querySelectorAll(".field-error")).to.have.length.of(1);
         expect(errorDetail.textContent).eql(
-          "does not meet minimum length of 8"
+          "should NOT be shorter than 8 characters"
         );
       });
     });
@@ -1034,7 +1032,7 @@ describe("Form", () => {
         const { comp } = createFormComponent(formProps);
 
         expect(comp.state.errorSchema).eql({
-          1: { __errors: ["does not meet minimum length of 4"] },
+          1: { __errors: ["should NOT be shorter than 4 characters"] },
         });
       });
 
@@ -1048,7 +1046,7 @@ describe("Form", () => {
         const errors = [].map.call(liNodes, li => li.textContent);
 
         expect(fieldNodes[1].classList.contains("field-error")).eql(true);
-        expect(errors).eql(["does not meet minimum length of 4"]);
+        expect(errors).eql(["should NOT be shorter than 4 characters"]);
       });
 
       it("should not denote errors on non impacted fields", () => {
@@ -1086,8 +1084,8 @@ describe("Form", () => {
 
         expect(comp.state.errorSchema).eql({
           level1: {
-            1: { __errors: ["does not meet minimum length of 4"] },
-            3: { __errors: ["does not meet minimum length of 4"] },
+            1: { __errors: ["should NOT be shorter than 4 characters"] },
+            3: { __errors: ["should NOT be shorter than 4 characters"] },
           },
         });
       });
@@ -1103,7 +1101,7 @@ describe("Form", () => {
         const liNodes = node.querySelectorAll(".field-string .error-detail li");
         const errors = [].map.call(liNodes, li => li.textContent);
 
-        expect(errors).eql(["does not meet minimum length of 4"]);
+        expect(errors).eql(["should NOT be shorter than 4 characters"]);
       });
     });
 
@@ -1136,10 +1134,10 @@ describe("Form", () => {
         expect(comp.state.errorSchema).eql({
           outer: {
             0: {
-              1: { __errors: ["does not meet minimum length of 4"] },
+              1: { __errors: ["should NOT be shorter than 4 characters"] },
             },
             1: {
-              0: { __errors: ["does not meet minimum length of 4"] },
+              0: { __errors: ["should NOT be shorter than 4 characters"] },
             },
           },
         });
@@ -1155,8 +1153,8 @@ describe("Form", () => {
 
         expect(errors).eql([
           null,
-          "does not meet minimum length of 4",
-          "does not meet minimum length of 4",
+          "should NOT be shorter than 4 characters",
+          "should NOT be shorter than 4 characters",
           null,
         ]);
       });
@@ -1188,7 +1186,7 @@ describe("Form", () => {
         expect(comp.state.errorSchema).eql({
           1: {
             foo: {
-              __errors: ["does not meet minimum length of 4"],
+              __errors: ["should NOT be shorter than 4 characters"],
             },
           },
         });
@@ -1204,7 +1202,7 @@ describe("Form", () => {
         const errors = [].map.call(liNodes, li => li.textContent);
 
         expect(fieldNodes[1].classList.contains("field-error")).eql(true);
-        expect(errors).eql(["does not meet minimum length of 4"]);
+        expect(errors).eql(["should NOT be shorter than 4 characters"]);
       });
     });
   });
diff --git a/test/StringField_test.js b/test/StringField_test.js
index aa22aab97..de55ddf89 100644
--- a/test/StringField_test.js
+++ b/test/StringField_test.js
@@ -607,24 +607,6 @@ describe("StringField", () => {
       expect(comp.state.errors).to.have.length.of(1);
     });
 
-    it("should properly reset field value when null is selected", () => {
-      const { comp, node } = createFormComponent({
-        schema: {
-          type: "string",
-          format: "date",
-        },
-        uiSchema,
-        liveValidate: true,
-      });
-
-      Simulate.change(node.querySelector("[type=date]"), {
-        target: { value: null },
-      });
-
-      expect(comp.state.formData).to.be.a("undefined");
-      expect(comp.state.errors).to.have.length.of(0);
-    });
-
     it("should render customized DateWidget", () => {
       const { node } = createFormComponent({
         schema: {
diff --git a/test/uiSchema_test.js b/test/uiSchema_test.js
index 50d5a020d..d46669a39 100644
--- a/test/uiSchema_test.js
+++ b/test/uiSchema_test.js
@@ -1,7 +1,7 @@
 import { expect } from "chai";
 import React from "react";
 import { Simulate } from "react-addons-test-utils";
-
+import SelectWidget from "../src/components/widgets/SelectWidget";
 import { createFormComponent, createSandbox } from "./test_utils";
 
 describe("uiSchema", () => {
@@ -19,14 +19,22 @@ describe("uiSchema", () => {
     const schema = {
       type: "object",
       properties: {
-        foo: { type: "string" },
-        bar: { type: "string" },
+        foo: {
+          type: "string",
+        },
+        bar: {
+          type: "string",
+        },
       },
     };
 
     const uiSchema = {
-      foo: { classNames: "class-for-foo" },
-      bar: { classNames: "class-for-bar another-for-bar" },
+      foo: {
+        classNames: "class-for-foo",
+      },
+      bar: {
+        classNames: "class-for-bar another-for-bar",
+      },
     };
 
     it("should apply custom class names to target widgets", () => {
@@ -75,21 +83,36 @@ describe("uiSchema", () => {
 
         widget = ({ label, options }) => <div id={label} style={options} />;
         widget.defaultProps = {
-          options: { background: "yellow", color: "green" },
+          options: {
+            background: "yellow",
+            color: "green",
+          },
         };
 
-        widgets = { widget };
+        widgets = {
+          widget,
+        };
 
-        // all fields in one schema to catch errors where options passed to one
-        // instance of a widget are persistent across all instances
+        // all fields in one schema to catch errors where options passed to one instance
+        // of a widget are persistent across all instances
         schema = {
           type: "object",
           properties: {
-            funcAll: { type: "string" },
-            funcNone: { type: "string" },
-            stringAll: { type: "string" },
-            stringNone: { type: "string" },
-            stringTel: { type: "string" },
+            funcAll: {
+              type: "string",
+            },
+            funcNone: {
+              type: "string",
+            },
+            stringAll: {
+              type: "string",
+            },
+            stringNone: {
+              type: "string",
+            },
+            stringTel: {
+              type: "string",
+            },
           },
         };
 
@@ -137,8 +160,14 @@ describe("uiSchema", () => {
 
       it("should log warning when deprecated ui:widget: {component, options} api is used", () => {
         createFormComponent({
-          schema: { type: "string" },
-          uiSchema: { "ui:widget": { component: "widget" } },
+          schema: {
+            type: "string",
+          },
+          uiSchema: {
+            "ui:widget": {
+              component: "widget",
+            },
+          },
           widgets,
         });
         expect(console.warn.calledWithMatch(/ui:widget object is deprecated/))
@@ -148,15 +177,23 @@ describe("uiSchema", () => {
       it("should cache MergedWidget instance", () => {
         expect(widget.MergedWidget).not.to.be.ok;
         createFormComponent({
-          schema: { type: "string" },
-          uiSchema: { "ui:widget": "widget" },
+          schema: {
+            type: "string",
+          },
+          uiSchema: {
+            "ui:widget": "widget",
+          },
           widgets,
         });
         const cached = widget.MergedWidget;
         expect(cached).to.be.ok;
         createFormComponent({
-          schema: { type: "string" },
-          uiSchema: { "ui:widget": "widget" },
+          schema: {
+            type: "string",
+          },
+          uiSchema: {
+            "ui:widget": "widget",
+          },
           widgets,
         });
         expect(widget.MergedWidget).to.equal(cached);
@@ -342,12 +379,47 @@ describe("uiSchema", () => {
         expect(node.querySelectorAll(".custom option")).to.have.length.of(2);
       });
     });
+    describe("enum fields disbaled options", () => {
+      const schema = {
+        type: "object",
+        properties: {
+          field: {
+            type: "string",
+            enum: ["foo", "bar"],
+          },
+        },
+      };
+      const uiSchema = {
+        field: {
+          "ui:widget": SelectWidget,
+          "ui:options": {
+            className: "custom",
+          },
+          "ui:enumDisabled": ["foo"],
+        },
+      };
+      it("should have atleast one option disabled", () => {
+        const { node } = createFormComponent({ schema, uiSchema });
+        const disabledOptionsLen = uiSchema.field["ui:enumDisabled"].length;
+        expect(node.querySelectorAll("option:disabled")).to.have.length.of(
+          disabledOptionsLen
+        );
+        expect(node.querySelectorAll("option:enabled")).to.have.length.of(
+          // Two options, one disabled, plus the placeholder
+          2 - disabledOptionsLen + 1
+        );
+      });
+    });
   });
 
   describe("ui:help", () => {
     it("should render the provided help text", () => {
-      const schema = { type: "string" };
-      const uiSchema = { "ui:help": "plop" };
+      const schema = {
+        type: "string",
+      };
+      const uiSchema = {
+        "ui:help": "plop",
+      };
 
       const { node } = createFormComponent({ schema, uiSchema });
 
@@ -357,8 +429,12 @@ describe("uiSchema", () => {
 
   describe("ui:title", () => {
     it("should render the provided title text", () => {
-      const schema = { type: "string" };
-      const uiSchema = { "ui:title": "plop" };
+      const schema = {
+        type: "string",
+      };
+      const uiSchema = {
+        "ui:title": "plop",
+      };
 
       const { node } = createFormComponent({ schema, uiSchema });
 
@@ -368,8 +444,12 @@ describe("uiSchema", () => {
 
   describe("ui:description", () => {
     it("should render the provided description text", () => {
-      const schema = { type: "string" };
-      const uiSchema = { "ui:description": "plop" };
+      const schema = {
+        type: "string",
+      };
+      const uiSchema = {
+        "ui:description": "plop",
+      };
 
       const { node } = createFormComponent({ schema, uiSchema });
 
@@ -378,8 +458,12 @@ describe("uiSchema", () => {
   });
 
   it("should accept a react element as help", () => {
-    const schema = { type: "string" };
-    const uiSchema = { "ui:help": <b>plop</b> };
+    const schema = {
+      type: "string",
+    };
+    const uiSchema = {
+      "ui:help": <b>plop</b>,
+    };
 
     const { node } = createFormComponent({ schema, uiSchema });
 
@@ -388,7 +472,10 @@ describe("uiSchema", () => {
 
   describe("ui:focus", () => {
     const shouldFocus = (schema, uiSchema, selector = "input", formData) => {
-      const props = { schema, uiSchema };
+      const props = {
+        schema,
+        uiSchema,
+      };
       if (typeof formData !== "undefined") {
         props.formData = formData;
       }
@@ -399,27 +486,47 @@ describe("uiSchema", () => {
 
     describe("number", () => {
       it("should focus on integer input", () => {
-        shouldFocus({ type: "integer" }, { "ui:autofocus": true });
+        shouldFocus(
+          {
+            type: "integer",
+          },
+          { "ui:autofocus": true }
+        );
       });
 
       it("should focus on integer input, updown widget", () => {
         shouldFocus(
-          { type: "integer" },
-          { "ui:widget": "updown", "ui:autofocus": true }
+          {
+            type: "integer",
+          },
+          {
+            "ui:widget": "updown",
+            "ui:autofocus": true,
+          }
         );
       });
 
       it("should focus on integer input, range widget", () => {
         shouldFocus(
-          { type: "integer" },
-          { "ui:widget": "range", "ui:autofocus": true }
+          {
+            type: "integer",
+          },
+          {
+            "ui:widget": "range",
+            "ui:autofocus": true,
+          }
         );
       });
 
       it("should focus on integer enum input", () => {
         shouldFocus(
-          { type: "integer", enum: [1, 2, 3] },
-          { "ui:autofocus": true },
+          {
+            type: "integer",
+            enum: [1, 2, 3],
+          },
+          {
+            "ui:autofocus": true,
+          },
           "select"
         );
       });
@@ -427,48 +534,77 @@ describe("uiSchema", () => {
 
     describe("string", () => {
       it("should focus on text input", () => {
-        shouldFocus({ type: "string" }, { "ui:autofocus": true });
+        shouldFocus(
+          {
+            type: "string",
+          },
+          { "ui:autofocus": true }
+        );
       });
 
       it("should focus on textarea", () => {
         shouldFocus(
-          { type: "string" },
-          { "ui:widget": "textarea", "ui:autofocus": true },
+          {
+            type: "string",
+          },
+          {
+            "ui:widget": "textarea",
+            "ui:autofocus": true,
+          },
           "textarea"
         );
       });
 
       it("should focus on password input", () => {
         shouldFocus(
-          { type: "string" },
-          { "ui:widget": "password", "ui:autofocus": true }
+          {
+            type: "string",
+          },
+          {
+            "ui:widget": "password",
+            "ui:autofocus": true,
+          }
         );
       });
 
       it("should focus on color input", () => {
         shouldFocus(
-          { type: "string" },
-          { "ui:widget": "color", "ui:autofocus": true }
+          {
+            type: "string",
+          },
+          {
+            "ui:widget": "color",
+            "ui:autofocus": true,
+          }
         );
       });
 
       it("should focus on email input", () => {
         shouldFocus(
-          { type: "string", format: "email" },
+          {
+            type: "string",
+            format: "email",
+          },
           { "ui:autofocus": true }
         );
       });
 
       it("should focus on uri input", () => {
         shouldFocus(
-          { type: "string", format: "uri" },
+          {
+            type: "string",
+            format: "uri",
+          },
           { "ui:autofocus": true }
         );
       });
 
       it("should focus on data-url input", () => {
         shouldFocus(
-          { type: "string", format: "data-url" },
+          {
+            type: "string",
+            format: "data-url",
+          },
           { "ui:autofocus": true }
         );
       });
@@ -477,30 +613,48 @@ describe("uiSchema", () => {
     describe("object", () => {
       it("should focus on date input", () => {
         shouldFocus(
-          { type: "string", format: "date" },
+          {
+            type: "string",
+            format: "date",
+          },
           { "ui:autofocus": true }
         );
       });
 
       it("should focus on date-time input", () => {
         shouldFocus(
-          { type: "string", format: "date-time" },
+          {
+            type: "string",
+            format: "date-time",
+          },
           { "ui:autofocus": true }
         );
       });
 
       it("should focus on alt-date input", () => {
         shouldFocus(
-          { type: "string", format: "date" },
-          { "ui:widget": "alt-date", "ui:autofocus": true },
+          {
+            type: "string",
+            format: "date",
+          },
+          {
+            "ui:widget": "alt-date",
+            "ui:autofocus": true,
+          },
           "select"
         );
       });
 
       it("should focus on alt-date-time input", () => {
         shouldFocus(
-          { type: "string", format: "date-time" },
-          { "ui:widget": "alt-datetime", "ui:autofocus": true },
+          {
+            type: "string",
+            format: "date-time",
+          },
+          {
+            "ui:widget": "alt-datetime",
+            "ui:autofocus": true,
+          },
           "select"
         );
       });
@@ -509,15 +663,29 @@ describe("uiSchema", () => {
     describe("array", () => {
       it("should focus on multiple files input", () => {
         shouldFocus(
-          { type: "array", items: { type: "string", format: "data-url" } },
+          {
+            type: "array",
+            items: {
+              type: "string",
+              format: "data-url",
+            },
+          },
           { "ui:autofocus": true }
         );
       });
 
       it("should focus on first item of a list of strings", () => {
         shouldFocus(
-          { type: "array", items: { type: "string", default: "foo" } },
-          { "ui:autofocus": true },
+          {
+            type: "array",
+            items: {
+              type: "string",
+              default: "foo",
+            },
+          },
+          {
+            "ui:autofocus": true,
+          },
           "input",
           ["foo", "bar"]
         );
@@ -527,10 +695,16 @@ describe("uiSchema", () => {
         shouldFocus(
           {
             type: "array",
-            items: { type: "string", enum: ["foo", "bar"] },
+            items: {
+              type: "string",
+              enum: ["foo", "bar"],
+            },
             uniqueItems: true,
           },
-          { "ui:widget": "checkboxes", "ui:autofocus": true },
+          {
+            "ui:widget": "checkboxes",
+            "ui:autofocus": true,
+          },
           "input",
           ["bar"]
         );
@@ -539,20 +713,35 @@ describe("uiSchema", () => {
 
     describe("boolean", () => {
       it("should focus on checkbox input", () => {
-        shouldFocus({ type: "boolean" }, { "ui:autofocus": true });
+        shouldFocus(
+          {
+            type: "boolean",
+          },
+          { "ui:autofocus": true }
+        );
       });
 
       it("should focus on radio input", () => {
         shouldFocus(
-          { type: "boolean" },
-          { "ui:widget": "radio", "ui:autofocus": true }
+          {
+            type: "boolean",
+          },
+          {
+            "ui:widget": "radio",
+            "ui:autofocus": true,
+          }
         );
       });
 
       it("should focus on select input", () => {
         shouldFocus(
-          { type: "boolean" },
-          { "ui:widget": "select", "ui:autofocus": true },
+          {
+            type: "boolean",
+          },
+          {
+            "ui:widget": "select",
+            "ui:autofocus": true,
+          },
           "select"
         );
       });
@@ -619,7 +808,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("textarea"), {
-          target: { value: "b" },
+          target: {
+            value: "b",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: "b" });
@@ -668,7 +859,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=password]"), {
-          target: { value: "b" },
+          target: {
+            value: "b",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: "b" });
@@ -716,7 +909,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=color]"), {
-          target: { value: "#001122" },
+          target: {
+            value: "#001122",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: "#001122" });
@@ -808,7 +1003,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelectorAll("[type=radio]")[1], {
-          target: { checked: true },
+          target: {
+            checked: true,
+          },
         });
 
         expect(comp.state.formData).eql({ foo: "b" });
@@ -864,7 +1061,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=number]"), {
-          target: { value: "6.28" },
+          target: {
+            value: "6.28",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 6.28 });
@@ -892,7 +1091,9 @@ describe("uiSchema", () => {
             minimum: 0,
             maximum: 0,
           };
-          const uiSchema = { "ui:widget": "updown" };
+          const uiSchema = {
+            "ui:widget": "updown",
+          };
           const { node } = createFormComponent({ schema, uiSchema });
           input = node.querySelector("[type=number]");
 
@@ -941,7 +1142,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=range]"), {
-          target: { value: "6.28" },
+          target: {
+            value: "6.28",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 6.28 });
@@ -969,7 +1172,9 @@ describe("uiSchema", () => {
             minimum: 0,
             maximum: 0,
           };
-          const uiSchema = { "ui:widget": "range" };
+          const uiSchema = {
+            "ui:widget": "range",
+          };
           const { node } = createFormComponent({ schema, uiSchema });
           input = node.querySelector("[type=range]");
 
@@ -1028,7 +1233,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelectorAll("[type=radio]")[2], {
-          target: { checked: true },
+          target: {
+            checked: true,
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 1.4142 });
@@ -1119,7 +1326,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=number]"), {
-          target: { value: "6" },
+          target: {
+            value: "6",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 6 });
@@ -1161,7 +1370,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelector("[type=range]"), {
-          target: { value: "6" },
+          target: {
+            value: "6",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 6 });
@@ -1213,7 +1424,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelectorAll("[type=radio]")[1], {
-          target: { checked: true },
+          target: {
+            checked: true,
+          },
         });
 
         expect(comp.state.formData).eql({ foo: 2 });
@@ -1316,7 +1529,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelectorAll("[type=radio]")[1], {
-          target: { checked: true },
+          target: {
+            checked: true,
+          },
         });
 
         expect(comp.state.formData).eql({ foo: false });
@@ -1332,7 +1547,9 @@ describe("uiSchema", () => {
         });
 
         Simulate.change(node.querySelectorAll("[type=radio]")[0], {
-          target: { checked: true },
+          target: {
+            checked: true,
+          },
         });
 
         expect(comp.state.formData).eql({ foo: true });
@@ -1370,7 +1587,9 @@ describe("uiSchema", () => {
 
         Simulate.change(node.querySelector("select"), {
           // DOM option change events always return strings
-          target: { value: "true" },
+          target: {
+            value: "true",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: true });
@@ -1387,7 +1606,9 @@ describe("uiSchema", () => {
 
         Simulate.change(node.querySelector("select"), {
           // DOM option change events always return strings
-          target: { value: "false" },
+          target: {
+            value: "false",
+          },
         });
 
         expect(comp.state.formData).eql({ foo: false });
@@ -1438,11 +1659,17 @@ describe("uiSchema", () => {
       const schema = {
         type: "object",
         properties: {
-          foo: { type: "string" },
-          bar: { type: "string" },
+          foo: {
+            type: "string",
+          },
+          bar: {
+            type: "string",
+          },
         },
       };
-      const uiSchema = { "ui:rootFieldId": "myform" };
+      const uiSchema = {
+        "ui:rootFieldId": "myform",
+      };
       const { node } = createFormComponent({ schema, uiSchema });
 
       const ids = [].map.call(
@@ -1455,9 +1682,13 @@ describe("uiSchema", () => {
     it("should use a custom root field id for arrays", () => {
       const schema = {
         type: "array",
-        items: { type: "string" },
+        items: {
+          type: "string",
+        },
+      };
+      const uiSchema = {
+        "ui:rootFieldId": "myform",
       };
-      const uiSchema = { "ui:rootFieldId": "myform" };
       const { node } = createFormComponent({
         schema,
         uiSchema,
@@ -1477,16 +1708,31 @@ describe("uiSchema", () => {
         items: {
           type: "object",
           properties: {
-            foo: { type: "string" },
-            bar: { type: "string" },
+            foo: {
+              type: "string",
+            },
+            bar: {
+              type: "string",
+            },
           },
         },
       };
-      const uiSchema = { "ui:rootFieldId": "myform" };
+      const uiSchema = {
+        "ui:rootFieldId": "myform",
+      };
       const { node } = createFormComponent({
         schema,
         uiSchema,
-        formData: [{ foo: "foo1", bar: "bar1" }, { foo: "foo2", bar: "bar2" }],
+        formData: [
+          {
+            foo: "foo1",
+            bar: "bar1",
+          },
+          {
+            foo: "foo2",
+            bar: "bar2",
+          },
+        ],
       });
 
       const ids = [].map.call(
@@ -1508,8 +1754,15 @@ describe("uiSchema", () => {
         let node;
 
         beforeEach(() => {
-          const schema = { type: "array", items: { type: "string" } };
-          const uiSchema = { "ui:disabled": true };
+          const schema = {
+            type: "array",
+            items: {
+              type: "string",
+            },
+          };
+          const uiSchema = {
+            "ui:disabled": true,
+          };
           const formData = ["a", "b"];
 
           let rendered = createFormComponent({ schema, uiSchema, formData });
@@ -1542,11 +1795,17 @@ describe("uiSchema", () => {
           const schema = {
             type: "object",
             properties: {
-              foo: { type: "string" },
-              bar: { type: "string" },
+              foo: {
+                type: "string",
+              },
+              bar: {
+                type: "string",
+              },
             },
           };
-          const uiSchema = { "ui:disabled": true };
+          const uiSchema = {
+            "ui:disabled": true,
+          };
 
           let rendered = createFormComponent({ schema, uiSchema });
           node = rendered.node;
@@ -1571,15 +1830,22 @@ describe("uiSchema", () => {
       it("should disable a text widget", () => {
         shouldBeDisabled(
           "input[type=text]",
-          { type: "string" },
+          {
+            type: "string",
+          },
           { "ui:disabled": true }
         );
       });
 
       it("should disabled a file widget", () => {
         const { node } = createFormComponent({
-          schema: { type: "string", format: "data-url" },
-          uiSchema: { "ui:disabled": true },
+          schema: {
+            type: "string",
+            format: "data-url",
+          },
+          uiSchema: {
+            "ui:disabled": true,
+          },
         });
         expect(
           node.querySelector("input[type=file]").hasAttribute("disabled")
@@ -1589,15 +1855,22 @@ describe("uiSchema", () => {
       it("should disable a textarea widget", () => {
         shouldBeDisabled(
           "textarea",
-          { type: "string" },
-          { "ui:disabled": true, "ui:widget": "textarea" }
+          {
+            type: "string",
+          },
+          {
+            "ui:disabled": true,
+            "ui:widget": "textarea",
+          }
         );
       });
 
       it("should disable a number text widget", () => {
         shouldBeDisabled(
           "input[type=text]",
-          { type: "number" },
+          {
+            type: "number",
+          },
           { "ui:disabled": true }
         );
       });
@@ -1605,23 +1878,36 @@ describe("uiSchema", () => {
       it("should disable a number widget", () => {
         shouldBeDisabled(
           "input[type=number]",
-          { type: "number" },
-          { "ui:disabled": true, "ui:widget": "updown" }
+          {
+            type: "number",
+          },
+          {
+            "ui:disabled": true,
+            "ui:widget": "updown",
+          }
         );
       });
 
       it("should disable a range widget", () => {
         shouldBeDisabled(
           "input[type=range]",
-          { type: "number" },
-          { "ui:disabled": true, "ui:widget": "range" }
+          {
+            type: "number",
+          },
+          {
+            "ui:disabled": true,
+            "ui:widget": "range",
+          }
         );
       });
 
       it("should disable a select widget", () => {
         shouldBeDisabled(
           "select",
-          { type: "string", enum: ["a", "b"] },
+          {
+            type: "string",
+            enum: ["a", "b"],
+          },
           { "ui:disabled": true }
         );
       });
@@ -1629,7 +1915,9 @@ describe("uiSchema", () => {
       it("should disable a checkbox widget", () => {
         shouldBeDisabled(
           "input[type=checkbox]",
-          { type: "boolean" },
+          {
+            type: "boolean",
+          },
           { "ui:disabled": true }
         );
       });
@@ -1637,15 +1925,23 @@ describe("uiSchema", () => {
       it("should disable a radio widget", () => {
         shouldBeDisabled(
           "input[type=radio]",
-          { type: "boolean" },
-          { "ui:disabled": true, "ui:widget": "radio" }
+          {
+            type: "boolean",
+          },
+          {
+            "ui:disabled": true,
+            "ui:widget": "radio",
+          }
         );
       });
 
       it("should disable a color widget", () => {
         shouldBeDisabled(
           "input[type=color]",
-          { type: "string", format: "color" },
+          {
+            type: "string",
+            format: "color",
+          },
           { "ui:disabled": true }
         );
       });
@@ -1653,15 +1949,23 @@ describe("uiSchema", () => {
       it("should disable a password widget", () => {
         shouldBeDisabled(
           "input[type=password]",
-          { type: "string" },
-          { "ui:disabled": true, "ui:widget": "password" }
+          {
+            type: "string",
+          },
+          {
+            "ui:disabled": true,
+            "ui:widget": "password",
+          }
         );
       });
 
       it("should disable an email widget", () => {
         shouldBeDisabled(
           "input[type=email]",
-          { type: "string", format: "email" },
+          {
+            type: "string",
+            format: "email",
+          },
           { "ui:disabled": true }
         );
       });
@@ -1669,7 +1973,10 @@ describe("uiSchema", () => {
       it("should disable a date widget", () => {
         shouldBeDisabled(
           "input[type=date]",
-          { type: "string", format: "date" },
+          {
+            type: "string",
+            format: "date",
+          },
           { "ui:disabled": true }
         );
       });
@@ -1677,15 +1984,24 @@ describe("uiSchema", () => {
       it("should disable a datetime widget", () => {
         shouldBeDisabled(
           "input[type=datetime-local]",
-          { type: "string", format: "date-time" },
+          {
+            type: "string",
+            format: "date-time",
+          },
           { "ui:disabled": true }
         );
       });
 
       it("should disable an alternative date widget", () => {
         const { node } = createFormComponent({
-          schema: { type: "string", format: "date" },
-          uiSchema: { "ui:disabled": true, "ui:widget": "alt-date" },
+          schema: {
+            type: "string",
+            format: "date",
+          },
+          uiSchema: {
+            "ui:disabled": true,
+            "ui:widget": "alt-date",
+          },
         });
 
         const disabled = [].map.call(
@@ -1697,8 +2013,14 @@ describe("uiSchema", () => {
 
       it("should disable an alternative datetime widget", () => {
         const { node } = createFormComponent({
-          schema: { type: "string", format: "date-time" },
-          uiSchema: { "ui:disabled": true, "ui:widget": "alt-datetime" },
+          schema: {
+            type: "string",
+            format: "date-time",
+          },
+          uiSchema: {
+            "ui:disabled": true,
+            "ui:widget": "alt-datetime",
+          },
         });
 
         const disabled = [].map.call(
@@ -1716,8 +2038,15 @@ describe("uiSchema", () => {
         let node;
 
         beforeEach(() => {
-          const schema = { type: "array", items: { type: "string" } };
-          const uiSchema = { "ui:readonly": true };
+          const schema = {
+            type: "array",
+            items: {
+              type: "string",
+            },
+          };
+          const uiSchema = {
+            "ui:readonly": true,
+          };
           const formData = ["a", "b"];
 
           let rendered = createFormComponent({ schema, uiSchema, formData });
@@ -1750,11 +2079,17 @@ describe("uiSchema", () => {
           const schema = {
             type: "object",
             properties: {
-              foo: { type: "string" },
-              bar: { type: "string" },
+              foo: {
+                type: "string",
+              },
+              bar: {
+                type: "string",
+              },
             },
           };
-          const uiSchema = { "ui:readonly": true };
+          const uiSchema = {
+            "ui:readonly": true,
+          };
 
           let rendered = createFormComponent({ schema, uiSchema });
           node = rendered.node;
@@ -1783,7 +2118,9 @@ describe("uiSchema", () => {
       it("should mark as readonly a text widget", () => {
         shouldBeReadonly(
           "input[type=text]",
-          { type: "string" },
+          {
+            type: "string",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1791,8 +2128,13 @@ describe("uiSchema", () => {
       it("should mark as readonly a file widget", () => {
         // We mark a file widget as readonly by disabling it.
         const { node } = createFormComponent({
-          schema: { type: "string", format: "data-url" },
-          uiSchema: { "ui:readonly": true },
+          schema: {
+            type: "string",
+            format: "data-url",
+          },
+          uiSchema: {
+            "ui:readonly": true,
+          },
         });
         expect(
           node.querySelector("input[type=file]").hasAttribute("disabled")
@@ -1802,15 +2144,22 @@ describe("uiSchema", () => {
       it("should mark as readonly a textarea widget", () => {
         shouldBeReadonly(
           "textarea",
-          { type: "string" },
-          { "ui:readonly": true, "ui:widget": "textarea" }
+          {
+            type: "string",
+          },
+          {
+            "ui:readonly": true,
+            "ui:widget": "textarea",
+          }
         );
       });
 
       it("should mark as readonly a number text widget", () => {
         shouldBeReadonly(
           "input[type=text]",
-          { type: "number" },
+          {
+            type: "number",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1818,23 +2167,36 @@ describe("uiSchema", () => {
       it("should mark as readonly a number widget", () => {
         shouldBeReadonly(
           "input[type=number]",
-          { type: "number" },
-          { "ui:readonly": true, "ui:widget": "updown" }
+          {
+            type: "number",
+          },
+          {
+            "ui:readonly": true,
+            "ui:widget": "updown",
+          }
         );
       });
 
       it("should mark as readonly a range widget", () => {
         shouldBeReadonly(
           "input[type=range]",
-          { type: "number" },
-          { "ui:readonly": true, "ui:widget": "range" }
+          {
+            type: "number",
+          },
+          {
+            "ui:readonly": true,
+            "ui:widget": "range",
+          }
         );
       });
 
       it("should mark readonly as disabled on a select widget", () => {
         shouldBeDisabled(
           "select",
-          { type: "string", enum: ["a", "b"] },
+          {
+            type: "string",
+            enum: ["a", "b"],
+          },
           { "ui:readonly": true }
         );
       });
@@ -1842,7 +2204,10 @@ describe("uiSchema", () => {
       it("should mark as readonly a color widget", () => {
         shouldBeReadonly(
           "input[type=color]",
-          { type: "string", format: "color" },
+          {
+            type: "string",
+            format: "color",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1850,15 +2215,23 @@ describe("uiSchema", () => {
       it("should mark as readonly a password widget", () => {
         shouldBeReadonly(
           "input[type=password]",
-          { type: "string" },
-          { "ui:readonly": true, "ui:widget": "password" }
+          {
+            type: "string",
+          },
+          {
+            "ui:readonly": true,
+            "ui:widget": "password",
+          }
         );
       });
 
       it("should mark as readonly a url widget", () => {
         shouldBeReadonly(
           "input[type=url]",
-          { type: "string", format: "uri" },
+          {
+            type: "string",
+            format: "uri",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1866,7 +2239,10 @@ describe("uiSchema", () => {
       it("should mark as readonly an email widget", () => {
         shouldBeReadonly(
           "input[type=email]",
-          { type: "string", format: "email" },
+          {
+            type: "string",
+            format: "email",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1874,7 +2250,10 @@ describe("uiSchema", () => {
       it("should mark as readonly a date widget", () => {
         shouldBeReadonly(
           "input[type=date]",
-          { type: "string", format: "date" },
+          {
+            type: "string",
+            format: "date",
+          },
           { "ui:readonly": true }
         );
       });
@@ -1882,15 +2261,24 @@ describe("uiSchema", () => {
       it("should mark as readonly a datetime widget", () => {
         shouldBeReadonly(
           "input[type=datetime-local]",
-          { type: "string", format: "date-time" },
+          {
+            type: "string",
+            format: "date-time",
+          },
           { "ui:readonly": true }
         );
       });
 
       it("should mark readonly as disabled on an alternative date widget", () => {
         const { node } = createFormComponent({
-          schema: { type: "string", format: "date" },
-          uiSchema: { "ui:readonly": true, "ui:widget": "alt-date" },
+          schema: {
+            type: "string",
+            format: "date",
+          },
+          uiSchema: {
+            "ui:readonly": true,
+            "ui:widget": "alt-date",
+          },
         });
 
         const readonly = [].map.call(node.querySelectorAll("select"), node =>
@@ -1901,8 +2289,14 @@ describe("uiSchema", () => {
 
       it("should mark readonly as disabled on an alternative datetime widget", () => {
         const { node } = createFormComponent({
-          schema: { type: "string", format: "date-time" },
-          uiSchema: { "ui:readonly": true, "ui:widget": "alt-datetime" },
+          schema: {
+            type: "string",
+            format: "date-time",
+          },
+          uiSchema: {
+            "ui:readonly": true,
+            "ui:widget": "alt-datetime",
+          },
         });
 
         const readonly = [].map.call(node.querySelectorAll("select"), node =>
diff --git a/test/validate_test.js b/test/validate_test.js
index e8ebfe780..c5c274833 100644
--- a/test/validate_test.js
+++ b/test/validate_test.js
@@ -31,17 +31,15 @@ describe("Validation", () => {
 
       it("should return an error list", () => {
         expect(errors).to.have.length.of(2);
-        expect(errors[0].message).eql("is not of a type(s) string");
-        expect(errors[1].message).eql("is not of a type(s) string");
+        expect(errors[0].message).eql("should be string");
+        expect(errors[1].message).eql("should be string");
       });
 
       it("should return an errorSchema", () => {
         expect(errorSchema.foo.__errors).to.have.length.of(1);
-        expect(errorSchema.foo.__errors[0]).eql("is not of a type(s) string");
+        expect(errorSchema.foo.__errors[0]).eql("should be string");
         expect(errorSchema[illFormedKey].__errors).to.have.length.of(1);
-        expect(errorSchema[illFormedKey].__errors[0]).eql(
-          "is not of a type(s) string"
-        );
+        expect(errorSchema[illFormedKey].__errors[0]).eql("should be string");
       });
     });
 
@@ -179,13 +177,13 @@ describe("Validation", () => {
 
         it("should validate a required field", () => {
           expect(comp.state.errors).to.have.length.of(1);
-          expect(comp.state.errors[0].message).eql('requires property "foo"');
+          expect(comp.state.errors[0].message).eql("is a required property");
         });
 
         it("should render errors", () => {
           expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
           expect(node.querySelector(".errors li").textContent).eql(
-            'instance requires property "foo"'
+            ".foo is a required property"
           );
         });
 
@@ -193,7 +191,7 @@ describe("Validation", () => {
           sinon.assert.calledWith(
             onError,
             sinon.match(errors => {
-              return errors[0].message === 'requires property "foo"';
+              return errors[0].message === "is a required property";
             })
           );
         });
@@ -231,14 +229,14 @@ describe("Validation", () => {
         it("should validate a minLength field", () => {
           expect(comp.state.errors).to.have.length.of(1);
           expect(comp.state.errors[0].message).eql(
-            "does not meet minimum length of 10"
+            "should NOT be shorter than 10 characters"
           );
         });
 
         it("should render errors", () => {
           expect(node.querySelectorAll(".errors li")).to.have.length.of(1);
           expect(node.querySelector(".errors li").textContent).eql(
-            "instance.foo does not meet minimum length of 10"
+            ".foo should NOT be shorter than 10 characters"
           );
         });
 
@@ -246,7 +244,9 @@ describe("Validation", () => {
           sinon.assert.calledWith(
             onError,
             sinon.match(errors => {
-              return errors[0].message === "does not meet minimum length of 10";
+              return (
+                errors[0].message === "should NOT be shorter than 10 characters"
+              );
             })
           );
         });
@@ -361,7 +361,7 @@ describe("Validation", () => {
           },
           pass2: {
             __errors: [
-              "does not meet minimum length of 3",
+              "should NOT be shorter than 3 characters",
               "Passwords don't match",
             ],
           },
@@ -488,7 +488,7 @@ describe("Validation", () => {
 
         it("should validate a required field", () => {
           expect(comp.state.errors).to.have.length.of(1);
-          expect(comp.state.errors[0].message).eql('requires property "foo"');
+          expect(comp.state.errors[0].message).eql("is a required property");
         });
 
         it("should not render error list if showErrorList prop true", () => {
@@ -499,7 +499,7 @@ describe("Validation", () => {
           sinon.assert.calledWith(
             onError,
             sinon.match(errors => {
-              return errors[0].message === 'requires property "foo"';
+              return errors[0].message === "is a required property";
             })
           );
         });
@@ -509,7 +509,6 @@ describe("Validation", () => {
     describe("Custom ErrorList", () => {
       const schema = {
         type: "string",
-        required: true,
         minLength: 1,
       };
 
@@ -550,7 +549,7 @@ describe("Validation", () => {
         );
         expect(node.querySelectorAll(".ErrorSchema")).to.have.length.of(1);
         expect(node.querySelector(".ErrorSchema").textContent).eql(
-          "is required"
+          "should be string"
         );
         expect(node.querySelectorAll(".Schema")).to.have.length.of(1);
         expect(node.querySelector(".Schema").textContent).eql("string");