Skip to content

Commit

Permalink
added radio groups (Availity#5)
Browse files Browse the repository at this point in the history
* added radio groups
* updates. form-check classes on label and component will update from validation and other cleanup
* classes for the label
* placeholder tests added
* update bootstrap, added tests, pass inline value, resetable group
  • Loading branch information
JamieCrisman authored and TheSharpieOne committed Sep 27, 2016
1 parent 9fae3a3 commit 2ac69d5
Show file tree
Hide file tree
Showing 10 changed files with 541 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
"rules": {
"semi" : [2, "always"],
"comma-dangle": [2, "always-multiline"]
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
}
}
139 changes: 139 additions & 0 deletions __test__/AvRadio.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React from 'react';
import { shallow } from 'enzyme';
import { AvRadio } from 'availity-reactstrap-validation';
import { Input } from 'reactstrap';

let options, props, inputState, component, setStateSpy;

describe('AvRadio', () => {
beforeEach(() => {
options = {
context: {
Group: {
name: 'test'
},
FormCtrl: {
inputs: {},
getDefaultValue: ()=> {},
getInputState: ()=> {return {}},
hasError: {},
isDirty: {},
isTouched: {},
setDirty: ()=> {},
setTouched: ()=> {},
setBad: ()=> {},
register: ()=> {},
unregister: ()=> {},
validate: ()=> {},
validationEvent: ()=> {},
validation: {},
parent: null,
},
},
};
});

it('should render a reactstrap Input', () => {
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.type()).to.not.eql(undefined);
});

it('should have "av-untouched" class when untouched', () => {
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-untouched')).to.be.true;
expect(wrapper.find(Input).hasClass('av-touched')).to.be.false;
});

it('should have "av-pristine" class when not dirty', () => {
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-pristine')).to.be.true;
expect(wrapper.find(Input).hasClass('av-dirty')).to.be.false;
});

it('should have "av-valid" class when not invalid', () => {
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-valid')).to.be.true;
expect(wrapper.find(Input).hasClass('av-invalid')).to.be.false;
});

it('should have "av-touched" class when touched', () => {
options.context.FormCtrl.isTouched.yo = true;
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-untouched')).to.be.false;
expect(wrapper.find(Input).hasClass('av-touched')).to.be.true;
});

it('should have "av-pristine" class when not dirty', () => {
options.context.FormCtrl.isDirty.yo = true;
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-pristine')).to.be.false;
expect(wrapper.find(Input).hasClass('av-dirty')).to.be.true;
});

it('should have "av-valid" class when not invalid', () => {
options.context.FormCtrl.hasError.yo = true;
const wrapper = shallow(<AvRadio name="yo" />, options);

expect(wrapper.find(Input).hasClass('av-valid')).to.be.false;
expect(wrapper.find(Input).hasClass('av-invalid')).to.be.true;
});

describe('on change handler', () => {
beforeEach(() => {
inputState = 'danger';
props = {
name: 'fieldName',
validateEvent: '',
validate: {},
value: 'testValue'
};
options = {
context: {
Group: {
name: 'test',
update: sinon.spy()
},
FormCtrl: {
inputs: {},
getDefaultValue: sinon.spy(),
getInputState: sinon.stub().returns(inputState),
hasError: {},
isDirty: {},
isTouched: {},
setDirty: sinon.spy(),
setTouched: sinon.spy(),
setBad: sinon.spy(),
register: sinon.spy(),
unregister: sinon.spy(),
validate: sinon.spy(),
validationEvent: 'formCtrlValidationEvent',
validation: {},
parent: null,
},
},
};

component = new AvRadio(props);
component.context = options.context;
setStateSpy = component.setState = sinon.spy();
});

it('should update group value on change', () => {
component.onChangeHandler();
expect(options.context.Group.update).to.have.been.calledWith(props.value);
});

it('should run props on change if it\'s there', () => {
props.onChange = sinon.spy();
component.onChangeHandler();
expect(props.onChange).to.have.been.called;
});
});

});
166 changes: 166 additions & 0 deletions __test__/AvRadioGroup.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React from 'react';
import { shallow } from 'enzyme';
import { AvRadioGroup, AvFeedback } from 'availity-reactstrap-validation';
import { Input } from 'reactstrap';

let options;

describe('AvRadioGroup', () => {
beforeEach(() => {
options = {
context: {
FormCtrl: {
inputs: {},
getDefaultValue: ()=> {},
getInputState: ()=> {return {}},
hasError: {},
isDirty: {},
isTouched: {},
setDirty: sinon.spy(),
setTouched: sinon.spy(),
setBad: sinon.spy(),
register: sinon.spy(),
unregister: sinon.spy(),
validate: sinon.spy(),
validationEvent: ()=> {},
validation: {},
parent: null,
},
},
};
});

it('should render a reactstrap Input', () => {
const wrapper = shallow(<AvRadioGroup name="yo" />, options);

expect(wrapper.type()).to.not.eql(undefined);
});

it('should register a validation', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.props.required = true;
component.context = options.context;
component.componentWillMount();
expect(component.validations.required).to.eql({value: true});
component.props.required = false;
component.componentWillMount();
expect(component.validations.required).to.eql(undefined);
});

it('should register then remove a disabled validation', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.props.required = true;
component.context = options.context;
component.componentWillMount();
component.props.required = false;
component.componentWillMount();
expect(component.validations.required).to.eql(undefined);
});

it('should return the set value', ()=> {
let component = new AvRadioGroup({validate: ()=> {}});
component.selection = 'boop';
expect(component.getValue()).to.equal('boop');
})

it('should unregister when unmounted', ()=> {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.componentWillUnmount();
expect(options.context.FormCtrl.unregister).to.have.been.called;
});

it('should give default value from props', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.props.defaultValue = 'momo';
expect(component.getDefaultValue()).to.eql({key: 'defaultValue', value: 'momo'});
});

it('should give default value from context', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.context.FormCtrl.getDefaultValue = () => {
return 'jiri';
}
expect(component.getDefaultValue()).to.eql({key: 'defaultValue', value: 'jiri'});
});

it('should give default fallback when no one set up their stuff', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
expect(component.getDefaultValue()).to.eql({key: 'defaultValue', value: ''});
});

it('should reset properly', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.setState = sinon.spy();
component.props.defaultValue = 'momo';
component.props.name = 'test';
component.reset();
expect(component.selection).to.equal('momo');
expect(component.setState).to.have.been.calledWith({selection: 'momo'});
expect(options.context.FormCtrl.setDirty).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.setTouched).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.setBad).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.validate).to.have.been.calledWith('test');
});

it('should reset properly and call props reset', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.props.defaultValue = 'momo';
component.props.name = 'test';
component.setState = sinon.spy();
component.props.onReset = sinon.spy();
component.reset();
expect(component.selection).to.equal('momo');
expect(component.setState).to.have.been.calledWith({selection: 'momo'});
expect(options.context.FormCtrl.setDirty).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.setTouched).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.setBad).to.have.been.calledWith('test', false);
expect(options.context.FormCtrl.validate).to.have.been.calledWith('test');
expect(component.props.onReset).to.have.been.calledWith('momo');
});

it('should disconnect child context from form registration and validation', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.getChildContext().FormCtrl.register('charmander');
component.getChildContext().FormCtrl.validate('squirtle');
expect(options.context.FormCtrl.register).to.not.have.been.called;
expect(options.context.FormCtrl.validate).to.not.have.been.called;
});

it('should update the group via child context', () => {
let component = new AvRadioGroup({validate: ()=> {}});
component.context = options.context;
component.setState = sinon.spy();
component.getChildContext().Group.update('momo');
expect(component.selection).to.equal('momo');
expect(component.setState).to.have.been.calledWith({selection: 'momo'});
expect(options.context.FormCtrl.validate).to.have.been.called;
});

it('should render validation message when sent', () => {
options.context.FormCtrl.getInputState = () => {
return {'errorMessage': 'WHAT ARE YOU DOING?!'};
}
const wrapper = shallow(<AvRadioGroup name="yo" />, options);
expect(wrapper.find(AvFeedback).prop('children')).to.equal('WHAT ARE YOU DOING?!');
});

it('should not render validation message when false', () => {
options.context.FormCtrl.getInputState = () => {
return {'errorMessage': false};
}
const wrapper = shallow(<AvRadioGroup name="yo" />, options);
expect(wrapper.find(AvFeedback)).to.have.length(0);
});

it('should show a legend if we defined a label', () => {
const wrapper = shallow(<AvRadioGroup name="yo" label="test" />, options);
expect(wrapper.contains(<legend>test</legend>)).to.equal(true)
});

});
15 changes: 14 additions & 1 deletion docs/lib/examples/Form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { AvForm, AvField, AvGroup, AvInput, AvFeedback } from 'availity-reactstrap-validation';
import { AvForm, AvField, AvGroup, AvInput, AvFeedback, AvRadioGroup, AvRadio } from 'availity-reactstrap-validation';
import { Button, Label, FormGroup } from 'reactstrap';

export default class FormExample extends React.Component {
Expand All @@ -14,6 +14,19 @@ export default class FormExample extends React.Component {
<AvInput name="rank" id="example" required />
<AvFeedback>This is an error!</AvFeedback>
</AvGroup>
<AvRadioGroup name="radioExample" label="Radio Buttons!" required>
<AvRadio label="Bulbasaur" value="Bulbasaur" id="radioOption1"/>
<AvRadio label="Squirtle" value="Squirtle" id="radioOption2"/>
<AvRadio label="Charmander" value="Charmander" id="radioOption3"/>
<AvRadio label="Pikachu" value="Pikachu" id="radioOption4" disabled/>
</AvRadioGroup>

<AvRadioGroup inline name="radioExample2" label="Radio Buttons! (inline)" required>
<AvRadio label="Bulbasaur" value="Bulbasaur" id="radioOption5"/>
<AvRadio label="Squirtle" value="Squirtle" id="radioOption6"/>
<AvRadio label="Charmander" value="Charmander" id="radioOption7"/>
<AvRadio label="Pikachu" value="Pikachu" id="radioOption8" disabled/>
</AvRadioGroup>
{/* With select and AvField */}
<AvField type="select" name="select" label="Option" helpMessage="Idk, this is an example. Deal with it!">
<option>1</option>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"build:docs": "cross-env WEBPACK_BUILD=production webpack --config ./webpack.dev.config.js --colors",
"build": "cross-env WEBPACK_BUILD=production webpack --progress --colors && webpack --colors",
"prebuild": "babel src --out-dir lib",
"lint": "eslint src",
"create-release": "npm test && sh ./scripts/release",
"publish-release": "npm test && sh ./scripts/publish"
},
Expand Down Expand Up @@ -79,7 +80,7 @@
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.11.6",
"bootstrap": "^4.0.0-alpha.3",
"bootstrap": "^4.0.0-alpha.4",
"chai": "^3.5.0",
"chai-as-promised": "^5.3.0",
"chai-enzyme": "^0.5.0",
Expand Down
Binary file removed src/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion src/AvGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class AvGroup extends Component {
},
FormCtrl: this.FormCtrl,
};
};
}

getInputState () {
return this.context.FormCtrl.getInputState(this.state.input.props.name);
Expand Down
Loading

0 comments on commit 2ac69d5

Please sign in to comment.