Skip to content

Commit

Permalink
Merge 97e3a6a into fc65369
Browse files Browse the repository at this point in the history
  • Loading branch information
unjust committed Feb 8, 2017
2 parents fc65369 + 97e3a6a commit 6c7b618
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/TextField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import cx from 'classnames';

/**
* @module TextField
*/
class TextField extends React.Component {

constructor(props) {
super(props);
this.state = {
value: props.value,
error: props.error
};
this.onChange = this.onChange.bind(this);
}

onChange(e) {
this.setState({ value: e.target.value });
}

render() {
const {
name,
value, // eslint-disable-line no-unused-vars
label,
labelClassName,
className,
children,
error,
required,
...other
} = this.props;

const classNames = cx(
{ 'field--error': this.state.error },
className
);

const labelClassNames = cx(
{ required },
labelClassName
);

return (
<div>
<label className={labelClassNames} htmlFor={other.id}>
{label}
</label>

<input type='text'
name={name}
value={this.state.value}
required={required}
className={classNames}
onChange={this.onChange}
{...other} />

{ this.props.maxLength && <p className='text--caption align--right'>{this.state.value.length} / {this.props.maxLength}</p> }

{ this.state.error && <p className='text--error'>{error}</p> }
{children}
</div>
);
}
}

TextField.propTypes = {
name: React.PropTypes.string.isRequired
};

export default TextField;
74 changes: 74 additions & 0 deletions src/textField.story.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import TextField from './TextField';
import Button from './Button';
import { storiesOf } from '@kadira/storybook';

storiesOf('TextField', module)
.add('default', () => <TextField
label='Your name'
id='fullname'
name='name'
value=''
placeholder='Not your email' />)
.add('with value', () => <TextField
label='Your name'
id='fullname'
name='name'
value='Phife Dawg'
placeholder='Not your email' />)
.add('error state', () => <TextField
label='Your name'
id='fullname'
name='name'
value=''
error='Not so fast. You have an error.'
placeholder='Not your email' />)
.add('required', () => {
return (<form>
<TextField
label='Your name'
id='fullname'
name='name'
value=''
required='true'
placeholder='Not your email' />
<Button
contrast
fullWidth>
Submit
</Button>
</form>);
})
.add('with char counter (maxLength)', () => {
const rules = {
maxLength: 10,
pattern:'.{5,10}'
};
return (<TextField
label='Your name'
id='fullname'
name='name'
value=''
placeholder='Not your email'
{...rules} />);
})
.add('has a pattern for min length', () => {
const rules = {
pattern:'.{5,10}'
};
return (<form>
<TextField
label='Your name'
id='fullname'
name='name'
value='>5'
placeholder='Not your email'
{...rules} />
<Button
contrast
fullWidth>
Submit
</Button>
</form>);
});

100 changes: 100 additions & 0 deletions src/textField.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import TextField from './TextField';

describe('TextField', function() {

const LABEL_TEXT = 'Super Hero',
VALUE = 'Batman',
NAME_ATTR = 'superhero',
MAX_LEN = '20',
ERROR_TEXT = 'Too wimpy.';

let textField,
textFieldEl,
inputEl;

beforeEach(() => {
const formAttrs = {
id: NAME_ATTR,
maxLength: MAX_LEN,
error: ERROR_TEXT,
required: 'required'
};
textField = TestUtils.renderIntoDocument(<TextField
name={NAME_ATTR}
label={LABEL_TEXT}
value={VALUE}
{...formAttrs} />);

textFieldEl = ReactDOM.findDOMNode(textField);
inputEl = textFieldEl.querySelector('input');
});

afterEach(() => {
textFieldEl = null;
inputEl = null;
});

it('exists', () => {
expect(textFieldEl).not.toBeNull();
});

it('should have a name attribute', () => {
expect(inputEl.name).toEqual(NAME_ATTR);
});

it('should have a value when one is specified', () => {
expect(inputEl.value).toEqual(VALUE);
});

it('should have a label when label is given', () => {
const labelEl = textFieldEl.querySelector('label');
expect(labelEl).not.toBeNull();
expect(labelEl.textContent).toEqual(LABEL_TEXT);
});

it('should have a required attribute when specified', () => {
expect(inputEl.attributes.required).not.toBeNull();
});

it('should specify attributes that are passed in', function() {
expect(inputEl.getAttribute('maxLength')).toEqual(MAX_LEN);
});

it('should have an error when one is specified', function() {
const errorEl = textFieldEl.querySelector('.text--error');
expect(errorEl).not.toBeNull();
expect(errorEl.textContent).toEqual(ERROR_TEXT);
});

it('should specify attributes that are passed in', function() {
expect(inputEl.getAttribute('maxLength')).toEqual(MAX_LEN);
});

it('should set its value on input change', function() {
const newValue = `${VALUE}r`;
expect(inputEl.value).toEqual(VALUE);
TestUtils.Simulate.change(inputEl, { target: { value: newValue } });
expect(inputEl.value).toEqual(newValue);
});

it('should call onChange and setState with input change', function() {
const newValue = `${VALUE}r`;
const changeSpy = spyOn(TextField.prototype, 'onChange').and.callThrough();
const stateSpy = spyOn(TextField.prototype, 'setState').and.callThrough();

const boundTextField = TestUtils.renderIntoDocument(<TextField
name={NAME_ATTR}
label={LABEL_TEXT}
value={VALUE} />);

textFieldEl = ReactDOM.findDOMNode(boundTextField);
inputEl = textFieldEl.querySelector('input');
TestUtils.Simulate.change(inputEl, { target: { value: newValue } });

expect(changeSpy).toHaveBeenCalled();
expect(stateSpy).toHaveBeenCalledWith({ value: newValue });
});
});

0 comments on commit 6c7b618

Please sign in to comment.