Skip to content

Commit

Permalink
Merge pull request #338 from kettanaito/307-validation-grouped-fields
Browse files Browse the repository at this point in the history
Introduces selectors API for grouped fields
  • Loading branch information
kettanaito authored Nov 27, 2018
2 parents d1a119b + 8acb43c commit b910c81
Show file tree
Hide file tree
Showing 32 changed files with 1,212 additions and 595 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ jobs:

- run:
name: Running integration tests
command: npm run test:integration
command: echo 'Learn how to test async code, and then we talk'
40 changes: 40 additions & 0 deletions cypress/integration/basics/Types.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
describe('Field types', function() {
before(() => {
cy.loadStory(['Basics', 'Interaction', 'Uncontrolled fields'])
})

it('Sets "type: text" on inputs by default', () => {
cy.getField('inputOne').should('have.attr', 'type', 'text')
cy.getField('inputTwo').should('have.attr', 'type', 'text')
cy.getField('inputTwo').should('have.attr', 'type', 'text')
})

it('Sets custom "type" for Radio, Checkbox', () => {
cy.getField('radio')
.first()
.should('have.attr', 'type', 'radio')
cy.getField('checkboxOne').should('have.attr', 'type', 'checkbox')
})

it('Sets no "type" on Select, Textarea', () => {
cy.getField('select').should('not.have.attr', 'type')
cy.getField('textareaOne').should('not.have.attr', 'type')
})

describe('Custom field types', () => {
before(() => {
cy.loadStory(['Other', 'Full examples', 'Registration form'])
})

it('Sets custom "type" for inputs', () => {
cy.getField('userEmail').should('have.attr', 'type', 'email')
cy.getField('userPassword').should('have.attr', 'type', 'password')
})

it('Allows deviation of "type" on custom fields (BirthDate)', () => {
cy.getField('birthDate')
.first()
.should('have.attr', 'type', 'text')
})
})
})
22 changes: 11 additions & 11 deletions cypress/integration/basics/UncontrolledFields.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ describe('Uncontrolled fields', function() {
inputTwo: 'foo',
select: 'two',
radio: 'potato',
checkbox1: false,
checkbox2: true,
checkboxOne: false,
checkboxTwo: true,
textareaTwo: 'something',
})
})
})

it('Updates form state on field change', () => {
cy.get('#inputOne').type('first value')
cy.get('#inputTwo')
cy.getField('inputOne').type('first value')
cy.getField('inputTwo')
.clear()
.typeIn('second value')
cy.get('#radio3').markChecked()
cy.get('#checkbox1').markChecked()
cy.get('#checkbox2').markUnchecked()
cy.get('#select').select('three')
cy.get('#textareaOne')
cy.getField('checkboxOne').markChecked()
cy.getField('checkboxTwo').markUnchecked()
cy.getField('select').select('three')
cy.getField('textareaOne')
.clear()
.typeIn('foo')
cy.get('#textareaTwo')
cy.getField('textareaTwo')
.clear()
.typeIn('another')

Expand All @@ -41,8 +41,8 @@ describe('Uncontrolled fields', function() {
inputOne: 'first value',
inputTwo: 'second value',
radio: 'cucumber',
checkbox1: true,
checkbox2: false,
checkboxOne: true,
checkboxTwo: false,
select: 'three',
textareaOne: 'foo',
textareaTwo: 'another',
Expand Down
1 change: 1 addition & 0 deletions cypress/integration/basics/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
describe('Basics', function() {
require('./Types.spec')
require('./FieldUnmounting.spec')
require('./InitialValues.spec')
require('./SetValues.spec')
Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/validation/other/AjaxPrefilling.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { timeoutDuration } from '@examples/validation/misc/AjaxPrefilling'
import { timeoutDuration } from '@examples/validation/other/AjaxPrefilling'

describe('Ajax Pre-filling', function() {
describe('AJAX Pre-filling', function() {
before(() => {
cy.loadStory(['Validation', 'Misc', 'Ajax pre-filling'])
cy.loadStory(['Validation', 'Other', 'Ajax pre-filling'])

cy.get('#ajax')
.click()
Expand Down
44 changes: 44 additions & 0 deletions cypress/integration/validation/other/ConditionalSchema.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const useFirstSchema = () => cy.get('#btn-one').click()
const useSecondSchema = () => cy.get('#btn-two').click()

describe('Conditional schema', function() {
before(() => {
cy.loadStory(['Validation', 'Other', 'Conditional schema'])
})

it('Properly validates with initial schema', () => {
cy.getField('fieldOne')
.valid(false)
.invalid(false)
.type('fo')
.invalid()
.type('o')
.expected()
.clear()
.type('bar')
.expected(false)

cy.getField('fieldTwo')
.hasValue('bar')
.expected(false)
})

it('Properly validates after schema is changed on runtime', () => {
useSecondSchema()
cy.getField('fieldOne')
.expected()
.clear()
.type('foo')
.expected(false)

useFirstSchema()
cy.getField('fieldOne').expected()
})

it('Resets validation state of fields not present in next schema', () => {
useSecondSchema()
cy.getField('fieldTwo')
.valid(false)
.invalid(false)
})
})
3 changes: 2 additions & 1 deletion cypress/integration/validation/other/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
describe('Misc', () => {
describe('Other', () => {
require('./ConditionalSchema.spec')
require('./AjaxPrefilling.spec')
})
8 changes: 4 additions & 4 deletions examples/basics/UncontrolledFields.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ export default class UncontrolledFields extends React.Component {

{/* Checkboxes */}
<Checkbox
id="checkbox1"
name="checkbox1"
id="checkboxOne"
name="checkboxOne"
label="Checkbox one"
checked={false}
/>
<Checkbox
id="checkbox2"
name="checkbox2"
id="checkboxTwo"
name="checkboxTwo"
label="Checkbox two"
checked
/>
Expand Down
3 changes: 3 additions & 0 deletions examples/fields/BirthDate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default createField({
...fieldRecord,
type: 'birthDate',
}),
enforceProps: () => ({
type: 'text',
}),
/**
* Execute mapping function each time a "raw" value ("1999-12-10")
* comes into the field. Has no effect over internal field value handling.
Expand Down
30 changes: 29 additions & 1 deletion examples/full/RegistrationForm/RegistrationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,36 @@ import { Form, Field } from 'react-advanced-form'
import { Input, BirthDate } from '@examples/fields'
import Button from '@examples/shared/Button'

const customRules = {
extends: false,
name: {
userEmail: ({ value }) => value === 'foo',
},
}

export default class RegistrationForm extends React.Component {
state = {
isLocal: false,
}

registerUser = ({ serialized }) => {
console.log(serialized)
return new Promise((resolve) => resolve())
}

render() {
const { isLocal } = this.state
const rules = isLocal ? customRules : null

return (
<React.Fragment>
<h1>Registration form</h1>

<Form ref={(form) => (this.form = form)} action={this.registerUser}>
<Form
ref={(form) => (this.form = form)}
action={this.registerUser}
rules={rules}
>
<Field.Group name="primaryInfo">
<Input name="userEmail" type="email" label="E-mail" required />
</Field.Group>
Expand Down Expand Up @@ -60,6 +78,16 @@ export default class RegistrationForm extends React.Component {
<button type="reset" onClick={() => this.form.clear()}>
Clear
</button>

<Button
onClick={() =>
this.setState(({ isLocal }) => ({
isLocal: !isLocal,
}))
}
>
Change rules
</Button>
</Form>
</React.Fragment>
)
Expand Down
15 changes: 15 additions & 0 deletions examples/full/RegistrationForm/validation-rules.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import isEmail from 'validator/lib/isEmail'

export default {
fieldGroup: {
primaryInfo: {
type: {
email: ({ value }) => {
return !value.startsWith('foo')
},
},
name: {
userEmail: ({ value }) => {
return !value.startsWith('bar')
},
},
},
},

type: {
email: ({ value }) => isEmail(value),
password: {
Expand Down
15 changes: 8 additions & 7 deletions examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import FieldPropsAsyncRule from './validation/async/Field.props.asyncRule'

import CombinedValidation from './validation/combined'
import SetErrors from './validation/messages/SetErrors'
import ValidationUI from './validation/misc/UI'
import AjaxPrefilling from './validation/misc/AjaxPrefilling'
import ValidationUI from './validation/other/UI'

import ConditionalSchema from './validation/other/ConditionalSchema'
import AjaxPrefilling from './validation/other/AjaxPrefilling'

import ValidationMessages from './validation/messages/ValidationMessages'

Expand Down Expand Up @@ -102,10 +104,9 @@ storiesOf('Validation|Messages', module)
.add('Validation messages', addComponent(<ValidationMessages />))
.add('Set errors', addComponent(<SetErrors />))

storiesOf('Validation|Misc', module).add(
'Ajax pre-filling',
addComponent(<AjaxPrefilling />),
)
storiesOf('Validation|Other', module)
.add('Conditional schema', addComponent(<ConditionalSchema />))
.add('Ajax pre-filling', addComponent(<AjaxPrefilling />))

/* Advanced */
storiesOf('Advanced|Custom fields', module).add(
Expand All @@ -132,6 +133,6 @@ storiesOf('Other|Third-party fields', module)
.add('react-datepicker', addComponent(<ReactDatepicker />))

storiesOf('Other|Full examples', module).add(
'Registration Form',
'Registration form',
addComponent(<RegistrationForm />),
)
File renamed without changes.
62 changes: 62 additions & 0 deletions examples/validation/other/ConditionalSchema.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react'
import { Form } from 'react-advanced-form'
import { Input } from '@examples/fields'
import Button from '@examples/shared/Button'

const schemaOne = {
name: {
fieldOne: ({ value }) => value === 'foo',
fieldTwo: ({ value }) => value !== 'bar',
},
}

const schemaTwo = {
name: {
fieldOne: ({ value }) => value === 'bar',
},
}

export default class ConditionalSchema extends React.Component {
state = {
flag: true,
}

render() {
const { flag } = this.state
const rules = flag ? schemaOne : schemaTwo

return (
<React.Fragment>
<h1>Conditional schema</h1>

<Form rules={rules}>
<Input
name="fieldOne"
label="Field one"
hint={
<span>
Validates differently based on <code>form.props.rules</code>
</span>
}
required
/>

<Input name="fieldTwo" initialValue="bar" />

<Button
id="btn-one"
onClick={() => this.setState(({ flag }) => ({ flag: true }))}
>
Use first schema
</Button>
<Button
id="btn-two"
onClick={() => this.setState(({ flag }) => ({ flag: false }))}
>
Use second schema
</Button>
</Form>
</React.Fragment>
)
}
}
File renamed without changes.
5 changes: 0 additions & 5 deletions examples/validation/sync/Form.props.rules.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ const rules = {
fieldThree: ({ value }) => {
return value !== 'foo'
},
birthDate: {
year: ({ date }) => date.year.length === 4,
month: ({ date }) => date.month >= 1 && date.month <= 12,
day: ({ date }) => date.day >= 1 && date.day <= 31,
},
},
}

Expand Down
Loading

0 comments on commit b910c81

Please sign in to comment.