Compare commits

...

82 Commits

Author SHA1 Message Date
Aesop Wolf ebd84bb026 Fix warning about isMounted being deprecated 2017-06-17 19:30:05 -07:00
Aesop Wolf 7dc68fc633 Migrate from React.createClass to create-react-class 2017-06-16 14:28:30 -07:00
Aesop Wolf a20da5b7cd Migrate from React.PropTypes to prop-types 2017-06-15 20:10:13 -07:00
Aesop Wolf c5dfa4b31a v0.19.2 2017-01-23 19:18:56 -08:00
Aesop Wolf f1ba9d417e Revert "fix: show the default validation message when there is a required error (#386)"
This reverts commit ac301f8950.
2017-01-23 19:13:05 -08:00
Aesop Wolf 57f97cb045 v0.19.1 2017-01-18 13:32:55 -08:00
CT Wu ac301f8950 fix: show the default validation message when there is a required error (#386)
* Show the default validation error message when required is invalid

* Fix showError
2017-01-18 13:31:02 -08:00
Tim Brayshaw d68586180a Remove stray sentence. (#415) 2017-01-12 09:37:57 -08:00
Aesop Wolf f395839e2c Update API.md
Add note about xregexp
2016-12-29 15:11:36 -08:00
David Blurton 73dd46b5e3 Fix crash when comparing array with null (#404) 2016-12-10 09:20:21 -08:00
Scott Silvi 84cdcb117c docs: Removing unnecessary single quote (#410) 2016-12-08 22:51:17 -08:00
Aesop Wolf c56e8c3328 0.19.0 2016-12-08 22:34:08 -08:00
Aesop Wolf 2b249113d8 feat: expose inner element when using HOC (#399)
Use `innerRef={(c) => { this.myInput = c; }}` on your HOC to access the internal element
2016-12-08 22:24:19 -08:00
Christian Alfoni 15493dce67 Update README.md 2016-10-05 16:04:08 +02:00
Christian Alfoni 89dbed0468 Update LICENSE 2016-10-05 14:46:46 +02:00
Dmitry Semigradsky 168521e578 Merge pull request #366 from maniart/patch-2
Nit-picky typo
2016-07-21 17:57:27 +03:00
Mani Nilchiani 42ba9616d2 Nit-picky typo
This is some *great* documentation work. I read it so carefully that this nit-picky typo came to my attention.
2016-07-13 12:57:29 -04:00
Semigradsky 6b167cbfe1 0.18.1 2016-07-05 11:52:41 +03:00
Semigradsky 24a6b6217d Fixed #335: getErrorMessages is undocumented in API 2016-07-05 11:49:29 +03:00
Semigradsky d6950885a1 Fixed #298: RangeError: Maximum call stack size exceeded with custom validator 2016-07-05 11:36:06 +03:00
Semigradsky 3cdcdf57ce Fixed uncontrollable/controllable components in examples 2016-07-05 11:22:25 +03:00
Semigradsky 1800442ea1 Fixed #361: Unknown props `onValidSubmit`, `onSubmitted` on <form> tag 2016-07-05 10:54:18 +03:00
Dmitry Semigradsky da8f041eca Merge pull request #357 from wizardzloy/patch-1
Use better displayNames for components wrapped into HOC
2016-07-05 10:41:16 +03:00
Vladimir Guguiev 9e923dd0dc Use better names for components wrapped into HOC
Currently the `displayName` of the component wrapped into HOC looks like `Constructor`, which isn't very descriptive.
This PR introduces a better `displayName` for such components: `Formsy(ComponentName)`
2016-06-11 13:21:44 +02:00
Semigradsky 89a2d4287b Update documentation 2016-04-21 13:16:00 +03:00
Semigradsky 6767a6b9eb Fix: Cannot reset value unless truthy 2016-04-21 13:05:47 +03:00
Semigradsky d84397906b 0.18.0 2016-04-19 11:12:31 +03:00
Semigradsky 112819f699 Update dependencies. Some minor changes. 2016-04-19 11:12:14 +03:00
Dmitry Semigradsky 4931256105 Fix link 2015-12-12 16:19:38 +03:00
Dmitry Semigradsky 7658d06cc8 Merge pull request #258 from sdemjanenko/fix_multiple_on_change
Fix multiple on change
2015-12-10 21:23:05 +03:00
Stephen Demjanenko 1df3e3520f Formsy.Form: omit Formsy-only props from the `<form/>` component 2015-12-10 09:38:01 -08:00
Stephen Demjanenko fec4576d1b FormsySpec: test that `onChange` is called only once 2015-12-10 09:37:57 -08:00
Dmitry Semigradsky b39fd2ed74 Fix link 2015-12-10 09:22:53 +03:00
Semigradsky 18467a94a1 Added `updateInputsWithError` to documentation. 2015-12-10 09:21:53 +03:00
Semigradsky d5268a4eef Added mapping to `getModel()` 2015-12-08 13:10:48 +03:00
Semigradsky 5b5bd0fb67 Add `getModel()` to documentation. 2015-12-08 12:00:49 +03:00
Dmitry Semigradsky 46c1f2c250 Merge pull request #275 from christianalfoni/issue-267
Issue 267
2015-12-08 11:39:23 +03:00
Semigradsky dcac495d79 Renamed `this.updateModel` to `this.getModel` 2015-12-07 13:56:47 +03:00
Semigradsky c4fa202ebf Fix `component[Will|Did]Update` behavior. Build release. 2015-12-02 16:50:06 +03:00
Semigradsky 7481b6da64 Fix #267 2015-12-02 16:39:39 +03:00
Semigradsky fbd09119d3 Added `dynamic form fields` example. 2015-12-01 17:00:43 +03:00
Dmitry Semigradsky f67f8317c9 Merge pull request #252 from neoziro/patch-2
Prevent multiple rendering with PureRenderMixin
2015-11-05 13:43:10 +03:00
Semigradsky 59bde8324c Added test for rendering elements with PureRenderMixin and Formsy.Mixin 2015-11-05 13:41:48 +03:00
Dmitry Semigradsky 8636cdabc3 Added link to codepen demo. 2015-11-03 15:02:53 +03:00
Dmitry Semigradsky cfebf17aea Merge pull request #242 from easyrider/master
Fix: Form.validationErrors disables input validation #239
2015-11-03 14:34:45 +03:00
Greg Bergé 6bcdb61b27 Prevent multiple rendering with PureRenderMixin
Creating a new instance of error triggers two additional rendering when typing in an input, using same instance prevent it to happen.
2015-10-28 12:48:46 +01:00
Dmitry Semigradsky 300a53f172 Merge pull request #251 from neoziro/patch-1
Remove babelify transform
2015-10-28 13:50:49 +03:00
Greg Bergé 36b5dd1dab Remove babelify transform
To be able to use this module in a browserify project without having to install babelify.
2015-10-28 11:23:15 +01:00
adam.dymowski 8cf8409e3a Fix: Form.validationErrors disables input validation #239 2015-10-20 09:19:24 +02:00
Dmitry Semigradsky e4d35f999b Merge pull request #243 from mbrookes/patch-2
Add formsy-material-ui to elements section.
2015-10-19 18:13:05 +03:00
Matt Brookes 78b2ada909 Add formsy-material-ui to elements section. 2015-10-19 15:52:20 +01:00
adam.dymowski 2a384f40a6 Fix: Form.validationErrors disables input validation #239 2015-10-19 16:19:03 +02:00
Dmitry Semigradsky b797979873 Update README.md 2015-10-15 11:52:10 +03:00
Dmitry Semigradsky 13615e82e4 Merge pull request #238 from mbrookes/patch-1
Add formsy-material-ui
2015-10-15 11:51:08 +03:00
Matt Brookes 2639319f9b Add formsy-material-ui
In alphabetical order. ;)
2015-10-14 22:01:08 +01:00
Christian Alfoni 2be44f1b32 React 0.14 support 2015-10-08 09:31:59 +02:00
Christian Alfoni 97923876d9 Merge pull request #233 from almasakchabayev/lib-main
Update package.json
2015-10-08 09:29:17 +02:00
Christian Alfoni 9fb4b93fec Merge pull request #229 from Mayank1791989/fix_missing_props_in_hoc
fix extending props in HOC.
2015-10-08 09:28:41 +02:00
Almas Akchabayev dd15a840a0 Update package.json 2015-10-08 09:29:39 +06:00
Mayank Agarwal 6f2a21e3ef fix extending props in HOC. 2015-10-06 11:25:59 +05:30
Christian Alfoni 5bfa7e53f1 Bumped version due to beta testing 2015-10-04 17:53:48 +02:00
Christian Alfoni 4ca7a57af7 New travis container 2015-10-04 17:49:19 +02:00
Christian Alfoni 3bd3ad699e Changed travis version 2015-10-04 17:45:18 +02:00
Christian Alfoni 19e28c6437 Set correct version release 2015-10-04 17:42:33 +02:00
Christian Alfoni 2846f0fadd Refactored tests and made React 0.14 friendly 2015-10-04 17:40:58 +02:00
Christian Alfoni 4670a9cc1f Merge pull request #214 from gusaiani/init-state-in-readme
Add getInitialState to README example
2015-10-02 14:56:27 +02:00
Christian Alfoni 2c6f7fe8f0 Merge pull request #213 from kryogenic/patch-1
make server validation more obvious in docs
2015-10-02 14:56:02 +02:00
Christian Alfoni 404f696bfb Merge pull request #211 from LestaD/patch-1
Add display name
2015-10-02 14:55:41 +02:00
Christian Alfoni e72f34a3d1 Merge pull request #210 from rozzzly/patch-1
fix links so they work on githib
2015-10-02 14:52:44 +02:00
Christian Alfoni 43a8f66150 Merge pull request #197 from rblakeley/patch-1
fix #196
2015-10-02 13:46:25 +02:00
Gustavo Saiani f559c8f9d4 Add getInitialState to README example 2015-09-21 17:14:15 -03:00
Kale bf77058a87 make server validation more obvious in docs 2015-09-21 07:17:27 -06:00
Sergey Sova 32aac21a4e Add display name 2015-09-18 15:01:07 +03:00
Patrick Lienau 349d5c7922 fix links so they work on githib
Before the links to the examples pointed towards `localhost:8080`; therefore not easily—well, it wasn't exactly hard to find either—accessible from the github repository. 'Tis but a simple fix, will probably save somebody else a few seconds. Great repo btw.
2015-09-17 08:37:19 -05:00
Christian Alfoni 523ab69a4e Update LICENSE 2015-09-12 17:14:58 +02:00
Christian Alfoni a8dd273148 Merge pull request #176 from silvenon/patch-3
Make it browserify-friendly
2015-09-12 16:37:10 +02:00
Christian Alfoni 3791b1976f Merge pull request #174 from silvenon/patch-1
Add example use case for require="isFalse"
2015-09-12 16:33:09 +02:00
Ryan Blakeley 7f6ab4f52c add built files, fix #196 2015-09-04 10:48:06 -06:00
Ryan Blakeley 6b521d35e7 removed obsolete method call, fix #196 2015-09-04 10:35:47 -06:00
Ryan Blakeley c311e144fb fix #196 2015-09-04 10:27:14 -06:00
Matija Marohnić c04623ae05 Make it browserify-friendly
Because `main.js` uses ES2015, this tells browserify to compile it with babelify first.
2015-07-28 16:27:13 +02:00
Matija Marohnić dcac01159a Add example use case for require="isFalse" 2015-07-26 16:44:58 +02:00
70 changed files with 3186 additions and 2022 deletions

7
.babelrc Normal file
View File

@ -0,0 +1,7 @@
{
"presets": [
"react",
"es2015",
"stage-2"
]
}

2
.gitignore vendored
View File

@ -1,4 +1,2 @@
.DS_Store
build
node_modules node_modules
lib lib

View File

@ -1,3 +1,8 @@
build/ .babelrc
bower.json .editorconfig
Gulpfile.js .travis.yml
testrunner.js
webpack.production.config.js
examples/
release/
tests/

View File

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

227
API.md
View File

@ -1,6 +1,5 @@
# API # API
- [Formsy.defaults - DEPRECATED](#formsydefaults)
- [Formsy.Form](#formsyform) - [Formsy.Form](#formsyform)
- [className](#classname) - [className](#classname)
- [mapping](#mapping) - [mapping](#mapping)
@ -12,19 +11,21 @@
- [onInvalidSubmit()](#oninvalidsubmit) - [onInvalidSubmit()](#oninvalidsubmit)
- [onChange()](#onchange) - [onChange()](#onchange)
- [reset()](#resetform) - [reset()](#resetform)
- [getModel()](#getmodel)
- [updateInputsWithError()](#updateinputswitherrorerrors)
- [preventExternalInvalidation](#preventexternalinvalidation) - [preventExternalInvalidation](#preventexternalinvalidation)
- [Formsy.Mixin](#formsymixin) - [Formsy.Mixin](#formsymixin)
- [name](#name) - [name](#name)
- [value](#value) - [value](#value)
- [validations](#validations) - [validations](#validations)
- [validationError](#validationerror) - [validationError](#validationerror)
- [validationErrors](#validationerrors) - [validationErrors](#validationerrors-1)
- [required](#required) - [required](#required)
- [getValue()](#getvalue) - [getValue()](#getvalue)
- [setValue()](#setvalue) - [setValue()](#setvalue)
- [hasValue() - DEPRECATED](#hasvalue)
- [resetValue()](#resetvalue) - [resetValue()](#resetvalue)
- [getErrorMessage()](#geterrormessage) - [getErrorMessage()](#geterrormessage)
- [getErrorMessages()](#geterrormessages)
- [isValid()](#isvalid) - [isValid()](#isvalid)
- [isValidValue()](#isvalidvalue) - [isValidValue()](#isvalidvalue)
- [isRequired()](#isrequired) - [isRequired()](#isrequired)
@ -36,22 +37,21 @@
- [validate](#validate) - [validate](#validate)
- [formNoValidate](#formnovalidate) - [formNoValidate](#formnovalidate)
- [Formsy.HOC](#formsyhoc) - [Formsy.HOC](#formsyhoc)
- [innerRef](#innerRef)
- [Formsy.Decorator](#formsydecorator) - [Formsy.Decorator](#formsydecorator)
- [Formsy.addValidationRule](#formsyaddvalidationrule) - [Formsy.addValidationRule](#formsyaddvalidationrule)
- [Validators](#validators) - [Validators](#validators)
### <a name="formsydefaults">Formsy.defaults(options) - DEPRECATED</a>
### <a name="formsyform">Formsy.Form</a> ### <a name="formsyform">Formsy.Form</a>
#### <a name="classname">className</a> #### <a name="classname">className</a>
```html ```jsx
<Formsy.Form className="my-class"></Formsy.Form> <Formsy.Form className="my-class"></Formsy.Form>
``` ```
Sets a class name on the form itself. Sets a class name on the form itself.
#### <a name="mapping">mapping</a> #### <a name="mapping">mapping</a>
```javascript ```jsx
var MyForm = React.createClass({ var MyForm = React.createClass({
mapInputs: function (inputs) { mapInputs: function (inputs) {
return { return {
@ -77,7 +77,7 @@ Use mapping to change the data structure of your input elements. This structure
#### <a name="validationerrors">validationErrors</a> #### <a name="validationerrors">validationErrors</a>
You can manually pass down errors to your form. In combination with `onChange` you are able to validate using an external validator. You can manually pass down errors to your form. In combination with `onChange` you are able to validate using an external validator.
```js ```jsx
var Form = React.createClass({ var Form = React.createClass({
getInitialState: function () { getInitialState: function () {
return { return {
@ -108,45 +108,45 @@ var Form = React.createClass({
``` ```
#### <a name="onsubmit">onSubmit(data, resetForm, invalidateForm)</a> #### <a name="onsubmit">onSubmit(data, resetForm, invalidateForm)</a>
```html ```jsx
<Formsy.Form onSubmit={this.showFormLoader}></Formsy.Form> <Formsy.Form onSubmit={this.showFormLoader}></Formsy.Form>
``` ```
Takes a function to run when the submit button has been clicked. Takes a function to run when the submit button has been clicked.
The first argument is the data of the form. The second argument will reset the form. The third argument will invalidate the form by taking an object that maps to inputs. E.g. `{email: "This email is taken"}`. Resetting or invalidating the form will cause **setState** to run on the form element component. The first argument is the data of the form. The second argument will reset the form. The third argument will invalidate the form by taking an object that maps to inputs. This is useful for server side validation. E.g. `{email: "This email is taken"}`. Resetting or invalidating the form will cause **setState** to run on the form element component.
#### <a name="onvalid">onValid()</a> #### <a name="onvalid">onValid()</a>
```html ```jsx
<Formsy.Form onValid={this.enableSubmitButton}></Formsy.Form> <Formsy.Form onValid={this.enableSubmitButton}></Formsy.Form>
``` ```
Whenever the form becomes valid the "onValid" handler is called. Use it to change state of buttons or whatever your heart desires. Whenever the form becomes valid the "onValid" handler is called. Use it to change state of buttons or whatever your heart desires.
#### <a name="oninvalid">onInvalid()</a> #### <a name="oninvalid">onInvalid()</a>
```html ```jsx
<Formsy.Form onInvalid={this.disableSubmitButton}></Formsy.Form> <Formsy.Form onInvalid={this.disableSubmitButton}></Formsy.Form>
``` ```
Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state. Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state.
#### <a name="onvalidsubmit">onValidSubmit(model, resetForm, invalidateForm)</a> #### <a name="onvalidsubmit">onValidSubmit(model, resetForm, invalidateForm)</a>
```html ```jsx
<Formsy.Form onValidSubmit={this.sendToServer}></Formsy.Form> <Formsy.Form onValidSubmit={this.sendToServer}></Formsy.Form>
``` ```
Triggers when form is submitted with a valid state. The arguments are the same as on `onSubmit`. Triggers when form is submitted with a valid state. The arguments are the same as on `onSubmit`.
#### <a name="oninvalidsubmit">onInvalidSubmit(model, resetForm, invalidateForm)</a> #### <a name="oninvalidsubmit">onInvalidSubmit(model, resetForm, invalidateForm)</a>
```html ```jsx
<Formsy.Form onInvalidSubmit={this.notifyFormError}></Formsy.Form> <Formsy.Form onInvalidSubmit={this.notifyFormError}></Formsy.Form>
``` ```
Triggers when form is submitted with an invalid state. The arguments are the same as on `onSubmit`. Triggers when form is submitted with an invalid state. The arguments are the same as on `onSubmit`.
#### <a name="onchange">onChange(currentValues, isChanged)</a> #### <a name="onchange">onChange(currentValues, isChanged)</a>
```html ```jsx
<Formsy.Form onChange={this.saveCurrentValuesToLocalStorage}></Formsy.Form> <Formsy.Form onChange={this.saveCurrentValuesToLocalStorage}></Formsy.Form>
``` ```
"onChange" triggers when setValue is called on your form elements. It is also triggered when dynamic form elements have been added to the form. The "currentValues" is an object where the key is the name of the input and the value is the current value. The second argument states if the forms initial values actually has changed. "onChange" triggers when setValue is called on your form elements. It is also triggered when dynamic form elements have been added to the form. The "currentValues" is an object where the key is the name of the input and the value is the current value. The second argument states if the forms initial values actually has changed.
#### <a name="resetform">reset(values)</a> #### <a name="resetform">reset(values)</a>
```html ```jsx
var MyForm = React.createClass({ var MyForm = React.createClass({
resetForm: function () { resetForm: function () {
this.refs.form.reset(); this.refs.form.reset();
@ -162,8 +162,45 @@ var MyForm = React.createClass({
``` ```
Manually reset the form to its pristine state. You can also pass an object that inserts new values into the inputs. Keys are name of input and value is of course the value. Manually reset the form to its pristine state. You can also pass an object that inserts new values into the inputs. Keys are name of input and value is of course the value.
#### <a name="getmodel">getModel()</a>
```jsx
var MyForm = React.createClass({
getMyData: function () {
alert(this.refs.form.getModel());
},
render: function () {
return (
<Formsy.Form ref="form">
...
</Formsy.Form>
);
}
});
```
Manually get values from all registered components. Keys are name of input and value is of course the value.
#### <a name="updateInputsWithError">updateInputsWithError(errors)</a>
```jsx
var MyForm = React.createClass({
someFunction: function () {
this.refs.form.updateInputsWithError({
email: 'This email is taken',
'field[10]': 'Some error!'
});
},
render: function () {
return (
<Formsy.Form ref="form">
...
</Formsy.Form>
);
}
});
```
Manually invalidate the form by taking an object that maps to inputs. This is useful for server side validation. You can also use a third parameter to the [`onSubmit`](#onsubmitdata-resetform-invalidateform), [`onValidSubmit`](#onvalidsubmitmodel-resetform-invalidateform) or [`onInvalidSubmit`](#oninvalidsubmitmodel-resetform-invalidateform).
#### <a name="preventExternalInvalidation">preventExternalInvalidation</a> #### <a name="preventExternalInvalidation">preventExternalInvalidation</a>
```html ```jsx
var MyForm = React.createClass({ var MyForm = React.createClass({
onSubmit: function (model, reset, invalidate) { onSubmit: function (model, reset, invalidate) {
invalidate({ invalidate({
@ -184,20 +221,20 @@ With the `preventExternalInvalidation` the input will not be invalidated though
### <a name="formsymixin">Formsy.Mixin</a> ### <a name="formsymixin">Formsy.Mixin</a>
#### <a name="name">name</a> #### <a name="name">name</a>
```html ```jsx
<MyInputComponent name="email"/> <MyInputComponent name="email"/>
<MyInputComponent name="address.street"/> <MyInputComponent name="address.street"/>
``` ```
The name is required to register the form input component in the form. You can also use dot notation. This will result in the "form model" being a nested object. `{email: 'value', address: {street: 'value'}}`. The name is required to register the form input component in the form. You can also use dot notation. This will result in the "form model" being a nested object. `{email: 'value', address: {street: 'value'}}`.
#### <a name="value">value</a> #### <a name="value">value</a>
```html ```jsx
<MyInputComponent name="email" value="My initial value"/> <MyInputComponent name="email" value="My initial value"/>
``` ```
You should always use the [**getValue()**](#getvalue) method inside your formsy form element. To pass an initial value, use the value attribute. This value will become the "pristine" value and any reset of the form will bring back this value. You should always use the [**getValue()**](#getvalue) method inside your formsy form element. To pass an initial value, use the value attribute. This value will become the "pristine" value and any reset of the form will bring back this value.
#### <a name="validations">validations</a> #### <a name="validations">validations</a>
```html ```jsx
<MyInputComponent name="email" validations="isEmail"/> <MyInputComponent name="email" validations="isEmail"/>
<MyInputComponent name="number" validations="isNumeric,isLength:5"/> <MyInputComponent name="number" validations="isNumeric,isLength:5"/>
<MyInputComponent name="number" validations={{ <MyInputComponent name="number" validations={{
@ -212,22 +249,22 @@ You should always use the [**getValue()**](#getvalue) method inside your formsy
} }
}}/> }}/>
``` ```
An comma separated list with validation rules. Take a look at [**Validators**](#validators) to see default rules. Use ":" to separate argument passed to the validator. The argument will go through a **JSON.parse** converting them into correct JavaScript types. Meaning: A comma separated list with validation rules. Take a look at [**Validators**](#validators) to see default rules. Use ":" to separate argument passed to the validator. The argument will go through a **JSON.parse** converting them into correct JavaScript types. Meaning:
```html ```jsx
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange']"/> <MyInputComponent name="fruit" validations="isIn:['apple', 'orange']"/>
<MyInputComponent name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/> <MyInputComponent name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/>
``` ```
Works just fine. Works just fine.
#### <a name="validationerror">validationError</a> #### <a name="validationerror">validationError</a>
```html ```jsx
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email"/> <MyInputComponent name="email" validations="isEmail" validationError="This is not an email"/>
``` ```
The message that will show when the form input component is invalid. It will be used as a default error. The message that will show when the form input component is invalid. It will be used as a default error.
#### <a name="validationerrors">validationErrors</a> #### <a name="validationerrors">validationErrors</a>
```html ```jsx
<MyInputComponent <MyInputComponent
name="email" name="email"
validations={{ validations={{
@ -243,19 +280,19 @@ The message that will show when the form input component is invalid. It will be
The message that will show when the form input component is invalid. You can combine this with `validationError`. Keys not found in `validationErrors` defaults to the general error message. The message that will show when the form input component is invalid. You can combine this with `validationError`. Keys not found in `validationErrors` defaults to the general error message.
#### <a name="required">required</a> #### <a name="required">required</a>
```html ```jsx
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email" required/> <MyInputComponent name="email" validations="isEmail" validationError="This is not an email" required/>
``` ```
A property that tells the form that the form input component value is required. By default it uses `isDefaultRequiredValue`, but you can define your own definition of what defined a required state. A property that tells the form that the form input component value is required. By default it uses `isDefaultRequiredValue`, but you can define your own definition of what defined a required state.
```html ```jsx
<MyInputComponent name="email" required="isFalse"/> <MyInputComponent name="email" required="isFalse"/>
``` ```
Would be typical for a checkbox type of form element. Would be typical for a checkbox type of form element that must be checked, e.g. agreeing to Terms of Service.
#### <a name="getvalue">getValue()</a> #### <a name="getvalue">getValue()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
render: function () { render: function () {
@ -268,7 +305,7 @@ var MyInput = React.createClass({
Gets the current value of the form input component. Gets the current value of the form input component.
#### <a name="setvalue">setValue(value)</a> #### <a name="setvalue">setValue(value)</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -283,27 +320,8 @@ var MyInput = React.createClass({
``` ```
Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc. Running this method will trigger a **setState()** on the component and do a render. Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc. Running this method will trigger a **setState()** on the component and do a render.
#### <a name="hasvalue">hasValue() - DEPRECATED</a>
```javascript
var MyInput = React.createClass({
mixins: [Formsy.Mixin],
changeValue: function (event) {
this.setValue(event.currentTarget.value);
},
render: function () {
return (
<div>
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
{this.hasValue() ? 'There is a value here' : 'No value entered yet'}
</div>
);
}
});
```
The hasValue() method helps you identify if there actually is a value or not. The only invalid value in Formsy is an empty string, "". All other values are valid as they could be something you want to send to the server. F.ex. the number zero (0), or false.
#### <a name="resetvalue">resetValue()</a> #### <a name="resetvalue">resetValue()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -322,7 +340,7 @@ var MyInput = React.createClass({
Resets to empty value. This will run a **setState()** on the component and do a render. Resets to empty value. This will run a **setState()** on the component and do a render.
#### <a name="geterrormessage">getErrorMessage()</a> #### <a name="geterrormessage">getErrorMessage()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -340,8 +358,11 @@ var MyInput = React.createClass({
``` ```
Will return the validation message set if the form input component is invalid. If form input component is valid it returns **null**. Will return the validation message set if the form input component is invalid. If form input component is valid it returns **null**.
#### <a name="geterrormessages">getErrorMessages()</a>
Will return the validation messages set if the form input component is invalid. If form input component is valid it returns empty array.
#### <a name="isvalid">isValid()</a> #### <a name="isvalid">isValid()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -364,7 +385,7 @@ Returns the valid state of the form input component.
#### <a name="isvalidvalue">isValidValue()</a> #### <a name="isvalidvalue">isValidValue()</a>
You can pre-verify a value against the passed validators to the form element. You can pre-verify a value against the passed validators to the form element.
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -389,7 +410,7 @@ var MyForm = React.createClass({
``` ```
#### <a name="isrequired">isRequired()</a> #### <a name="isrequired">isRequired()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -409,7 +430,7 @@ var MyInput = React.createClass({
Returns true if the required property has been passed. Returns true if the required property has been passed.
#### <a name="showrequired">showRequired()</a> #### <a name="showrequired">showRequired()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -429,7 +450,7 @@ var MyInput = React.createClass({
Lets you check if the form input component should indicate if it is a required field. This happens when the form input component value is empty and the required prop has been passed. Lets you check if the form input component should indicate if it is a required field. This happens when the form input component value is empty and the required prop has been passed.
#### <a name="showerror">showError()</a> #### <a name="showerror">showError()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -449,7 +470,7 @@ var MyInput = React.createClass({
Lets you check if the form input component should indicate if there is an error. This happens if there is a form input component value and it is invalid or if a server error is received. Lets you check if the form input component should indicate if there is an error. This happens if there is a form input component value and it is invalid or if a server error is received.
#### <a name="ispristine">isPristine()</a> #### <a name="ispristine">isPristine()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -470,7 +491,7 @@ By default all formsy input elements are pristine, which means they are not "tou
**note!** When the form is reset, using the resetForm callback function on for example [**onSubmit**](#onsubmitdata-resetform-invalidateform) the inputs are reset to their pristine state. **note!** When the form is reset, using the resetForm callback function on for example [**onSubmit**](#onsubmitdata-resetform-invalidateform) the inputs are reset to their pristine state.
#### <a name="isformdisabled">isFormDisabled()</a> #### <a name="isformdisabled">isFormDisabled()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
render: function () { render: function () {
@ -487,7 +508,7 @@ React.render(<Formy.Form disabled={true}/>);
You can now disable the form itself with a prop and use **isFormDisabled()** inside form elements to verify this prop. You can now disable the form itself with a prop and use **isFormDisabled()** inside form elements to verify this prop.
#### <a name="isformsubmitted">isFormSubmitted()</a> #### <a name="isformsubmitted">isFormSubmitted()</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
render: function () { render: function () {
@ -504,7 +525,7 @@ var MyInput = React.createClass({
You can check if the form has been submitted. You can check if the form has been submitted.
#### <a name="validate">validate</a> #### <a name="validate">validate</a>
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
changeValue: function (event) { changeValue: function (event) {
@ -528,7 +549,7 @@ You can create custom validation inside a form element. The validate method defi
#### <a name="formnovalidate">formNoValidate</a> #### <a name="formnovalidate">formNoValidate</a>
To avoid native validation behavior on inputs, use the React `formNoValidate` property. To avoid native validation behavior on inputs, use the React `formNoValidate` property.
```javascript ```jsx
var MyInput = React.createClass({ var MyInput = React.createClass({
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
render: function () { render: function () {
@ -543,10 +564,10 @@ var MyInput = React.createClass({
### <a name="formsyhoc">Formsy.HOC</a> ### <a name="formsyhoc">Formsy.HOC</a>
The same methods as the mixin are exposed to the HOC version of the element component, though through the `props`, not on the instance. The same methods as the mixin are exposed to the HOC version of the element component, though through the `props`, not on the instance.
```js ```jsx
import {HOC} from 'formsy-react'; import {HOC} from 'formsy-react';
class MyInput extends React.Component { class MyInputHoc extends React.Component {
render() { render() {
return ( return (
<div> <div>
@ -555,12 +576,31 @@ class MyInput extends React.Component {
); );
} }
}; };
export default HOC(MyInput); export default HOC(MyInputHoc);
```
#### <a name="innerRef">innerRef</a>
Use an `innerRef` prop to get a reference to your DOM node.
```jsx
var MyForm = React.createClass({
componentDidMount() {
this.searchInput.focus()
},
render: function () {
return (
<Formsy.Form>
<MyInputHoc name="search" innerRef={(c) => { this.searchInput = c; }} />
</Formsy.Form>
);
}
})
``` ```
### <a name="formsydecorator">Formsy.Decorator</a> ### <a name="formsydecorator">Formsy.Decorator</a>
The same methods as the mixin are exposed to the decorator version of the element component, though through the `props`, not on the instance. The same methods as the mixin are exposed to the decorator version of the element component, though through the `props`, not on the instance.
```js ```jsx
import {Decorator as FormsyElement} from 'formsy-react'; import {Decorator as FormsyElement} from 'formsy-react';
@FormsyElement() @FormsyElement()
@ -578,25 +618,25 @@ export default MyInput
### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a> ### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a>
An example: An example:
```javascript ```jsx
Formsy.addValidationRule('isFruit', function (values, value) { Formsy.addValidationRule('isFruit', function (values, value) {
return ['apple', 'orange', 'pear'].indexOf(value) >= 0; return ['apple', 'orange', 'pear'].indexOf(value) >= 0;
}); });
``` ```
```html ```jsx
<MyInputComponent name="fruit" validations="'isFruit"/> <MyInputComponent name="fruit" validations="isFruit"/>
``` ```
Another example: Another example:
```javascript ```jsx
Formsy.addValidationRule('isIn', function (values, value, array) { Formsy.addValidationRule('isIn', function (values, value, array) {
return array.indexOf(value) >= 0; return array.indexOf(value) >= 0;
}); });
``` ```
```html ```jsx
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange', 'pear']"/> <MyInputComponent name="fruit" validations="isIn:['apple', 'orange', 'pear']"/>
``` ```
Cross input validation: Cross input validation:
```javascript ```jsx
Formsy.addValidationRule('isMoreThan', function (values, value, otherField) { Formsy.addValidationRule('isMoreThan', function (values, value, otherField) {
// The this context points to an object containing the values // The this context points to an object containing the values
// {childAge: "", parentAge: "5"} // {childAge: "", parentAge: "5"}
@ -604,130 +644,133 @@ Formsy.addValidationRule('isMoreThan', function (values, value, otherField) {
return Number(value) > Number(values[otherField]); return Number(value) > Number(values[otherField]);
}); });
``` ```
```html ```jsx
<MyInputComponent name="childAge"/> <MyInputComponent name="childAge"/>
<MyInputComponent name="parentAge" validations="isMoreThan:childAge"/> <MyInputComponent name="parentAge" validations="isMoreThan:childAge"/>
``` ```
## <a name="validators">Validators</a> ## <a name="validators">Validators</a>
**matchRegexp** **matchRegexp**
```html ```jsx
<MyInputComponent name="foo" validations={{ <MyInputComponent name="foo" validations={{
matchRegexp: /foo/ matchRegexp: /foo/
}}/> }}/>
``` ```
Returns true if the value is thruthful Returns true if the value is thruthful
_For more complicated regular expressions (emoji, international characters) you can use [xregexp](https://github.com/slevithan/xregexp). See [this comment](https://github.com/christianalfoni/formsy-react/issues/407#issuecomment-266306783) for an example._
**isEmail** **isEmail**
```html ```jsx
<MyInputComponent name="foo" validations="isEmail"/> <MyInputComponent name="foo" validations="isEmail"/>
``` ```
Return true if it is an email Return true if it is an email
**isUrl** **isUrl**
```html ```jsx
<MyInputComponent name="foo" validations="isUrl"/> <MyInputComponent name="foo" validations="isUrl"/>
``` ```
Return true if it is an url Return true if it is an url
**isExisty** **isExisty**
```html ```jsx
<MyInputComponent name="foo" validations="isExisty"/> <MyInputComponent name="foo" validations="isExisty"/>
``` ```
Returns true if the value is not undefined or null Returns true if the value is not undefined or null
**isUndefined** **isUndefined**
```html ```jsx
<MyInputComponent name="foo" validations="isUndefined"/> <MyInputComponent name="foo" validations="isUndefined"/>
``` ```
Returns true if the value is the undefined Returns true if the value is the undefined
**isEmptyString** **isEmptyString**
```html ```jsx
<MyInputComponent name="foo" validations="isEmptyString"/> <MyInputComponent name="foo" validations="isEmptyString"/>
``` ```
Returns true if the value is an empty string Returns true if the value is an empty string
**isTrue** **isTrue**
```html ```jsx
<MyInputComponent name="foo" validations="isTrue"/> <MyInputComponent name="foo" validations="isTrue"/>
``` ```
Returns true if the value is the boolean true Returns true if the value is the boolean true
**isFalse** **isFalse**
```html ```jsx
<MyInputComponent name="foo" validations="isFalse"/> <MyInputComponent name="foo" validations="isFalse"/>
``` ```
Returns true if the value is the boolean false Returns true if the value is the boolean false
**isAlpha** **isAlpha**
```html ```jsx
<MyInputComponent name="foo" validations="isAlpha"/> <MyInputComponent name="foo" validations="isAlpha"/>
``` ```
Returns true if string is only letters Returns true if string is only letters
**isNumeric** **isNumeric**
```html ```jsx
<MyInputComponent name="foo" validations="isNumeric"/> <MyInputComponent name="foo" validations="isNumeric"/>
``` ```
Returns true if string only contains numbers. Examples: 42; -3.14 Returns true if string only contains numbers. Examples: 42; -3.14
**isAlphanumeric** **isAlphanumeric**
```html ```jsx
<MyInputComponent name="foo" validations="isAlphanumeric"/> <MyInputComponent name="foo" validations="isAlphanumeric"/>
``` ```
Returns true if string only contains letters or numbers Returns true if string only contains letters or numbers
**isInt** **isInt**
```html ```jsx
<MyInputComponent name="foo" validations="isInt"/> <MyInputComponent name="foo" validations="isInt"/>
``` ```
Returns true if string represents integer value. Examples: 42; -12; 0 Returns true if string represents integer value. Examples: 42; -12; 0
**isFloat** **isFloat**
```html ```jsx
<MyInputComponent name="foo" validations="isFloat"/> <MyInputComponent name="foo" validations="isFloat"/>
``` ```
Returns true if string represents float value. Examples: 42; -3.14; 1e3 Returns true if string represents float value. Examples: 42; -3.14; 1e3
**isWords** **isWords**
```html ```jsx
<MyInputComponent name="foo" validations="isWords"/> <MyInputComponent name="foo" validations="isWords"/>
``` ```
Returns true if string is only letters, including spaces and tabs Returns true if string is only letters, including spaces and tabs
**isSpecialWords** **isSpecialWords**
```html ```jsx
<MyInputComponent name="foo" validations="isSpecialWords"/> <MyInputComponent name="foo" validations="isSpecialWords"/>
``` ```
Returns true if string is only letters, including special letters (a-z,ú,ø,æ,å) Returns true if string is only letters, including special letters (a-z,ú,ø,æ,å)
**equals:value** **equals:value**
```html ```jsx
<MyInputComponent name="foo" validations="equals:4"/> <MyInputComponent name="foo" validations="equals:4"/>
``` ```
Return true if the value from input component matches value passed (==). Return true if the value from input component matches value passed (==).
**equalsField:fieldName** **equalsField:fieldName**
```html ```jsx
<MyInputComponent name="password"/> <MyInputComponent name="password"/>
<MyInputComponent name="repeated_password" validations="equalsField:password"/> <MyInputComponent name="repeated_password" validations="equalsField:password"/>
``` ```
Return true if the value from input component matches value passed (==). Return true if the value from input component matches value passed (==).
**isLength:length** **isLength:length**
```html ```jsx
<MyInputComponent name="foo" validations="isLength:8"/> <MyInputComponent name="foo" validations="isLength:8"/>
``` ```
Returns true if the value length is the equal. Returns true if the value length is the equal.
**minLength:length** **minLength:length**
```html ```jsx
<MyInputComponent name="number" validations="minLength:1"/> <MyInputComponent name="number" validations="minLength:1"/>
``` ```
Return true if the value is more or equal to argument Return true if the value is more or equal to argument.
**Also returns true for an empty value.** If you want to get false, then you should use [`required`](#required) additionally.
**maxLength:length** **maxLength:length**
```html ```jsx
<MyInputComponent name="number" validations="maxLength:5"/> <MyInputComponent name="number" validations="maxLength:5"/>
``` ```
Return true if the value is less or equal to argument Return true if the value is less or equal to argument

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014 Christian Alfoni Copyright (c) 2014-2016 PatientSky A/S
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -6,10 +6,6 @@ A form input builder and validator for React JS
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) | | [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|---|---|---| |---|---|---|
### Currently, the development is in 'react-0.14' branch. For more information see [#158](https://github.com/christianalfoni/formsy-react/issues/158)
### From version 0.12.0 Formsy only supports React 0.13.1 and up
## <a name="background">Background</a> ## <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 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.
@ -28,7 +24,7 @@ The main concept is that forms, inputs and validation is done very differently a
5. You can dynamically add form elements to your form and they will register/unregister to the form 5. You can dynamically add form elements to your form and they will register/unregister to the form
## Default elements ## 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. 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
@ -44,30 +40,35 @@ You can look at examples in this repo or use the [formsy-react-components](https
## How to use ## How to use
See [`examples` folder](/examples) for examples. See [`examples` folder](/examples) for examples. [Codepen demo](http://codepen.io/semigradsky/pen/dYYpwv?editors=001).
Complete API reference is available [here](/API.md). Complete API reference is available [here](/API.md).
#### Formsy gives you a form straight out of the box #### Formsy gives you a form straight out of the box
```javascript ```jsx
/** @jsx React.DOM */ import Formsy from 'formsy-react';
var Formsy = require('formsy-react');
var MyAppForm = React.createClass({ const MyAppForm = React.createClass({
enableButton: function () { getInitialState() {
return {
canSubmit: false
}
},
enableButton() {
this.setState({ this.setState({
canSubmit: true canSubmit: true
}); });
}, },
disableButton: function () { disableButton() {
this.setState({ this.setState({
canSubmit: false canSubmit: false
}); });
}, },
submit: function (model) { submit(model) {
someDep.saveEmail(model.email); someDep.saveEmail(model.email);
}, },
render: function () { render() {
return ( return (
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/> <MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/>
@ -81,31 +82,31 @@ Complete API reference is available [here](/API.md).
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". 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) #### Building a form element (required)
```javascript ```jsx
/** @jsx React.DOM */ import Formsy from 'formsy-react';
var Formsy = require('formsy-react');
var MyOwnInput = React.createClass({ const MyOwnInput = React.createClass({
// Add the Formsy Mixin // Add the Formsy Mixin
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
// setValue() will set the value of the component, which in // setValue() will set the value of the component, which in
// turn will validate it and the rest of the form // turn will validate it and the rest of the form
changeValue: function (event) { changeValue(event) {
this.setValue(event.currentTarget.value); this.setValue(event.currentTarget.value);
}, },
render: function () {
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
var className = this.showRequired() ? 'required' : this.showError() ? 'error' : null; const className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
// 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
var errorMessage = this.getErrorMessage(); const errorMessage = this.getErrorMessage();
return ( return (
<div className={className}> <div className={className}>
@ -119,39 +120,19 @@ This code results in a form with a submit button that will run the `submit` meth
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. 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 ## Related projects
- [formsy-react-components](https://github.com/twisty/formsy-react-components) - A set of React JS components for use in a formsy-react form - [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! - Send PR for adding your project to this list!
## Contribute ## Contribute
- Fork repo - Fork repo
- `npm install` - `npm install`
- `npm start` runs the development server on `localhost:8080` - `npm run examples` runs the development server on `localhost:8080`
- `npm test` runs the tests - `npm test` runs the tests
License ## License
-------
formsy-react is licensed under the [MIT license](LICENSE). [The MIT License (MIT)](/LICENSE)
> The MIT License (MIT) Copyright (c) 2014-2016 PatientSky A/S
>
> Copyright (c) 2015 Gloppens EDB Lag
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.

View File

@ -1,6 +1,6 @@
{ {
"name": "formsy-react", "name": "formsy-react",
"version": "0.14.1", "version": "0.18.0",
"description": "A form input builder and validator for React JS", "description": "A form input builder and validator for React JS",
"repository": { "repository": {
"type": "git", "type": "git",
@ -13,7 +13,7 @@
"Gulpfile.js" "Gulpfile.js"
], ],
"dependencies": { "dependencies": {
"react": "^0.13.1" "react": "^0.14.7 || ^15.0.0"
}, },
"keywords": [ "keywords": [
"react", "react",

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Formsy Test</title>
</head>
<body>
<div id="app"></div>
<script src="build.js"></script>
</body>
</html>

View File

@ -1,55 +0,0 @@
var React = require('react');
var ReactDOM = require('react-dom');
var Formsy = require('./../src/main.js');
var Input = React.createClass({
onChange: function (event) {
this.props.setValue(event.currentTarget.value);
},
render: function () {
return (
<div>
{this.props.showRequired() ? 'required' : ''}
<input disabled={this.props.isFormDisabled()} value={this.props.getValue()} onChange={this.onChange}/>
</div>
);
}
});
Input = Formsy.HOC(Input);
var SomeComp = React.createClass({
getInitialState: function () {
return {
isRequired: false
};
},
toggleRequired: function () {
this.setState({
isRequired: !this.state.isRequired
});
},
render: function () {
return (
<div>
<Input name="foo[0]" value={''} validations="isEmail" validationError="No email" required={this.state.isRequired}/>
<button onClick={this.toggleRequired}>Test</button>
</div>
)
}
});
var FormApp = React.createClass({
onSubmit: function (model) {
console.log('model', model);
},
render: function () {
return (
<Formsy.Form ref="form" onSubmit={this.onSubmit}>
<SomeComp/>
</Formsy.Form>
);
}
});
ReactDOM.render(<FormApp />, document.getElementById('app'));

View File

@ -34,3 +34,7 @@ If it is not helped try update your node.js and npm.
3. [**Reset Values**](reset-values) 3. [**Reset Values**](reset-values)
Reset text input, checkbox and select to their pristine values. Reset text input, checkbox and select to their pristine values.
4. [**Dynamic Form Fields**](dynamic-form-fields)
Dynamically adding and removing fields to form.

View File

@ -18,14 +18,15 @@ const MyInput = React.createClass({
// 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 = this.props.className + ' ' + (this.showRequired() ? 'required' : this.showError() ? 'error' : null); 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.getErrorMessage(); const errorMessage = this.getErrorMessage();
return ( return (
<div className='form-group'> <div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label> <label htmlFor={this.props.name}>{this.props.title}</label>
<input <input
type={this.props.type || 'text'} type={this.props.type || 'text'}

View File

@ -0,0 +1,66 @@
import React from 'react';
import Formsy from 'formsy-react';
function contains(container, item, cmp) {
for (const it of container) {
if (cmp(it, item)) {
return true;
}
}
return false;
}
const MyRadioGroup = React.createClass({
mixins: [Formsy.Mixin],
getInitialState() {
return { value: [], cmp: (a, b) => a === b };
},
componentDidMount() {
const value = this.props.value || [];
this.setValue(value);
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
},
changeValue(value, event) {
const checked = event.currentTarget.checked;
let newValue = [];
if (checked) {
newValue = this.state.value.concat(value);
} else {
newValue = this.state.value.filter(it => !this.state.cmp(it, value));
}
this.setValue(newValue);
this.setState({ value: newValue });
},
render() {
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage();
const { name, title, items } = this.props;
return (
<div className={className}>
<label htmlFor={name}>{title}</label>
{items.map((item, i) => (
<div key={i}>
<input
type="checkbox"
name={name}
onChange={this.changeValue.bind(this, item)}
checked={contains(this.state.value, item, this.state.cmp)}
/>
<span>{JSON.stringify(item)}</span>
</div>
))
}
<span className='validation-error'>{errorMessage}</span>
</div>
);
}
});
export default MyRadioGroup;

View File

@ -0,0 +1,46 @@
import React from 'react';
import Formsy from 'formsy-react';
const MyRadioGroup = React.createClass({
mixins: [Formsy.Mixin],
componentDidMount() {
const value = this.props.value;
this.setValue(value);
this.setState({ value });
},
changeValue(value) {
this.setValue(value);
this.setState({ value });
},
render() {
const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage();
const { name, title, items } = this.props;
return (
<div className={className}>
<label htmlFor={name}>{title}</label>
{items.map((item, i) => (
<div key={i}>
<input
type="radio"
name={name}
onChange={this.changeValue.bind(this, item)}
checked={this.state.value === item}
/>
<span>{item.toString()}</span>
</div>
))
}
<span className='validation-error'>{errorMessage}</span>
</div>
);
}
});
export default MyRadioGroup;

View File

@ -9,7 +9,8 @@ const MySelect = React.createClass({
}, },
render() { render() {
const className = this.props.className + ' ' + (this.showRequired() ? 'required' : this.showError() ? 'error' : null); const className = 'form-group' + (this.props.className || ' ') +
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
const errorMessage = this.getErrorMessage(); const errorMessage = this.getErrorMessage();
const options = this.props.options.map((option, i) => ( const options = this.props.options.map((option, i) => (
@ -19,7 +20,7 @@ const MySelect = React.createClass({
)); ));
return ( return (
<div className='form-group'> <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.getValue()}> <select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
{options} {options}

View File

@ -58,19 +58,20 @@ const DynamicInput = React.createClass({
}, },
validate() { validate() {
const value = this.getValue(); const value = this.getValue();
return value !== '' ? validators[this.state.validationType].regexp.test(value) : true; console.log(value, this.state.validationType);
return value ? validators[this.state.validationType].regexp.test(value) : true;
}, },
getCustomErrorMessage() { getCustomErrorMessage() {
return this.showError() ? validators[this.state.validationType].message : ''; return this.showError() ? validators[this.state.validationType].message : '';
}, },
render() { render() {
const className = this.props.className + ' ' + (this.showError() ? 'error' : null); const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
const errorMessage = this.getCustomErrorMessage(); const errorMessage = this.getCustomErrorMessage();
return ( return (
<div className='form-group'> <div className={className}>
<label htmlFor={this.props.name}>{this.props.title}</label> <label htmlFor={this.props.name}>{this.props.title}</label>
<input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue()}/> <input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue() || ''}/>
<span className='validation-error'>{errorMessage}</span> <span className='validation-error'>{errorMessage}</span>
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/> <Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/>
</div> </div>

View File

@ -0,0 +1,24 @@
.many-fields-conf {
width: 400px;
margin: 0 auto;
}
.many-fields {
width: 600px;
margin: 0 auto;
}
.field {
overflow: hidden;
}
.many-fields .form-group {
width: calc(100% - 20px);
float: left;
}
.many-fields .remove-field {
margin-top: 30px;
margin-left: 8px;
display: inline-block;
text-decoration: none;
}

View File

@ -0,0 +1,113 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Form } from 'formsy-react';
import MyInput from './../components/Input';
import MySelect from './../components/Select';
import MyRadioGroup from './../components/RadioGroup';
import MyMultiCheckboxSet from './../components/MultiCheckboxSet';
const Fields = props => {
function onRemove(pos) {
return event => {
event.preventDefault();
props.onRemove(pos);
};
}
const foo = 'required';
return (
<div className="fields">
{props.data.map((field, i) => (
<div className="field" key={field.id}>
{
field.type === 'input' ?
(
<MyInput
value=""
name={`fields[${i}]`}
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
required={field.required}
validations={field.validations}
/>
) :
(
<MySelect
name={`fields[${i}]`}
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
required={field.required}
validations={field.validations}
options={[
{title: '123', value: '123'},
{title: 'some long text', value: 'some long text'},
{title: '`empty string`', value: ''},
{title: 'alpha42', value: 'alpha42'},
{title: 'test@mail.com', value: 'test@mail.com'}
]}
/>
)
}
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
</div>
))
}
</div>
);
};
const App = React.createClass({
getInitialState() {
return { fields: [], canSubmit: false };
},
submit(data) {
alert(JSON.stringify(data, null, 4));
},
addField(fieldData) {
fieldData.validations = fieldData.validations.length ?
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
null;
fieldData.id = Date.now();
this.setState({ fields: this.state.fields.concat(fieldData) });
},
removeField(pos) {
const fields = this.state.fields;
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
},
enableButton() {
this.setState({ canSubmit: true });
},
disableButton() {
this.setState({ canSubmit: false });
},
render() {
const { fields, canSubmit } = this.state;
return (
<div>
<Form onSubmit={this.addField} className="many-fields-conf">
<MyMultiCheckboxSet name="validations" title="Validations"
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
items={[
{isEmail: true},
{isEmptyString: true},
{isNumeric: true},
{isAlphanumeric: true},
{equals: 5},
{minLength: 3},
{maxLength: 7}
]}
/>
<MyRadioGroup name="required" value={false} title="Required"
items={[true, false]} />
<MyRadioGroup name="type" value="input" title="Type"
items={['input', 'select']} />
<button type="submit">Add</button>
</Form>
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
<Fields data={fields} onRemove={this.removeField} />
<button type="submit" disabled={!canSubmit}>Submit</button>
</Form>
</div>
);
}
});
ReactDOM.render(<App/>, document.getElementById('example'));

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<title>Dynamic Form Fields</title>
<link href="../global.css" rel="stylesheet"/>
<link href="app.css" rel="stylesheet"/>
</head>
<body>
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
<div id="example"/>
<script src="/__build__/shared.js"></script>
<script src="/__build__/dynamic-form-fields.js"></script>
</body>
</html>

View File

@ -49,11 +49,28 @@ form {
color: #555; color: #555;
background-color: #FFF; background-color: #FFF;
background-image: none; background-image: none;
border: 1px solid #CCC; border: 2px solid #CCC;
border-radius: 4px; border-radius: 4px;
box-sizing: border-box; box-sizing: border-box;
} }
.form-group.error input[type='text'],
.form-group.error input[type='email'],
.form-group.error input[type='number'],
.form-group.error input[type='password'],
.form-group.error select {
border-color: red;
color: red;
}
.form-group.required input[type='text'],
.form-group.required input[type='email'],
.form-group.required input[type='number'],
.form-group.required input[type='password'],
.form-group.required select {
border-color: #FF9696;
}
.validation-error { .validation-error {
color: red; color: red;
margin: 5px 0; margin: 5px 0;

View File

@ -10,6 +10,7 @@
<li><a href="login/index.html">Login Page</a></li> <li><a href="login/index.html">Login Page</a></li>
<li><a href="custom-validation/index.html">Custom Validation</a></li> <li><a href="custom-validation/index.html">Custom Validation</a></li>
<li><a href="reset-values/index.html">Reset Values</a></li> <li><a href="reset-values/index.html">Reset Values</a></li>
<li><a href="dynamic-form-fields/index.html">Dynamic Form Fields</a></li>
</ul> </ul>
</body> </body>
</html> </html>

View File

@ -20,8 +20,8 @@ const App = React.createClass({
render() { render() {
return ( return (
<Form 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>
</Form> </Form>
); );

View File

@ -1,6 +1,6 @@
{ {
"name": "formsy-react", "name": "formsy-react",
"version": "0.15.1", "version": "0.19.5",
"description": "A form input builder and validator for React JS", "description": "A form input builder and validator for React JS",
"repository": { "repository": {
"type": "git", "type": "git",
@ -8,9 +8,8 @@
}, },
"main": "lib/main.js", "main": "lib/main.js",
"scripts": { "scripts": {
"start": "webpack-dev-server --content-base build", "build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
"deploy": "NODE_ENV=production webpack -p --config webpack.production.config.js", "test": "babel-node testrunner",
"test": "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",
"prepublish": "babel ./src/ -d ./lib/" "prepublish": "babel ./src/ -d ./lib/"
}, },
@ -24,21 +23,27 @@
"react-component" "react-component"
], ],
"dependencies": { "dependencies": {
"form-data-to-object": "^0.1.0" "form-data-to-object": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"babel": "^5.6.4", "babel-cli": "^6.6.5",
"babel-core": "^5.1.11", "babel-loader": "^6.2.4",
"babel-loader": "^5.0.0", "babel-preset-es2015": "^6.6.0",
"jasmine-node": "^1.14.5", "babel-preset-react": "^6.5.0",
"jsdom": "^3.1.2", "babel-preset-stage-2": "^6.5.0",
"react": "^0.14.0-beta3", "create-react-class": "^15.6.0",
"react-addons-test-utils": "^0.14.0-beta3", "jsdom": "^6.5.1",
"react-dom": "^0.14.0-beta3", "nodeunit": "^0.9.1",
"webpack": "^1.7.3", "prop-types": "^15.5.10",
"webpack-dev-server": "^1.7.0" "react": "^15.0.0",
"react-addons-pure-render-mixin": "^15.0.0",
"react-addons-test-utils": "^15.0.0",
"react-dom": "^15.0.0",
"sinon": "^1.17.3",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^0.14.0-beta3" "react": "^0.14.0 || ^15.0.0"
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,519 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import TestInput from './utils/TestInput';
import immediate from './utils/immediate';
describe('Formsy', function () {
describe('Setting up a form', function () {
it('should render a form into the document', function () {
const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
expect(React.findDOMNode(form).tagName).toEqual('FORM');
});
it('should set a class name if passed', function () {
const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
expect(React.findDOMNode(form).className).toEqual('foo');
});
it('should allow for null/undefined children', function (done) {
let model = null;
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<h1>Test</h1>
{ null }
{ undefined }
<TestInput name="name" value={ 'foo' } />
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
immediate(() => {
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(model).toEqual({name: 'foo'});
done();
});
});
it('should allow for inputs being added dynamically', function (done) {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
setTimeout(() => {
inputs.push(<TestInput name="test" value="" key={inputs.length}/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(model.test).toBeDefined();
done();
});
});
}, 10);
});
it('should allow dynamically added inputs to update the form-model', function (done) {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
immediate(() => {
inputs.push(<TestInput name="test" key={inputs.length}/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'foo'}});
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(model.test).toBe('foo');
done();
});
});
});
});
it('should allow a dynamically updated input to update the form-model', function (done) {
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
const input = <TestInput name="test" value={this.props.value} />;
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{input}
</Formsy.Form>);
}
});
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
// Wait before changing the input
immediate(() => {
form = TestUtils.renderIntoDocument(<TestForm value="bar"/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(model.test).toBe('bar');
done();
});
});
});
});
describe('validations', function () {
let CheckValid, onSubmit, OtherCheckValid;
let isValid;
const TestForm = React.createClass({
getDefaultProps() {
return { inputs: [] };
},
render() {
const builtInputs = this.props.inputs.map((input) => <TestInput { ...input } key={ input.name } />);
return (
<Formsy.Form
onSubmit={(arg1) => onSubmit(arg1)}
onValid={() => (isValid = true)}
onInvalid={() => (isValid = false)}>
{ builtInputs }
</Formsy.Form>
);
}
});
beforeEach(() => {
isValid = true;
CheckValid = jasmine.createSpy('CheckValid');
Formsy.addValidationRule('CheckValid', CheckValid);
OtherCheckValid = jasmine.createSpy('CheckValid');
Formsy.addValidationRule('OtherCheckValid', OtherCheckValid);
onSubmit = jasmine.createSpy('onSubmit');
});
it('should run when the input changes', function () {
const inputs = [{name: 'one', validations: 'CheckValid', value: 'foo'}];
const form = TestUtils.renderIntoDocument(<TestForm inputs={inputs}/>);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'bar'}});
expect(CheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
expect(OtherCheckValid).not.toHaveBeenCalled();
});
it('should allow the validation to be changed', function () {
const inputs = [{name: 'one', validations: 'CheckValid', value: 'foo'}];
const form = TestUtils.renderIntoDocument(<TestForm inputs={inputs}/>);
form.setProps({inputs: [{name: 'one', validations: 'OtherCheckValid', value: 'foo'}] });
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'bar'}});
expect(OtherCheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
});
it('should invalidate a form if dynamically inserted input is invalid', function (done) {
const inputs = [{name: 'one', validations: 'isEmail', value: 'foo@bar.com'}];
const form = TestUtils.renderIntoDocument(<TestForm inputs={inputs}/>);
expect(isValid).toEqual(true);
form.setProps({inputs: [
{name: 'one', validations: 'isEmail', value: 'foo@bar.com'},
{name: 'two', validations: 'isEmail', value: 'foo@bar'}
]}, () => {
immediate(() => {
expect(isValid).toEqual(false);
done();
});
});
});
it('should validate a form when removing an invalid input', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm inputs={ [
{name: 'one', validations: 'isEmail', value: 'foo@bar.com'},
{name: 'two', validations: 'isEmail', value: 'foo@bar'}
] } />);
expect(isValid).toEqual(false);
form.setProps({inputs: [{name: 'one', validations: 'isEmail', value: 'foo@bar.com'}]}, () => {
immediate(() => {
expect(isValid).toEqual(true);
done();
});
});
});
it('runs multiple validations', function () {
const inputs = [{name: 'one', validations: 'CheckValid,OtherCheckValid', value: 'foo'}];
const form = TestUtils.renderIntoDocument(<TestForm inputs={inputs}/>);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'bar'}});
expect(CheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
expect(OtherCheckValid).toHaveBeenCalledWith({one: 'bar'}, 'bar', true);
});
});
it('should not trigger onChange when form is mounted', function () {
const hasChanged = jasmine.createSpy('onChange');
const TestForm = React.createClass({
render() {
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
}
});
TestUtils.renderIntoDocument(<TestForm/>);
expect(hasChanged).not.toHaveBeenCalled();
});
it('should trigger onChange when form element is changed', function () {
const hasChanged = jasmine.createSpy('onChange');
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onChange={hasChanged}>
<TestInput name="foo"/>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
expect(hasChanged).toHaveBeenCalled();
});
it('should trigger onChange when new input is added to form', function (done) {
const hasChanged = jasmine.createSpy('onChange');
const inputs = [];
let forceUpdate = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
return (
<Formsy.Form onChange={hasChanged}>
{inputs}
</Formsy.Form>);
}
});
TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
inputs.push(<TestInput name="test" key={inputs.length}/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
expect(hasChanged).toHaveBeenCalled();
done();
});
});
});
});
describe('Update a form', function () {
it('should allow elements to check if the form is disabled', function (done) {
const TestForm = React.createClass({
getInitialState() { return { disabled: true }; },
enableForm() { this.setState({ disabled: false }); },
render() {
return (
<Formsy.Form onChange={this.onChange} disabled={this.state.disabled}>
<TestInput name="foo"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.isFormDisabled()).toBe(true);
form.enableForm();
immediate(() => {
expect(input.isFormDisabled()).toBe(false);
done();
});
});
it('should be possible to pass error state of elements by changing an errors attribute', function (done) {
const TestForm = React.createClass({
getInitialState() { return { validationErrors: { foo: 'bar' } }; },
onChange(values) {
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
},
render() {
return (
<Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
<TestInput name="foo"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait for update
immediate(() => {
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.getErrorMessage()).toBe('bar');
input.setValue('gotValue');
// Wait for update
immediate(() => {
expect(input.getErrorMessage()).toBe(null);
done();
});
});
});
it('should trigger an onValidSubmit when submitting a valid form', function () {
let isCalled = false;
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onValidSubmit={() => (isCalled = true)}>
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(React.findDOMNode(FoundForm));
expect(isCalled).toBe(true);
});
it('should trigger an onInvalidSubmit when submitting an invalid form', function () {
let isCalled = false;
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onInvalidSubmit={() => (isCalled = true)}>
<TestInput name="foo" validations="isEmail" value="foo@bar"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(
<TestForm/>
);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(React.findDOMNode(FoundForm));
expect(isCalled).toBe(true);
});
});
describe("value === false", function () {
let onSubmit;
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onSubmit={(x) => onSubmit(x)}>
<TestInput name="foo" value={ this.props.value } type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
beforeEach(() => {
onSubmit = jasmine.createSpy('onSubmit');
});
it("should call onSubmit correctly", function () {
const form = TestUtils.renderIntoDocument(<TestForm value={ false }/>);
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(onSubmit).toHaveBeenCalledWith({foo: false});
});
it("should allow dynamic changes to false", function () {
const form = TestUtils.renderIntoDocument(<TestForm value={ true }/>);
form.setProps({value: false});
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(onSubmit).toHaveBeenCalledWith({foo: false});
});
it("should say the form is submitted", function () {
const form = TestUtils.renderIntoDocument(<TestForm value={ true }/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.isFormSubmitted()).toBe(false);
TestUtils.Simulate.submit(React.findDOMNode(form));
expect(input.isFormSubmitted()).toBe(true);
});
it("should be able to reset the form to its pristine state", function () {
const form = TestUtils.renderIntoDocument(<TestForm value={ true }/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
expect(input.getValue()).toBe(true);
form.setProps({value: false});
expect(input.getValue()).toBe(false);
formsyForm.reset();
expect(input.getValue()).toBe(true);
});
it("should be able to reset the form using custom data", function () {
const form = TestUtils.renderIntoDocument(<TestForm value={ true }/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
expect(input.getValue()).toBe(true);
form.setProps({value: false});
expect(input.getValue()).toBe(false);
formsyForm.reset({
foo: 'bar'
});
expect(input.getValue()).toBe('bar');
});
});
describe('.isChanged()', function () {
let onChange;
const TestForm = React.createClass({
getDefaultProps() {
return { inputs: [] };
},
render() {
const builtInputs = this.props.inputs.map((input) => <TestInput {...input} key={input.name}/>);
return (
<Formsy.Form ref="formsy" onChange={ onChange }>
{ builtInputs }
{ this.props.children }
</Formsy.Form>
);
}
});
beforeEach(() => {
onChange = jasmine.createSpy('onChange');
});
it('initially returns false', function () {
const form = TestUtils.renderIntoDocument(<TestForm inputs={ [{name: 'one', value: 'foo'}] }/>);
expect(form.refs.formsy.isChanged()).toEqual(false);
expect(onChange).not.toHaveBeenCalled();
});
it('returns true when changed', function () {
const form = TestUtils.renderIntoDocument(<TestForm inputs={ [{name: 'one', value: 'foo'}] }/>);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'bar'}});
expect(form.refs.formsy.isChanged()).toEqual(true);
expect(onChange).toHaveBeenCalledWith({one: 'bar'}, true);
});
it('returns false if changes are undone', function () {
const form = TestUtils.renderIntoDocument(<TestForm inputs={ [{name: 'one', value: 'foo'}] }/>);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'bar'}});
expect(onChange).toHaveBeenCalledWith({one: 'bar'}, true);
TestUtils.Simulate.change(React.findDOMNode(input), {target: {value: 'foo'}});
expect(form.refs.formsy.isChanged()).toEqual(false);
expect(onChange).toHaveBeenCalledWith({one: 'foo'}, false);
});
});
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: equals', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="equals:myValue"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should fail when the value is not equal', fail('foo'));
it('should pass when the value is equal', pass('myValue'));
it('should pass with an empty string', pass(''));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should fail with a number', fail(42));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isAlpha', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isAlpha"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with a string is only latin letters', pass('myValue'));
it('should fail with a string with numbers', fail('myValue42'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with an empty string', pass(''));
it('should fail with a number', fail(42));
});

View File

@ -1,62 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isAlphanumeric', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isAlphanumeric"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with a string is only latin letters', pass('myValue'));
it('should pass with a string with numbers', pass('myValue42'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with an empty string', pass(''));
it('should pass with a number', pass(42));
it('should fail with a non alpha and number symbols', fail('!@#$%^&*()'));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isEmail', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" value="foo" validations="isEmail"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should fail with "foo"', fail('foo'));
it('should pass with "foo@foo.com"', pass('foo@foo.com'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with an empty string', pass(''));
it('should fail with a number', fail(42));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isEmptyString', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isEmptyString"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should fail with a default value', fail());
it('should fail with non-empty string', fail('asd'));
it('should pass with an empty string', pass(''));
it('should fail with a undefined', fail(undefined));
it('should fail with a null', fail(null));
it('should fail with a number', fail(123));
it('should fail with a zero', fail(0));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isExisty', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isExisty"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should fail with a default value', fail());
it('should pass with a string', pass('myValue'));
it('should pass with an empty string', pass(''));
it('should fail with an undefined', fail(undefined));
it('should fail with a null', fail(null));
it('should pass with a number', pass(42));
it('should pass with a zero', pass(0));
});

View File

@ -1,68 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isFloat', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isFloat"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with an empty string', pass(''));
it('should fail with an unempty string', fail('myValue'));
it('should pass with a number as string', pass('+42'));
it('should fail with a number as string with not digits', fail('42 as an answer'));
it('should pass with an int', pass(42));
it('should pass with a float', pass(Math.PI));
it('should pass with a float in science notation', pass('-1e3'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with a zero', pass(0));
});

View File

@ -1,68 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isInt', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isInt"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with an empty string', pass(''));
it('should fail with an unempty string', fail('myValue'));
it('should pass with a number as string', pass('+42'));
it('should fail with a number as string with not digits', fail('42 as an answer'));
it('should pass with an int', pass(42));
it('should fail with a float', fail(Math.PI));
it('should fail with a float in science notation', fail('-1e3'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with a zero', pass(0));
});

View File

@ -1,93 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isLength', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
});
afterEach(() => {
Input = isValid = form = null;
});
describe('isLength:3', () => {
beforeEach(() => {
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isLength:3"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
it('should pass with a default value', pass());
it('should fail with a string too small', fail('hi'));
it('should fail with a string too long', fail('foo bar'));
it('should pass with the right length', pass('sup'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with an empty string', pass(''));
it('should fail with a number', fail(123));
});
describe('isLength:0', () => {
beforeEach(() => {
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isLength:0"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
it('should pass with a default value', pass());
it('should fail with a string too long', fail('foo bar'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with an empty string', pass(''));
it('should fail with a number', fail(123));
});
});

View File

@ -1,68 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isNumeric', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isNumeric"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with an empty string', pass(''));
it('should fail with an unempty string', fail('myValue'));
it('should pass with a number as string', pass('+42'));
it('should fail with a number as string with not digits', fail('42 as an answer'));
it('should pass with an int', pass(42));
it('should pass with a float', pass(Math.PI));
it('should fail with a float in science notation', fail('-1e3'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should pass with a zero', pass(0));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isUrl', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" value="foo" validations="isUrl"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should fail with "foo"', fail('foo'));
it('should pass with "https://www.google.com/"', pass('https://www.google.com/'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should fail with a number', fail(42));
it('should pass with an empty string', pass(''));
});

View File

@ -1,60 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: isWords', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="isWords"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass with a 1 word', pass('sup'));
it('should pass with 2 words', pass('sup dude'));
it('should fail with a string with numbers', fail('myValue 42'));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should fail with a number', fail(42));
});

View File

@ -1,62 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: maxLength', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="maxLength:3"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
afterEach(() => {
Input = isValid = form = null;
});
it('should pass with a default value', pass());
it('should pass when a string\'s length is smaller', pass('hi'));
it('should pass when a string\'s length is equal', pass('bar'));
it('should fail when a string\'s length is bigger', fail('myValue'));
it('should pass with an empty string', pass(''));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should fail with a number', fail(123));
});

View File

@ -1,93 +0,0 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { customizeInput } from './utils/TestInput';
describe('Rules: minLength', function () {
let Input, isValid, form, input;
function pass(value) {
return pass.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(true);
} : () => expect(isValid).toBe(true);
}
function fail(value) {
return fail.length ? () => {
TestUtils.Simulate.change(input, {target: {value}});
expect(isValid).toBe(false);
} : () => expect(isValid).toBe(false);
}
beforeEach(() => {
Input = customizeInput({
render() {
isValid = this.isValid();
return <input value={this.getValue()} onChange={this.updateValue}/>;
}
});
});
afterEach(() => {
Input = isValid = form = null;
});
describe('minLength:3', function () {
beforeEach(() => {
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="minLength:3"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
it('should pass with a default value', pass());
it('should fail when a string\'s length is smaller', fail('hi'));
it('should pass when a string\'s length is equal', pass('bar'));
it('should pass when a string\'s length is bigger', pass('myValue'));
it('should pass with an empty string', pass(''));
it('should pass with an undefined', pass(undefined));
it('should pass with a null', pass(null));
it('should fail with a number', fail(123));
});
describe('minLength:0', () => {
beforeEach(() => {
form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" validations="minLength:0"/>
</Formsy.Form>
);
input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
});
it('should pass with a default value', pass());
it('should pass when a string\'s length is bigger', pass('myValue'));
it('should pass with empty string', pass(''));
it('should pass with undefined', pass(undefined));
it('should pass with null', pass(null));
it('should fail with a number', fail(123));
});
});

View File

@ -1,29 +0,0 @@
import utils from './../src/utils.js';
describe('Utils', function () {
it('should check equality of objects and arrays', () => {
const objA = { foo: 'bar' };
const objB = { foo: 'bar' };
const objC = [{ foo: ['bar'] }];
const objD = [{ foo: ['bar'] }];
const objE = undefined;
const objF = undefined;
const objG = null;
const objH = null;
expect(utils.isSame(objA, objB)).toBe(true);
expect(utils.isSame(objC, objD)).toBe(true);
expect(utils.isSame(objA, objD)).toBe(false);
expect(utils.isSame(objE, objF)).toBe(true);
expect(utils.isSame(objA, objF)).toBe(false);
expect(utils.isSame(objE, objA)).toBe(false);
expect(utils.isSame(objG, objH)).toBe(true);
expect(utils.isSame(objA, objH)).toBe(false);
expect(utils.isSame(objG, objA)).toBe(false);
});
});

View File

@ -1,8 +1,9 @@
var React = global.React || require('react'); var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js'); var Mixin = require('./Mixin.js');
module.exports = function () { module.exports = function () {
return function (Component) { return function (Component) {
return React.createClass({ return createReactClass({
mixins: [Mixin], mixins: [Mixin],
render: function () { render: function () {
return React.createElement(Component, { return React.createElement(Component, {

View File

@ -1,10 +1,14 @@
var React = global.React || require('react'); var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Mixin = require('./Mixin.js'); var Mixin = require('./Mixin.js');
module.exports = function (Component) { module.exports = function (Component) {
return React.createClass({ return createReactClass({
displayName: 'Formsy(' + getDisplayName(Component) + ')',
mixins: [Mixin], mixins: [Mixin],
render: function () { render: function () {
return React.createElement(Component, { const { innerRef } = this.props;
const propsForElement = {
setValidations: this.setValidations, setValidations: this.setValidations,
setValue: this.setValue, setValue: this.setValue,
resetValue: this.resetValue, resetValue: this.resetValue,
@ -19,8 +23,22 @@ module.exports = function (Component) {
isRequired: this.isRequired, isRequired: this.isRequired,
showRequired: this.showRequired, showRequired: this.showRequired,
showError: this.showError, showError: this.showError,
isValidValue: this.isValidValue 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')
);
}

View File

@ -1,3 +1,4 @@
var PropTypes = require('prop-types');
var utils = require('./utils.js'); var utils = require('./utils.js');
var React = global.React || require('react'); var React = global.React || require('react');
@ -44,7 +45,7 @@ module.exports = {
}; };
}, },
contextTypes: { contextTypes: {
formsy: React.PropTypes.object // What about required? formsy: PropTypes.object // What about required?
}, },
getDefaultProps: function () { getDefaultProps: function () {
return { return {

View File

@ -1,4 +1,6 @@
var PropTypes = require('prop-types');
var React = global.React || require('react'); var React = global.React || require('react');
var createReactClass = require('create-react-class');
var Formsy = {}; var Formsy = {};
var validationRules = require('./validationRules.js'); var validationRules = require('./validationRules.js');
var formDataToObject = require('form-data-to-object'); var formDataToObject = require('form-data-to-object');
@ -7,6 +9,7 @@ var Mixin = require('./Mixin.js');
var HOC = require('./HOC.js'); var HOC = require('./HOC.js');
var Decorator = require('./Decorator.js'); var Decorator = require('./Decorator.js');
var options = {}; var options = {};
var emptyArray = [];
Formsy.Mixin = Mixin; Formsy.Mixin = Mixin;
Formsy.HOC = HOC; Formsy.HOC = HOC;
@ -20,7 +23,8 @@ Formsy.addValidationRule = function (name, func) {
validationRules[name] = func; validationRules[name] = func;
}; };
Formsy.Form = React.createClass({ Formsy.Form = createReactClass({
displayName: 'Formsy',
getInitialState: function () { getInitialState: function () {
return { return {
isValid: true, isValid: true,
@ -35,7 +39,6 @@ Formsy.Form = React.createClass({
onSubmit: function () {}, onSubmit: function () {},
onValidSubmit: function () {}, onValidSubmit: function () {},
onInvalidSubmit: function () {}, onInvalidSubmit: function () {},
onSubmitted: function () {},
onValid: function () {}, onValid: function () {},
onInvalid: function () {}, onInvalid: function () {},
onChange: function () {}, onChange: function () {},
@ -45,7 +48,7 @@ Formsy.Form = React.createClass({
}, },
childContextTypes: { childContextTypes: {
formsy: React.PropTypes.object formsy: PropTypes.object
}, },
getChildContext: function () { getChildContext: function () {
return { return {
@ -54,9 +57,9 @@ Formsy.Form = React.createClass({
detachFromForm: this.detachFromForm, detachFromForm: this.detachFromForm,
validate: this.validate, validate: this.validate,
isFormDisabled: this.isFormDisabled, isFormDisabled: this.isFormDisabled,
isValidValue: function (component, value) { isValidValue: (component, value) => {
return this.runValidation(component, value).isValid; return this.runValidation(component, value).isValid;
}.bind(this) }
} }
} }
}, },
@ -64,8 +67,7 @@ Formsy.Form = React.createClass({
// Add a map to store the inputs of the form, a model to store // Add a map to store the inputs of the form, a model to store
// the values of the form and register child inputs // the values of the form and register child inputs
componentWillMount: function () { componentWillMount: function () {
this.inputs = {}; this.inputs = [];
this.model = {};
}, },
componentDidMount: function () { componentDidMount: function () {
@ -73,21 +75,19 @@ Formsy.Form = React.createClass({
}, },
componentWillUpdate: function () { componentWillUpdate: function () {
// Keep a reference to input names before form updates,
// Keep a reference to input keys before form updates,
// to check if inputs has changed after render // to check if inputs has changed after render
this.prevInputKeys = Object.keys(this.inputs); this.prevInputNames = this.inputs.map(component => component.props.name);
}, },
componentDidUpdate: function () { componentDidUpdate: function () {
if (this.props.validationErrors) { if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
this.setInputValidationErrors(this.props.validationErrors); this.setInputValidationErrors(this.props.validationErrors);
} }
var newInputKeys = Object.keys(this.inputs); var newInputNames = this.inputs.map(component => component.props.name);
if (utils.arraysDiffer(this.prevInputKeys, newInputKeys)) { if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
this.validateForm(); this.validateForm();
} }
@ -108,62 +108,59 @@ Formsy.Form = React.createClass({
// If any inputs have not been touched yet this will make them dirty // If any inputs have not been touched yet this will make them dirty
// so validation becomes visible (if based on isPristine) // so validation becomes visible (if based on isPristine)
this.setFormPristine(false); this.setFormPristine(false);
this.updateModel(); var model = this.getModel();
var model = this.mapModel();
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError); 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); this.state.isValid ? this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError) : this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
}, },
mapModel: function () { mapModel: function (model) {
if (this.props.mapping) { if (this.props.mapping) {
return this.props.mapping(this.model) return this.props.mapping(model)
} else { } else {
return formDataToObject(Object.keys(this.model).reduce(function (mappedModel, key) { return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
var keyArray = key.split('.'); var keyArray = key.split('.');
var base = mappedModel; var base = mappedModel;
while (keyArray.length) { while (keyArray.length) {
var currentKey = keyArray.shift(); var currentKey = keyArray.shift();
base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : this.model[key]); base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : model[key]);
} }
return mappedModel; return mappedModel;
}.bind(this), {})); }, {}));
} }
}, },
// Goes through all registered components and getModel: function () {
// updates the model values var currentValues = this.getCurrentValues();
updateModel: function () { return this.mapModel(currentValues);
Object.keys(this.inputs).forEach(function (name) {
var component = this.inputs[name];
this.model[name] = component.state._value;
}.bind(this));
}, },
// Reset each key in the model to the original / initial / specified value // Reset each key in the model to the original / initial / specified value
resetModel: function (data) { resetModel: function (data) {
Object.keys(this.inputs).forEach(function (name) { this.inputs.forEach(component => {
if (data && data[name]) { var name = component.props.name;
this.inputs[name].setValue(data[name]); if (data && data.hasOwnProperty(name)) {
component.setValue(data[name]);
} else { } else {
this.inputs[name].resetValue(); component.resetValue();
} }
}.bind(this)); });
this.validateForm(); this.validateForm();
}, },
setInputValidationErrors: function (errors) { setInputValidationErrors: function (errors) {
Object.keys(this.inputs).forEach(function (name, index) { this.inputs.forEach(component => {
var component = this.inputs[name]; var name = component.props.name;
var args = [{ var args = [{
_isValid: !(name in errors), _isValid: !(name in errors),
_validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name] _validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}]; }];
component.setState.apply(component, args); component.setState.apply(component, args);
}.bind(this)); });
}, },
// Checks if the values have changed from their initial value // Checks if the values have changed from their initial value
@ -172,9 +169,8 @@ Formsy.Form = React.createClass({
}, },
getPristineValues: function() { getPristineValues: function() {
var inputs = this.inputs; return this.inputs.reduce((data, component) => {
return Object.keys(inputs).reduce(function (data, name) { var name = component.props.name;
var component = inputs[name];
data[name] = component.props.value; data[name] = component.props.value;
return data; return data;
}, {}); }, {});
@ -184,51 +180,18 @@ Formsy.Form = React.createClass({
// stored in the inputs map. Change their state to invalid // stored in the inputs map. Change their state to invalid
// and set the serverError message // and set the serverError message
updateInputsWithError: function (errors) { updateInputsWithError: function (errors) {
Object.keys(errors).forEach(function (name, index) { Object.keys(errors).forEach((name, index) => {
var component = this.inputs[name]; var component = utils.find(this.inputs, component => component.props.name === name);
if (!component) { 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)); 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 = [{ var args = [{
_isValid: this.props.preventExternalInvalidation || false, _isValid: this.props.preventExternalInvalidation || false,
_externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name] _externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
}]; }];
component.setState.apply(component, args); component.setState.apply(component, args);
}.bind(this)); });
},
// Traverse the children and children of children to find
// all inputs by checking the name prop. Maybe do a better
// check here
traverseChildrenAndRegisterInputs: function (children) {
if (typeof children !== 'object' || children === null) {
return children;
}
return React.Children.map(children, function (child) {
if (typeof child !== 'object' || child === null) {
return child;
}
if (child.props && child.props.name) {
return React.cloneElement(child, {
_attachToForm: this.attachToForm,
_detachFromForm: this.detachFromForm,
_validate: this.validate,
_isFormDisabled: this.isFormDisabled,
_isValidValue: function (component, value) {
return this.runValidation(component, value).isValid;
}.bind(this)
}, child.props && child.props.children);
} else {
return React.cloneElement(child, {}, this.traverseChildrenAndRegisterInputs(child.props && child.props.children));
}
}, this);
}, },
isFormDisabled: function () { isFormDisabled: function () {
@ -236,30 +199,26 @@ Formsy.Form = React.createClass({
}, },
getCurrentValues: function () { getCurrentValues: function () {
return Object.keys(this.inputs).reduce(function (data, name) { return this.inputs.reduce((data, component) => {
var component = this.inputs[name]; var name = component.props.name;
data[name] = component.state._value; data[name] = component.state._value;
return data; return data;
}.bind(this), {}); }, {});
}, },
setFormPristine: function (isPristine) { setFormPristine: function (isPristine) {
var inputs = this.inputs;
var inputKeys = Object.keys(inputs);
this.setState({ this.setState({
_formSubmitted: !isPristine _formSubmitted: !isPristine
}) });
// Iterate through each component and set it as pristine // Iterate through each component and set it as pristine
// or "dirty". // or "dirty".
inputKeys.forEach(function (name, index) { this.inputs.forEach((component, index) => {
var component = inputs[name];
component.setState({ component.setState({
_formSubmitted: !isPristine, _formSubmitted: !isPristine,
_isPristine: isPristine _isPristine: isPristine
}); });
}.bind(this)); });
}, },
// Use the binded values and the actual input value to // Use the binded values and the actual input value to
@ -309,7 +268,7 @@ Formsy.Form = React.createClass({
error: (function () { error: (function () {
if (isValid && !isRequired) { if (isValid && !isRequired) {
return []; return emptyArray;
} }
if (validationResults.errors.length) { if (validationResults.errors.length) {
@ -393,16 +352,13 @@ Formsy.Form = React.createClass({
// Validate the form by going through all child input components // Validate the form by going through all child input components
// and check their state // and check their state
validateForm: function () { validateForm: function () {
var allIsValid;
var inputs = this.inputs;
var inputKeys = Object.keys(inputs);
// We need a callback as we are validating all inputs again. This will // We need a callback as we are validating all inputs again. This will
// run when the last component has set its state // run when the last component has set its state
var onValidationComplete = function () { var onValidationComplete = function () {
allIsValid = inputKeys.every(function (name) { var allIsValid = this.inputs.every(component => {
return inputs[name].state._isValid; return component.state._isValid;
}.bind(this)); });
this.setState({ this.setState({
isValid: allIsValid isValid: allIsValid
@ -423,8 +379,7 @@ Formsy.Form = React.createClass({
// Run validation again in case affected by other inputs. The // Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback // last component validated will run the onValidationComplete callback
inputKeys.forEach(function (name, index) { this.inputs.forEach((component, index) => {
var component = inputs[name];
var validation = this.runValidation(component); var validation = this.runValidation(component);
if (validation.isValid && component.state._externalError) { if (validation.isValid && component.state._externalError) {
validation.isValid = false; validation.isValid = false;
@ -434,12 +389,12 @@ Formsy.Form = React.createClass({
_isRequired: validation.isRequired, _isRequired: validation.isRequired,
_validationError: validation.error, _validationError: validation.error,
_externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null _externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null
}, index === inputKeys.length - 1 ? onValidationComplete : null); }, index === this.inputs.length - 1 ? onValidationComplete : null);
}.bind(this)); });
// If there are no inputs, set state where form is ready to trigger // If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later // change event. New inputs might be added later
if (!inputKeys.length && this.isMounted()) { if (!this.inputs.length) {
this.setState({ this.setState({
canChange: true canChange: true
}); });
@ -449,23 +404,46 @@ Formsy.Form = React.createClass({
// Method put on each input component to register // Method put on each input component to register
// itself to the form // itself to the form
attachToForm: function (component) { attachToForm: function (component) {
this.inputs[component.props.name] = component;
this.model[component.props.name] = component.state._value; if (this.inputs.indexOf(component) === -1) {
this.inputs.push(component);
}
this.validate(component); this.validate(component);
}, },
// Method put on each input component to unregister // Method put on each input component to unregister
// itself from the form // itself from the form
detachFromForm: function (component) { detachFromForm: function (component) {
delete this.inputs[component.props.name]; var componentPos = this.inputs.indexOf(component);
delete this.model[component.props.name];
if (componentPos !== -1) {
this.inputs = this.inputs.slice(0, componentPos)
.concat(this.inputs.slice(componentPos + 1));
}
this.validateForm();
}, },
render: function () { render: function () {
var {
mapping,
validationErrors,
onSubmit,
onValid,
onValidSubmit,
onInvalid,
onInvalidSubmit,
onChange,
reset,
preventExternalInvalidation,
onSuccess,
onError,
...nonFormsyProps
} = this.props;
return ( return (
<form {...this.props} onSubmit={this.submit}> <form {...nonFormsyProps} onSubmit={this.submit}>
{this.props.children} {this.props.children}
{/*this.traverseChildrenAndRegisterInputs(this.props.children)*/}
</form> </form>
); );

View File

@ -30,12 +30,24 @@ module.exports = {
isSame: function (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)) { } else if (Array.isArray(a) && Array.isArray(b)) {
return !this.arraysDiffer(a, b); return !this.arraysDiffer(a, b);
} else if (typeof a === 'function') {
return a.toString() === b.toString();
} else if (typeof a === 'object' && a !== null && b !== null) { } else if (typeof a === 'object' && a !== null && b !== null) {
return !this.objectsDiffer(a, b); return !this.objectsDiffer(a, b);
} }
return a === b; return a === b;
},
find: function (collection, fn) {
for (var i = 0, l = collection.length; i < l; i++) {
var item = collection[i];
if (fn(item)) {
return item;
}
}
return null;
} }
}; };

View File

@ -1,22 +1,22 @@
require('babel/register'); import path from 'path';
import testrunner from 'nodeunit/lib/reporters/default.js';
var path = require('path'); import {jsdom} from 'jsdom';
var jsdom = require('jsdom').jsdom;
var jasmine = require('jasmine-node');
global.document = jsdom(); global.document = jsdom();
global.window = document.defaultView; global.window = document.defaultView;
global.navigator = global.window.navigator; global.navigator = global.window.navigator;
var jasmineOptions = { testrunner.run(['tests'], {
specFolders: [path.resolve(__dirname, 'specs')], "error_prefix": "\u001B[31m",
isVerbose: true, "error_suffix": "\u001B[39m",
showColors: true, "ok_prefix": "\u001B[32m",
teamcity: false, "ok_suffix": "\u001B[39m",
useRequireJs: false, "bold_prefix": "\u001B[1m",
regExpSpec: /spec\.js$/, "bold_suffix": "\u001B[22m",
junitreport: true, "assertion_prefix": "\u001B[35m",
includeStackTrace: true "assertion_suffix": "\u001B[39m"
}; }, function(err) {
if (err) {
jasmine.executeSpecsInFolder(jasmineOptions); process.exit(1);
}
});

View File

@ -1,13 +1,15 @@
import React from 'react'; import React from 'react';
import TestUtils from 'react-addons-test-utils'; import TestUtils from 'react-addons-test-utils';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import sinon from 'sinon';
import Formsy from './..'; import Formsy from './..';
import TestInput, { customizeInput } from './utils/TestInput'; import TestInput, { InputFactory } from './utils/TestInput';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
describe('Element', function () { export default {
it('should return passed and setValue() value when using getValue()', function () { 'should return passed and setValue() value when using getValue()': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy.Form> <Formsy.Form>
@ -16,16 +18,18 @@ describe('Element', function () {
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
expect(input.value).toBe('foo'); test.equal(input.value, 'foo');
TestUtils.Simulate.change(input, {target: {value: 'foobar'}}); TestUtils.Simulate.change(input, {target: {value: 'foobar'}});
expect(input.value).toBe('foobar'); test.equal(input.value, 'foobar');
}); test.done();
it('should set back to pristine value when running reset', function () { },
'should set back to pristine value when running reset': function (test) {
let reset = null; let reset = null;
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
reset = this.resetValue; reset = this.resetValue;
} }
@ -39,14 +43,16 @@ describe('Element', function () {
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
TestUtils.Simulate.change(input, {target: {value: 'foobar'}}); TestUtils.Simulate.change(input, {target: {value: 'foobar'}});
reset(); reset();
expect(input.value).toBe('foo'); test.equal(input.value, 'foo');
}); test.done();
it('should return error message passed when calling getErrorMessage()', function () { },
'should return error message passed when calling getErrorMessage()': function (test) {
let getErrorMessage = null; let getErrorMessage = null;
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
getErrorMessage = this.getErrorMessage; getErrorMessage = this.getErrorMessage;
} }
@ -57,14 +63,16 @@ describe('Element', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(getErrorMessage()).toBe('Has to be email'); test.equal(getErrorMessage(), 'Has to be email');
}); test.done();
it('should return true or false when calling isValid() depending on valid state', function () { },
'should return true or false when calling isValid() depending on valid state': function (test) {
let isValid = null; let isValid = null;
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
isValid = this.isValid; isValid = this.isValid;
} }
@ -75,17 +83,19 @@ describe('Element', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(isValid()).toBe(false); test.equal(isValid(), false);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
TestUtils.Simulate.change(input, {target: {value: 'foo@foo.com'}}); TestUtils.Simulate.change(input, {target: {value: 'foo@foo.com'}});
expect(isValid()).toBe(true); test.equal(isValid(), true);
}); test.done();
it('should return true or false when calling isRequired() depending on passed required attribute', function () { },
'should return true or false when calling isRequired() depending on passed required attribute': function (test) {
const isRequireds = []; const isRequireds = [];
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
isRequireds.push(this.isRequired); isRequireds.push(this.isRequired);
} }
@ -98,16 +108,18 @@ describe('Element', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(isRequireds[0]()).toBe(false); test.equal(isRequireds[0](), false);
expect(isRequireds[1]()).toBe(true); test.equal(isRequireds[1](), true);
expect(isRequireds[2]()).toBe(true); test.equal(isRequireds[2](), true);
}); test.done();
it('should return true or false when calling showRequired() depending on input being empty and required is passed, or not', function () { },
'should return true or false when calling showRequired() depending on input being empty and required is passed, or not': function (test) {
const showRequireds = []; const showRequireds = [];
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
showRequireds.push(this.showRequired); showRequireds.push(this.showRequired);
} }
@ -120,16 +132,18 @@ describe('Element', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(showRequireds[0]()).toBe(false); test.equal(showRequireds[0](), false);
expect(showRequireds[1]()).toBe(true); test.equal(showRequireds[1](), true);
expect(showRequireds[2]()).toBe(false); test.equal(showRequireds[2](), false);
}); test.done();
it('should return true or false when calling isPristine() depending on input has been "touched" or not', function () { },
'should return true or false when calling isPristine() depending on input has been "touched" or not': function (test) {
let isPristine = null; let isPristine = null;
const Input = customizeInput({ const Input = InputFactory({
componentDidMount() { componentDidMount() {
isPristine = this.isPristine; isPristine = this.isPristine;
} }
@ -140,14 +154,16 @@ describe('Element', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(isPristine()).toBe(true); test.equal(isPristine(), true);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
TestUtils.Simulate.change(input, {target: {value: 'foo'}}); TestUtils.Simulate.change(input, {target: {value: 'foo'}});
expect(isPristine()).toBe(false); test.equal(isPristine(), false);
}); test.done();
it('should allow an undefined value to be updated to a value', function (done) { },
'should allow an undefined value to be updated to a value': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
getInitialState() { getInitialState() {
@ -171,13 +187,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
form.changeValue(); form.changeValue();
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
immediate(() => { immediate(() => {
expect(input.value).toBe('foo'); test.equal(input.value, 'foo');
done(); test.done();
}); });
}); },
it('should be able to test a values validity', function () { 'should be able to test a values validity': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -191,12 +207,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.isValidValue('foo@bar.com')).toBe(true); test.equal(input.isValidValue('foo@bar.com'), true);
expect(input.isValidValue('foo@bar')).toBe(false); test.equal(input.isValidValue('foo@bar'), false);
test.done();
}); },
it('should be able to use an object as validations property', function () { 'should be able to use an object as validations property': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -212,12 +229,14 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.isValidValue('foo@bar.com')).toBe(true); test.equal(input.isValidValue('foo@bar.com'), true);
expect(input.isValidValue('foo@bar')).toBe(false); test.equal(input.isValidValue('foo@bar'), false);
}); test.done();
it('should be able to pass complex values to a validation rule', function () { },
'should be able to pass complex values to a validation rule': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -233,14 +252,16 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(inputComponent.isValid()).toBe(true); test.equal(inputComponent.isValid(), true);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
TestUtils.Simulate.change(input, {target: {value: 'bar'}}); TestUtils.Simulate.change(input, {target: {value: 'bar'}});
expect(inputComponent.isValid()).toBe(false); test.equal(inputComponent.isValid(), false);
}); test.done();
it('should be able to run a function to validate', function () { },
'should be able to run a function to validate': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
customValidationA(values, value) { customValidationA(values, value) {
@ -265,16 +286,41 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput); const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
expect(inputComponent[0].isValid()).toBe(true); test.equal(inputComponent[0].isValid(), true);
expect(inputComponent[1].isValid()).toBe(true); test.equal(inputComponent[1].isValid(), true);
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT'); const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT');
TestUtils.Simulate.change(input[0], {target: {value: 'bar'}}); TestUtils.Simulate.change(input[0], {target: {value: 'bar'}});
expect(inputComponent[0].isValid()).toBe(false); test.equal(inputComponent[0].isValid(), false);
expect(inputComponent[1].isValid()).toBe(false); test.equal(inputComponent[1].isValid(), false);
test.done();
},
'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) {
const TestForm = React.createClass({
render() {
return (
<Formsy.Form validationErrors={{}}>
<TestInput name="A" validations={{
isEmail: true
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
</Formsy.Form>
);
}
}); });
const form = TestUtils.renderIntoDocument(<TestForm/>);
it('should override all error messages with error messages passed by form', function () { const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.getErrorMessage(), 'bar3');
test.done();
},
'should override all error messages with error messages passed by form': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -290,11 +336,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(inputComponent.getErrorMessage()).toBe('bar'); test.equal(inputComponent.getErrorMessage(), 'bar');
}); test.done();
it('should override validation rules with required rules', function () { },
'should override validation rules with required rules': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -318,11 +366,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(inputComponent.getErrorMessage()).toBe('bar3'); test.equal(inputComponent.getErrorMessage(), 'bar3');
}); test.done();
it('should fall back to default error message when non exist in validationErrors map', function () { },
'should fall back to default error message when non exist in validationErrors map': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -343,11 +393,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(inputComponent.getErrorMessage()).toBe('bar'); test.equal(inputComponent.getErrorMessage(), 'bar');
}); test.done();
it('should not be valid if it is required and required rule is true', function () { },
'should not be valid if it is required and required rule is true': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -363,11 +415,13 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(inputComponent.isValid()).toBe(false); test.equal(inputComponent.isValid(), false);
}); test.done();
it('should handle objects and arrays as values', function () { },
'should handle objects and arrays as values': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
getInitialState() { getInitialState() {
@ -393,12 +447,14 @@ it('should allow an undefined value to be updated to a value', function (done) {
}); });
const inputs = TestUtils.scryRenderedComponentsWithType(form, TestInput); const inputs = TestUtils.scryRenderedComponentsWithType(form, TestInput);
expect(inputs[0].getValue()).toEqual({foo: 'foo'}); test.deepEqual(inputs[0].getValue(), {foo: 'foo'});
expect(inputs[1].getValue()).toEqual(['bar']); test.deepEqual(inputs[1].getValue(), ['bar']);
}); test.done();
it('should handle isFormDisabled with dynamic inputs', function () { },
'should handle isFormDisabled with dynamic inputs': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
getInitialState() { getInitialState() {
@ -425,17 +481,19 @@ it('should allow an undefined value to be updated to a value', function (done) {
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
expect(input.isFormDisabled()).toBe(true); test.equal(input.isFormDisabled(), true);
form.flip(); form.flip();
expect(input.isFormDisabled()).toBe(false); test.equal(input.isFormDisabled(), false);
}); test.done();
it('should allow for dot notation in name which maps to a deep object', function () { },
'should allow for dot notation in name which maps to a deep object': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
onSubmit(model) { onSubmit(model) {
expect(model).toEqual({foo: {bar: 'foo', test: 'test'}}); test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}});
}, },
render() { render() {
return ( return (
@ -448,16 +506,20 @@ it('should allow an undefined value to be updated to a value', function (done) {
}); });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
}); test.done();
it('should allow for application/x-www-form-urlencoded syntax and convert to object', function () { },
'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
onSubmit(model) { onSubmit(model) {
expect(model).toEqual({foo: ['foo', 'bar']}); test.deepEqual(model, {foo: ['foo', 'bar']});
}, },
render() { render() {
return ( return (
@ -470,9 +532,37 @@ it('should allow an undefined value to be updated to a value', function (done) {
}); });
const form = TestUtils.renderIntoDocument(<TestForm/>); const form = TestUtils.renderIntoDocument(<TestForm/>);
test.expect(1);
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
test.done();
},
'input should rendered once with PureRenderMixin': function (test) {
var renderSpy = sinon.spy();
const Input = InputFactory({
mixins: [Formsy.Mixin, PureRenderMixin],
render() {
renderSpy();
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
}
}); });
}); const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<Input name="foo" value="foo"/>
</Formsy.Form>
);
test.equal(renderSpy.calledOnce, true);
test.done();
}
};

748
tests/Formsy-spec.js Executable file
View File

@ -0,0 +1,748 @@
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import TestInput from './utils/TestInput';
import TestInputHoc from './utils/TestInputHoc';
import immediate from './utils/immediate';
import sinon from 'sinon';
export default {
'Setting up a form': {
'should expose the users DOM node through an innerRef prop': function (test) {
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = form.name;
test.equal(input.methodOnWrappedInstance('foo'), 'foo');
test.done();
},
'should render a form into the document': function (test) {
const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM');
test.done();
},
'should set a class name if passed': function (test) {
const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
test.equal(ReactDOM.findDOMNode(form).className, 'foo');
test.done();
},
'should allow for null/undefined children': function (test) {
let model = null;
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
<h1>Test</h1>
{ null }
{ undefined }
<TestInput name="name" value={ 'foo' } />
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
immediate(() => {
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.deepEqual(model, {name: 'foo'});
test.done();
});
},
'should allow for inputs being added dynamically': function (test) {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
setTimeout(() => {
inputs.push(<TestInput name="test" value="" key={inputs.length}/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.ok('test' in model);
test.done();
});
});
}, 10);
},
'should allow dynamically added inputs to update the form-model': function (test) {
const inputs = [];
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{inputs}
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait before adding the input
immediate(() => {
inputs.push(<TestInput name="test" key={inputs.length}/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'foo'}});
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.equal(model.test, 'foo');
test.done();
});
});
});
},
'should allow a dynamically updated input to update the form-model': function (test) {
let forceUpdate = null;
let model = null;
const TestForm = React.createClass({
componentWillMount() {
forceUpdate = this.forceUpdate.bind(this);
},
render() {
const input = <TestInput name="test" value={this.props.value} />;
return (
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
{input}
</Formsy.Form>);
}
});
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
// Wait before changing the input
immediate(() => {
form = TestUtils.renderIntoDocument(<TestForm value="bar"/>);
forceUpdate(() => {
// Wait for next event loop, as that does the form
immediate(() => {
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.equal(model.test, 'bar');
test.done();
});
});
});
}
},
'validations': {
'should run when the input changes': function (test) {
const runRule = sinon.spy();
const notRunRule = sinon.spy();
Formsy.addValidationRule('runRule', runRule);
Formsy.addValidationRule('notRunRule', notRunRule);
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<TestInput name="one" validations="runRule" value="foo"/>
</Formsy.Form>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
test.equal(runRule.calledWith({one: 'bar'}, 'bar', true), true);
test.equal(notRunRule.called, false);
test.done();
},
'should allow the validation to be changed': function (test) {
const ruleA = sinon.spy();
const ruleB = sinon.spy();
Formsy.addValidationRule('ruleA', ruleA);
Formsy.addValidationRule('ruleB', ruleB);
class TestForm extends React.Component {
constructor(props) {
super(props);
this.state = {rule: 'ruleA'};
}
changeRule() {
this.setState({
rule: 'ruleB'
});
}
render() {
return (
<Formsy.Form>
<TestInput name="one" validations={this.state.rule} value="foo"/>
</Formsy.Form>
);
}
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeRule();
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
test.equal(ruleB.calledWith({one: 'bar'}, 'bar', true), true);
test.done();
},
'should invalidate a form if dynamically inserted input is invalid': function (test) {
const isInValidSpy = sinon.spy();
class TestForm extends React.Component {
constructor(props) {
super(props);
this.state = {showSecondInput: false};
}
addInput() {
this.setState({
showSecondInput: true
});
}
render() {
return (
<Formsy.Form ref="formsy" onInvalid={isInValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{
this.state.showSecondInput ?
<TestInput name="two" validations="isEmail" value="foo@bar"/>
:
null
}
</Formsy.Form>
);
}
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
test.equal(form.refs.formsy.state.isValid, true);
form.addInput();
immediate(() => {
test.equal(isInValidSpy.called, true);
test.done();
});
},
'should validate a form when removing an invalid input': function (test) {
const isValidSpy = sinon.spy();
class TestForm extends React.Component {
constructor(props) {
super(props);
this.state = {showSecondInput: true};
}
removeInput() {
this.setState({
showSecondInput: false
});
}
render() {
return (
<Formsy.Form ref="formsy" onValid={isValidSpy}>
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
{
this.state.showSecondInput ?
<TestInput name="two" validations="isEmail" value="foo@bar"/>
:
null
}
</Formsy.Form>
);
}
}
const form = TestUtils.renderIntoDocument(<TestForm/>);
test.equal(form.refs.formsy.state.isValid, false);
form.removeInput();
immediate(() => {
test.equal(isValidSpy.called, true);
test.done();
});
},
'runs multiple validations': function (test) {
const ruleA = sinon.spy();
const ruleB = sinon.spy();
Formsy.addValidationRule('ruleA', ruleA);
Formsy.addValidationRule('ruleB', ruleB);
const form = TestUtils.renderIntoDocument(
<Formsy.Form>
<TestInput name="one" validations="ruleA,ruleB" value="foo" />
</Formsy.Form>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
test.equal(ruleA.calledWith({one: 'bar'}, 'bar', true), true);
test.equal(ruleB.calledWith({one: 'bar'}, 'bar', true), true);
test.done();
}
},
'should not trigger onChange when form is mounted': function (test) {
const hasChanged = sinon.spy();
const TestForm = React.createClass({
render() {
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
}
});
TestUtils.renderIntoDocument(<TestForm/>);
test.equal(hasChanged.called, false);
test.done();
},
'should trigger onChange once when form element is changed': function (test) {
const hasChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasChanged}>
<TestInput name="foo"/>
</Formsy.Form>
);
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
test.equal(hasChanged.calledOnce, true);
test.done();
},
'should trigger onChange once when new input is added to form': function (test) {
const hasChanged = sinon.spy();
const TestForm = React.createClass({
getInitialState() {
return {
showInput: false
};
},
addInput() {
this.setState({
showInput: true
})
},
render() {
return (
<Formsy.Form onChange={hasChanged}>
{
this.state.showInput ?
<TestInput name="test"/>
:
null
}
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.addInput();
immediate(() => {
test.equal(hasChanged.calledOnce, true);
test.done();
});
},
'Update a form': {
'should allow elements to check if the form is disabled': function (test) {
const TestForm = React.createClass({
getInitialState() { return { disabled: true }; },
enableForm() { this.setState({ disabled: false }); },
render() {
return (
<Formsy.Form disabled={this.state.disabled}>
<TestInput name="foo"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(input.isFormDisabled(), true);
form.enableForm();
immediate(() => {
test.equal(input.isFormDisabled(), false);
test.done();
});
},
'should be possible to pass error state of elements by changing an errors attribute': function (test) {
const TestForm = React.createClass({
getInitialState() { return { validationErrors: { foo: 'bar' } }; },
onChange(values) {
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
},
render() {
return (
<Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
<TestInput name="foo"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
// Wait for update
immediate(() => {
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(input.getErrorMessage(), 'bar');
input.setValue('gotValue');
// Wait for update
immediate(() => {
test.equal(input.getErrorMessage(), null);
test.done();
});
});
},
'should trigger an onValidSubmit when submitting a valid form': function (test) {
let isCalled = sinon.spy();
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onValidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
test.equal(isCalled.called,true);
test.done();
},
'should trigger an onInvalidSubmit when submitting an invalid form': function (test) {
let isCalled = sinon.spy();
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onInvalidSubmit={isCalled}>
<TestInput name="foo" validations="isEmail" value="foo@bar"/>
</Formsy.Form>);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
test.equal(isCalled.called, true);
test.done();
}
},
'value === false': {
'should call onSubmit correctly': function (test) {
const onSubmit = sinon.spy();
const TestForm = React.createClass({
render() {
return (
<Formsy.Form onSubmit={onSubmit}>
<TestInput name="foo" value={false} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.equal(onSubmit.calledWith({foo: false}), true);
test.done();
},
'should allow dynamic changes to false': function (test) {
const onSubmit = sinon.spy();
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
changeValue() {
this.setState({
value: false
});
},
render() {
return (
<Formsy.Form onSubmit={onSubmit}>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
form.changeValue();
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.equal(onSubmit.calledWith({foo: false}), true);
test.done();
},
'should say the form is submitted': function (test) {
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" value={true} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(input.isFormSubmitted(), false);
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
test.equal(input.isFormSubmitted(), true);
test.done();
},
'should be able to reset the form to its pristine state': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
changeValue() {
this.setState({
value: false
});
},
render() {
return (
<Formsy.Form>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
test.equal(input.getValue(), true);
form.changeValue();
test.equal(input.getValue(), false);
formsyForm.reset();
test.equal(input.getValue(), true);
test.done();
},
'should be able to reset the form using custom data': function (test) {
const TestForm = React.createClass({
getInitialState() {
return {
value: true
};
},
changeValue() {
this.setState({
value: false
});
},
render() {
return (
<Formsy.Form>
<TestInput name="foo" value={this.state.value} type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
test.equal(input.getValue(), true);
form.changeValue();
test.equal(input.getValue(), false);
formsyForm.reset({
foo: 'bar'
});
test.equal(input.getValue(), 'bar');
test.done();
}
},
'should be able to reset the form to empty values': function (test) {
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" value="42" type="checkbox" />
<button type="submit">Save</button>
</Formsy.Form>
);
}
});
const form = TestUtils.renderIntoDocument(<TestForm/>);
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
formsyForm.reset({
foo: ''
});
test.equal(input.getValue(), '');
test.done();
},
'.isChanged()': {
'initially returns false': function (test) {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
);
test.equal(form.isChanged(), false);
test.equal(hasOnChanged.called, false);
test.done();
},
'returns true when changed': function (test) {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
test.equal(form.isChanged(), true);
test.equal(hasOnChanged.calledWith({one: 'bar'}), true);
test.done();
},
'returns false if changes are undone': function (test) {
const hasOnChanged = sinon.spy();
const form = TestUtils.renderIntoDocument(
<Formsy.Form onChange={hasOnChanged}>
<TestInput name="one" value="foo" />
</Formsy.Form>
);
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
test.equal(hasOnChanged.calledWith({one: 'bar'}, true), true);
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'foo'}});
test.equal(form.isChanged(), false);
test.equal(hasOnChanged.calledWith({one: 'foo'}, false), true);
test.done();
}
}
};

View File

@ -0,0 +1,80 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import immediate from './utils/immediate';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass when the value is equal': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail when the value is not equal': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="fo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a string is only latin letters': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string with numbers': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,98 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a string is only latin letters': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string with numbers': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a non alpha and number symbols': function (test) {
const value = '!@#$%^&*()';
const form = TestUtils.renderIntoDocument(<TestForm inputValue={value}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with "foo"': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with "foo@foo.com"': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo@foo.com"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with non-empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with a zero': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with a string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a zero': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
}
};

125
tests/Rules-isFloat-spec.js Normal file
View File

@ -0,0 +1,125 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with a number as string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail string with digits': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an int': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a float': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a float in science notation': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a zero': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
}
};

125
tests/Rules-isInt-spec.js Normal file
View File

@ -0,0 +1,125 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with a number as string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail string with digits': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an int': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a float': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with a float in science notation': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a zero': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
}
};

View File

@ -0,0 +1,177 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'isLength:3': {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string too small': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="hi"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with a string too long': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="hi ho happ"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with matching length': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="foo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={123}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
},
'isLength:0': {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string too small': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue="hi"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should fail with a string too long': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue="hi ho happ"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with matching length': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={123}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
}
};

View File

@ -0,0 +1,124 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with an unempty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with a number as string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number as string with not digits': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an int': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a float': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a float in science notation': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a zero': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
}
};

88
tests/Rules-isUrl-spec.js Normal file
View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with "foo"': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with "https://www.google.com/"': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="https://www.google.com/"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
}
};

View File

@ -0,0 +1,88 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a 1 word': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with 2 words': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup dude"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a string with numbers': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup 42"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,97 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass when a string\'s length is smaller': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="hi"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass when a string\'s length is equal': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="bar"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail when a string\'s length is bigger': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foobar"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
};

View File

@ -0,0 +1,150 @@
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import Formsy from './..';
import { InputFactory } from './utils/TestInput';
const TestInput = InputFactory({
render() {
return <input value={this.getValue()} readOnly/>;
}
});
const TestForm = React.createClass({
render() {
return (
<Formsy.Form>
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
</Formsy.Form>
);
}
});
export default {
'minLength:3': {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass when a string\'s length is bigger': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue="myValue"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail when a string\'s length is smaller': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue="my"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
},
'should pass with empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
},
'minLength:0': {
'should pass with a default value': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass when a string\'s length is bigger': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue="myValue"/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with empty string': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue=""/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with an undefined': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={undefined}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should pass with a null': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={null}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), true);
test.done();
},
'should fail with a number': function (test) {
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={42}/>);
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
test.equal(inputComponent.isValid(), false);
test.done();
}
}
};

37
tests/Utils-spec.js Normal file
View File

@ -0,0 +1,37 @@
import utils from './../src/utils.js';
export default {
'should check equality of objects and arrays': function (test) {
const objA = { foo: 'bar' };
const objB = { foo: 'bar' };
const objC = [{ foo: ['bar'] }];
const objD = [{ foo: ['bar'] }];
const objE = undefined;
const objF = undefined;
const objG = null;
const objH = null;
test.equal(utils.isSame(objA, objB), true);
test.equal(utils.isSame(objC, objD), true);
test.equal(utils.isSame(objA, objD), false);
test.equal(utils.isSame(objE, objF), true);
test.equal(utils.isSame(objA, objF), false);
test.equal(utils.isSame(objE, objA), false);
test.equal(utils.isSame(objG, objH), true);
test.equal(utils.isSame(objA, objH), false);
test.equal(utils.isSame(objC, objH), false);
test.equal(utils.isSame(objG, objA), false);
test.equal(utils.isSame(() => {}, () => {}), true);
test.equal(utils.isSame(objA, () => {}), false);
test.equal(utils.isSame(() => {}, objA), false);
test.done();
}
};

View File

@ -2,12 +2,13 @@ import React from 'react';
import TestUtils from 'react-addons-test-utils'; import TestUtils from 'react-addons-test-utils';
import Formsy from './..'; import Formsy from './..';
import TestInput, { customizeInput } from './utils/TestInput'; import TestInput, {InputFactory} from './utils/TestInput';
import immediate from './utils/immediate'; import immediate from './utils/immediate';
import sinon from 'sinon';
describe('Validation', function () { export default {
it('should reset only changed form element when external error is passed', function (done) { 'should reset only changed form element when external error is passed': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}> <Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
@ -20,19 +21,19 @@ describe('Validation', function () {
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput); const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput);
form.submit(); form.submit();
expect(inputComponents[0].isValid()).toBe(false); test.equal(inputComponents[0].isValid(), false);
expect(inputComponents[1].isValid()).toBe(false); test.equal(inputComponents[1].isValid(), false);
TestUtils.Simulate.change(input, {target: {value: 'bar'}}); TestUtils.Simulate.change(input, {target: {value: 'bar'}});
immediate(() => { immediate(() => {
expect(inputComponents[0].isValid()).toBe(true); test.equal(inputComponents[0].isValid(), true);
expect(inputComponents[1].isValid()).toBe(false); test.equal(inputComponents[1].isValid(), false);
done(); test.done();
}); });
}); },
it('should let normal validation take over when component with external error is changed', function (done) { 'should let normal validation take over when component with external error is changed': function (test) {
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}> <Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
@ -44,21 +45,21 @@ describe('Validation', function () {
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput); const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
form.submit(); form.submit();
expect(inputComponent.isValid()).toBe(false); test.equal(inputComponent.isValid(), false);
TestUtils.Simulate.change(input, {target: {value: 'bar'}}); TestUtils.Simulate.change(input, {target: {value: 'bar'}});
immediate(() => { immediate(() => {
expect(inputComponent.getValue()).toBe('bar'); test.equal(inputComponent.getValue(), 'bar');
expect(inputComponent.isValid()).toBe(false); test.equal(inputComponent.isValid(), false);
done(); test.done();
}); });
}); },
it('should trigger an onValid handler, if passed, when form is valid', function () { 'should trigger an onValid handler, if passed, when form is valid': function (test) {
const onValid = jasmine.createSpy('valid'); const onValid = sinon.spy();
const onInvalid = jasmine.createSpy('invalid'); const onInvalid = sinon.spy();
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}> <Formsy.Form onValid={onValid} onInvalid={onInvalid}>
@ -66,15 +67,16 @@ describe('Validation', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(onValid).toHaveBeenCalled(); test.equal(onValid.called, true);
expect(onInvalid).not.toHaveBeenCalled(); test.equal(onInvalid.called, false);
test.done();
}); },
it('should trigger an onInvalid handler, if passed, when form is invalid', function () { 'should trigger an onInvalid handler, if passed, when form is invalid': function (test) {
const onValid = jasmine.createSpy('valid'); const onValid = sinon.spy();
const onInvalid = jasmine.createSpy('invalid'); const onInvalid = sinon.spy();
TestUtils.renderIntoDocument( TestUtils.renderIntoDocument(
<Formsy.Form onValid={onValid} onInvalid={onInvalid}> <Formsy.Form onValid={onValid} onInvalid={onInvalid}>
@ -82,40 +84,33 @@ describe('Validation', function () {
</Formsy.Form> </Formsy.Form>
); );
expect(onValid).not.toHaveBeenCalled(); test.equal(onValid.called, false);
expect(onInvalid).toHaveBeenCalled(); test.equal(onInvalid.called, true);
test.done();
});
it('should use provided validate function', function () {
const isValid = jasmine.createSpy('valid');
const Input = customizeInput({
render() {
if (this.isValid()) {
isValid();
}
return <input value={this.getValue()} onChange={this.updateValue}/>;
}, },
validate() {
return this.getValue() === 'checkValidity'; 'should be able to use provided validate function': function (test) {
let isValid = false;
const CustomInput = InputFactory({
componentDidMount() {
isValid = this.isValid();
} }
}); });
const form = TestUtils.renderIntoDocument( const form = TestUtils.renderIntoDocument(
<Formsy.Form> <Formsy.Form>
<Input name="foo" value="checkInvalidity"/> <CustomInput name="foo" value="foo" required/>
</Formsy.Form> </Formsy.Form>
); );
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'); const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
TestUtils.Simulate.change(input, {target: {value: 'checkValidity'}}); test.equal(isValid, true);
expect(isValid).toHaveBeenCalled(); test.done();
}); },
it('should provide invalidate callback on onValiSubmit', function () { 'should provide invalidate callback on onValiSubmit': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -132,11 +127,12 @@ describe('Validation', function () {
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
expect(input.isValid()).toBe(false); test.equal(input.isValid(), false);
test.done();
}); },
it('should provide invalidate callback on onInvalidSubmit', function () { 'should provide invalidate callback on onInvalidSubmit': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -152,12 +148,13 @@ describe('Validation', function () {
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
expect(input.getErrorMessage()).toBe('bar'); test.equal(input.getErrorMessage(), 'bar');
}); test.done();
},
it('should not invalidate inputs on external errors with preventExternalInvalidation prop', function () { 'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -175,11 +172,12 @@ describe('Validation', function () {
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
expect(input.isValid()).toBe(true); test.equal(input.isValid(), true);
test.done();
}); },
it('should invalidate inputs on external errors without preventExternalInvalidation prop', function () { 'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) {
const TestForm = React.createClass({ const TestForm = React.createClass({
render() { render() {
@ -195,8 +193,9 @@ describe('Validation', function () {
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form'); const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
const input = TestUtils.findRenderedComponentWithType(form, TestInput); const input = TestUtils.findRenderedComponentWithType(form, TestInput);
TestUtils.Simulate.submit(formEl); TestUtils.Simulate.submit(formEl);
expect(input.isValid()).toBe(false); test.equal(input.isValid(), false);
test.done();
}); }
}); };

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import Formsy from './../..'; import Formsy from './../..';
import assign from 'react/lib/Object.assign';
const defaultProps = { const defaultProps = {
mixins: [Formsy.Mixin], mixins: [Formsy.Mixin],
@ -15,8 +14,8 @@ const defaultProps = {
} }
}; };
export function customizeInput(props) { export function InputFactory(props) {
return React.createClass(assign(defaultProps, props)); return React.createClass(Object.assign(defaultProps, props));
} }
export default React.createClass(defaultProps); export default React.createClass(defaultProps);

View File

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

View File

@ -1,21 +0,0 @@
var path = require('path');
module.exports = {
devtool: 'inline-source-map',
entry: path.resolve(__dirname, 'build', 'test.js'),
output: {
path: path.resolve(__dirname, 'build'),
filename: 'build.js'
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
{ test: /\.json$/, loader: 'json' }
]
}
};