{
/** @override */
render() {
return (
- {this.props.variables.map(variable => {
+ {this.props.variables.map((variable) => {
const Control = this.controlForVariable(variable);
- return (
-
- );
+ if (Control) {
+ return (
+
+ );
+ }
})}
);
}
+
+ /**
+ * Returns a control as determined by the variable `dataType` property.
+ * @private
+ * @param {Variable} variable The variable to provide the control for.
+ * @return {any} A control component.
+ */
+ private controlForVariable(variable: Variable): any {
+ // TODO(cjcox): Provide support for controls:
+ // BUTTON, COLOR_INPUT, STEPPER
+ switch (variable.controlType) {
+ case ControlType.COLOR_LIST:
+ return ColorSwatchControl;
+ case ControlType.SEGMENTED:
+ return RadioListControl;
+ case ControlType.SLIDER:
+ return SliderControl;
+ case ControlType.SWITCH:
+ return SwitchControl;
+ case ControlType.TEXT_INPUT:
+ return TextFieldControl;
+ case ControlType.TEXT_LIST:
+ return DropdownControl;
+ default:
+ return null;
+ }
+ }
}
diff --git a/src/ui/__tests__/ColorSwatchControl_test.tsx b/src/ui/__tests__/ColorSwatchControl_test.tsx
new file mode 100644
index 0000000..39a9ff4
--- /dev/null
+++ b/src/ui/__tests__/ColorSwatchControl_test.tsx
@@ -0,0 +1,48 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { ColorSwatchControl } from "../controls/ColorSwatchControl";
+import { CSS } from "../../lib/Constants";
+import { Variable } from "../../core/variables/Variable";
+
+const expect = chai.expect;
+
+describe("ColorSwatchControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: string = "#4285F4";
+ const limitedToValues: string[] = ["#4285F4", "#0F9D58", "#DB4437"];
+ let variable: Variable;
+
+ beforeEach(() => {
+ variable = remixer.addColorVariable(key, defaultValue, limitedToValues);
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_COLOR_SWATCH
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct number of children with proper data values", () => {
+ let list = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.MDL_SECONDARY
+ );
+
+ expect(list.children.length).to.equal(3);
+
+ for (let i = 0; i < list.children.length; i++) {
+ let element = list.children[i] as HTMLElement;
+ expect(element.dataset["value"]).to.equal(limitedToValues[i]);
+ }
+ });
+});
diff --git a/src/ui/__tests__/DropdownControl_test.tsx b/src/ui/__tests__/DropdownControl_test.tsx
new file mode 100644
index 0000000..64352d5
--- /dev/null
+++ b/src/ui/__tests__/DropdownControl_test.tsx
@@ -0,0 +1,48 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { CSS } from "../../lib/Constants";
+import { DropdownControl } from "../controls/DropdownControl";
+import { Variable } from "../../core/variables/Variable";
+
+const expect = chai.expect;
+
+describe("DropdownControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: string = "a";
+ const limitedToValues: string[] = ["a", "b", "c"];
+ let variable: Variable;
+
+ beforeEach(() => {
+ variable = remixer.addStringVariable(key, defaultValue, limitedToValues);
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_DROPDOWN
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct number of children with proper data values", () => {
+ let list = TestUtils.findRenderedDOMComponentWithTag(
+ this.component, "ul"
+ );
+
+ expect(list.children.length).to.equal(3);
+
+ for (let i = 0; i < list.children.length; i++) {
+ let element = list.children[i] as HTMLElement;
+ expect(element.dataset["value"]).to.equal(limitedToValues[i]);
+ }
+ });
+});
diff --git a/src/ui/__tests__/RadioListControl_test.tsx b/src/ui/__tests__/RadioListControl_test.tsx
new file mode 100644
index 0000000..1600c9c
--- /dev/null
+++ b/src/ui/__tests__/RadioListControl_test.tsx
@@ -0,0 +1,51 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { CSS } from "../../lib/Constants";
+import { RadioListControl } from "../controls/RadioListControl";
+import { Variable } from "../../core/variables/Variable";
+
+const expect = chai.expect;
+
+describe("RadioListControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: string = "a";
+ const limitedToValues: string[] = ["a", "b"];
+ let variable: Variable;
+
+ beforeEach(() => {
+ variable = remixer.addStringVariable(key, defaultValue, limitedToValues);
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_RADIO_LIST
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct number of children with proper data values", () => {
+ let list = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.MDL_SECONDARY
+ );
+
+ expect(list.children.length).to.equal(2);
+
+ let elements = TestUtils.scryRenderedDOMComponentsWithClass(
+ this.component, "mdl-radio__button"
+ ) as HTMLInputElement[];
+
+ for (let i = 0; i < elements.length; i++) {
+ expect(elements[i].value).to.equal(limitedToValues[i]);
+ }
+ });
+});
diff --git a/src/ui/__tests__/SliderControl_test.tsx b/src/ui/__tests__/SliderControl_test.tsx
new file mode 100644
index 0000000..70a6e57
--- /dev/null
+++ b/src/ui/__tests__/SliderControl_test.tsx
@@ -0,0 +1,64 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { CSS } from "../../lib/Constants";
+import { RangeVariable } from "../../core/variables/RangeVariable";
+import { SliderControl } from "../controls/SliderControl";
+
+const expect = chai.expect;
+
+describe("SliderControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: number = 0.5;
+ const minValue: number = 0;
+ const maxValue: number = 1;
+ const increment: number = 0.1;
+ let variable: RangeVariable;
+
+ beforeEach(() => {
+ variable = remixer.addRangeVariable(key, defaultValue, minValue, maxValue, increment);
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_SLIDER
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct min label", () => {
+ let label = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_SLIDER_MIN
+ );
+
+ expect(Number(label.textContent)).to.equal(minValue);
+ });
+
+ it("have correct max label", () => {
+ let label = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_SLIDER_MAX
+ );
+
+ expect(Number(label.textContent)).to.equal(maxValue);
+ });
+
+ it("have correct slider input value and attributes", () => {
+ let slider = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, "mdl-slider"
+ ) as HTMLInputElement;
+
+ expect(Number(slider.min)).to.equal(minValue);
+ expect(Number(slider.max)).to.equal(maxValue);
+ expect(Number(slider.step)).to.equal(increment);
+ expect(Number(slider.value)).to.equal(defaultValue);
+ });
+});
diff --git a/src/ui/__tests__/SwitchControl_test.tsx b/src/ui/__tests__/SwitchControl_test.tsx
new file mode 100644
index 0000000..3ee71d9
--- /dev/null
+++ b/src/ui/__tests__/SwitchControl_test.tsx
@@ -0,0 +1,42 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { CSS } from "../../lib/Constants";
+import { SwitchControl } from "../controls/SwitchControl";
+import { Variable } from "../../core/variables/Variable";
+
+const expect = chai.expect;
+
+describe("SwitchControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: boolean = true;
+ let variable: Variable;
+
+ beforeEach(() => {
+ variable = remixer.addBooleanVariable(key, defaultValue,);
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_SWITCH
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct switch checked value", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, "mdl-switch__input"
+ ) as HTMLInputElement;
+
+ expect(control.checked).to.equal(defaultValue);
+ });
+});
diff --git a/src/ui/__tests__/TextFieldControl_test.tsx b/src/ui/__tests__/TextFieldControl_test.tsx
new file mode 100644
index 0000000..06040bb
--- /dev/null
+++ b/src/ui/__tests__/TextFieldControl_test.tsx
@@ -0,0 +1,43 @@
+import * as React from "react";
+import * as TestUtils from "react-addons-test-utils";
+import * as chai from "chai";
+
+import { remixer } from "../../core/Remixer";
+import { CSS } from "../../lib/Constants";
+import { TextFieldControl } from "../controls/TextFieldControl";
+import { Variable } from "../../core/variables/Variable";
+
+const expect = chai.expect;
+
+describe("TextFieldControl", () => {
+ const key: string = "test_variable";
+ const defaultValue: string = "test string value";
+ let variable: Variable;
+
+ beforeEach(() => {
+ variable = remixer.addStringVariable(key, defaultValue);
+ variable.selectedValue = defaultValue;
+ this.component = TestUtils.renderIntoDocument(
+
+ );
+ });
+
+ it("should render with proper class name", () => {
+ let control = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, CSS.RMX_TEXTFIELD
+ );
+
+ expect(TestUtils.isDOMComponent(control)).to.be.true;
+ });
+
+ it("have correct innertext checked value", () => {
+ let textField = TestUtils.findRenderedDOMComponentWithClass(
+ this.component, "mdl-textfield__input"
+ ) as HTMLInputElement;
+
+ expect(textField.value).to.equal(defaultValue);
+ });
+});
diff --git a/src/ui/controls/ColorSwatchControl.tsx b/src/ui/controls/ColorSwatchControl.tsx
index 2b704f9..03183de 100644
--- a/src/ui/controls/ColorSwatchControl.tsx
+++ b/src/ui/controls/ColorSwatchControl.tsx
@@ -15,27 +15,29 @@
*/
import * as React from "react";
-import { ColorControlProps } from "./controlProps";
-import { CSS, VariableType } from "../../lib/Constants";
+import * as TinyColor from "tinycolor2";
+
+import { CSS } from "../../lib/Constants";
+import { IColorControlProps } from "./controlProps";
/**
* A color swatch picker control consisting of a single color swatch for each
- * possible value.
+ * allowed value.
* @class
* @extends React.Component
*/
-export class ColorSwatchControl extends React.Component {
+export class ColorSwatchControl extends React.Component {
/** Handles the update event for this control. */
onClick = (event: React.FormEvent): void => {
this.props.updateVariable(
this.props.variable,
- (event.target as HTMLElement).dataset["value"]
+ (event.target as HTMLElement).dataset["value"],
);
}
/** @override */
- shouldComponentUpdate(nextProps: ColorControlProps) {
+ shouldComponentUpdate(nextProps: IColorControlProps) {
return nextProps.variable !== this.props.variable;
}
@@ -43,20 +45,25 @@ export class ColorSwatchControl extends React.Component
render() {
const {
title,
- possibleValues,
- selectedValue
+ limitedToValues,
+ selectedValue,
} = this.props.variable;
return (
{title}
- {selectedValue}
+
+ {TinyColor(selectedValue).toString()}
+
- {possibleValues.map((value: string) => (
+ {limitedToValues.map((value: string) => (
))}
@@ -71,7 +78,7 @@ export class ColorSwatchControl extends React.Component
* Interface containing properties for a single color swatch.
* @interface
*/
-interface ColorSwatchProps {
+interface IColorSwatchProps {
color: string;
isSelected: boolean;
onClick: any;
@@ -79,14 +86,18 @@ interface ColorSwatchProps {
/**
* Returns a single color swatch displayed within the `ColorSwatchControl`.
- * @param {ColorSwatchProps} props The color swatch properties.
+ * @param {IColorSwatchProps} props The color swatch properties.
*/
-function ColorSwatch(props: ColorSwatchProps) {
+function ColorSwatch(props: IColorSwatchProps) {
const {
color,
isSelected,
onClick,
} = props;
+ // Determine a readable color to prevent a white checkmark on a light
+ // color swatch.
+ let readableCheckColors = [TinyColor("white"), TinyColor("gray")];
+ let checkColor = TinyColor.mostReadable(TinyColor(color), readableCheckColors);
return (
- {isSelected ? check : ""}
+ {
+ isSelected ? check : ""
+ }
);
}
diff --git a/src/ui/controls/DropdownControl.tsx b/src/ui/controls/DropdownControl.tsx
index 4bce1af..03cfd53 100644
--- a/src/ui/controls/DropdownControl.tsx
+++ b/src/ui/controls/DropdownControl.tsx
@@ -15,26 +15,27 @@
*/
import * as React from "react";
+
import { CSS } from "../../lib/Constants";
-import { StringControlProps } from "./controlProps";
+import { IStringControlProps } from "./controlProps";
/**
* A dropdown control.
* @class
* @extends React.Component
*/
-export class DropdownControl extends React.Component {
+export class DropdownControl extends React.Component {
/** Handles the update event for this control. */
onClick = (event: React.FormEvent): void => {
this.props.updateVariable(
this.props.variable,
- (event.target as HTMLElement).dataset["value"]
+ (event.target as HTMLElement).dataset["value"],
);
}
/** @override */
- shouldComponentUpdate(nextProps: StringControlProps) {
+ shouldComponentUpdate(nextProps: IStringControlProps) {
return nextProps.variable !== this.props.variable;
}
@@ -43,8 +44,8 @@ export class DropdownControl extends React.Component {
const {
title,
key,
- possibleValues,
- selectedValue
+ limitedToValues,
+ selectedValue,
} = this.props.variable;
const id = `${CSS.RMX_DROPDOWN}-${key}`;
@@ -61,7 +62,7 @@ export class DropdownControl extends React.Component {
className="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect"
htmlFor={id}
>
- {possibleValues.map((value: string) => (
+ {limitedToValues.map((value: string) => (
{value}
diff --git a/src/ui/controls/RadioListControl.tsx b/src/ui/controls/RadioListControl.tsx
index d875218..5636e82 100644
--- a/src/ui/controls/RadioListControl.tsx
+++ b/src/ui/controls/RadioListControl.tsx
@@ -15,26 +15,27 @@
*/
import * as React from "react";
-import { CSS, VariableType } from "../../lib/Constants";
-import { StringControlProps } from "./controlProps";
+
+import { CSS } from "../../lib/Constants";
+import { IStringControlProps } from "./controlProps";
/**
* A radio list control.
* @class
* @extends React.Component
*/
-export class RadioListControl extends React.Component {
+export class RadioListControl extends React.Component {
/** Handles the update event for this control. */
onChange = (event: React.FormEvent): void => {
this.props.updateVariable(
this.props.variable,
- (event.target as HTMLInputElement).value
+ (event.target as HTMLInputElement).value,
);
}
/** @override */
- shouldComponentUpdate(nextProps: StringControlProps) {
+ shouldComponentUpdate(nextProps: IStringControlProps) {
return nextProps.variable !== this.props.variable;
}
@@ -43,8 +44,8 @@ export class RadioListControl extends React.Component
const {
title,
key,
- possibleValues,
- selectedValue
+ limitedToValues,
+ selectedValue,
} = this.props.variable;
const id = `${CSS.RMX_RADIO_LIST_ITEM}-${key}`;
@@ -52,7 +53,7 @@ export class RadioListControl extends React.Component
{title}
- {possibleValues.map((value: string, i: number) => (
+ {limitedToValues.map((value: string, i: number) => (