Initial ReactInput work

This commit is contained in:
Eric Schultz 2018-07-06 14:58:02 -05:00
parent 1d20129397
commit 5ff8fdc86b
7 changed files with 335 additions and 23 deletions

View file

@ -0,0 +1,49 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import { observer, Provider } from 'mobx-react';
import {Field, Form} from "mobx-react-form";
import {observable, action, toJS} from 'mobx';
export interface ReactFormProps
{
form:Form
}
///Mostly useless class but, at some point, will replace all our form elements
class ReactForm extends React.Component<ReactFormProps, {}> {
@observable
form:Form
@action.bound
componentWillMount()
{
this.form = this.props.form
}
componentDidUpdate(){
}
componentWillUnmount(){
}
render() {
return <form >
{this.props.children}
</form>
}
}
export default observer(ReactForm)

View file

@ -0,0 +1,162 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import 'jest';
import ReactInput from './ReactInput'
import {Form} from "mobx-react-form";
import ReactForm from "./ReactForm";
import {mount} from 'enzyme';
import {toJS, observable, action, runInAction} from 'mobx';
import {observer} from 'mobx-react';
import {InputHTMLAttributes} from 'react';
@observer
class TestChange extends React.Component{
@observable
remove:boolean
@observable
form: Form
@action.bound
componentWillMount(){
this.form = new Form({fields:[{
name: 'name',
extra: null}
]})
}
@action.bound
onClick(){
this.remove = true
}
render() {
let reactInput = !this.remove ? <ReactInput field={this.form.$('name')} label={'label1'} placeholder={"holder"}>
{this.props.children}
</ReactInput> : undefined
return <ReactForm form={this.form}>
{reactInput}
<button onClick={() => this.onClick()}/>
</ReactForm>
}
}
class WrappedInput extends React.Component<InputHTMLAttributes<HTMLInputElement>>{
render(){
let notChildren = {...this.props}
delete notChildren.children
return <div>
<input {...notChildren} />
</div>
}
}
describe('ReactInput', () => {
let form: Form
beforeEach(() => {
form = new Form({
fields: [
{
name: 'name',
extra: null
}
]
})
})
describe('no children passed in', () => {
test('gets added properly', () => {
let res = mount(<ReactForm form={form}>
<ReactInput field={form.$('name')} label={"label"}
placeholder={"holder"} value={'snapshot'} aria-required={true}/>
</ReactForm>)
//Did the attributes settings work as expected back to the objects
expect(form.$('name').label).toEqual('label')
expect(form.$('name').placeholder).toEqual('holder')
expect(form.$('name').value).toEqual('')
//is the aria attribute passted through to the input
let input = res.find('input')
expect(input.prop('aria-required')).toEqual(true)
// is the input properly bound?
input.simulate('change', {target: { value: 'something' } })
expect(form.$('name').value).toEqual('something')
})
test('gets removed properly', () => {
let res = mount(<TestChange/>)
let f = res.find('ReactForm').instance() as ReactForm
expect(f.form.size).toEqual(1)
res.find('input').simulate('change', {target: { value: 'something' } })
expect(f.form.$('name').value).toEqual('something')
res.find('button').simulate('click')
expect(f.form.size).toEqual(1)
expect(toJS(res.find('form'))).toMatchSnapshot()
expect(f.form.$('name').label).toEqual('label1')
expect(f.form.$('name').placeholder).toEqual('holder')
})
})
describe('children passed in', () => {
test('gets added properly', () => {
let res = mount(<ReactForm form={form}>
<ReactInput field={form.$('name')} label={"label"}
placeholder={"holder"} value={'snapshot'} aria-required={true}>
<WrappedInput/>
</ReactInput>
</ReactForm>)
//Did the attributes settings work as expected back to the objects
expect(form.$('name').label).toEqual('label')
expect(form.$('name').placeholder).toEqual('holder')
expect(form.$('name').value).toEqual('')
//is the aria attribute passted through to the input
let input = res.find('input')
expect(input.prop('aria-required')).toEqual(true)
// is the input properly bound?
input.simulate('change', {target: { value: 'something' } })
expect(form.$('name').value).toEqual('something')
})
test('gets removed properly', () => {
let res = mount(<TestChange>
<WrappedInput/>
</TestChange>)
let f = res.find('ReactForm').instance() as ReactForm
res.find('input').simulate('change', {target: { value: 'something' } })
expect(f.form.$('name').value).toEqual('something')
expect(f.form.size).toEqual(1)
res.find('button').simulate('click')
expect(f.form.size).toEqual(1)
expect(f.form.$('name').label).toEqual('label1')
expect(f.form.$('name').placeholder).toEqual('holder')
})
})
})

View file

@ -0,0 +1,89 @@
// License: LGPL-3.0-or-later
import * as React from 'react';
import { observer, inject, Provider } from 'mobx-react';
import {InjectedIntlProps, injectIntl} from 'react-intl';
import {Field} from "mobx-react-form";
import {observable, action, toJS, runInAction} from 'mobx';
import {InputHTMLAttributes} from 'react';
export interface ReactInputProps
{
field:Field
label?:string
placeholder?:string
children: React.ReactElement<InputHTMLAttributes<HTMLInputElement>>
}
function castToNullIfUndef(i:any){
return i === undefined ? null : i
}
class ReactInput extends React.Component<ReactInputProps & InputHTMLAttributes<HTMLInputElement>, {}> {
constructor(props:ReactInputProps){
super(props)
}
@observable
field:Field
@action.bound
componentWillMount(){
this.field = this.props.field
this.updateProps()
}
componentWillUnmount(){
}
componentDidUpdate(prevProps: Readonly<ReactInputProps>, prevState: Readonly<{}>): void {
this.updateProps()
}
@action.bound
updateProps() {
this.field.set('label', castToNullIfUndef(this.props.label))
this.field.set('placeholder', castToNullIfUndef(this.props.placeholder))
}
@action.bound
renderChildren(){
let ourProps = this.winnowProps()
let elem = React.cloneElement(this.props.children as React.ReactElement<any>,
{...ourProps, ...this.field.bind() })
return elem
}
///Removes the properties we don't want to put into the input element
@action.bound
winnowProps(): ReactInputProps & InputHTMLAttributes<HTMLInputElement> {
let ourProps = {...this.props}
delete ourProps.field
delete ourProps.value
return ourProps
}
render() {
if (this.props.children)
{
return this.renderChildren()
}
else {
return <input {...this.winnowProps()} {...this.field.bind()}/>
}
}
}
export default observer(ReactInput)

View file

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactInput no children passed in gets removed properly 1`] = `
<form>
<button
onClick={[Function]}
/>
</form>
`;

30
package-lock.json generated
View file

@ -5697,9 +5697,9 @@
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
},
"hoist-non-react-statics": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz",
"integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==",
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"dev": true
},
"home-or-tmp": {
@ -8900,19 +8900,19 @@
}
},
"mobx": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.2.1.tgz",
"integrity": "sha1-3UGQ2vG0PUGjoihYUlP5lwsKJ90=",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.3.1.tgz",
"integrity": "sha1-M05aq0kWsdQ/D682BaZLG0s8y40=",
"dev": true
},
"mobx-react": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.1.2.tgz",
"integrity": "sha1-7FwtKaHfgj29GzfiFPo2oJBwVOI=",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.3.tgz",
"integrity": "sha512-OuSlF2nJEa1PGookZcZnINbvEK4iWNNYiqUh6aebk2AkWxj3sG8OafDOQMcMYApQALTHRsrBIjOx/K8TFxcz7w==",
"dev": true,
"requires": {
"hoist-non-react-statics": "2.5.0",
"react-lifecycles-compat": "3.0.3"
"hoist-non-react-statics": "2.5.5",
"react-lifecycles-compat": "3.0.4"
}
},
"mobx-react-devtools": {
@ -8922,7 +8922,7 @@
"dev": true
},
"mobx-react-form": {
"version": "github:houdiniproject/mobx-react-form#63a163397102d86745b7ac381d42b794ec172231",
"version": "github:houdiniproject/mobx-react-form#8421c456a88cd3d7f887168a8228bb1a04911e7d",
"dev": true,
"requires": {
"lodash": "4.17.10"
@ -11584,9 +11584,9 @@
"dev": true
},
"react-lifecycles-compat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.3.tgz",
"integrity": "sha512-bOr65SSYgxDgDNqLnDqt+gropXGPNB1Wbyys4tOYiNuP/qYWC4qFM9XH1ruzq+tT6EjE29pJsCr19rclKtpUEg==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"dev": true
},
"react-reconciler": {

View file

@ -54,8 +54,8 @@
"less": "^3.0.4",
"less-loader": "^4.1.0",
"lodash": "^4.17.5",
"mobx": "^4.2.0",
"mobx-react": "^5.0.0",
"mobx": "^4.3.1",
"mobx-react": "^5.2.3",
"mobx-react-devtools": "^5.0.1",
"mobx-react-form": "github:houdiniproject/mobx-react-form#our_fix",
"no-scroll": "^2.1.0",

View file

@ -72,7 +72,7 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
$(fieldName: string): Field;
add(obj: any): any;
add(obj:{FieldDefinition}): any;
bind(): object;
@ -82,7 +82,7 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
container(): Form |Field
del(key: any);
del(path?: string);
each(callback: (i: Field) => void);
@ -173,6 +173,7 @@ interface FieldHandlers {
interface FieldDefinition {
name: string
key?: string
label?: string
value?: any
default?: any
@ -253,12 +254,13 @@ interface FormInitializer{
interface initializationDefinition {
fields?:FieldDefinitions[]
hooks?: FormHooks
}
export class Form implements Base {
constructor(definition:initializationDefinition, options?:any)
constructor(definition?:initializationDefinition, options?:any)
plugins(): void
setup(): any
onInit(): void
@ -269,7 +271,7 @@ export class Form implements Base {
$(fieldName: string): Field;
add(obj: any): any;
add(obj:FieldDefinition): any;
check(computed: string, deep?: boolean): boolean;
@ -292,7 +294,7 @@ export class Form implements Base {
map(callback: (i: Field) => void);
observe(obj: any);
observe(...obj: any)
select(path: string): Field;
@ -304,6 +306,7 @@ export class Form implements Base {
update(obj: any): void;
readonly submitting: boolean;
readonly isValid:boolean;
protected validator :any
@ -327,7 +330,7 @@ interface SharedFieldFormMethods {
has(key:string):boolean
map(callback:(i:Field) => void)
each(callback:(i:Field) => void)
add(obj:any):any
add(obj:FieldDefinition): any;
del(key:any)
observe(obj:any)
intercept(obj:any)