Skip to content

Commit

Permalink
Infer type from enum if a type is not provided to SelectWidget (#1100)
Browse files Browse the repository at this point in the history
Connects to #1098

Change-type: minor
Signed-off-by: Lucian <lucian.buzzo@gmail.com>
  • Loading branch information
LucianBuzzo authored and edi9999 committed Dec 13, 2018
1 parent 1ad9a15 commit 0ac0da7
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
6 changes: 5 additions & 1 deletion src/components/fields/StringField.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ if (process.env.NODE_ENV !== "production") {
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
formData: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
formData: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
]),
registry: PropTypes.shape({
widgets: PropTypes.objectOf(
PropTypes.oneOfType([PropTypes.func, PropTypes.object])
Expand Down
17 changes: 15 additions & 2 deletions src/components/widgets/SelectWidget.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React from "react";
import PropTypes from "prop-types";

import { asNumber } from "../../utils";
import { asNumber, guessType } from "../../utils";

const nums = new Set(["number", "integer"]);

/**
* This is a silly limitation in the DOM where option change event values are
* always retrieved as strings.
*/
function processValue({ type, items }, value) {
function processValue(schema, value) {
// "enum" is a reserved word, so only "type" and "items" can be destructured
const { type, items } = schema;
if (value === "") {
return undefined;
} else if (type === "array" && items && nums.has(items.type)) {
Expand All @@ -19,6 +21,17 @@ function processValue({ type, items }, value) {
} else if (type === "number") {
return asNumber(value);
}

// If type is undefined, but an enum is present, try and infer the type from
// the enum values
if (schema.enum) {
if (schema.enum.every(x => guessType(x) === "number")) {
return asNumber(value);
} else if (schema.enum.every(x => guessType(x) === "boolean")) {
return value === "true";
}
}

return value;
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ function findSchemaDefinition($ref, definitions = {}) {

// In the case where we have to implicitly create a schema, it is useful to know what type to use
// based on the data we are defining
const guessType = function guessType(value) {
export const guessType = function guessType(value) {
if (Array.isArray(value)) {
return "array";
} else if (typeof value === "string") {
Expand Down
79 changes: 79 additions & 0 deletions test/BooleanField_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { expect } from "chai";
import { Simulate } from "react-addons-test-utils";
import sinon from "sinon";

import { createFormComponent, createSandbox } from "./test_utils";

Expand Down Expand Up @@ -227,4 +228,82 @@ describe("BooleanField", () => {
expect(node.querySelector("#label-")).to.not.be.null;
});
});

describe("SelectWidget", () => {
it("should render a field that contains an enum of booleans", () => {
const { node } = createFormComponent({
schema: {
enum: [true, false],
},
});

expect(node.querySelectorAll(".field select")).to.have.length.of(1);
});

it("should infer the value from an enum on change", () => {
const spy = sinon.spy();
const { node } = createFormComponent({
schema: {
enum: [true, false],
},
onChange: spy,
});

expect(node.querySelectorAll(".field select")).to.have.length.of(1);
const $select = node.querySelector(".field select");
expect($select.value).eql("");

Simulate.change($select, {
target: { value: "true" },
});
expect($select.value).eql("true");
expect(spy.lastCall.args[0].formData).eql(true);
});

it("should render a string field with a label", () => {
const { node } = createFormComponent({
schema: {
enum: [true, false],
title: "foo",
},
});

expect(node.querySelector(".field label").textContent).eql("foo");
});

it("should assign a default value", () => {
const { comp } = createFormComponent({
schema: {
enum: [true, false],
default: true,
},
});

expect(comp.state.formData).eql(true);
});

it("should handle a change event", () => {
const { comp, node } = createFormComponent({
schema: {
enum: [true, false],
},
});

Simulate.change(node.querySelector("select"), {
target: { value: "false" },
});

expect(comp.state.formData).eql(false);
});

it("should render the widget with the expected id", () => {
const { node } = createFormComponent({
schema: {
enum: [true, false],
},
});

expect(node.querySelector("select").id).eql("root");
});
});
});
21 changes: 21 additions & 0 deletions test/NumberField_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { expect } from "chai";
import { Simulate } from "react-addons-test-utils";
import sinon from "sinon";

import { createFormComponent, createSandbox } from "./test_utils";

Expand Down Expand Up @@ -208,6 +209,26 @@ describe("NumberField", () => {
expect(node.querySelectorAll(".field select")).to.have.length.of(1);
});

it("should infer the value from an enum on change", () => {
const spy = sinon.spy();
const { node } = createFormComponent({
schema: {
enum: [1, 2],
},
onChange: spy,
});

expect(node.querySelectorAll(".field select")).to.have.length.of(1);
const $select = node.querySelector(".field select");
expect($select.value).eql("");

Simulate.change(node.querySelector(".field select"), {
target: { value: "1" },
});
expect($select.value).eql("1");
expect(spy.lastCall.args[0].formData).eql(1);
});

it("should render a string field with a label", () => {
const { node } = createFormComponent({
schema: {
Expand Down
27 changes: 27 additions & 0 deletions test/utils_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
shouldRender,
toDateString,
toIdSchema,
guessType,
} from "../src/utils";

describe("utils", () => {
Expand Down Expand Up @@ -1322,4 +1323,30 @@ describe("utils", () => {
);
});
});

describe("guessType()", () => {
it("should guess the type of array values", () => {
expect(guessType([1, 2, 3])).eql("array");
});

it("should guess the type of string values", () => {
expect(guessType("foobar")).eql("string");
});

it("should guess the type of null values", () => {
expect(guessType(null)).eql("null");
});

it("should treat undefined values as null values", () => {
expect(guessType()).eql("null");
});

it("should guess the type of boolean values", () => {
expect(guessType(true)).eql("boolean");
});

it("should guess the type of object values", () => {
expect(guessType({})).eql("object");
});
});
});

0 comments on commit 0ac0da7

Please sign in to comment.