Compare commits

..

No commits in common. "beta" and "master" have entirely different histories.
beta ... master

60 changed files with 2034 additions and 7273 deletions

View File

@ -1,3 +0,0 @@
{
"extends": "airbnb"
}

View File

@ -2,7 +2,7 @@
.editorconfig .editorconfig
.travis.yml .travis.yml
testrunner.js testrunner.js
webpack.config.js webpack.production.config.js
examples/ examples/
release/ release/
tests/ tests/

1
.nvmrc
View File

@ -1 +0,0 @@
8.2.1

View File

@ -1,4 +1,4 @@
language: node_js language: node_js
sudo: false sudo: false
node_js: node_js:
- '8.2.1' - '4.1'

797
API.md

File diff suppressed because it is too large Load Diff

81
CHANGES.md Normal file
View File

@ -0,0 +1,81 @@
This is the old CHANGES file. Please look at [releases](https://github.com/christianalfoni/formsy-react/releases) for latest changes.
**0.8.0**
- Fixed bug where dynamic form elements gave "not mounted" error (Thanks @sdemjanenko)
- React is now a peer dependency (Thanks @snario)
- Dynamically updated values should now work with initial "undefined" value (Thanks @sdemjanenko)
- Validations are now dynamic. Change the prop and existing values are re-validated (thanks @bryannaegele)
- You can now set a "disabled" prop on the form and check "isFormDisabled()" in form elements
- Refactored some code and written a couple of tests
**0.7.2**:
- isNumber validation now supports float (Thanks @hahahana)
- Form XHR calls now includes CSRF headers, if exists (Thanks @hahahana)
**0.7.1**
- Fixed bug where external update of value on pristine form element did not update the form model (Thanks @sdemjanenko)
- Fixed bug where children are null/undefined (Thanks @sdemjanenko)
**0.7.0**
- Dynamic form elements. Add them at any point and they will be registered with the form
- **onChange()** handler is called whenever an form element has changed its value or a new form element is added to the form
- isNumeric validator now also handles actual numbers, not only strings
- Some more tests
**0.6.0**
- **onSubmit()** now has the same signature regardless of passing url attribute or not
- **isPristine()** is a new method to handle "touched" form elements (thanks @FoxxMD)
- Mapping attributes to pass a function that maps input values to new structure. The new structure is either passed to *onSubmit* and/or to the server when using a url attribute (thanks for feedback @MattAitchison)
- Added default "equalsField" validation rule
- Lots of tests!
**0.5.2**
- Fixed bug with handlers in ajax requests (Thanks @smokku)
**0.5.1**
- Fixed bug with empty validations
**0.5.0**
- Added [cross input validation](#formsyaddvalidationrule)
- Fixed bug where validation rule refers to a string
- Added "invalidateForm" function when manually submitting the form
**0.4.1**
- Fixed bug where form element is required, but no validations
**0.4.0**:
- Possibility to handle form data manually using "onSubmit"
- Added two more default rules. *isWords* and *isSpecialWords*
**0.3.0**:
- Deprecated everything related to buttons automatically added
- Added onValid and onInvalid handlers, use those to manipulate submit buttons etc.
**0.2.3**:
- Fixed bug where child does not have props property
**0.2.2**:
- Fixed bug with updating the props
**0.2.1**:
- Cancel button displays if onCancel handler is defined
**0.2.0**:
- Implemented hasValue() method
**0.1.3**:
- Fixed resetValue bug
**0.1.2**:
- Fixed isValue check to empty string, needs to support false and 0
**0.1.1**:
- Added resetValue method
- Changed value check of showRequired

234
README.md
View File

@ -1,132 +1,136 @@
# formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react) formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](https://travis-ci.org/christianalfoni/formsy-react)
============
A form input builder and validator for React. A form input builder and validator for React JS
| [Quick Start](#quick-start) | [API](/API.md) | [Examples](/examples) | | [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
| --------------------------- | -------------- | --------------------- | |---|---|---|
## Background ## <a name="background">Background</a>
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
I wrote an article on forms and validation with React, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this component. The main concept is that forms, inputs and validation is done very differently across developers and projects. This extension to React JS aims to be that "sweet spot" between flexibility and reusability.
The main concept is that forms, inputs, and validation are done very differently across developers and projects. This React component aims to be that “sweet spot” between flexibility and reusability. ## What you can do
## What You Can Do 1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
1. Build any kind of form element components. Not just traditional inputs, but anything you want, and get that validation for free 2. Add validation rules and use them with simple syntax
2. Add validation rules and use them with simple syntax
3. Use handlers for different states of your form. (`onError`, `onSubmit`, `onValid`, etc.) 3. Use handlers for different states of your form. Ex. "onSubmit", "onError", "onValid" etc.
4. Pass external errors to the form to invalidate elements (E.g. a response from a server)
5. Dynamically add form elements to your form and they will register/unregister to the form 4. Pass external errors to the form to invalidate elements
5. You can dynamically add form elements to your form and they will register/unregister to the form
## Default elements
You can look at examples in this repo or use the [formsy-react-components](https://github.com/twisty/formsy-react-components) project to use bootstrap with formsy-react, or use [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) to use [Material-UI](http://material-ui.com/) with formsy-react.
## Install ## Install
`yarn add formsy-react react react-dom` and use with webpack, browserify, etc. 1. Download from this REPO and use globally (Formsy) or with requirejs
2. Install with `npm install formsy-react` and use with browserify etc.
3. Install with `bower install formsy-react`
## Quick Start ## Changes
### 1. Build a Formsy element
```jsx
// MyInput.js
import { withFormsy } from 'formsy-react';
import React from 'react';
class MyInput extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
}
changeValue(event) {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
// Important: Don't skip this step. This pattern is required
// for Formsy to work.
this.props.setValue(event.currentTarget.value);
}
render() {
// An error message is returned only if the component is invalid
const errorMessage = this.props.getErrorMessage();
return (
<div>
<input
onChange={this.changeValue}
type="text"
value={this.props.getValue() || ''}
/>
<span>{errorMessage}</span>
</div>
);
}
}
export default withFormsy(MyInput);
```
`withFormsy` is a [Higher-Order Component](https://facebook.github.io/react/docs/higher-order-components.html) that exposes additional props to `MyInput`. See the [API](/API.md#withFormsy) documentation to view a complete list of the props.
### 2. Use your Formsy element
```jsx
import Formsy from 'formsy-react';
import React from 'react';
import MyInput from './MyInput';
export default class App extends React.Component {
constructor(props) {
super(props);
this.disableButton = this.disableButton.bind(this);
this.enableButton = this.enableButton.bind(this);
this.state = { canSubmit: false };
}
disableButton() {
this.setState({ canSubmit: false });
}
enableButton() {
this.setState({ canSubmit: true });
}
submit(model) {
fetch('http://example.com/', {
method: 'post',
body: JSON.stringify(model)
});
}
render() {
return (
<Formsy onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<MyInput
name="email"
validations="isEmail"
validationError="This is not a valid email"
required
/>
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy>
);
}
}
```
This code results in a form with a submit button that will run the `submit` method when the form is submitted with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) and the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
## Contribute
- Fork repo
- `yarn`
- `yarn examples` runs the development server on `localhost:8080`
- `yarn test` runs the tests
## Changelog
[Check out releases](https://github.com/christianalfoni/formsy-react/releases) [Check out releases](https://github.com/christianalfoni/formsy-react/releases)
[Older changes](CHANGES.md)
## How to use
See [`examples` folder](/examples) for examples. [Codepen demo](http://codepen.io/semigradsky/pen/dYYpwv?editors=001).
Complete API reference is available [here](/API.md).
#### Formsy gives you a form straight out of the box
```jsx
import Formsy from 'formsy-react';
const MyAppForm = React.createClass({
getInitialState() {
return {
canSubmit: false
}
},
enableButton() {
this.setState({
canSubmit: true
});
},
disableButton() {
this.setState({
canSubmit: false
});
},
submit(model) {
someDep.saveEmail(model.email);
},
render() {
return (
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/>
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy.Form>
);
}
});
```
This code results in a form with a submit button that will run the `submit` method when the submit button is clicked with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) or the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
#### Building a form element (required)
```jsx
import Formsy from 'formsy-react';
const MyOwnInput = React.createClass({
// Add the Formsy Mixin
mixins: [Formsy.Mixin],
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
changeValue(event) {
this.setValue(event.currentTarget.value);
},
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
// An error message is returned ONLY if the component is invalid
// or the server has returned an error message
const errorMessage = this.getErrorMessage();
return (
<div className={className}>
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
<span>{errorMessage}</span>
</div>
);
}
});
```
The form element component is what gives the form validation functionality to whatever you want to put inside this wrapper. You do not have to use traditional inputs, it can be anything you want and the value of the form element can also be anything you want. As you can see it is very flexible, you just have a small API to help you identify the state of the component and set its value.
## Related projects
- [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) - A formsy-react compatibility wrapper for [Material-UI](http://material-ui.com/) form components.
- [formsy-react-components](https://github.com/twisty/formsy-react-components) - A set of React JS components for use in a formsy-react form.
- ...
- Send PR for adding your project to this list!
## Contribute
- Fork repo
- `npm install`
- `npm run examples` runs the development server on `localhost:8080`
- `npm test` runs the tests
## License ## License
[The MIT License (MIT)](/LICENSE) [The MIT License (MIT)](/LICENSE)

25
bower.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "formsy-react",
"version": "0.18.0",
"description": "A form input builder and validator for React JS",
"repository": {
"type": "git",
"url": "https://github.com/christianalfoni/formsy-react.git"
},
"main": "src/main.js",
"license": "MIT",
"ignore": [
"build/",
"Gulpfile.js"
],
"dependencies": {
"react": "^0.14.7 || ^15.0.0"
},
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
]
}

View File

@ -1,10 +0,0 @@
{
"env": {
"browser": true
},
"extends": "eslint:recommended",
"rules": {
"react/jsx-filename-extension": 0,
"react/no-multi-comp": 0
}
}

View File

@ -29,7 +29,7 @@ If it is not helped try update your node.js and npm.
2. [**Custom Validation**](custom-validation) 2. [**Custom Validation**](custom-validation)
One field with added validation rule (`addValidationRule`) and one field with dynamically added validation and error messages. One field with added validation rule (`Formsy.addValidationRule`) and one field with dynamically added validation and error messages.
3. [**Reset Values**](reset-values) 3. [**Reset Values**](reset-values)

View File

@ -1,48 +0,0 @@
import React from 'react';
import { propTypes, withFormsy } from 'formsy-react';
class MyCheckbox extends React.Component {
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
this.state = {
value: true,
};
}
changeValue(event) {
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
this.props.setValue(event.target.checked)
}
render() {
// Set a specific className based on the validation
// state of this component. showRequired() is true
// when the value is empty and the required prop is
// passed to the input. showError() is true when the
// value typed is invalid
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`;
const value = this.props.getValue();
return (
<div
className={className}
>
<label htmlFor={this.props.name}>{this.props.title}</label>
<input
onChange={this.changeValue}
id={this.props.name}
type='checkbox'
checked={value}
data-checked={value}
/>
</div>
);
}
}
MyCheckbox.propTypes = {
...propTypes,
};
export default withFormsy(MyCheckbox);

View File

@ -1,47 +1,44 @@
import React from 'react'; import React from 'react';
import { propTypes, withFormsy } from 'formsy-react'; import Formsy from 'formsy-react';
class MyInput extends React.Component { const MyInput = React.createClass({
constructor(props) {
super(props);
this.changeValue = this.changeValue.bind(this);
}
// Add the Formsy Mixin
mixins: [Formsy.Mixin],
// setValue() will set the value of the component, which in
// turn will validate it and the rest of the form
changeValue(event) { changeValue(event) {
// setValue() will set the value of the component, which in this.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
// turn will validate it and the rest of the form },
this.props.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
}
render() { render() {
// Set a specific className based on the validation // Set a specific className based on the validation
// state of this component. showRequired() is true // state of this component. showRequired() is true
// when the value is empty and the required prop is // when the value is empty and the required prop is
// passed to the input. showError() is true when the // passed to the input. showError() is true when the
// value typed is invalid // value typed is invalid
const className = `form-group ${this.props.className} ${this.props.showRequired() ? 'required' : ''} ${this.props.showError() ? 'error' : ''}`; const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
// An error message is returned ONLY if the component is invalid // An error message is returned ONLY if the component is invalid
// or the server has returned an error message // or the server has returned an error message
const errorMessage = this.props.getErrorMessage(); const errorMessage = this.getErrorMessage();
return ( return (
<div className={className}> <div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label> <label htmlFor={this.props.name}>{this.props.title}</label>
<input <input
onChange={this.changeValue}
name={this.props.name}
type={this.props.type || 'text'} type={this.props.type || 'text'}
value={this.props.getValue() || ''} name={this.props.name}
onChange={this.changeValue}
value={this.getValue()}
checked={this.props.type === 'checkbox' && this.getValue() ? 'checked' : null}
/> />
<span className='validation-error'>{errorMessage}</span> <span className='validation-error'>{errorMessage}</span>
</div> </div>
); );
} }
} });
MyInput.propTypes = { export default MyInput;
...propTypes,
};
export default withFormsy(MyInput);

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { withFormsy } from 'formsy-react'; import Formsy from 'formsy-react';
function contains(container, item, cmp) { function contains(container, item, cmp) {
for (const it of container) { for (const it of container) {
@ -10,16 +10,18 @@ function contains(container, item, cmp) {
return false; return false;
} }
class MyRadioGroup extends React.Component { const MyRadioGroup = React.createClass({
state = { value: [], cmp: (a, b) => a === b }; mixins: [Formsy.Mixin],
getInitialState() {
return { value: [], cmp: (a, b) => a === b };
},
componentDidMount() { componentDidMount() {
const value = this.props.value || []; const value = this.props.value || [];
this.props.setValue(value); this.setValue(value);
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp }); this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
} },
changeValue = (value, event) => { changeValue(value, event) {
const checked = event.currentTarget.checked; const checked = event.currentTarget.checked;
let newValue = []; let newValue = [];
@ -29,14 +31,14 @@ class MyRadioGroup extends React.Component {
newValue = this.state.value.filter(it => !this.state.cmp(it, value)); newValue = this.state.value.filter(it => !this.state.cmp(it, value));
} }
this.props.setValue(newValue); this.setValue(newValue);
this.setState({ value: newValue }); this.setState({ value: newValue });
} },
render() { render() {
const className = 'form-group' + (this.props.className || ' ') + const className = 'form-group' + (this.props.className || ' ') +
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : ''); (this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage(); const errorMessage = this.getErrorMessage();
const { name, title, items } = this.props; const { name, title, items } = this.props;
return ( return (
@ -59,6 +61,6 @@ class MyRadioGroup extends React.Component {
); );
} }
} });
export default withFormsy(MyRadioGroup); export default MyRadioGroup;

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import { withFormsy } from 'formsy-react'; import Formsy from 'formsy-react';
class MyRadioGroup extends React.Component { const MyRadioGroup = React.createClass({
state = {}; mixins: [Formsy.Mixin],
componentDidMount() { componentDidMount() {
const value = this.props.value; const value = this.props.value;
this.props.setValue(value); this.setValue(value);
this.setState({ value }); this.setState({ value });
} },
changeValue = (value) => { changeValue(value) {
this.props.setValue(value); this.setValue(value);
this.setState({ value }); this.setState({ value });
} },
render() { render() {
const className = 'form-group' + (this.props.className || ' ') + const className = 'form-group' + (this.props.className || ' ') +
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : ''); (this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage(); const errorMessage = this.getErrorMessage();
const { name, title, items } = this.props; const { name, title, items } = this.props;
return ( return (
@ -40,6 +40,7 @@ class MyRadioGroup extends React.Component {
</div> </div>
); );
} }
}
export default withFormsy(MyRadioGroup); });
export default MyRadioGroup;

View File

@ -1,15 +1,17 @@
import React from 'react'; import React from 'react';
import { withFormsy } from 'formsy-react'; import Formsy from 'formsy-react';
class MySelect extends React.Component { const MySelect = React.createClass({
changeValue = (event) => { mixins: [Formsy.Mixin],
this.props.setValue(event.currentTarget.value);
} changeValue(event) {
this.setValue(event.currentTarget.value);
},
render() { render() {
const className = 'form-group' + (this.props.className || ' ') + const className = 'form-group' + (this.props.className || ' ') +
(this.props.showRequired() ? 'required' : this.props.showError() ? 'error' : ''); (this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.props.getErrorMessage(); const errorMessage = this.getErrorMessage();
const options = this.props.options.map((option, i) => ( const options = this.props.options.map((option, i) => (
<option key={option.title+option.value} value={option.value}> <option key={option.title+option.value} value={option.value}>
@ -20,13 +22,14 @@ class MySelect extends React.Component {
return ( return (
<div className={className}> <div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label> <label htmlFor={this.props.name}>{this.props.title}</label>
<select name={this.props.name} onChange={this.changeValue} value={this.props.getValue()}> <select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
{options} {options}
</select> </select>
<span className='validation-error'>{errorMessage}</span> <span className='validation-error'>{errorMessage}</span>
</div> </div>
); );
} }
}
export default withFormsy(MySelect); });
export default MySelect;

View File

@ -1,124 +1,105 @@
import Formsy, { addValidationRule, propTypes, withFormsy } from 'formsy-react';
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Formsy from 'formsy-react';
import MyInput from './../components/Input'; import MyInput from './../components/Input';
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
addValidationRule('time', (values, value) => { const validators = {
return /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/.test(value); time: {
}); regexp: /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/,
addValidationRule('decimal', (values, value) => { message: 'Not valid time'
return /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/.test(value); },
}); decimal: {
addValidationRule('binary', (values, value) => { regexp: /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/,
return /^([0-1])*$/.test(value); message: 'Please type decimal value'
}); },
addValidationRule('isYearOfBirth', (values, value) => { binary: {
const parsedValue = parseInt(value, 10); regexp: /^([0-1])*$/,
if (typeof parsedValue !== 'number') { message: '10101000'
}
};
Formsy.addValidationRule('isYearOfBirth', (values, value) => {
value = parseInt(value);
if (typeof value !== 'number') {
return false; return false;
} }
return parsedValue < currentYear && parsedValue > currentYear - 130; return value < currentYear && value > currentYear - 130;
}); });
class App extends React.Component { const App = React.createClass({
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit(data) { submit(data) {
alert(JSON.stringify(data, null, 4)); alert(JSON.stringify(data, null, 4));
} },
render() { render() {
return ( return (
<Formsy onSubmit={this.submit} className="custom-validation"> <Formsy.Form onSubmit={this.submit} className="custom-validation">
<MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" /> <MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" />
<FormsyDynamicInput name="dynamic" title="..." /> <DynamicInput name="dynamic" title="..." />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</Formsy> </Formsy.Form>
); );
} }
} });
class DynamicInput extends React.Component {
constructor(props) {
super(props);
this.state = { validationType: 'time' };
this.changeValidation = this.changeValidation.bind(this);
this.changeValue = this.changeValue.bind(this);
}
const DynamicInput = React.createClass({
mixins: [Formsy.Mixin],
getInitialState() {
return { validationType: 'time' };
},
changeValue(event) {
this.setValue(event.currentTarget.value);
},
changeValidation(validationType) { changeValidation(validationType) {
this.setState({ validationType: validationType }); this.setState({ validationType: validationType });
this.props.setValue(this.props.getValue()); this.setValue(this.getValue());
} },
validate() {
changeValue(event) { const value = this.getValue();
this.props.setValue(event.currentTarget.value); console.log(value, this.state.validationType);
} return value ? validators[this.state.validationType].regexp.test(value) : true;
},
getCustomErrorMessage() {
return this.showError() ? validators[this.state.validationType].message : '';
},
render() { render() {
const errorMessage = { const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
time: 'Not a valid time format', const errorMessage = this.getCustomErrorMessage();
decimal: "Not a valid decimal value",
binary: "Not a valid binary number"
}
return ( return (
<div> <div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label> <label htmlFor={this.props.name}>{this.props.title}</label>
<MyInput validations={this.state.validationType} validationError={errorMessage[this.state.validationType]} type='text' name={this.props.name} onChange={this.changeValue} value={this.props.getValue() || ''} /> <input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue() || ''}/>
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation} /> <span className='validation-error'>{errorMessage}</span>
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/>
</div> </div>
); );
} }
} });
DynamicInput.displayName = "dynamic input";
DynamicInput.propTypes = {
...propTypes,
};
const FormsyDynamicInput = withFormsy(DynamicInput);
class Validations extends React.Component {
constructor(props) {
super(props);
this.changeValidation = this.changeValidation.bind(this);
}
const Validations = React.createClass({
changeValidation(e) { changeValidation(e) {
this.props.changeValidation(e.target.value); this.props.changeValidation(e.target.value);
} },
render() { render() {
const { validationType } = this.props; const { validationType } = this.props;
return ( return (
<fieldset> <fieldset onChange={this.changeValidation}>
<legend>Validation Type</legend> <legend>Validation Type</legend>
<div> <div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='time' checked={validationType === 'time'} />Time <input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
</div> </div>
<div> <div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='decimal' checked={validationType === 'decimal'} />Decimal <input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
</div> </div>
<div> <div>
<input onChange={this.changeValidation} name='validationType' type='radio' value='binary' checked={validationType === 'binary'} />Binary <input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
</div> </div>
</fieldset> </fieldset>
); );
} }
} });
Validations.propTypes = { ReactDOM.render(<App/>, document.getElementById('example'));
changeValidation: PropTypes.func.isRequired,
validationType: PropTypes.string.isRequired,
};
ReactDOM.render(<App />, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<body> <body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Custom Validation</h1> <h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Custom Validation</h1>
<div id="example"/> <div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/custom-validation.js"></script> <script src="/__build__/custom-validation.js"></script>
</body> </body>
</html> </html>

View File

@ -9,8 +9,7 @@
} }
.field { .field {
display: flex; overflow: hidden;
align-items: center;
} }
.many-fields .form-group { .many-fields .form-group {
@ -18,5 +17,8 @@
float: left; float: left;
} }
.many-fields .remove-field { .many-fields .remove-field {
max-height: 2rem; margin-top: 30px;
margin-left: 8px;
display: inline-block;
text-decoration: none;
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Formsy from 'formsy-react'; import { Form } from 'formsy-react';
import MyInput from './../components/Input'; import MyInput from './../components/Input';
import MySelect from './../components/Select'; import MySelect from './../components/Select';
@ -14,7 +14,7 @@ const Fields = props => {
props.onRemove(pos); props.onRemove(pos);
}; };
} }
const foo = 'required';
return ( return (
<div className="fields"> <div className="fields">
{props.data.map((field, i) => ( {props.data.map((field, i) => (
@ -46,7 +46,7 @@ const Fields = props => {
/> />
) )
} }
<button className="remove-field" onClick={onRemove(i)}>X</button> <a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
</div> </div>
)) ))
} }
@ -54,49 +54,36 @@ const Fields = props => {
); );
}; };
class App extends React.Component { const App = React.createClass({
constructor(props) { getInitialState() {
super(props); return { fields: [], canSubmit: false };
this.state = { fields: [], canSubmit: false }; },
this.addField = this.addField.bind(this);
this.removeField = this.removeField.bind(this);
this.enableButton = this.enableButton.bind(this);
this.disableButton = this.disableButton.bind(this);
}
submit(data) { submit(data) {
alert(JSON.stringify(data, null, 4)); alert(JSON.stringify(data, null, 4));
} },
addField(fieldData) { addField(fieldData) {
fieldData.validations = fieldData.validations.length ? fieldData.validations = fieldData.validations.length ?
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) : fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
null; null;
fieldData.id = Date.now(); fieldData.id = Date.now();
this.setState({ fields: this.state.fields.concat(fieldData) }); this.setState({ fields: this.state.fields.concat(fieldData) });
} },
removeField(pos) { removeField(pos) {
const fields = this.state.fields; const fields = this.state.fields;
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) }) this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
} },
enableButton() { enableButton() {
this.setState({ canSubmit: true }); this.setState({ canSubmit: true });
} },
disableButton() { disableButton() {
this.setState({ canSubmit: false }); this.setState({ canSubmit: false });
} },
render() { render() {
const { fields, canSubmit } = this.state; const { fields, canSubmit } = this.state;
return ( return (
<div> <div>
<Formsy onSubmit={this.addField} className="many-fields-conf"> <Form onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet <MyMultiCheckboxSet name="validations" title="Validations"
name="validations"
title="Validations"
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)} cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
items={[ items={[
{isEmail: true}, {isEmail: true},
@ -108,27 +95,19 @@ class App extends React.Component {
{maxLength: 7} {maxLength: 7}
]} ]}
/> />
<MyRadioGroup <MyRadioGroup name="required" value={false} title="Required"
name="required" items={[true, false]} />
value={false} <MyRadioGroup name="type" value="input" title="Type"
title="Required" items={['input', 'select']} />
items={[true, false]}
/>
<MyRadioGroup
name="type"
value="input"
title="Type"
items={['input', 'select']}
/>
<button type="submit">Add</button> <button type="submit">Add</button>
</Formsy> </Form>
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields"> <Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
<Fields data={fields} onRemove={this.removeField} /> <Fields data={fields} onRemove={this.removeField} />
<button type="submit" disabled={!canSubmit}>Submit</button> <button type="submit" disabled={!canSubmit}>Submit</button>
</Formsy> </Form>
</div> </div>
); );
} }
} });
ReactDOM.render(<App />, document.getElementById('example')); ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<body> <body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1> <h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
<div id="example"/> <div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/dynamic-form-fields.js"></script> <script src="/__build__/dynamic-form-fields.js"></script>
</body> </body>
</html> </html>

View File

@ -1,38 +1,31 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Formsy from 'formsy-react'; import { Form } from 'formsy-react';
import MyInput from './../components/Input'; import MyInput from './../components/Input';
class App extends React.Component { const App = React.createClass({
constructor(props) { getInitialState() {
super(props); return { canSubmit: false };
this.state = { canSubmit: false }; },
this.disableButton = this.disableButton.bind(this);
this.enableButton = this.enableButton.bind(this);
}
submit(data) { submit(data) {
alert(JSON.stringify(data, null, 4)); alert(JSON.stringify(data, null, 4));
} },
enableButton() { enableButton() {
this.setState({ canSubmit: true }); this.setState({ canSubmit: true });
} },
disableButton() { disableButton() {
this.setState({ canSubmit: false }); this.setState({ canSubmit: false });
} },
render() { render() {
return ( return (
<Formsy onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login"> <Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
<MyInput name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required /> <MyInput value="" name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
<MyInput name="password" title="Password" type="password" required /> <MyInput value="" name="password" title="Password" type="password" required />
<button type="submit" disabled={!this.state.canSubmit}>Submit</button> <button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy> </Form>
); );
} }
} });
ReactDOM.render(<App/>, document.getElementById('example')); ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<body> <body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Login</h1> <h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Login</h1>
<div id="example"/> <div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/login.js"></script> <script src="/__build__/login.js"></script>
</body> </body>
</html> </html>

View File

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Formsy from 'formsy-react'; import { Form } from 'formsy-react';
import MyCheckbox from './../components/Checkbox';
import MyInput from './../components/Input'; import MyInput from './../components/Input';
import MySelect from './../components/Select'; import MySelect from './../components/Select';
@ -12,45 +11,19 @@ const user = {
hair: 'brown' hair: 'brown'
}; };
const randomNames = ['Christian', 'Dmitry', 'Aesop']; const App = React.createClass({
const randomFree = [true, false];
const randomHair = ['brown', 'black', 'blonde', 'red'];
class App extends React.Component {
constructor(props) {
super(props);
this.randomize = this.randomize.bind(this);
this.submit = this.submit.bind(this);
}
randomize() {
const random = {
name: randomNames[Math.floor(Math.random()*randomNames.length)],
free: randomFree[Math.floor(Math.random()*randomFree.length)],
hair: randomHair[Math.floor(Math.random()*randomHair.length)],
};
this.form.reset(random);
}
submit(data) { submit(data) {
alert(JSON.stringify(data, null, 4)); alert(JSON.stringify(data, null, 4));
} },
resetForm() {
this.refs.form.reset();
},
render() { render() {
return ( return (
<Formsy <Formsy.Form ref="form" onSubmit={this.submit} className="form">
ref={(c) => this.form = c}
onSubmit={this.submit}
onReset={this.reset}
className="form"
>
<MyInput name="name" title="Name" value={user.name} /> <MyInput name="name" title="Name" value={user.name} />
<MyCheckbox name="free" title="Free to hire" value={user.free} /> <MyInput name="free" title="Free to hire" type="checkbox" value={user.free} />
<MySelect <MySelect name="hair" title="Hair" value={user.hair}
name="hair"
title="Hair"
value={user.hair}
options={[ options={[
{ value: "black", title: "Black" }, { value: "black", title: "Black" },
{ value: "brown", title: "Brown" }, { value: "brown", title: "Brown" },
@ -60,13 +33,12 @@ class App extends React.Component {
/> />
<div className="buttons"> <div className="buttons">
<button type="reset">Reset</button> <button type="reset" onClick={this.resetForm}>Reset</button>
<button type="button" onClick={this.randomize}>Randomize</button>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</div> </div>
</Formsy> </Formsy.Form>
); );
} }
} });
ReactDOM.render(<App />, document.getElementById('example')); ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -8,6 +8,7 @@
<body> <body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Reset Values</h1> <h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Reset Values</h1>
<div id="example"/> <div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/reset-values.js"></script> <script src="/__build__/reset-values.js"></script>
</body> </body>
</html> </html>

View File

@ -1,41 +1,49 @@
const fs = require('fs'); var fs = require('fs');
const path = require('path'); var path = require('path');
const webpack = require('webpack'); var webpack = require('webpack');
function isDirectory(dir) { function isDirectory(dir) {
return fs.lstatSync(dir).isDirectory(); return fs.lstatSync(dir).isDirectory();
} }
module.exports = { module.exports = {
devtool: 'inline-source-map', devtool: 'inline-source-map',
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0; entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
var isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
if (!isDraft && isDirectory(path.join(__dirname, dir))) { if (!isDraft && isDirectory(path.join(__dirname, dir))) {
entries[dir] = path.join(__dirname, dir, 'app.js'); entries[dir] = path.join(__dirname, dir, 'app.js');
} }
return entries; return entries;
}, {}), }, {}),
output: { output: {
path: path.resolve(__dirname, 'examples/__build__'), path: 'examples/__build__',
filename: '[name].js', filename: '[name].js',
chunkFilename: '[id].chunk.js', chunkFilename: '[id].chunk.js',
publicPath: '/__build__/', publicPath: '/__build__/'
}, },
module: { module: {
loaders: [{ loaders: [
test: /\.js$/, { test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
exclude: /node_modules/, ]
loader: 'babel-loader',
}],
}, },
resolve: { resolve: {
alias: { alias: {
'formsy-react': '../../src/index', 'formsy-react': '../../src/main'
}, }
}, },
plugins: [ plugins: [
new webpack.optimize.CommonsChunkPlugin('shared.js'),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}), })
], ]
}; };

View File

@ -1,61 +1,49 @@
{ {
"name": "formsy-react", "name": "formsy-react",
"version": "1.0.0", "version": "0.19.5",
"description": "A form input builder and validator for React", "description": "A form input builder and validator for React JS",
"keywords": [
"form",
"forms",
"formsy",
"react",
"react-component",
"validation"
],
"license": "MIT",
"homepage": "https://github.com/christianalfoni/formsy-react",
"bugs": "https://github.com/christianalfoni/formsy-react/issues",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/christianalfoni/formsy-react.git" "url": "https://github.com/christianalfoni/formsy-react.git"
}, },
"author": "Christian Alfoni", "main": "lib/main.js",
"files": [
"lib"
],
"main": "release/formsy-react.js",
"scripts": { "scripts": {
"build": "NODE_ENV=production webpack -p --config webpack.config.js && yarn run prepublish", "build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
"test": "babel-node testrunner",
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples", "examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
"lint": "eslint src/**/*.js", "prepublish": "babel ./src/ -d ./lib/"
"prepublish": "rm -Rf ./lib && babel ./src/ -d ./lib/",
"test": "babel-node testrunner"
}, },
"author": "Christian Alfoni",
"license": "MIT",
"keywords": [
"react",
"form",
"forms",
"validation",
"react-component"
],
"dependencies": { "dependencies": {
"form-data-to-object": "^0.2.0", "form-data-to-object": "^0.2.0"
"prop-types": "^15.5.10"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "^6.24.1", "babel-cli": "^6.6.5",
"babel-loader": "^7.1.1", "babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.24.1", "babel-preset-stage-2": "^6.5.0",
"eslint": "^3.19.0 || ^4.3.0", "create-react-class": "^15.6.0",
"eslint-config-airbnb": "^15.1.0", "jsdom": "^6.5.1",
"eslint-plugin-import": "^2.7.0", "nodeunit": "^0.9.1",
"eslint-plugin-jsx-a11y": "^5.1.1", "prop-types": "^15.5.10",
"eslint-plugin-react": "^7.1.0", "react": "^15.0.0",
"jsdom": "^11.1.0", "react-addons-pure-render-mixin": "^15.0.0",
"nodeunit": "^0.11.1", "react-addons-test-utils": "^15.0.0",
"react": "^15.6.1", "react-dom": "^15.0.0",
"react-addons-pure-render-mixin": "^15.6.0", "sinon": "^1.17.3",
"react-addons-test-utils": "^15.6.0", "webpack": "^1.12.14",
"react-dom": "^15.6.1", "webpack-dev-server": "^1.14.1"
"sinon": "^3.2.0",
"webpack": "^3.5.4",
"webpack-dev-server": "^2.7.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^15.6.1", "react": "^0.14.0 || ^15.0.0"
"react-dom": "^15.6.1"
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

30
src/Decorator.js Normal file
View File

@ -0,0 +1,30 @@
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js');
module.exports = function () {
return function (Component) {
return createReactClass({
mixins: [Mixin],
render: function () {
return React.createElement(Component, {
setValidations: this.setValidations,
setValue: this.setValue,
resetValue: this.resetValue,
getValue: this.getValue,
hasValue: this.hasValue,
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
showRequired: this.showRequired,
showError: this.showError,
isValidValue: this.isValidValue,
...this.props
});
}
});
};
};

44
src/HOC.js Normal file
View File

@ -0,0 +1,44 @@
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js');
module.exports = function (Component) {
return createReactClass({
displayName: 'Formsy(' + getDisplayName(Component) + ')',
mixins: [Mixin],
render: function () {
const { innerRef } = this.props;
const propsForElement = {
setValidations: this.setValidations,
setValue: this.setValue,
resetValue: this.resetValue,
getValue: this.getValue,
hasValue: this.hasValue,
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
showRequired: this.showRequired,
showError: this.showError,
isValidValue: this.isValidValue,
...this.props
};
if (innerRef) {
propsForElement.ref = innerRef;
}
return React.createElement(Component, propsForElement);
}
});
};
function getDisplayName(Component) {
return (
Component.displayName ||
Component.name ||
(typeof Component === 'string' ? Component : 'Component')
);
}

176
src/Mixin.js Normal file
View File

@ -0,0 +1,176 @@
var PropTypes = require('prop-types');
var utils = require('./utils.js');
var React = global.React || require('react');
var convertValidationsToObject = function (validations) {
if (typeof validations === 'string') {
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
var args = validation.split(':');
var validateMethod = args.shift();
args = args.map(function (arg) {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
if (args.length > 1) {
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
}
validations[validateMethod] = args.length ? args[0] : true;
return validations;
}, {});
}
return validations || {};
};
module.exports = {
getInitialState: function () {
return {
_value: this.props.value,
_isRequired: false,
_isValid: true,
_isPristine: true,
_pristineValue: this.props.value,
_validationError: [],
_externalError: null,
_formSubmitted: false
};
},
contextTypes: {
formsy: PropTypes.object // What about required?
},
getDefaultProps: function () {
return {
validationError: '',
validationErrors: {}
};
},
componentWillMount: function () {
var configure = function () {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
//this.props._attachToForm(this);
}.bind(this);
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
/*
if (!this.props._attachToForm) {
return setTimeout(function () {
if (!this.isMounted()) return;
if (!this.props._attachToForm) {
throw new Error('Form Mixin requires component to be nested in a Form');
}
configure();
}.bind(this), 0);
}
*/
configure();
},
// We have to make the validate method is kept when new props are added
componentWillReceiveProps: function (nextProps) {
this.setValidations(nextProps.validations, nextProps.required);
},
componentDidUpdate: function (prevProps) {
// If the value passed has changed, set it. If value is not passed it will
// internally update, and this will never run
if (!utils.isSame(this.props.value, prevProps.value)) {
this.setValue(this.props.value);
}
// If validations or required is changed, run a new validation
if (!utils.isSame(this.props.validations, prevProps.validations) || !utils.isSame(this.props.required, prevProps.required)) {
this.context.formsy.validate(this);
}
},
// Detach it when component unmounts
componentWillUnmount: function () {
this.context.formsy.detachFromForm(this);
//this.props._detachFromForm(this);
},
setValidations: function (validations, required) {
// Add validations to the store itself as the props object can not be modified
this._validations = convertValidationsToObject(validations) || {};
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
},
// We validate after the value has been set
setValue: function (value) {
this.setState({
_value: value,
_isPristine: false
}, function () {
this.context.formsy.validate(this);
//this.props._validate(this);
}.bind(this));
},
resetValue: function () {
this.setState({
_value: this.state._pristineValue,
_isPristine: true
}, function () {
this.context.formsy.validate(this);
//this.props._validate(this);
});
},
getValue: function () {
return this.state._value;
},
hasValue: function () {
return this.state._value !== '';
},
getErrorMessage: function () {
var messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
},
getErrorMessages: function () {
return !this.isValid() || this.showRequired() ? (this.state._externalError || this.state._validationError || []) : [];
},
isFormDisabled: function () {
return this.context.formsy.isFormDisabled();
//return this.props._isFormDisabled();
},
isValid: function () {
return this.state._isValid;
},
isPristine: function () {
return this.state._isPristine;
},
isFormSubmitted: function () {
return this.state._formSubmitted;
},
isRequired: function () {
return !!this.props.required;
},
showRequired: function () {
return this.state._isRequired;
},
showError: function () {
return !this.showRequired() && !this.isValid();
},
isValidValue: function (value) {
return this.context.formsy.isValidValue.call(null, this, value);
//return this.props._isValidValue.call(null, this, value);
}
};

View File

@ -1,255 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
const convertValidationsToObject = (validations) => {
if (typeof validations === 'string') {
return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
let args = validation.split(':');
const validateMethod = args.shift();
args = args.map((arg) => {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
if (args.length > 1) {
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
}
// Avoid parameter reassignment
const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
return validationsAccumulatorCopy;
}, {});
}
return validations || {};
};
const propTypes = {
innerRef: PropTypes.func,
name: PropTypes.string.isRequired,
required: PropTypes.bool,
validations: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]),
value: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]),
};
export {
propTypes,
};
export default (Component) => {
class WrappedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
isRequired: false,
isValid: true,
isPristine: true,
pristineValue: props.value,
validationError: [],
externalError: null,
formSubmitted: false,
};
this.getErrorMessage = this.getErrorMessage.bind(this);
this.getErrorMessages = this.getErrorMessages.bind(this);
this.getValue = this.getValue.bind(this);
this.isFormDisabled = this.isFormDisabled.bind(this);
this.isPristine = this.isPristine.bind(this);
this.isRequired = this.isRequired.bind(this);
this.isValid = this.isValid.bind(this);
this.resetValue = this.resetValue.bind(this);
this.setValue = this.setValue.bind(this);
this.showRequired = this.showRequired.bind(this);
}
componentWillMount() {
const configure = () => {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
};
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
configure();
}
// We have to make sure the validate method is kept when new props are added
componentWillReceiveProps(nextProps) {
this.setValidations(nextProps.validations, nextProps.required);
}
componentDidUpdate(prevProps) {
// If the value passed has changed, set it. If value is not passed it will
// internally update, and this will never run
if (!utils.isSame(this.props.value, prevProps.value)) {
this.setValue(this.props.value);
}
// If validations or required is changed, run a new validation
if (!utils.isSame(this.props.validations, prevProps.validations) ||
!utils.isSame(this.props.required, prevProps.required)) {
this.context.formsy.validate(this);
}
}
// Detach it when component unmounts
componentWillUnmount() {
this.context.formsy.detachFromForm(this);
}
getErrorMessage() {
const messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
}
getErrorMessages() {
return !this.isValid() || this.showRequired() ?
(this.state.externalError || this.state.validationError || []) : [];
}
getValue() {
return this.state.value;
}
hasValue() {
return this.state.value !== '';
}
isFormDisabled() {
return this.context.formsy.isFormDisabled();
}
isFormSubmitted() {
return this.state.formSubmitted;
}
isPristine() {
return this.state.isPristine;
}
isRequired() {
return !!this.props.required;
}
isValid() {
return this.state.isValid;
}
isValidValue(value) {
return this.context.formsy.isValidValue.call(null, this, value);
// return this.props.isValidValue.call(null, this, value);
}
resetValue() {
this.setState({
value: this.state.pristineValue,
isPristine: true,
}, () => {
this.context.formsy.validate(this);
});
}
setValidations(validations, required) {
// Add validations to the store itself as the props object can not be modified
this.validations = convertValidationsToObject(validations) || {};
this.requiredValidations = required === true ? { isDefaultRequiredValue: true } :
convertValidationsToObject(required);
}
// By default, we validate after the value has been set.
// A user can override this and pass a second parameter of `false` to skip validation.
setValue(value, validate = true) {
if (!validate) {
this.setState({
value,
});
} else {
this.setState({
value,
isPristine: false,
}, () => {
this.context.formsy.validate(this);
});
}
}
showError() {
return !this.showRequired() && !this.isValid();
}
showRequired() {
return this.state.isRequired;
}
render() {
const { innerRef } = this.props;
const propsForElement = {
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
getValue: this.getValue,
hasValue: this.hasValue,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
isValidValue: this.isValidValue,
resetValue: this.resetValue,
setValidations: this.setValidations,
setValue: this.setValue,
showRequired: this.showRequired,
showError: this.showError,
...this.props,
};
if (innerRef) {
propsForElement.ref = innerRef;
}
return React.createElement(Component, propsForElement);
}
}
function getDisplayName(component) {
return (
component.displayName ||
component.name ||
(typeof component === 'string' ? component : 'Component')
);
}
WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;
WrappedComponent.contextTypes = {
formsy: PropTypes.object, // What about required?
};
WrappedComponent.defaultProps = {
innerRef: () => {},
required: false,
validationError: '',
validationErrors: {},
validations: null,
value: Component.defaultValue,
};
WrappedComponent.propTypes = propTypes;
return WrappedComponent;
};

View File

@ -1,468 +0,0 @@
import formDataToObject from 'form-data-to-object';
import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
import validationRules from './validationRules';
import Wrapper, { propTypes } from './Wrapper';
class Formsy extends React.Component {
constructor(props) {
super(props);
this.state = {
isValid: true,
isSubmitting: false,
canChange: false,
};
this.inputs = [];
this.attachToForm = this.attachToForm.bind(this);
this.detachFromForm = this.detachFromForm.bind(this);
this.getCurrentValues = this.getCurrentValues.bind(this);
this.getPristineValues = this.getPristineValues.bind(this);
this.isChanged = this.isChanged.bind(this);
this.isFormDisabled = this.isFormDisabled.bind(this);
this.reset = this.reset.bind(this);
this.resetModel = this.resetModel.bind(this);
this.resetInternal = this.resetInternal.bind(this);
this.runValidation = this.runValidation.bind(this);
this.submit = this.submit.bind(this);
this.updateInputsWithError = this.updateInputsWithError.bind(this);
this.validate = this.validate.bind(this);
this.validateForm = this.validateForm.bind(this);
}
getChildContext() {
return {
formsy: {
attachToForm: this.attachToForm,
detachFromForm: this.detachFromForm,
validate: this.validate,
isFormDisabled: this.isFormDisabled,
isValidValue: (component, value) => this.runValidation(component, value).isValid,
},
};
}
componentDidMount() {
this.validateForm();
}
componentWillUpdate() {
// Keep a reference to input names before form updates,
// to check if inputs has changed after render
this.prevInputNames = this.inputs.map(component => component.props.name);
}
componentDidUpdate() {
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
this.setInputValidationErrors(this.props.validationErrors);
}
const newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm();
}
}
// Method put on each input component to register
// itself to the form
attachToForm(component) {
if (this.inputs.indexOf(component) === -1) {
this.inputs.push(component);
}
this.validate(component);
}
// Method put on each input component to unregister
// itself from the form
detachFromForm(component) {
const componentPos = this.inputs.indexOf(component);
if (componentPos !== -1) {
this.inputs = this.inputs.slice(0, componentPos).concat(this.inputs.slice(componentPos + 1));
}
this.validateForm();
}
getCurrentValues() {
return this.inputs.reduce((data, component) => {
const name = component.props.name;
const dataCopy = Object.assign({}, data); // avoid param reassignment
dataCopy[name] = component.state.value;
return dataCopy;
}, {});
}
getModel() {
const currentValues = this.getCurrentValues();
return this.mapModel(currentValues);
}
getPristineValues() {
return this.inputs.reduce((data, component) => {
const name = component.props.name;
const dataCopy = Object.assign({}, data); // avoid param reassignment
dataCopy[name] = component.props.value;
return dataCopy;
}, {});
}
// Checks if the values have changed from their initial value
isChanged() {
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
}
isFormDisabled() {
return this.props.disabled;
}
mapModel(model) {
if (this.props.mapping) {
return this.props.mapping(model);
}
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
const keyArray = key.split('.');
let base = mappedModel;
while (keyArray.length) {
const currentKey = keyArray.shift();
base[currentKey] = (keyArray.length ? base[currentKey] || {} : model[key]);
base = base[currentKey];
}
return mappedModel;
}, {}));
}
reset(data) {
this.setFormPristine(true);
this.resetModel(data);
}
resetInternal(event) {
event.preventDefault();
this.reset();
if (this.props.onReset) {
this.props.onReset();
}
}
// Reset each key in the model to the original / initial / specified value
resetModel(data) {
this.inputs.forEach((component) => {
const name = component.props.name;
if (data && Object.prototype.hasOwnProperty.call(data, name)) {
component.setValue(data[name]);
} else {
component.resetValue();
}
});
this.validateForm();
}
// Checks validation on current value or a passed value
runValidation(component, value = component.state.value) {
const currentValues = this.getCurrentValues();
const validationErrors = component.props.validationErrors;
const validationError = component.props.validationError;
const validationResults = utils.runRules(
value, currentValues, component.validations, validationRules,
);
const requiredResults = utils.runRules(
value, currentValues, component.requiredValidations, validationRules,
);
const isRequired = Object.keys(component.requiredValidations).length ?
!!requiredResults.success.length : false;
const isValid = !validationResults.failed.length &&
!(this.props.validationErrors && this.props.validationErrors[component.props.name]);
return {
isRequired,
isValid: isRequired ? false : isValid,
error: (() => {
if (isValid && !isRequired) {
return [];
}
if (validationResults.errors.length) {
return validationResults.errors;
}
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
}
if (isRequired) {
const error = validationErrors[requiredResults.success[0]];
return error ? [error] : null;
}
if (validationResults.failed.length) {
return validationResults.failed.map(failed =>
(validationErrors[failed] ? validationErrors[failed] : validationError))
.filter((x, pos, arr) => arr.indexOf(x) === pos); // remove duplicates
}
return undefined;
})(),
};
}
setInputValidationErrors(errors) {
this.inputs.forEach((component) => {
const name = component.props.name;
const args = [{
isValid: !(name in errors),
validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
}];
component.setState(...args);
});
}
setFormPristine(isPristine) {
this.setState({
formSubmitted: !isPristine,
});
// Iterate through each component and set it as pristine
// or "dirty".
this.inputs.forEach((component) => {
component.setState({
formSubmitted: !isPristine,
isPristine,
});
});
}
// Update model, submit to url prop and send the model
submit(event) {
if (event && event.preventDefault) {
event.preventDefault();
}
// Trigger form as not pristine.
// If any inputs have not been touched yet this will make them dirty
// so validation becomes visible (if based on isPristine)
this.setFormPristine(false);
const model = this.getModel();
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
if (this.state.isValid) {
this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError);
} else {
this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
}
}
// Go through errors from server and grab the components
// stored in the inputs map. Change their state to invalid
// and set the serverError message
updateInputsWithError(errors) {
Object.keys(errors).forEach((name) => {
const component = utils.find(this.inputs, input => input.props.name === name);
if (!component) {
throw new Error(`You are trying to update an input that does not exist. Verify errors object with input names. ${JSON.stringify(errors)}`);
}
const args = [{
isValid: this.props.preventExternalInvalidation,
externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name],
}];
component.setState(...args);
});
}
// Use the binded values and the actual input value to
// validate the input and set its state. Then check the
// state of the form itself
validate(component) {
// Trigger onChange
if (this.state.canChange) {
this.props.onChange(this.getCurrentValues(), this.isChanged());
}
const validation = this.runValidation(component);
// Run through the validations, split them up and call
// the validator IF there is a value or it is required
component.setState({
isValid: validation.isValid,
isRequired: validation.isRequired,
validationError: validation.error,
externalError: null,
}, this.validateForm);
}
// Validate the form by going through all child input components
// and check their state
validateForm() {
// We need a callback as we are validating all inputs again. This will
// run when the last component has set its state
const onValidationComplete = () => {
const allIsValid = this.inputs.every(component => component.state.isValid);
this.setState({
isValid: allIsValid,
});
if (allIsValid) {
this.props.onValid();
} else {
this.props.onInvalid();
}
// Tell the form that it can start to trigger change events
this.setState({
canChange: true,
});
};
// Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback
this.inputs.forEach((component, index) => {
const validation = this.runValidation(component);
if (validation.isValid && component.state.externalError) {
validation.isValid = false;
}
component.setState({
isValid: validation.isValid,
isRequired: validation.isRequired,
validationError: validation.error,
externalError: !validation.isValid && component.state.externalError ?
component.state.externalError : null,
}, index === this.inputs.length - 1 ? onValidationComplete : null);
});
// If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later
if (!this.inputs.length) {
this.setState({
canChange: true,
});
}
}
render() {
const {
getErrorMessage,
getErrorMessages,
getValue,
hasValue,
isFormDisabled,
isFormSubmitted,
isPristine,
isRequired,
isValid,
isValidValue,
mapping,
onChange,
// onError,
onInvalidSubmit,
onInvalid,
onReset,
onSubmit,
onValid,
onValidSubmit,
preventExternalInvalidation,
// reset,
resetValue,
setValidations,
setValue,
showError,
showRequired,
validationErrors,
...nonFormsyProps
} = this.props;
return React.createElement(
'form',
{
onReset: this.resetInternal,
onSubmit: this.submit,
...nonFormsyProps,
},
this.props.children,
);
}
}
Formsy.displayName = 'Formsy';
Formsy.defaultProps = {
children: null,
disabled: false,
getErrorMessage: () => {},
getErrorMessages: () => {},
getValue: () => {},
hasValue: () => {},
isFormDisabled: () => {},
isFormSubmitted: () => {},
isPristine: () => {},
isRequired: () => {},
isValid: () => {},
isValidValue: () => {},
mapping: null,
onChange: () => {},
onError: () => {},
onInvalid: () => {},
onInvalidSubmit: () => {},
onReset: () => {},
onSubmit: () => {},
onValid: () => {},
onValidSubmit: () => {},
preventExternalInvalidation: false,
resetValue: () => {},
setValidations: () => {},
setValue: () => {},
showError: () => {},
showRequired: () => {},
validationErrors: null,
};
Formsy.propTypes = {
children: PropTypes.node,
disabled: PropTypes.bool,
getErrorMessage: PropTypes.func,
getErrorMessages: PropTypes.func,
getValue: PropTypes.func,
hasValue: PropTypes.func,
isFormDisabled: PropTypes.func,
isFormSubmitted: PropTypes.func,
isPristine: PropTypes.func,
isRequired: PropTypes.func,
isValid: PropTypes.func,
isValidValue: PropTypes.func,
mapping: PropTypes.object, // eslint-disable-line
preventExternalInvalidation: PropTypes.bool,
onChange: PropTypes.func,
onInvalid: PropTypes.func,
onInvalidSubmit: PropTypes.func,
onReset: PropTypes.func,
onSubmit: PropTypes.func,
onValid: PropTypes.func,
onValidSubmit: PropTypes.func,
resetValue: PropTypes.func,
setValidations: PropTypes.func,
setValue: PropTypes.func,
showError: PropTypes.func,
showRequired: PropTypes.func,
validationErrors: PropTypes.object, // eslint-disable-line
};
Formsy.childContextTypes = {
formsy: PropTypes.object,
};
const addValidationRule = (name, func) => {
validationRules[name] = func;
};
const withFormsy = Wrapper;
export {
addValidationRule,
propTypes,
withFormsy,
};
export default Formsy;

457
src/main.js Normal file
View File

@ -0,0 +1,457 @@
var PropTypes = require('prop-types');
var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Formsy = {};
var validationRules = require('./validationRules.js');
var formDataToObject = require('form-data-to-object');
var utils = require('./utils.js');
var Mixin = require('./Mixin.js');
var HOC = require('./HOC.js');
var Decorator = require('./Decorator.js');
var options = {};
var emptyArray = [];
Formsy.Mixin = Mixin;
Formsy.HOC = HOC;
Formsy.Decorator = Decorator;
Formsy.defaults = function (passedOptions) {
options = passedOptions;
};
Formsy.addValidationRule = function (name, func) {
validationRules[name] = func;
};
Formsy.Form = createReactClass({
displayName: 'Formsy',
getInitialState: function () {
return {
isValid: true,
isSubmitting: false,
canChange: false
};
},
getDefaultProps: function () {
return {
onSuccess: function () {},
onError: function () {},
onSubmit: function () {},
onValidSubmit: function () {},
onInvalidSubmit: function () {},
onValid: function () {},
onInvalid: function () {},
onChange: function () {},
validationErrors: null,
preventExternalInvalidation: false
};
},
childContextTypes: {
formsy: PropTypes.object
},
getChildContext: function () {
return {
formsy: {
attachToForm: this.attachToForm,
detachFromForm: this.detachFromForm,
validate: this.validate,
isFormDisabled: this.isFormDisabled,
isValidValue: (component, value) => {
return this.runValidation(component, value).isValid;
}
}
}
},
// Add a map to store the inputs of the form, a model to store
// the values of the form and register child inputs
componentWillMount: function () {
this.inputs = [];
},
componentDidMount: function () {
this.validateForm();
},
componentWillUpdate: function () {
// Keep a reference to input names before form updates,
// to check if inputs has changed after render
this.prevInputNames = this.inputs.map(component => component.props.name);
},
componentDidUpdate: function () {
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
this.setInputValidationErrors(this.props.validationErrors);
}
var newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm();
}
},
// Allow resetting to specified data
reset: function (data) {
this.setFormPristine(true);
this.resetModel(data);
},
// Update model, submit to url prop and send the model
submit: function (event) {
event && event.preventDefault();
// Trigger form as not pristine.
// If any inputs have not been touched yet this will make them dirty
// so validation becomes visible (if based on isPristine)
this.setFormPristine(false);
var model = this.getModel();
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
this.state.isValid ? this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError) : this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
},
mapModel: function (model) {
if (this.props.mapping) {
return this.props.mapping(model)
} else {
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
var keyArray = key.split('.');
var base = mappedModel;
while (keyArray.length) {
var currentKey = keyArray.shift();
base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : model[key]);
}
return mappedModel;
}, {}));
}
},
getModel: function () {
var currentValues = this.getCurrentValues();
return this.mapModel(currentValues);
},
// Reset each key in the model to the original / initial / specified value
resetModel: function (data) {
this.inputs.forEach(component => {
var name = component.props.name;
if (data && data.hasOwnProperty(name)) {
component.setValue(data[name]);
} else {
component.resetValue();
}
});
this.validateForm();
},
setInputValidationErrors: function (errors) {
this.inputs.forEach(component => {
var name = component.props.name;
var args = [{
_isValid: !(name in errors),
_validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}];
component.setState.apply(component, args);
});
},
// Checks if the values have changed from their initial value
isChanged: function() {
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
},
getPristineValues: function() {
return this.inputs.reduce((data, component) => {
var name = component.props.name;
data[name] = component.props.value;
return data;
}, {});
},
// Go through errors from server and grab the components
// stored in the inputs map. Change their state to invalid
// and set the serverError message
updateInputsWithError: function (errors) {
Object.keys(errors).forEach((name, index) => {
var component = utils.find(this.inputs, component => component.props.name === name);
if (!component) {
throw new Error('You are trying to update an input that does not exist. ' +
'Verify errors object with input names. ' + JSON.stringify(errors));
}
var args = [{
_isValid: this.props.preventExternalInvalidation || false,
_externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}];
component.setState.apply(component, args);
});
},
isFormDisabled: function () {
return this.props.disabled;
},
getCurrentValues: function () {
return this.inputs.reduce((data, component) => {
var name = component.props.name;
data[name] = component.state._value;
return data;
}, {});
},
setFormPristine: function (isPristine) {
this.setState({
_formSubmitted: !isPristine
});
// Iterate through each component and set it as pristine
// or "dirty".
this.inputs.forEach((component, index) => {
component.setState({
_formSubmitted: !isPristine,
_isPristine: isPristine
});
});
},
// Use the binded values and the actual input value to
// validate the input and set its state. Then check the
// state of the form itself
validate: function (component) {
// Trigger onChange
if (this.state.canChange) {
this.props.onChange(this.getCurrentValues(), this.isChanged());
}
var validation = this.runValidation(component);
// Run through the validations, split them up and call
// the validator IF there is a value or it is required
component.setState({
_isValid: validation.isValid,
_isRequired: validation.isRequired,
_validationError: validation.error,
_externalError: null
}, this.validateForm);
},
// Checks validation on current value or a passed value
runValidation: function (component, value) {
var currentValues = this.getCurrentValues();
var validationErrors = component.props.validationErrors;
var validationError = component.props.validationError;
value = arguments.length === 2 ? value : component.state._value;
var validationResults = this.runRules(value, currentValues, component._validations);
var requiredResults = this.runRules(value, currentValues, component._requiredValidations);
// the component defines an explicit validate function
if (typeof component.validate === "function") {
validationResults.failed = component.validate() ? [] : ['failed'];
}
var isRequired = Object.keys(component._requiredValidations).length ? !!requiredResults.success.length : false;
var isValid = !validationResults.failed.length && !(this.props.validationErrors && this.props.validationErrors[component.props.name]);
return {
isRequired: isRequired,
isValid: isRequired ? false : isValid,
error: (function () {
if (isValid && !isRequired) {
return emptyArray;
}
if (validationResults.errors.length) {
return validationResults.errors;
}
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
}
if (isRequired) {
var error = validationErrors[requiredResults.success[0]];
return error ? [error] : null;
}
if (validationResults.failed.length) {
return validationResults.failed.map(function(failed) {
return validationErrors[failed] ? validationErrors[failed] : validationError;
}).filter(function(x, pos, arr) {
// Remove duplicates
return arr.indexOf(x) === pos;
});
}
}.call(this))
};
},
runRules: function (value, currentValues, validations) {
var results = {
errors: [],
failed: [],
success: []
};
if (Object.keys(validations).length) {
Object.keys(validations).forEach(function (validationMethod) {
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
throw new Error('Formsy does not allow you to override default validations: ' + validationMethod);
}
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
throw new Error('Formsy does not have the validation rule: ' + validationMethod);
}
if (typeof validations[validationMethod] === 'function') {
var validation = validations[validationMethod](currentValues, value);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
}
return;
} else if (typeof validations[validationMethod] !== 'function') {
var validation = validationRules[validationMethod](currentValues, value, validations[validationMethod]);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
} else {
results.success.push(validationMethod);
}
return;
}
return results.success.push(validationMethod);
});
}
return results;
},
// Validate the form by going through all child input components
// and check their state
validateForm: function () {
// We need a callback as we are validating all inputs again. This will
// run when the last component has set its state
var onValidationComplete = function () {
var allIsValid = this.inputs.every(component => {
return component.state._isValid;
});
this.setState({
isValid: allIsValid
});
if (allIsValid) {
this.props.onValid();
} else {
this.props.onInvalid();
}
// Tell the form that it can start to trigger change events
this.setState({
canChange: true
});
}.bind(this);
// Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback
this.inputs.forEach((component, index) => {
var validation = this.runValidation(component);
if (validation.isValid && component.state._externalError) {
validation.isValid = false;
}
component.setState({
_isValid: validation.isValid,
_isRequired: validation.isRequired,
_validationError: validation.error,
_externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null
}, index === this.inputs.length - 1 ? onValidationComplete : null);
});
// If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later
if (!this.inputs.length) {
this.setState({
canChange: true
});
}
},
// Method put on each input component to register
// itself to the form
attachToForm: function (component) {
if (this.inputs.indexOf(component) === -1) {
this.inputs.push(component);
}
this.validate(component);
},
// Method put on each input component to unregister
// itself from the form
detachFromForm: function (component) {
var componentPos = this.inputs.indexOf(component);
if (componentPos !== -1) {
this.inputs = this.inputs.slice(0, componentPos)
.concat(this.inputs.slice(componentPos + 1));
}
this.validateForm();
},
render: function () {
var {
mapping,
validationErrors,
onSubmit,
onValid,
onValidSubmit,
onInvalid,
onInvalidSubmit,
onChange,
reset,
preventExternalInvalidation,
onSuccess,
onError,
...nonFormsyProps
} = this.props;
return (
<form {...nonFormsyProps} onSubmit={this.submit}>
{this.props.children}
</form>
);
}
});
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
global.Formsy = Formsy;
}
module.exports = Formsy;

View File

@ -1,10 +1,10 @@
export default { module.exports = {
arraysDiffer(a, b) { arraysDiffer: function (a, b) {
let isDifferent = false; var isDifferent = false;
if (a.length !== b.length) { if (a.length !== b.length) {
isDifferent = true; isDifferent = true;
} else { } else {
a.forEach((item, index) => { a.forEach(function (item, index) {
if (!this.isSame(item, b[index])) { if (!this.isSame(item, b[index])) {
isDifferent = true; isDifferent = true;
} }
@ -13,12 +13,12 @@ export default {
return isDifferent; return isDifferent;
}, },
objectsDiffer(a, b) { objectsDiffer: function (a, b) {
let isDifferent = false; var isDifferent = false;
if (Object.keys(a).length !== Object.keys(b).length) { if (Object.keys(a).length !== Object.keys(b).length) {
isDifferent = true; isDifferent = true;
} else { } else {
Object.keys(a).forEach((key) => { Object.keys(a).forEach(function (key) {
if (!this.isSame(a[key], b[key])) { if (!this.isSame(a[key], b[key])) {
isDifferent = true; isDifferent = true;
} }
@ -27,7 +27,7 @@ export default {
return isDifferent; return isDifferent;
}, },
isSame(a, b) { isSame: function (a, b) {
if (typeof a !== typeof b) { if (typeof a !== typeof b) {
return false; return false;
} else if (Array.isArray(a) && Array.isArray(b)) { } else if (Array.isArray(a) && Array.isArray(b)) {
@ -41,61 +41,13 @@ export default {
return a === b; return a === b;
}, },
find(collection, fn) { find: function (collection, fn) {
for (let i = 0, l = collection.length; i < l; i += 1) { for (var i = 0, l = collection.length; i < l; i++) {
const item = collection[i]; var item = collection[i];
if (fn(item)) { if (fn(item)) {
return item; return item;
} }
} }
return null; return null;
}, }
runRules(value, currentValues, validations, validationRules) {
const results = {
errors: [],
failed: [],
success: [],
};
if (Object.keys(validations).length) {
Object.keys(validations).forEach((validationMethod) => {
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
throw new Error(`Formsy does not allow you to override default validations: ${validationMethod}`);
}
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
throw new Error(`Formsy does not have the validation rule: ${validationMethod}`);
}
if (typeof validations[validationMethod] === 'function') {
const validation = validations[validationMethod](currentValues, value);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
}
return;
} else if (typeof validations[validationMethod] !== 'function') {
const validation = validationRules[validationMethod](
currentValues, value, validations[validationMethod],
);
if (typeof validation === 'string') {
results.errors.push(validation);
results.failed.push(validationMethod);
} else if (!validation) {
results.failed.push(validationMethod);
} else {
results.success.push(validationMethod);
}
return;
}
results.success.push(validationMethod);
});
}
return results;
},
}; };

View File

@ -1,73 +1,78 @@
const isExisty = value => value !== null && value !== undefined; var isExisty = function (value) {
const isEmpty = value => value === ''; return value !== null && value !== undefined;
};
const validations = { var isEmpty = function (value) {
isDefaultRequiredValue(values, value) { return value === '';
};
var validations = {
isDefaultRequiredValue: function (values, value) {
return value === undefined || value === ''; return value === undefined || value === '';
}, },
isExisty(values, value) { isExisty: function (values, value) {
return isExisty(value); return isExisty(value);
}, },
matchRegexp(values, value, regexp) { matchRegexp: function (values, value, regexp) {
return !isExisty(value) || isEmpty(value) || regexp.test(value); return !isExisty(value) || isEmpty(value) || regexp.test(value);
}, },
isUndefined(values, value) { isUndefined: function (values, value) {
return value === undefined; return value === undefined;
}, },
isEmptyString(values, value) { isEmptyString: function (values, value) {
return isEmpty(value); return isEmpty(value);
}, },
isEmail(values, value) { isEmail: function (values, value) {
return validations.matchRegexp(values, value, /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i); return validations.matchRegexp(values, value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
}, },
isUrl(values, value) { isUrl: function (values, value) {
return validations.matchRegexp(values, value, /^(?:\w+:)?\/\/([^\s.]+\.\S{2}|localhost[:?\d]*)\S*$/i); return validations.matchRegexp(values, value, /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
}, },
isTrue(values, value) { isTrue: function (values, value) {
return value === true; return value === true;
}, },
isFalse(values, value) { isFalse: function (values, value) {
return value === false; return value === false;
}, },
isNumeric(values, value) { isNumeric: function (values, value) {
if (typeof value === 'number') { if (typeof value === 'number') {
return true; return true;
} }
return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/); return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/);
}, },
isAlpha(values, value) { isAlpha: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z]+$/i); return validations.matchRegexp(values, value, /^[A-Z]+$/i);
}, },
isAlphanumeric(values, value) { isAlphanumeric: function (values, value) {
return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i); return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i);
}, },
isInt(values, value) { isInt: function (values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/); return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/);
}, },
isFloat(values, value) { isFloat: function (values, value) {
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][+-]?(?:\d+))?$/); return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/);
}, },
isWords(values, value) { isWords: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s]+$/i); return validations.matchRegexp(values, value, /^[A-Z\s]+$/i);
}, },
isSpecialWords(values, value) { isSpecialWords: function (values, value) {
return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i); return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i);
}, },
isLength(values, value, length) { isLength: function (values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length === length; return !isExisty(value) || isEmpty(value) || value.length === length;
}, },
equals(values, value, eql) { equals: function (values, value, eql) {
return !isExisty(value) || isEmpty(value) || value === eql; return !isExisty(value) || isEmpty(value) || value == eql;
}, },
equalsField(values, value, field) { equalsField: function (values, value, field) {
return value === values[field]; return value == values[field];
}, },
maxLength(values, value, length) { maxLength: function (values, value, length) {
return !isExisty(value) || value.length <= length; return !isExisty(value) || value.length <= length;
}, },
minLength(values, value, length) { minLength: function (values, value, length) {
return !isExisty(value) || isEmpty(value) || value.length >= length; return !isExisty(value) || isEmpty(value) || value.length >= length;
}, }
}; };
export default validations; module.exports = validations;

View File

@ -1,21 +1,22 @@
import testrunner from 'nodeunit/lib/reporters/default'; import path from 'path';
import { jsdom } from 'jsdom/lib/old-api'; import testrunner from 'nodeunit/lib/reporters/default.js';
import {jsdom} from 'jsdom';
global.document = jsdom(); global.document = jsdom();
global.window = global.document.defaultView; global.window = document.defaultView;
global.navigator = global.window.navigator; global.navigator = global.window.navigator;
testrunner.run(['tests'], { testrunner.run(['tests'], {
error_prefix: '\u001B[31m', "error_prefix": "\u001B[31m",
error_suffix: '\u001B[39m', "error_suffix": "\u001B[39m",
ok_prefix: '\u001B[32m', "ok_prefix": "\u001B[32m",
ok_suffix: '\u001B[39m', "ok_suffix": "\u001B[39m",
bold_prefix: '\u001B[1m', "bold_prefix": "\u001B[1m",
bold_suffix: '\u001B[22m', "bold_suffix": "\u001B[22m",
assertion_prefix: '\u001B[35m', "assertion_prefix": "\u001B[35m",
assertion_suffix: '\u001B[39m', "assertion_suffix": "\u001B[39m"
}, (err) => { }, function(err) {
if (err) { if (err) {
process.exit(1); process.exit(1);
} }
}); });

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import sinon from 'sinon'; import sinon from 'sinon';
import Formsy, { withFormsy } from './..'; import Formsy from './..';
import TestInput, { InputFactory } from './utils/TestInput'; import TestInput, { InputFactory } from './utils/TestInput';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
@ -12,9 +12,9 @@ export default {
'should return passed and setValue() value when using getValue()': function (test) { 'should return passed and setValue() value when using getValue()': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<TestInput name="foo" value="foo"/> <TestInput name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -26,45 +26,18 @@ export default {
}, },
'should only set the value and not validate when calling setValue(val, false)': function (test) {
const Input = withFormsy(class TestInput extends React.Component {
updateValue = (event) => {
this.props.setValue(event.target.value, false);
}
render() {
return <input type="text" value={this.props.getValue()} onChange={this.updateValue}/>;
}
})
const form = TestUtils.renderIntoDocument(
<Formsy>
<Input name="foo" value="foo" innerRef="comp" />
</Formsy>
);
const inputComponent = TestUtils.findRenderedComponentWithType(form, Input);
const setStateSpy = sinon.spy(inputComponent, 'setState');
const inputElement = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
test.equal(setStateSpy.called, false);
TestUtils.Simulate.change(inputElement, {target: {value: 'foobar'}});
test.equal(setStateSpy.calledOnce, true);
test.equal(setStateSpy.calledWithExactly({ value: 'foobar' }), true);
test.done();
},
'should set back to pristine value when running reset': function (test) { 'should set back to pristine value when running reset': function (test) {
let reset = null; let reset = null;
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
reset = this.props.resetValue; reset = this.resetValue;
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<Input name="foo" value="foo"/> <Input name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -80,14 +53,14 @@ export default {
let getErrorMessage = null; let getErrorMessage = null;
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
getErrorMessage = this.props.getErrorMessage; getErrorMessage = this.getErrorMessage;
} }
}); });
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<Input name="foo" value="foo" validations="isEmail" validationError="Has to be email"/> <Input name="foo" value="foo" validations="isEmail" validationError="Has to be email"/>
</Formsy> </Formsy.Form>
); );
test.equal(getErrorMessage(), 'Has to be email'); test.equal(getErrorMessage(), 'Has to be email');
@ -100,14 +73,14 @@ export default {
let isValid = null; let isValid = null;
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
isValid = this.props.isValid; isValid = this.isValid;
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy action="/users"> <Formsy.Form url="/users">
<Input name="foo" value="foo" validations="isEmail"/> <Input name="foo" value="foo" validations="isEmail"/>
</Formsy> </Formsy.Form>
); );
test.equal(isValid(), false); test.equal(isValid(), false);
@ -123,16 +96,16 @@ export default {
const isRequireds = []; const isRequireds = [];
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
isRequireds.push(this.props.isRequired); isRequireds.push(this.isRequired);
} }
}); });
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy action="/users"> <Formsy.Form url="/users">
<Input name="foo" value=""/> <Input name="foo" value=""/>
<Input name="foo" value="" required/> <Input name="foo" value="" required/>
<Input name="foo" value="foo" required="isLength:3"/> <Input name="foo" value="foo" required="isLength:3"/>
</Formsy> </Formsy.Form>
); );
test.equal(isRequireds[0](), false); test.equal(isRequireds[0](), false);
@ -147,16 +120,16 @@ export default {
const showRequireds = []; const showRequireds = [];
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
showRequireds.push(this.props.showRequired); showRequireds.push(this.showRequired);
} }
}); });
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy action="/users"> <Formsy.Form url="/users">
<Input name="A" value="foo"/> <Input name="A" value="foo"/>
<Input name="B" value="" required/> <Input name="B" value="" required/>
<Input name="C" value=""/> <Input name="C" value=""/>
</Formsy> </Formsy.Form>
); );
test.equal(showRequireds[0](), false); test.equal(showRequireds[0](), false);
@ -171,14 +144,14 @@ export default {
let isPristine = null; let isPristine = null;
const Input = InputFactory({ const Input = InputFactory({
componentDidMount: function() { componentDidMount() {
isPristine = this.props.isPristine; isPristine = this.isPristine;
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy action="/users"> <Formsy.Form url="/users">
<Input name="A" value="foo"/> <Input name="A" value="foo"/>
</Formsy> </Formsy.Form>
); );
test.equal(isPristine(), true); test.equal(isPristine(), true);
@ -192,21 +165,23 @@ export default {
'should allow an undefined value to be updated to a value': function (test) { 'should allow an undefined value to be updated to a value': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = {value: undefined}; getInitialState() {
changeValue = () => { return {value: undefined};
},
changeValue() {
this.setState({ this.setState({
value: 'foo' value: 'foo'
}); });
} },
render() { render() {
return ( return (
<Formsy action="/users"> <Formsy.Form url="/users">
<TestInput name="A" value={this.state.value} /> <TestInput name="A" value={this.state.value}/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeValue(); form.changeValue();
@ -220,15 +195,15 @@ export default {
'should be able to test a values validity': function (test) { 'should be able to test a values validity': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" validations="isEmail"/> <TestInput name="A" validations="isEmail"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -240,17 +215,17 @@ export default {
'should be able to use an object as validations property': function (test) { 'should be able to use an object as validations property': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" validations={{ <TestInput name="A" validations={{
isEmail: true isEmail: true
}}/> }}/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -263,17 +238,17 @@ export default {
'should be able to pass complex values to a validation rule': function (test) { 'should be able to pass complex values to a validation rule': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" validations={{ <TestInput name="A" validations={{
matchRegexp: /foo/ matchRegexp: /foo/
}} value="foo"/> }} value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -288,26 +263,26 @@ export default {
'should be able to run a function to validate': function (test) { 'should be able to run a function to validate': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
customValidationA(values, value) { customValidationA(values, value) {
return value === 'foo'; return value === 'foo';
} },
customValidationB(values, value) { customValidationB(values, value) {
return value === 'foo' && values.A === 'foo'; return value === 'foo' && values.A === 'foo';
} },
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" validations={{ <TestInput name="A" validations={{
custom: this.customValidationA custom: this.customValidationA
}} value="foo"/> }} value="foo"/>
<TestInput name="B" validations={{ <TestInput name="B" validations={{
custom: this.customValidationB custom: this.customValidationB
}} value="foo"/> }} value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput); const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
@ -324,17 +299,17 @@ export default {
'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) { 'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy validationErrors={{}}> <Formsy.Form validationErrors={{}}>
<TestInput name="A" validations={{ <TestInput name="A" validations={{
isEmail: true isEmail: true
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/> }} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -344,19 +319,20 @@ export default {
}, },
'should override all error messages with error messages passed by form': function (test) { 'should override all error messages with error messages passed by form': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy validationErrors={{A: 'bar'}}> <Formsy.Form validationErrors={{A: 'bar'}}>
<TestInput name="A" validations={{ <TestInput name="A" validations={{
isEmail: true isEmail: true
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/> }} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -368,10 +344,10 @@ export default {
'should override validation rules with required rules': function (test) { 'should override validation rules with required rules': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" <TestInput name="A"
validations={{ validations={{
isEmail: true isEmail: true
@ -383,10 +359,10 @@ export default {
isLength: 1 isLength: 1
}} }}
/> />
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -398,26 +374,26 @@ export default {
'should fall back to default error message when non exist in validationErrors map': function (test) { 'should fall back to default error message when non exist in validationErrors map': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" <TestInput name="A"
validations={{ validations={{
isEmail: true isEmail: true
}} }}
validationError="bar1" validationError="bar"
validationErrors={{foo: 'bar2'}} validationErrors={{foo: 'bar'}}
value="foo" value="foo"
/> />
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.getErrorMessage(), 'bar1'); test.equal(inputComponent.getErrorMessage(), 'bar');
test.done(); test.done();
@ -425,17 +401,17 @@ export default {
'should not be valid if it is required and required rule is true': function (test) { 'should not be valid if it is required and required rule is true': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="A" <TestInput name="A"
required required
/> />
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -447,20 +423,22 @@ export default {
'should handle objects and arrays as values': function (test) { 'should handle objects and arrays as values': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { getInitialState() {
foo: {foo: 'bar'}, return {
bar: ['foo'] foo: {foo: 'bar'},
} bar: ['foo']
};
},
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" value={this.state.foo}/> <TestInput name="foo" value={this.state.foo}/>
<TestInput name="bar" value={this.state.bar}/> <TestInput name="bar" value={this.state.bar}/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
form.setState({ form.setState({
@ -478,24 +456,28 @@ export default {
'should handle isFormDisabled with dynamic inputs': function (test) { 'should handle isFormDisabled with dynamic inputs': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { bool: true } getInitialState() {
flip = () => { return {
bool: true
};
},
flip() {
this.setState({ this.setState({
bool: !this.state.bool bool: !this.state.bool
}); });
} },
render() { render() {
return ( return (
<Formsy disabled={this.state.bool}> <Formsy.Form disabled={this.state.bool}>
{this.state.bool ? {this.state.bool ?
<TestInput name="foo" /> : <TestInput name="foo" /> :
<TestInput name="bar" /> <TestInput name="bar" />
} }
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -509,19 +491,19 @@ export default {
'should allow for dot notation in name which maps to a deep object': function (test) { 'should allow for dot notation in name which maps to a deep object': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
onSubmit(model) { onSubmit(model) {
test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}}); test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}});
} },
render() { render() {
return ( return (
<Formsy onSubmit={this.onSubmit}> <Formsy.Form onSubmit={this.onSubmit}>
<TestInput name="foo.bar" value="foo"/> <TestInput name="foo.bar" value="foo"/>
<TestInput name="foo.test" value="test"/> <TestInput name="foo.test" value="test"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1); test.expect(1);
@ -535,19 +517,19 @@ export default {
'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) { 'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
onSubmit(model) { onSubmit(model) {
test.deepEqual(model, {foo: ['foo', 'bar']}); test.deepEqual(model, {foo: ['foo', 'bar']});
} },
render() { render() {
return ( return (
<Formsy onSubmit={this.onSubmit}> <Formsy.Form onSubmit={this.onSubmit}>
<TestInput name="foo[0]" value="foo"/> <TestInput name="foo[0]" value="foo"/>
<TestInput name="foo[1]" value="bar"/> <TestInput name="foo[1]" value="bar"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1); test.expect(1);
@ -564,17 +546,17 @@ export default {
var renderSpy = sinon.spy(); var renderSpy = sinon.spy();
const Input = InputFactory({ const Input = InputFactory({
shouldComponentUpdate: function() { return false }, mixins: [Formsy.Mixin, PureRenderMixin],
render: function() { render() {
renderSpy(); renderSpy();
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>; return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<Input name="foo" value="foo"/> <Input name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
test.equal(renderSpy.calledOnce, true); test.equal(renderSpy.calledOnce, true);

View File

@ -1,25 +1,26 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy, { addValidationRule } from './..'; import Formsy from './..';
import TestInput from './utils/TestInput'; import TestInput from './utils/TestInput';
import TestInputHoc from './utils/TestInputHoc'; import TestInputHoc from './utils/TestInputHoc';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
import sinon from 'sinon'; import sinon from 'sinon';
export default { export default {
'Setting up a form': { 'Setting up a form': {
'should expose the users DOM node through an innerRef prop': function (test) { 'should expose the users DOM node through an innerRef prop': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} /> <TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = form.name; const input = form.name;
@ -30,7 +31,7 @@ export default {
'should render a form into the document': function (test) { 'should render a form into the document': function (test) {
const form = TestUtils.renderIntoDocument(<Formsy></Formsy>); const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM'); test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM');
test.done(); test.done();
@ -39,7 +40,7 @@ export default {
'should set a class name if passed': function (test) { 'should set a class name if passed': function (test) {
const form = TestUtils.renderIntoDocument( <Formsy className="foo"></Formsy>); const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
test.equal(ReactDOM.findDOMNode(form).className, 'foo'); test.equal(ReactDOM.findDOMNode(form).className, 'foo');
test.done(); test.done();
@ -49,18 +50,18 @@ export default {
'should allow for null/undefined children': function (test) { 'should allow for null/undefined children': function (test) {
let model = null; let model = null;
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onSubmit={(formModel) => (model = formModel)}> <Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<h1>Test</h1> <h1>Test</h1>
{ null } { null }
{ undefined } { undefined }
<TestInput name="name" value={ 'foo' } /> <TestInput name="name" value={ 'foo' } />
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
immediate(() => { immediate(() => {
@ -76,17 +77,17 @@ export default {
const inputs = []; const inputs = [];
let forceUpdate = null; let forceUpdate = null;
let model = null; let model = null;
class TestForm extends React.Component { const TestForm = React.createClass({
componentWillMount() { componentWillMount() {
forceUpdate = this.forceUpdate.bind(this); forceUpdate = this.forceUpdate.bind(this);
} },
render() { render() {
return ( return (
<Formsy onSubmit={(formModel) => (model = formModel)}> <Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs} {inputs}
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input // Wait before adding the input
@ -112,17 +113,17 @@ export default {
const inputs = []; const inputs = [];
let forceUpdate = null; let forceUpdate = null;
let model = null; let model = null;
class TestForm extends React.Component { const TestForm = React.createClass({
componentWillMount() { componentWillMount() {
forceUpdate = this.forceUpdate.bind(this); forceUpdate = this.forceUpdate.bind(this);
} },
render() { render() {
return ( return (
<Formsy onSubmit={(formModel) => (model = formModel)}> <Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs} {inputs}
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input // Wait before adding the input
@ -150,19 +151,19 @@ export default {
let forceUpdate = null; let forceUpdate = null;
let model = null; let model = null;
class TestForm extends React.Component { const TestForm = React.createClass({
componentWillMount() { componentWillMount() {
forceUpdate = this.forceUpdate.bind(this); forceUpdate = this.forceUpdate.bind(this);
} },
render() { render() {
const input = <TestInput name="test" value={this.props.value} />; const input = <TestInput name="test" value={this.props.value} />;
return ( return (
<Formsy onSubmit={(formModel) => (model = formModel)}> <Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{input} {input}
</Formsy>); </Formsy.Form>);
} }
} });
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>); let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
// Wait before changing the input // Wait before changing the input
@ -192,13 +193,13 @@ export default {
const runRule = sinon.spy(); const runRule = sinon.spy();
const notRunRule = sinon.spy(); const notRunRule = sinon.spy();
addValidationRule('runRule', runRule); Formsy.addValidationRule('runRule', runRule);
addValidationRule('notRunRule', notRunRule); Formsy.addValidationRule('notRunRule', notRunRule);
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<TestInput name="one" validations="runRule" value="foo"/> <TestInput name="one" validations="runRule" value="foo"/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
@ -214,24 +215,24 @@ export default {
const ruleA = sinon.spy(); const ruleA = sinon.spy();
const ruleB = sinon.spy(); const ruleB = sinon.spy();
addValidationRule('ruleA', ruleA); Formsy.addValidationRule('ruleA', ruleA);
addValidationRule('ruleB', ruleB); Formsy.addValidationRule('ruleB', ruleB);
class TestForm extends React.Component { class TestForm extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {rule: 'ruleA'}; this.state = {rule: 'ruleA'};
} }
changeRule = () => { changeRule() {
this.setState({ this.setState({
rule: 'ruleB' rule: 'ruleB'
}); });
} }
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="one" validations={this.state.rule} value="foo"/> <TestInput name="one" validations={this.state.rule} value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} }
@ -255,14 +256,14 @@ export default {
super(props); super(props);
this.state = {showSecondInput: false}; this.state = {showSecondInput: false};
} }
addInput = () => { addInput() {
this.setState({ this.setState({
showSecondInput: true showSecondInput: true
}); });
} }
render() { render() {
return ( return (
<Formsy ref="formsy" onInvalid={isInValidSpy}> <Formsy.Form ref="formsy" onInvalid={isInValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/> <TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{ {
this.state.showSecondInput ? this.state.showSecondInput ?
@ -270,7 +271,7 @@ export default {
: :
null null
} }
</Formsy> </Formsy.Form>
); );
} }
} }
@ -303,7 +304,7 @@ export default {
} }
render() { render() {
return ( return (
<Formsy ref="formsy" onValid={isValidSpy}> <Formsy.Form ref="formsy" onValid={isValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/> <TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{ {
this.state.showSecondInput ? this.state.showSecondInput ?
@ -311,7 +312,7 @@ export default {
: :
null null
} }
</Formsy> </Formsy.Form>
); );
} }
} }
@ -333,13 +334,13 @@ export default {
const ruleA = sinon.spy(); const ruleA = sinon.spy();
const ruleB = sinon.spy(); const ruleB = sinon.spy();
addValidationRule('ruleA', ruleA); Formsy.addValidationRule('ruleA', ruleA);
addValidationRule('ruleB', ruleB); Formsy.addValidationRule('ruleB', ruleB);
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<TestInput name="one" validations="ruleA,ruleB" value="foo" /> <TestInput name="one" validations="ruleA,ruleB" value="foo" />
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
@ -356,11 +357,11 @@ export default {
const hasChanged = sinon.spy(); const hasChanged = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return <Formsy onChange={hasChanged}></Formsy>; return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
} }
} });
TestUtils.renderIntoDocument(<TestForm/>); TestUtils.renderIntoDocument(<TestForm/>);
test.equal(hasChanged.called, false); test.equal(hasChanged.called, false);
test.done(); test.done();
@ -371,9 +372,9 @@ export default {
const hasChanged = sinon.spy(); const hasChanged = sinon.spy();
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onChange={hasChanged}> <Formsy.Form onChange={hasChanged}>
<TestInput name="foo"/> <TestInput name="foo"/>
</Formsy> </Formsy.Form>
); );
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}}); TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
test.equal(hasChanged.calledOnce, true); test.equal(hasChanged.calledOnce, true);
@ -384,27 +385,29 @@ export default {
'should trigger onChange once when new input is added to form': function (test) { 'should trigger onChange once when new input is added to form': function (test) {
const hasChanged = sinon.spy(); const hasChanged = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
state = { getInitialState() {
showInput: false return {
} showInput: false
};
},
addInput() { addInput() {
this.setState({ this.setState({
showInput: true showInput: true
}) })
} },
render() { render() {
return ( return (
<Formsy onChange={hasChanged}> <Formsy.Form onChange={hasChanged}>
{ {
this.state.showInput ? this.state.showInput ?
<TestInput name="test"/> <TestInput name="test"/>
: :
null null
} }
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
form.addInput(); form.addInput();
@ -419,16 +422,16 @@ export default {
'should allow elements to check if the form is disabled': function (test) { 'should allow elements to check if the form is disabled': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { disabled: true }; getInitialState() { return { disabled: true }; },
enableForm() { this.setState({ disabled: false }); } enableForm() { this.setState({ disabled: false }); },
render() { render() {
return ( return (
<Formsy disabled={this.state.disabled}> <Formsy.Form disabled={this.state.disabled}>
<TestInput name="foo"/> <TestInput name="foo"/>
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
@ -444,18 +447,18 @@ export default {
'should be possible to pass error state of elements by changing an errors attribute': function (test) { 'should be possible to pass error state of elements by changing an errors attribute': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { validationErrors: { foo: 'bar' } }; getInitialState() { return { validationErrors: { foo: 'bar' } }; },
onChange = (values) => { onChange(values) {
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} }); this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
} },
render() { render() {
return ( return (
<Formsy onChange={this.onChange} validationErrors={this.state.validationErrors}> <Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
<TestInput name="foo"/> <TestInput name="foo"/>
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait for update // Wait for update
@ -476,14 +479,14 @@ export default {
'should trigger an onValidSubmit when submitting a valid form': function (test) { 'should trigger an onValidSubmit when submitting a valid form': function (test) {
let isCalled = sinon.spy(); let isCalled = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onValidSubmit={isCalled}> <Formsy.Form onValidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/> <TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm); const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm)); TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
@ -495,14 +498,14 @@ export default {
'should trigger an onInvalidSubmit when submitting an invalid form': function (test) { 'should trigger an onInvalidSubmit when submitting an invalid form': function (test) {
let isCalled = sinon.spy(); let isCalled = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onInvalidSubmit={isCalled}> <Formsy.Form onInvalidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar"/> <TestInput name="foo" validations="isEmail" value="foo@bar"/>
</Formsy>); </Formsy.Form>);
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm); const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
@ -520,16 +523,16 @@ export default {
'should call onSubmit correctly': function (test) { 'should call onSubmit correctly': function (test) {
const onSubmit = sinon.spy(); const onSubmit = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onSubmit={onSubmit}> <Formsy.Form onSubmit={onSubmit}>
<TestInput name="foo" value={false} type="checkbox" /> <TestInput name="foo" value={false} type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form)); TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
@ -541,24 +544,26 @@ export default {
'should allow dynamic changes to false': function (test) { 'should allow dynamic changes to false': function (test) {
const onSubmit = sinon.spy(); const onSubmit = sinon.spy();
class TestForm extends React.Component { const TestForm = React.createClass({
state = { getInitialState() {
value: true return {
} value: true
};
},
changeValue() { changeValue() {
this.setState({ this.setState({
value: false value: false
}); });
} },
render() { render() {
return ( return (
<Formsy onSubmit={onSubmit}> <Formsy.Form onSubmit={onSubmit}>
<TestInput name="foo" value={this.state.value} type="checkbox" /> <TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeValue(); form.changeValue();
@ -570,16 +575,16 @@ export default {
'should say the form is submitted': function (test) { 'should say the form is submitted': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" value={true} type="checkbox" /> <TestInput name="foo" value={true} type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(input.isFormSubmitted(), false); test.equal(input.isFormSubmitted(), false);
@ -591,27 +596,29 @@ export default {
'should be able to reset the form to its pristine state': function (test) { 'should be able to reset the form to its pristine state': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { getInitialState() {
value: true return {
} value: true
};
},
changeValue() { changeValue() {
this.setState({ this.setState({
value: false value: false
}); });
} },
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" value={this.state.value} type="checkbox" /> <TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy); const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
test.equal(input.getValue(), true); test.equal(input.getValue(), true);
form.changeValue(); form.changeValue();
test.equal(input.getValue(), false); test.equal(input.getValue(), false);
@ -624,27 +631,29 @@ export default {
'should be able to reset the form using custom data': function (test) { 'should be able to reset the form using custom data': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
state = { getInitialState() {
value: true return {
} value: true
};
},
changeValue() { changeValue() {
this.setState({ this.setState({
value: false value: false
}); });
} },
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" value={this.state.value} type="checkbox" /> <TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy); const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
test.equal(input.getValue(), true); test.equal(input.getValue(), true);
form.changeValue(); form.changeValue();
@ -661,19 +670,19 @@ export default {
'should be able to reset the form to empty values': function (test) { 'should be able to reset the form to empty values': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" value="42" type="checkbox" /> <TestInput name="foo" value="42" type="checkbox" />
<button type="submit">Save</button> <button type="submit">Save</button>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy); const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
formsyForm.reset({ formsyForm.reset({
foo: '' foo: ''
@ -689,9 +698,9 @@ export default {
const hasOnChanged = sinon.spy(); const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onChange={hasOnChanged}> <Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" /> <TestInput name="one" value="foo" />
</Formsy> </Formsy.Form>
); );
test.equal(form.isChanged(), false); test.equal(form.isChanged(), false);
test.equal(hasOnChanged.called, false); test.equal(hasOnChanged.called, false);
@ -703,9 +712,9 @@ export default {
const hasOnChanged = sinon.spy(); const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onChange={hasOnChanged}> <Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" /> <TestInput name="one" value="foo" />
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}}); TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
@ -719,9 +728,9 @@ export default {
const hasOnChanged = sinon.spy(); const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onChange={hasOnChanged}> <Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" /> <TestInput name="one" value="foo" />
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}}); TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});

View File

@ -1,25 +1,25 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render: function() { render() {
return <input value={this.props.getValue()} readOnly />; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/> <TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render: function() { render() {
return <input value={this.props.getValue()} readOnly />; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/> <TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/> <TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/> <TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/> <TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/> <TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/> <TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/> <TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/> <TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/> <TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/> <TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/> <TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/> <TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({ const TestInput = InputFactory({
render() { render() {
return <input value={this.props.getValue()} readOnly/>; return <input value={this.getValue()} readOnly/>;
} }
}); });
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy> <Formsy.Form>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/> <TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy> </Formsy.Form>
); );
} }
} });
export default { export default {

View File

@ -1,37 +1,24 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-dom/test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy, { withFormsy } from './..'; import Formsy from './..';
import { InputFactory } from './utils/TestInput'; import TestInput, {InputFactory} from './utils/TestInput';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
import sinon from 'sinon'; import sinon from 'sinon';
class MyTest extends React.Component {
static defaultProps = { type: 'text' };
handleChange = (event) => {
this.props.setValue(event.target.value);
}
render() {
return <input type={this.props.type} value={this.props.getValue()} onChange={this.handleChange}/>;
}
}
const FormsyTest = withFormsy(MyTest);
export default { export default {
'should reset only changed form element when external error is passed': function (test) { 'should reset only changed form element when external error is passed': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}> <Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
<FormsyTest name="foo"/> <TestInput name="foo"/>
<FormsyTest name="bar"/> <TestInput name="bar"/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0]; const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0];
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, FormsyTest); const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput);
form.submit(); form.submit();
test.equal(inputComponents[0].isValid(), false); test.equal(inputComponents[0].isValid(), false);
@ -49,13 +36,13 @@ export default {
'should let normal validation take over when component with external error is changed': function (test) { 'should let normal validation take over when component with external error is changed': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> <Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" validations="isEmail"/> <TestInput name="foo" validations="isEmail"/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
const inputComponent = TestUtils.findRenderedComponentWithType(form, FormsyTest); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
form.submit(); form.submit();
test.equal(inputComponent.isValid(), false); test.equal(inputComponent.isValid(), false);
@ -75,9 +62,9 @@ export default {
const onInvalid = sinon.spy(); const onInvalid = sinon.spy();
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy onValid={onValid} onInvalid={onInvalid}> <Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" value="bar" required/> <TestInput name="foo" value="bar" required/>
</Formsy> </Formsy.Form>
); );
test.equal(onValid.called, true); test.equal(onValid.called, true);
@ -92,9 +79,9 @@ export default {
const onInvalid = sinon.spy(); const onInvalid = sinon.spy();
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy onValid={onValid} onInvalid={onInvalid}> <Formsy.Form onValid={onValid} onInvalid={onInvalid}>
<FormsyTest name="foo" required /> <TestInput name="foo" required />
</Formsy> </Formsy.Form>
); );
test.equal(onValid.called, false); test.equal(onValid.called, false);
@ -107,14 +94,14 @@ export default {
let isValid = false; let isValid = false;
const CustomInput = InputFactory({ const CustomInput = InputFactory({
componentDidMount: function() { componentDidMount() {
isValid = this.props.isValid(); isValid = this.isValid();
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy> <Formsy.Form>
<CustomInput name="foo" value="foo" required/> <CustomInput name="foo" value="foo" required/>
</Formsy> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
@ -123,22 +110,22 @@ export default {
}, },
'should provide invalidate callback on onValidSubmit': function (test) { 'should provide invalidate callback on onValiSubmit': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> <Formsy.Form onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/> <TestInput name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), false); test.equal(input.isValid(), false);
test.done(); test.done();
@ -147,19 +134,19 @@ export default {
'should provide invalidate callback on onInvalidSubmit': function (test) { 'should provide invalidate callback on onInvalidSubmit': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> <Formsy.Form onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo" validations="isEmail"/> <TestInput name="foo" value="foo" validations="isEmail"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
test.equal(input.getErrorMessage(), 'bar'); test.equal(input.getErrorMessage(), 'bar');
@ -169,21 +156,21 @@ export default {
'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) { 'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy <Formsy.Form
preventExternalInvalidation preventExternalInvalidation
onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/> <TestInput name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), true); test.equal(input.isValid(), true);
test.done(); test.done();
@ -192,19 +179,19 @@ export default {
'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) { 'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) {
class TestForm extends React.Component { const TestForm = React.createClass({
render() { render() {
return ( return (
<Formsy onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> <Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
<FormsyTest name="foo" value="foo"/> <TestInput name="foo" value="foo"/>
</Formsy> </Formsy.Form>
); );
} }
} });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, FormsyTest); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
test.equal(input.isValid(), false); test.equal(input.isValid(), false);
test.done(); test.done();

View File

@ -1,25 +1,21 @@
import React from 'react'; import React from 'react';
import Formsy, { withFormsy } from './../..'; import Formsy from './../..';
class TestInput extends React.Component { const defaultProps = {
static defaultProps = { type: 'text' }; mixins: [Formsy.Mixin],
getDefaultProps() {
return { type: 'text' };
},
updateValue(event) {
this.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
},
render() {
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
}
};
updateValue = (event) => { export function InputFactory(props) {
this.props.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']); return React.createClass(Object.assign(defaultProps, props));
}
render() {
return <input type={this.props.type} value={this.props.getValue()} onChange={this.updateValue}/>;
}
} }
export function InputFactory(methods) { export default React.createClass(defaultProps);
for (let method in methods) {
if (methods.hasOwnProperty(method)) {
TestInput.prototype[method] = methods[method];
}
}
return withFormsy(TestInput);
}
export default withFormsy(TestInput);

View File

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import Formsy, { withFormsy } from './../..'; import { HOC as formsyHoc } from './../..';
class TestComponent extends React.Component { const defaultProps = {
methodOnWrappedInstance = (param) => { methodOnWrappedInstance(param) {
return param; return param;
} },
render() {
return (<input />);
},
};
render() { export default formsyHoc(React.createClass(defaultProps));
return (<input />);
}
}
export default withFormsy(TestComponent);

View File

@ -1,3 +1,3 @@
export default function (fn) { export default function (fn) {
setTimeout(fn, 0); setTimeout(fn, 0);
} }

View File

@ -1,20 +0,0 @@
const path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'index.js'),
externals: 'react',
output: {
path: path.resolve(__dirname, 'release'),
filename: 'formsy-react.js',
libraryTarget: 'umd',
library: 'Formsy',
},
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
}],
},
};

View File

@ -0,0 +1,21 @@
var path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'main.js'),
externals: 'react',
output: {
path: path.resolve(__dirname, 'release'),
filename: 'formsy-react.js',
libraryTarget: 'umd',
library: 'Formsy'
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
{ test: /\.json$/, loader: 'json' }
]
}
};

5063
yarn.lock

File diff suppressed because it is too large Load Diff