diff options
author | Shin'ya Ueoka <ueokande@i-beam.org> | 2020-09-21 15:20:41 +0900 |
---|---|---|
committer | Shin'ya Ueoka <ueokande@i-beam.org> | 2020-09-21 22:07:55 +0900 |
commit | 063ceb215f858a8e2a5bde85d8f9ca24240894c6 (patch) | |
tree | 1d5f856885e072124d1d025808ee366e7add2ce1 | |
parent | 5d82441ce3d12301bb8f04b78b79fffb04121a37 (diff) |
Separate input component
-rw-r--r-- | src/settings/components/form/KeymapsForm.tsx | 5 | ||||
-rw-r--r-- | src/settings/components/index.tsx | 12 | ||||
-rw-r--r-- | src/settings/components/ui/Input.tsx | 89 | ||||
-rw-r--r-- | src/settings/components/ui/Radio.tsx | 44 | ||||
-rw-r--r-- | src/settings/components/ui/Text.tsx | 42 | ||||
-rw-r--r-- | src/settings/components/ui/TextArea.tsx | 42 | ||||
-rw-r--r-- | test/settings/components/ui/Radio.test.tsx | 56 | ||||
-rw-r--r-- | test/settings/components/ui/Text.test.tsx | 55 | ||||
-rw-r--r-- | test/settings/components/ui/TextArea.test.tsx | 63 | ||||
-rw-r--r-- | test/settings/components/ui/input.test.tsx | 146 |
10 files changed, 309 insertions, 245 deletions
diff --git a/src/settings/components/form/KeymapsForm.tsx b/src/settings/components/form/KeymapsForm.tsx index b9af0df..25578ad 100644 --- a/src/settings/components/form/KeymapsForm.tsx +++ b/src/settings/components/form/KeymapsForm.tsx @@ -1,6 +1,6 @@ import "./KeymapsForm.scss"; import React from "react"; -import Input from "../ui/Input"; +import Text from "../ui/Text"; import keymaps from "../../keymaps"; import { FormKeymaps } from "../../../shared/SettingData"; @@ -27,8 +27,7 @@ class KeymapsForm extends React.Component<Props> { {group.map(([name, label]) => { const value = values[name] || ""; return ( - <Input - type="text" + <Text id={name} name={name} key={name} diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx index 9d71cac..d204210 100644 --- a/src/settings/components/index.tsx +++ b/src/settings/components/index.tsx @@ -1,7 +1,8 @@ import "./site.scss"; import React from "react"; import { connect } from "react-redux"; -import Input from "./ui/Input"; +import TextArea from "./ui/TextArea"; +import Radio from "./ui/Radio"; import SearchForm from "./form/SearchForm"; import KeymapsForm from "./form/KeymapsForm"; import BlacklistForm from "./form/BlacklistForm"; @@ -88,8 +89,7 @@ class SettingsComponent extends React.Component<Props> { renderJsonFields(json: JSONTextSettings, error: string) { return ( <div> - <Input - type="textarea" + <TextArea name="json" label="Plain JSON" spellCheck={false} @@ -114,8 +114,7 @@ class SettingsComponent extends React.Component<Props> { <div> <h1>Configure Vim-Vixen</h1> <form className="vimvixen-settings-form"> - <Input - type="radio" + <Radio id="setting-source-form" name="source" label="Use form" @@ -125,8 +124,7 @@ class SettingsComponent extends React.Component<Props> { disabled={disabled} /> - <Input - type="radio" + <Radio name="source" label="Use plain JSON" checked={this.props.source === "json"} diff --git a/src/settings/components/ui/Input.tsx b/src/settings/components/ui/Input.tsx deleted file mode 100644 index 0e24277..0000000 --- a/src/settings/components/ui/Input.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from "react"; -import "./Input.scss"; - -interface Props extends React.AllHTMLAttributes<HTMLElement> { - name: string; - type: string; - error?: string; - label: string; - value: string; - onValueChange?: (name: string, value: string) => void; - onBlur?: (e: React.FocusEvent<Element>) => void; -} - -class Input extends React.Component<Props> { - renderText(props: Props) { - const inputClassName = props.error ? "input-error" : ""; - const pp = { ...props }; - delete pp.onValueChange; - return ( - <div className="settings-ui-input"> - <label htmlFor={props.id}>{props.label}</label> - <input - className={inputClassName} - onChange={this.bindOnChange.bind(this)} - {...pp} - /> - </div> - ); - } - - renderRadio(props: Props) { - const inputClassName = props.error ? "input-error" : ""; - const pp = { ...props }; - delete pp.onValueChange; - return ( - <div className="settings-ui-input"> - <label> - <input - className={inputClassName} - onChange={this.bindOnChange.bind(this)} - {...pp} - /> - {props.label} - </label> - </div> - ); - } - - renderTextArea(props: Props) { - const inputClassName = props.error ? "input-error" : ""; - const pp = { ...props }; - delete pp.onValueChange; - return ( - <div className="settings-ui-input"> - <label htmlFor={props.id}>{props.label}</label> - <textarea - className={inputClassName} - onChange={this.bindOnChange.bind(this)} - {...pp} - /> - <p className="settings-ui-input-error">{this.props.error}</p> - </div> - ); - } - - render() { - const { type } = this.props; - - switch (this.props.type) { - case "text": - return this.renderText(this.props); - case "radio": - return this.renderRadio(this.props); - case "textarea": - return this.renderTextArea(this.props); - default: - console.warn(`Unsupported input type ${type}`); - } - return null; - } - - bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) { - if (this.props.onValueChange) { - this.props.onValueChange(e.target.name, e.target.value); - } - } -} - -export default Input; diff --git a/src/settings/components/ui/Radio.tsx b/src/settings/components/ui/Radio.tsx new file mode 100644 index 0000000..20d4ad6 --- /dev/null +++ b/src/settings/components/ui/Radio.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import "./Input.scss"; + +interface Props extends React.AllHTMLAttributes<HTMLElement> { + name: string; + error?: string; + label: string; + value: string; + onValueChange?: (name: string, value: string) => void; + onBlur?: (e: React.FocusEvent<Element>) => void; +} + +class Input extends React.Component<Props> { + renderRadio(props: Props) { + const inputClassName = props.error ? "input-error" : ""; + const pp = { ...props }; + delete pp.onValueChange; + return ( + <div className="settings-ui-input"> + <label> + <input + type="radio" + className={inputClassName} + onChange={this.bindOnChange.bind(this)} + {...pp} + /> + {props.label} + </label> + </div> + ); + } + + render() { + return this.renderRadio(this.props); + } + + bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) { + if (this.props.onValueChange) { + this.props.onValueChange(e.target.name, e.target.value); + } + } +} + +export default Input; diff --git a/src/settings/components/ui/Text.tsx b/src/settings/components/ui/Text.tsx new file mode 100644 index 0000000..7ecef88 --- /dev/null +++ b/src/settings/components/ui/Text.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import "./Input.scss"; + +interface Props extends React.AllHTMLAttributes<HTMLElement> { + name: string; + error?: string; + label: string; + value: string; + onValueChange?: (name: string, value: string) => void; + onBlur?: (e: React.FocusEvent<Element>) => void; +} + +class Input extends React.Component<Props> { + renderText(props: Props) { + const inputClassName = props.error ? "input-error" : ""; + const pp = { ...props }; + delete pp.onValueChange; + return ( + <div className="settings-ui-input"> + <label htmlFor={props.id}>{props.label}</label> + <input + type="text" + className={inputClassName} + onChange={this.bindOnChange.bind(this)} + {...pp} + /> + </div> + ); + } + + render() { + return this.renderText(this.props); + } + + bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) { + if (this.props.onValueChange) { + this.props.onValueChange(e.target.name, e.target.value); + } + } +} + +export default Input; diff --git a/src/settings/components/ui/TextArea.tsx b/src/settings/components/ui/TextArea.tsx new file mode 100644 index 0000000..f2b832f --- /dev/null +++ b/src/settings/components/ui/TextArea.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import "./Input.scss"; + +interface Props extends React.AllHTMLAttributes<HTMLElement> { + name: string; + error?: string; + label: string; + value: string; + onValueChange?: (name: string, value: string) => void; + onBlur?: (e: React.FocusEvent<Element>) => void; +} + +class TextArea extends React.Component<Props> { + renderTextArea(props: Props) { + const inputClassName = props.error ? "input-error" : ""; + const pp = { ...props }; + delete pp.onValueChange; + return ( + <div className="settings-ui-input"> + <label htmlFor={props.id}>{props.label}</label> + <textarea + className={inputClassName} + onChange={this.bindOnChange.bind(this)} + {...pp} + /> + <p className="settings-ui-input-error">{this.props.error}</p> + </div> + ); + } + + render() { + return this.renderTextArea(this.props); + } + + bindOnChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) { + if (this.props.onValueChange) { + this.props.onValueChange(e.target.name, e.target.value); + } + } +} + +export default TextArea; diff --git a/test/settings/components/ui/Radio.test.tsx b/test/settings/components/ui/Radio.test.tsx new file mode 100644 index 0000000..f929ee3 --- /dev/null +++ b/test/settings/components/ui/Radio.test.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import ReactTestUtils from "react-dom/test-utils"; +import Radio from "../../../../src/settings/components/ui/Radio"; +import { expect } from "chai"; + +describe("settings/ui/Radio", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + it("renders radio button", () => { + ReactTestUtils.act(() => { + ReactDOM.render( + <Radio name="myradio" label="myfield" value="myvalue" />, + container + ); + }); + + const label = document.querySelector("label")!; + const input = document.querySelector("input")!; + expect(label.textContent).to.contain("myfield"); + expect(input.type).to.contain("radio"); + expect(input.name).to.contain("myradio"); + expect(input.value).to.contain("myvalue"); + }); + + it("invoke onChange", (done) => { + ReactTestUtils.act(() => { + ReactDOM.render( + <Radio + name="myradio" + type="text" + label="myfield" + value="myvalue" + onChange={(e) => { + expect((e.target as HTMLInputElement).checked).to.be.true; + done(); + }} + />, + container + ); + }); + + const input = document.querySelector("input") as HTMLInputElement; + input.checked = true; + ReactTestUtils.Simulate.change(input); + }); +}); diff --git a/test/settings/components/ui/Text.test.tsx b/test/settings/components/ui/Text.test.tsx new file mode 100644 index 0000000..d5451bb --- /dev/null +++ b/test/settings/components/ui/Text.test.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import ReactTestUtils from "react-dom/test-utils"; +import Text from "../../../../src/settings/components/ui/Text"; +import { expect } from "chai"; + +describe("settings/ui/Text", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + it("renders text input", () => { + ReactTestUtils.act(() => { + ReactDOM.render( + <Text name="myname" label="myfield" value="myvalue" />, + container + ); + }); + + const label = document.querySelector("label")!; + const input = document.querySelector("input")!; + expect(label.textContent).to.contain("myfield"); + expect(input.type).to.contain("text"); + expect(input.name).to.contain("myname"); + expect(input.value).to.contain("myvalue"); + }); + + it("invoke onChange", (done) => { + ReactTestUtils.act(() => { + ReactDOM.render( + <Text + name="myname" + label="myfield" + value="myvalue" + onChange={(e) => { + expect((e.target as HTMLInputElement).value).to.equal("newvalue"); + done(); + }} + />, + container + ); + }); + + const input = document.querySelector("input")!; + input.value = "newvalue"; + ReactTestUtils.Simulate.change(input); + }); +}); diff --git a/test/settings/components/ui/TextArea.test.tsx b/test/settings/components/ui/TextArea.test.tsx new file mode 100644 index 0000000..50313cf --- /dev/null +++ b/test/settings/components/ui/TextArea.test.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import ReactTestUtils from "react-dom/test-utils"; +import TextArea from "../../../../src/settings/components/ui/TextArea"; +import { expect } from "chai"; + +describe("settings/ui/TextArea", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + it("renders textarea", () => { + ReactTestUtils.act(() => { + ReactDOM.render( + <TextArea + type="textarea" + name="myname" + label="myfield" + value="myvalue" + error="myerror" + />, + container + ); + }); + + const label = document.querySelector("label")!; + const textarea = document.querySelector("textarea")!; + const error = document.querySelector(".settings-ui-input-error")!; + expect(label.textContent).to.contain("myfield"); + expect(textarea.nodeName).to.contain("TEXTAREA"); + expect(textarea.name).to.contain("myname"); + expect(textarea.value).to.contain("myvalue"); + expect(error.textContent).to.contain("myerror"); + }); + + it("invoke onChange", (done) => { + ReactTestUtils.act(() => { + ReactDOM.render( + <TextArea + name="myname" + label="myfield" + value="myvalue" + onChange={(e) => { + expect((e.target as HTMLInputElement).value).to.equal("newvalue"); + done(); + }} + />, + container + ); + }); + + const input = document.querySelector("textarea")!; + input.value = "newvalue"; + ReactTestUtils.Simulate.change(input); + }); +}); diff --git a/test/settings/components/ui/input.test.tsx b/test/settings/components/ui/input.test.tsx deleted file mode 100644 index d244d8f..0000000 --- a/test/settings/components/ui/input.test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import ReactTestUtils from "react-dom/test-utils"; -import Input from "../../../../src/settings/components/ui/Input"; -import { expect } from "chai"; - -describe("settings/ui/Input", () => { - let container: HTMLDivElement; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - document.body.removeChild(container); - }); - - context("type=text", () => { - it("renders text input", () => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input type="text" name="myname" label="myfield" value="myvalue" />, - container - ); - }); - - const label = document.querySelector("label")!; - const input = document.querySelector("input")!; - expect(label.textContent).to.contain("myfield"); - expect(input.type).to.contain("text"); - expect(input.name).to.contain("myname"); - expect(input.value).to.contain("myvalue"); - }); - - it("invoke onChange", (done) => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input - type="text" - name="myname" - label="myfield" - value="myvalue" - onChange={(e) => { - expect((e.target as HTMLInputElement).value).to.equal("newvalue"); - done(); - }} - />, - container - ); - }); - - const input = document.querySelector("input")!; - input.value = "newvalue"; - ReactTestUtils.Simulate.change(input); - }); - }); - - context("type=radio", () => { - it("renders radio button", () => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input type="radio" name="myname" label="myfield" value="myvalue" />, - container - ); - }); - - const label = document.querySelector("label")!; - const input = document.querySelector("input")!; - expect(label.textContent).to.contain("myfield"); - expect(input.type).to.contain("radio"); - expect(input.name).to.contain("myname"); - expect(input.value).to.contain("myvalue"); - }); - - it("invoke onChange", (done) => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input - type="text" - name="radio" - label="myfield" - value="myvalue" - onChange={(e) => { - expect((e.target as HTMLInputElement).checked).to.be.true; - done(); - }} - />, - container - ); - }); - - const input = document.querySelector("input") as HTMLInputElement; - input.checked = true; - ReactTestUtils.Simulate.change(input); - }); - }); - - context("type=textarea", () => { - it("renders textarea button", () => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input - type="textarea" - name="myname" - label="myfield" - value="myvalue" - error="myerror" - />, - container - ); - }); - - const label = document.querySelector("label")!; - const textarea = document.querySelector("textarea")!; - const error = document.querySelector(".settings-ui-input-error")!; - expect(label.textContent).to.contain("myfield"); - expect(textarea.nodeName).to.contain("TEXTAREA"); - expect(textarea.name).to.contain("myname"); - expect(textarea.value).to.contain("myvalue"); - expect(error.textContent).to.contain("myerror"); - }); - - it("invoke onChange", (done) => { - ReactTestUtils.act(() => { - ReactDOM.render( - <Input - type="textarea" - name="myname" - label="myfield" - value="myvalue" - onChange={(e) => { - expect((e.target as HTMLInputElement).value).to.equal("newvalue"); - done(); - }} - />, - container - ); - }); - - const input = document.querySelector("textarea")!; - input.value = "newvalue"; - ReactTestUtils.Simulate.change(input); - }); - }); -}); |