Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infer type from enum if a type is not provided to SelectWidget (#1100) #2

Merged
merged 1 commit into from
Dec 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
});
});
});