Repository: mozilla-services/react-jsonschema-form
Pull request #707 (2017-09-29 19:13:25)

Try to reduce maintanence effort of RJSF by explicitly documenting some philosophy
### Reasons for making this change

I'd like to add @olzraiti and @knilink as maintainers to the project, but I thought that before I did that, I should try to formalize what I understood to be the RJSF "philosophy". It is my hope that this can give all maintainers a shared understanding of what react-jsonschema-form is and should be, and therefore give all maintainers confidence to merge a PR if it conforms to that philosophy, even if other maintainers are unavailable. Additionally, I'd like for contributors to be able to tell, before writing a single line of code or even understanding any of the rjsf architecture, whether their PR is likely to be accepted or not.

The philosophical notes are things I took from my notes and are probably not complete. People with a deeper understanding of the project than I have are welcome to offer their own bits of rjsf design or philosophy.

While I was here, I decided that maybe adding a couple of FAQ entries will reduce the maintenance burden of this project by clarifying what the status of certain much-demanded features is.

### Checklist

* [x] **I'm updating documentation**
  - [x] I've checked the rendering of the Markdown text I've added
  - [x] If I'm adding a new section, I've updated the Table of Content


glasserc: I've now invited the above-mentioned contributors to collaborate on this repo. Be sure to use your powers for good instead of evil, etc. Also, with great maintainership comes great renovation. I've been thinking that maybe we should revise the pull request template and issue template, what do you think?

Patch:
diff --git a/README.md b/README.md
index e0c5c8fae..b3350e3dc 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
 
 ## Table of Contents
 
+  - [Philosophy](#philosophy)
   - [Installation](#installation)
      - [As a npm-based project dependency](#as-a-npm-based-project-dependency)
      - [As a script served from a CDN](#as-a-script-served-from-a-cdn)
@@ -41,6 +42,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)
@@ -88,10 +90,17 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
      - [Development server](#development-server)
      - [Tests](#tests)
         - [TDD](#tdd)
+  - [FAQ](#faq)
   - [License](#license)
 
 ---
 
+## Philosophy
+
+react-jsonschema-form is meant to automatically generate a React form based on a [JSON Schema](http://json-schema.org/). It is a major component in the [kinto-admin](https://github.com/Kinto/kinto-admin/). If you want to generate a form for any data, sight unseen, simply given a JSON schema, react-jsonschema-form may be for you. If you have a priori knowledge of your data and want a toolkit for generating forms for it, you might look elsewhere.
+
+react-jsonschema-form validates that the data conforms to the given schema, but doesn't prevent the user from inputing data that doesn't fit (for example, stripping non-numbers from a number field, or not letting the user add values to an array that is already "full".
+
 ## Installation
 
 Requires React 15.0.0+.
@@ -196,7 +205,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 +218,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 +333,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 +453,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 +553,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 +830,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 +884,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 +904,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 +949,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
 
@@ -1469,6 +1503,8 @@ This component follows [JSON Schema](http://json-schema.org/documentation.html)
 
 * `additionalItems` keyword for arrays
     This keyword works when `items` is an array. `additionalItems: true` is not supported because there's no widget to represent an item of any type. In this case it will be treated as no additional items allowed. `additionalItems` being a valid schema is supported.
+* `anyOf`, `allOf`, and `oneOf`, or multiple `types` (i.e. `"type": ["string", "array"]`
+    Nobody yet has come up with a PR that adds this feature with a simple and easy-to-understand UX.
 
 ## Tips and tricks
 
@@ -1536,6 +1572,16 @@ $ npm publish
 $ git push --tags origin
 ```
 
+## FAQ
+
+### Q: Does rjsf support `oneOf`, `anyOf`, multiple types in an array, etc.?
+
+A: Not yet, but perhaps you will be the person whose PR will finally add the feature in a way that gets merged. For inspiration, see [#329](https://github.com/mozilla-services/react-jsonschema-form/pull/329) or [#417](https://github.com/mozilla-services/react-jsonschema-form/pull/417). See also: [#52](https://github.com/mozilla-services/react-jsonschema-form/issues/52), [#151](https://github.com/mozilla-services/react-jsonschema-form/issues/151), [#171](https://github.com/mozilla-services/react-jsonschema-form/issues/171), [#200](https://github.com/mozilla-services/react-jsonschema-form/issues/200), [#282](https://github.com/mozilla-services/react-jsonschema-form/issues/282), [#302](https://github.com/mozilla-services/react-jsonschema-form/pull/302), [#330](https://github.com/mozilla-services/react-jsonschema-form/issues/330), [#430](https://github.com/mozilla-services/react-jsonschema-form/issues/430), [#522](https://github.com/mozilla-services/react-jsonschema-form/issues/522), [#538](https://github.com/mozilla-services/react-jsonschema-form/issues/538), [#551](https://github.com/mozilla-services/react-jsonschema-form/issues/551), [#552](https://github.com/mozilla-services/react-jsonschema-form/issues/552), or [#648](https://github.com/mozilla-services/react-jsonschema-form/issues/648).
+
+### Q: Will react-jsonschema-form support Material, Ant-Design, Foundation, or [some other specific widget library or frontend style]?
+
+A: Probably not. We use Bootstrap v3 and it works fine for our needs. We would like for react-jsonschema-form to support other frameworks, we just don't want to support them ourselves. Ideally, these frontend styles could be added to react-jsonschema-form with a third-party library. If there is a technical limitation preventing this, please consider opening a PR. See also: [#91](https://github.com/mozilla-services/react-jsonschema-form/issues/91), [#99](https://github.com/mozilla-services/react-jsonschema-form/issues/99), [#125](https://github.com/mozilla-services/react-jsonschema-form/issues/125), [#237](https://github.com/mozilla-services/react-jsonschema-form/issues/237), [#287](https://github.com/mozilla-services/react-jsonschema-form/issues/287), [#299](https://github.com/mozilla-services/react-jsonschema-form/issues/299), [#440](https://github.com/mozilla-services/react-jsonschema-form/issues/440), [#461](https://github.com/mozilla-services/react-jsonschema-form/issues/461), [#546](https://github.com/mozilla-services/react-jsonschema-form/issues/546), [#555](https://github.com/mozilla-services/react-jsonschema-form/issues/555), [#626](https://github.com/mozilla-services/react-jsonschema-form/issues/626), and [#623](https://github.com/mozilla-services/react-jsonschema-form/pull/623).
+
 ## License
 
 Apache 2
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/playground/samples/customObject.js b/playground/samples/customObject.js
index 1c85e6ffe..8779c3451 100644
--- a/playground/samples/customObject.js
+++ b/playground/samples/customObject.js
@@ -6,7 +6,11 @@ function ObjectFieldTemplate({ TitleField, properties, title, description }) {
       <TitleField title={title} />
       <div className="row">
         {properties.map(prop => (
-          <div className="col-lg-2 col-md-4 col-sm-6 col-xs-12">{prop}</div>
+          <div
+            className="col-lg-2 col-md-4 col-sm-6 col-xs-12"
+            key={prop.content.key}>
+            {prop.content}
+          </div>
         ))}
       </div>
       {description}
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");