Merged in create-ticket-form (pull request #38)

Create ticket form
This commit is contained in:
Ivan Diaz 2016-08-20 19:21:24 -03:00
commit e87c38b743
40 changed files with 1197 additions and 143 deletions

View File

@ -55,6 +55,8 @@
"dependencies": {
"app-module-path": "^1.0.3",
"classnames": "^2.1.3",
"draft-js": "^0.8.1",
"draft-js-export-html": "^0.4.0",
"jquery": "^2.1.4",
"keycode": "^2.1.4",
"localStorage": "^1.0.3",

View File

@ -0,0 +1,119 @@
import React from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import { browserHistory } from 'react-router';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import store from 'app/store';
import SessionActions from 'actions/session-actions';
import Header from 'core-components/header';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
class CreateTicketForm extends React.Component {
static propTypes = {
userLogged: React.PropTypes.bool
};
static defaultProps = {
userLogged: true
};
constructor(props) {
super(props);
this.state = {
loading: false,
message: null
};
}
render() {
return (
<div className="create-ticket-form">
<Header title={i18n('CREATE_TICKET')} description={i18n('CREATE_TICKET_DESCRIPTION')} />
<Form loading={this.state.loading} onSubmit={this.onSubmit.bind(this)}>
{(!this.props.userLogged) ? this.renderEmailAndName() : null}
<div className="row">
<FormField className="col-md-7" label="Title" name="title" validation="TITLE" required field="input" fieldProps={{size: 'large'}}/>
<FormField className="col-md-5" label="Department" name="departmentId" field="select" fieldProps={{
items: [
{content: 'Sales Support'},
{content: 'Technical Issues'},
{content: 'System and Administration'}
],
size: 'medium'
}} />
</div>
<FormField label="Content" name="content" validation="TEXT_AREA" required field="textarea" />
{(!this.props.userLogged) ? this.renderCaptcha() : null}
<SubmitButton>Create Ticket</SubmitButton>
</Form>
{this.renderMessage()}
</div>
);
}
renderEmailAndName() {
return (
<div className="row">
<FormField className="col-md-6" label="Email" name="email" validation="EMAIL" required field="input" fieldProps={{size: 'large'}}/>
<FormField className="col-md-6" label="Full Name" name="name" validation="NAME" required field="input" fieldProps={{size: 'large'}}/>
</div>
);
}
renderCaptcha() {
return (
<div className="create-ticket-form__captcha">
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
</div>
);
}
renderMessage() {
switch (this.state.message) {
case 'success':
return <Message className="create-ticket-form__message" type="success">{i18n('TICKET_SENT')}</Message>;
case 'fail':
return <Message className="create-ticket-form__message" type="error">{i18n('TICKET_SENT_ERROR')}</Message>;
default:
return null;
}
}
onSubmit(formState) {
this.setState({
loading: true
});
API.call({
path: '/ticket/create',
data: formState
}).then(this.onTicketSuccess.bind(this)).catch(this.onTicketFail.bind(this));
}
onTicketSuccess() {
this.setState({
loading: false,
message: 'success'
});
store.dispatch(SessionActions.getUserData());
setTimeout(() => {browserHistory.push('/dashboard')}, 2000);
}
onTicketFail() {
this.setState({
loading: false,
message: 'fail'
});
}
}
export default CreateTicketForm;

View File

@ -0,0 +1,12 @@
.create-ticket-form {
&__message {
margin-top: 20px;
}
&__captcha {
margin: 0 auto 20px;
height: 78px;
width: 304px;
}
}

View File

@ -1,11 +1,13 @@
import React from 'react';
import CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form';
class DashboardCreateTicketPage extends React.Component {
render() {
return (
<div>
DASHBOARD CREATE TICKET
<CreateTicketForm />
</div>
);
}

View File

@ -10,9 +10,11 @@ class DashboardLayout extends React.Component {
return (this.props.session.logged) ? (
<div className="dashboard">
<div className="dashboard__menu col-md-3"><DashboardMenu location={this.props.location} /></div>
<Widget className="dashboard__content col-md-9">
{this.props.children}
</Widget>
<div className="dashboard__content col-md-9">
<Widget>
{this.props.children}
</Widget>
</div>
</div>
) : null;
}

View File

@ -1,9 +1,5 @@
.dashboard {
&__menu {
}
&__content {
margin-bottom: 10px;
}
}

View File

@ -1,6 +1,9 @@
import React from 'react';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import Header from 'core-components/header';
import Table from 'core-components/table';
import Button from 'core-components/button';
@ -16,7 +19,7 @@ class DashboardListTicketsPage extends React.Component {
render() {
return (
<div className="dashboard-ticket-list">
<div className="dashboard-ticket-list__header">Tickets</div>
<Header title={i18n('TICKET_LIST')} description={i18n('TICKET_LIST_DESCRIPTION')} />
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} />
</div>
);

View File

@ -2,12 +2,6 @@
.dashboard-ticket-list {
&__header {
text-align: left;
font-variant: small-caps;
font-size: 16px;
}
&__number {
text-align: left;
}

View File

@ -12,8 +12,7 @@ import i18n from 'lib-app/i18n';
import SubmitButton from 'core-components/submit-button';
import Button from 'core-components/button';
import Form from 'core-components/form';
import Input from 'core-components/input';
import Checkbox from 'core-components/checkbox';
import FormField from 'core-components/form-field';
import Widget from 'core-components/widget';
import WidgetTransition from 'core-components/widget-transition';
import Message from 'core-components/message';
@ -53,9 +52,9 @@ class MainHomePageLoginWidget extends React.Component {
<Widget className="main-home-page__widget" title="Login" ref="loginWidget">
<Form {...this.getLoginFormProps()}>
<div className="login-widget__inputs">
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
<Input placeholder="password" name="password" className="login-widget__input" password required/>
<Checkbox name="remember" label="Remember Me" className="login-widget__input"/>
<FormField placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
<FormField placeholder="password" name="password" className="login-widget__input" required fieldProps={{password: true}}/>
<FormField name="remember" label="Remember Me" className="login-widget__input" field="checkbox"/>
</div>
<div className="login-widget__submit-button">
<SubmitButton type="primary">LOG IN</SubmitButton>
@ -73,7 +72,7 @@ class MainHomePageLoginWidget extends React.Component {
<Widget className="main-home-page__widget login-widget_password" title={i18n('RECOVER_PASSWORD')} ref="recoverWidget">
<Form {...this.getRecoverFormProps()}>
<div className="login-widget__inputs">
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
<FormField placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
</div>
<div className="login-widget__submit-button">
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>

View File

@ -4,15 +4,19 @@ import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-wid
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
class MainHomePage extends React.Component {
render() {
return (
<div className="main-home-page">
<MainHomePageLoginWidget className="col-md-4" />
<MainHomePagePortal className="col-md-8" />
</div>
);
}
render() {
return (
<div className="main-home-page">
<div className="col-md-4">
<MainHomePageLoginWidget />
</div>
<div className="col-md-8">
<MainHomePagePortal />
</div>
</div>
);
}
}
export default MainHomePage;

View File

@ -55,7 +55,7 @@ class MainLayoutHeader extends React.Component {
return {
className: 'main-layout-header__languages',
items: this.getLanguageList(),
selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language),
selectedIndex: Object.keys(codeLanguages).map((key) => codeLanguages[key]).indexOf(this.props.config.language),
onChange: this.changeLanguage.bind(this)
};
}

View File

@ -9,7 +9,7 @@ class MainLayout extends React.Component {
return (
<div className="main-layout">
<MainHeader />
<div className="main-layout--content">
<div className="main-layout--content row">
{this.props.children}
</div>
<MainFooter />

View File

@ -6,7 +6,7 @@ import API from 'lib-app/api-call';
import Widget from 'core-components/widget';
import Form from 'core-components/form';
import Input from 'core-components/input';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
@ -32,8 +32,8 @@ class MainRecoverPasswordPage extends React.Component {
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
<div className="recover-password__inputs">
<Input placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
<Input placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
<FormField placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
<FormField placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
</div>
<div className="recover-password__submit-button">
<SubmitButton type="primary">{i18n('SUBMIT')}</SubmitButton>

View File

@ -7,7 +7,7 @@ import API from 'lib-app/api-call';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Form from 'core-components/form';
import Input from 'core-components/input';
import FormField from 'core-components/form-field';
import Widget from 'core-components/widget';
@ -28,10 +28,10 @@ class MainSignUpPageWidget extends React.Component {
<Widget className="signup-widget col-md-6 col-md-offset-3" title="Register">
<Form {...this.getFormProps()}>
<div className="signup-widget__inputs">
<Input {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
<Input {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
<Input {...this.getInputProps()} label="Password" name="password" password validation="PASSWORD" required/>
<Input {...this.getInputProps()} label="Repeat Password" name="repeated-password" password validation="REPEAT_PASSWORD" required/>
<FormField {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
<FormField {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
<FormField {...this.getInputProps(true)} label="Password" name="password" validation="PASSWORD" required/>
<FormField {...this.getInputProps(true)} label="Repeat Password" name="repeated-password" validation="REPEAT_PASSWORD" required/>
</div>
<div className="signup-widget__captcha">
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
@ -64,10 +64,13 @@ class MainSignUpPageWidget extends React.Component {
};
}
getInputProps() {
getInputProps(password) {
return {
inputType: 'secondary',
className: 'signup-widget__input'
className: 'signup-widget__input',
fieldProps: {
size: 'medium',
password: password
}
};
}

View File

@ -0,0 +1,449 @@
const Input = ReactMock();
const Checkbox = ReactMock();
const DropDown = ReactMock();
const TextEditor = ReactMock();
const {EditorState} = require('draft-js');
const FormField = requireUnit('core-components/form-field', {
'core-components/input': Input,
'core-components/checkbox': Checkbox,
'core-components/drop-down': DropDown,
'core-components/text-editor': TextEditor
});
describe('FormField component', function () {
let component, innerField;
function renderFormField(props = { field: 'input'}) {
let fields = {
'input': Input,
'checkbox': Checkbox,
'select': DropDown,
'textarea': TextEditor
};
component = reRenderIntoDocument(
<FormField {...props}/>
);
innerField = TestUtils.scryRenderedComponentsWithType(component, fields[props.field])[0];
}
describe('when calling static getDefaultValue', function () {
it('should return correct values', function () {
expect(FormField.getDefaultValue('input')).to.equal('');
expect(FormField.getDefaultValue('checkbox')).to.equal(false);
expect(FormField.getDefaultValue('select')).to.equal(0);
expect(FormField.getDefaultValue('textarea') instanceof EditorState).to.equal(true);
});
});
describe('when rendering an input field', function () {
beforeEach(function () {
renderFormField({
field: 'input',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: 'MOCK_ERROR',
value: 'VALUE_MOCK',
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
});
it('should be wrapped in a label', function () {
expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
renderFormField({
field: 'input',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: '',
value: 'VALUE_MOCK',
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select');
});
it('should render error and label in right order and pass correct classes', function () {
expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label');
expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL');
expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField));
expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error');
expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR');
});
it('should pass props correctly to Input', function () {
expect(innerField.props).to.include({
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
errored: true,
name: 'MOCK_NAME',
onBlur: component.props.onBlur,
required: true,
value: 'VALUE_MOCK'
});
});
it('should pass disable field when context is loading', function () {
component.context.loading = true;
component.forceUpdate();
expect(innerField.props.disabled).to.equal(true);
component.context.loading = false;
});
it('should pass callbacks correctly', function () {
component.props.onChange.reset();
component.props.onBlur.reset();
innerField.props.onChange({ target: { value: 'SOME_VALUE_2'}});
innerField.props.onBlur();
expect(component.props.onBlur).to.have.been.called;
expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: 'SOME_VALUE_2'}});
});
it('should pass focus to the field', function () {
innerField.focus = stub();
component.focus();
expect(innerField.focus).to.have.been.called;
});
});
describe('when rendering a checkbox field', function () {
beforeEach(function () {
renderFormField({
field: 'checkbox',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: 'MOCK_ERROR',
value: false,
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
});
it('should be wrapped in a label', function () {
expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_checkbox');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored');
renderFormField({
field: 'checkbox',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: '',
value: false,
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_checkbox');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored');
});
it('should render error and label in right order and pass correct classes', function () {
expect(ReactDOM.findDOMNode(component).children[0]).to.equal(ReactDOM.findDOMNode(innerField));
expect(ReactDOM.findDOMNode(component).children[1].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[1].className).to.equal('form-field__label');
expect(ReactDOM.findDOMNode(component).children[1].textContent).to.equal('MOCK_LABEL');
expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error');
expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR');
});
it('should pass props correctly to Checkbox', function () {
expect(innerField.props).to.include({
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
errored: true,
name: 'MOCK_NAME',
onBlur: component.props.onBlur,
required: true,
value: false
});
});
it('should pass disable field when context is loading', function () {
component.context.loading = true;
component.forceUpdate();
expect(innerField.props.disabled).to.equal(true);
component.context.loading = false;
});
it('should pass callbacks correctly', function () {
component.props.onChange.reset();
component.props.onBlur.reset();
innerField.props.onChange({ target: { checked: true }});
innerField.props.onBlur();
expect(component.props.onBlur).to.have.been.called;
expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: true}});
});
it('should pass focus to the field', function () {
innerField.focus = stub();
component.focus();
expect(innerField.focus).to.have.been.called;
});
});
describe('when rendering an select field', function () {
beforeEach(function () {
renderFormField({
field: 'select',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: 'MOCK_ERROR',
value: 5,
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
items: []
},
onChange: stub(),
onBlur: stub()
});
});
it('should be wrapped in a label', function () {
expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_select');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
renderFormField({
field: 'select',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: '',
value: 5,
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
items: []
},
onChange: stub(),
onBlur: stub()
});
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_select');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
});
it('should render error and label in right order and pass correct classes', function () {
expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label');
expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL');
expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField));
expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error');
expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR');
});
it('should pass props correctly to DropDown', function () {
expect(innerField.props).to.include({
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
errored: true,
name: 'MOCK_NAME',
onBlur: component.props.onBlur,
required: true,
selectedIndex: 5
});
});
it('should pass disable field when context is loading', function () {
component.context.loading = true;
component.forceUpdate();
expect(innerField.props.disabled).to.equal(true);
component.context.loading = false;
});
it('should pass callbacks correctly', function () {
component.props.onChange.reset();
component.props.onBlur.reset();
innerField.props.onChange({index: 2});
innerField.props.onBlur();
expect(component.props.onBlur).to.have.been.called;
expect(component.props.onChange).to.have.been.calledWithMatch({target: {value: 2}});
});
it('should pass focus to the field', function () {
innerField.focus = stub();
component.focus();
expect(innerField.focus).to.have.been.called;
});
});
describe('when rendering an textarea field', function () {
beforeEach(function () {
renderFormField({
field: 'textarea',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: 'MOCK_ERROR',
value: {value: 'VALUE_MOCk'},
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
});
it('should be wrapped in a div', function () {
expect(ReactDOM.findDOMNode(component).tagName).to.equal('DIV');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
renderFormField({
field: 'textarea',
name: 'MOCK_NAME',
label: 'MOCK_LABEL',
error: '',
value: {value: 'VALUE_MOCk'},
required: true,
validation: 'MOCK_VALIDATION',
fieldProps: {
prop1: 'value1',
prop2: 'value2',
prop3: 'value3'
},
onChange: stub(),
onBlur: stub()
});
expect(ReactDOM.findDOMNode(component).className).to.include('form-field');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox');
expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select');
});
it('should render error and label in right order and pass correct classes', function () {
expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label');
expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL');
expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField));
expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN');
expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error');
expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR');
});
it('should pass props correctly to TextEditor', function () {
expect(innerField.props).to.include({
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
errored: true,
name: 'MOCK_NAME',
onBlur: component.props.onBlur,
required: true,
});
expect(innerField.props.value).to.deep.equal({value: 'VALUE_MOCk'});
});
it('should pass disable field when context is loading', function () {
component.context.loading = true;
component.forceUpdate();
expect(innerField.props.disabled).to.equal(true);
component.context.loading = false;
});
it('should pass callbacks correctly', function () {
component.props.onChange.reset();
component.props.onBlur.reset();
innerField.props.onChange({ target: { value: 'SOME_VALUE_2'}});
innerField.props.onBlur();
expect(component.props.onBlur).to.have.been.called;
expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: 'SOME_VALUE_2'}});
});
it('should pass focus to the field', function () {
innerField.focus = stub();
component.focus();
expect(innerField.focus).to.have.been.called;
});
});
});

View File

@ -1,11 +1,16 @@
// MOCKS
const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock');
const Input = ReactMock();
const FormField = ReactMock();
const {EditorState} = require('draft-js');
const draftJsExportHTML = {
stateToHTML: stub().returns('HTML_CODE')
};
// COMPONENT
const Form = requireUnit('core-components/form', {
'lib-app/validations/validations-factory': ValidationFactoryMock,
'core-components/input': Input
'draft-js-export-html': draftJsExportHTML,
'core-components/form-field': FormField
});
describe('Form component', function () {
@ -15,13 +20,13 @@ describe('Form component', function () {
form = TestUtils.renderIntoDocument(
<Form {...props} onSubmit={onSubmit}>
<div>
<Input name="first" value="value1" required/>
<Input name="second" value="value2" required validation="CUSTOM"/>
<FormField name="first" value="value1" required/>
<FormField name="second" value="value2" required validation="CUSTOM"/>
</div>
<Input name="third" value="value3" />
<FormField name="third" value="value3" />
</Form>
);
fields = TestUtils.scryRenderedComponentsWithType(form, Input);
fields = TestUtils.scryRenderedComponentsWithType(form, FormField);
}
function resetStubs() {
@ -117,7 +122,7 @@ describe('Form component', function () {
});
afterEach(resetStubs);
it('should pass the errors to inputs', function () {
it('should pass the errors to fields', function () {
expect(fields[0].props.error).to.equal('MOCK_ERROR_CONTROLLED');
expect(fields[1].props.error).to.equal(undefined);
});
@ -138,13 +143,13 @@ describe('Form component', function () {
form = reRenderIntoDocument(
<Form errors={errors}>
<div>
<Input name="first" value="value1" required/>
<Input name="second" value="value2" required validation="CUSTOM"/>
<FormField name="first" value="value1" required/>
<FormField name="second" value="value2" required validation="CUSTOM"/>
</div>
<Input name="third" value="value3" />
<FormField name="third" value="value3" />
</Form>
);
fields = TestUtils.scryRenderedComponentsWithType(form, Input);
fields = TestUtils.scryRenderedComponentsWithType(form, FormField);
}
setErrorsOrRender();
@ -171,6 +176,19 @@ describe('Form component', function () {
expect(form.props.onSubmit).to.have.been.calledWith(form.state.form);
});
it('should tranform EditorState to HTML usign draft-js-export-html library', function () {
draftJsExportHTML.stateToHTML.reset();
form.state.form.first = EditorState.createEmpty();
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
expect(draftJsExportHTML.stateToHTML).to.have.been.calledWith(form.state.form.first.getCurrentContent());
expect(form.props.onSubmit).to.have.been.calledWith({
first: 'HTML_CODE',
second: 'value2',
third: 'value3'
});
});
it('should validate all fields and not call onSubmit if there are errors', function () {
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');

View File

@ -6,6 +6,9 @@ import classNames from 'classnames';
// CORE LIBS
import callback from 'lib-core/callback';
// CORE COMPONENTS
import Icon from 'core-components/icon';
class Button extends React.Component {
static contextTypes = {
@ -16,6 +19,7 @@ class Button extends React.Component {
children: React.PropTypes.node,
type: React.PropTypes.oneOf([
'primary',
'primary-icon',
'clean',
'link'
]),
@ -23,7 +27,8 @@ class Button extends React.Component {
to: React.PropTypes. string.isRequired,
params: React.PropTypes.object,
query: React.PropTypes.query
})
}),
iconName: React.PropTypes.string
};
static defaultProps = {
@ -33,7 +38,7 @@ class Button extends React.Component {
render() {
return (
<button {...this.getProps()}>
{this.props.children}
{(this.props.iconName) ? <Icon size="sm" name={this.props.iconName}/> : this.props.children}
</button>
);
}
@ -45,6 +50,7 @@ class Button extends React.Component {
props.className = this.getClass();
delete props.route;
delete props.iconName;
delete props.type;
return props;

View File

@ -2,7 +2,8 @@
.button {
&-primary {
&-primary,
&-primary-icon {
background-color: $primary-red;
border: solid transparent;
border-radius: 4px;
@ -12,6 +13,11 @@
width: 239px;
}
&-primary-icon {
width: initial;
height: initial;
}
&-clean {
background: none;
border: none;

View File

@ -28,13 +28,12 @@ class CheckBox extends React.Component {
render() {
return (
<label className={this.getClass()}>
<span className={this.getClass()}>
<span {...this.getIconProps()}>
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
</span>
<span className="checkbox--label">{this.props.label}</span>
<input {...this.getProps()}/>
</label>
</span>
);
}
@ -49,7 +48,7 @@ class CheckBox extends React.Component {
props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
delete props.alignment;
delete props.error;
delete props.errored;
delete props.label;
delete props.value;

View File

@ -18,12 +18,6 @@
}
}
&--label {
margin-left: 10px;
font-size: 14px;
user-select: none;
}
&_checked {
.checkbox--icon {
color: $primary-red;

View File

@ -10,7 +10,8 @@ class DropDown extends React.Component {
static propTypes = {
defaultSelectedIndex: React.PropTypes.number,
selectedIndex: React.PropTypes.number,
items: Menu.propTypes.items
items: Menu.propTypes.items,
size: React.PropTypes.oneOf(['small', 'medium', 'large'])
};
static defaultProps = {
@ -66,7 +67,7 @@ class DropDown extends React.Component {
};
return (
<div className="drop-down--list-container" style={style}>
<div className="drop-down__list-container" style={style}>
<Menu {...menuProps} />
</div>
);
@ -76,11 +77,11 @@ class DropDown extends React.Component {
var iconNode = null;
if (item.icon) {
iconNode = <Icon className="drop-down--current-item-icon" name={item.icon} />;
iconNode = <Icon className="drop-down__current-item-icon" name={item.icon} />;
}
return (
<div className="drop-down--current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
<div className="drop-down__current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
{iconNode}{item.content}
</div>
);
@ -91,6 +92,7 @@ class DropDown extends React.Component {
'drop-down': true,
'drop-down_closed': !this.state.opened,
['drop-down_' + this.props.size]: true,
[this.props.className]: (this.props.className)
};

View File

@ -7,19 +7,24 @@
user-select: none;
cursor: pointer;
&--current-item {
&__current-item {
background-color: $light-grey;
border-radius: 4px 4px 0 0;
color: $primary-black;
padding: 6px;
&:focus {
outline: none;
background-color: $medium-grey;
}
}
&--current-item-icon {
&__current-item-icon {
margin-right: 8px;
margin-bottom: 2px;
}
&--list-container {
&__list-container {
position: absolute;
width: 150px;
z-index: 5;
@ -27,8 +32,21 @@
&_closed {
.drop-down--list-container {
.drop-down__list-container {
pointer-events: none;
}
}
&_medium {
width: 200px;
.drop-down__current-item {
border-radius: 4px;
}
.drop-down__list-container {
width: 200px;
border: 1px solid $light-grey;
}
}
}

View File

@ -0,0 +1,151 @@
import React from 'react';
import {EditorState} from 'draft-js';
import classNames from 'classnames';
import _ from 'lodash';
import Input from 'core-components/input';
import DropDown from 'core-components/drop-down';
import Checkbox from 'core-components/checkbox';
import TextEditor from 'core-components/text-editor';
class FormField extends React.Component {
static contextTypes = {
loading: React.PropTypes.bool
};
static propTypes = {
validation: React.PropTypes.string,
onChange: React.PropTypes.func,
onBlur: React.PropTypes.func,
required: React.PropTypes.bool,
error: React.PropTypes.string,
value: React.PropTypes.any,
field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox']),
fieldProps: React.PropTypes.object
};
static defaultProps = {
field: 'input'
};
static getDefaultValue(field) {
if (field === 'input') {
return '';
}
else if (field === 'checkbox') {
return false;
}
else if (field === 'textarea') {
return EditorState.createEmpty();
}
else if (field === 'select') {
return 0;
}
}
render() {
const Wrapper = (this.props.field === 'textarea') ? 'div' : 'label';
const fieldContent = [
<span className="form-field__label" key="label">{this.props.label}</span>,
this.renderField(),
this.renderError()
];
if (this.props.field === 'checkbox') {
fieldContent.swap(0, 1);
}
return (
<Wrapper className={this.getClass()}>
{fieldContent}
</Wrapper>
);
}
renderField() {
const Field = {
'input': Input,
'textarea': TextEditor,
'select': DropDown,
'checkbox': Checkbox
}[this.props.field];
return <Field {...this.getFieldProps()} />;
}
renderError() {
let error = null;
if (this.props.error) {
error = <span className="form-field__error" key="error">{this.props.error}</span>;
}
return error;
}
getClass() {
let classes = {
'form-field': true,
'form-field_errored': (this.props.error),
'form-field_checkbox': (this.props.field === 'checkbox'),
'form-field_select': (this.props.field === 'select'),
[this.props.className]: (this.props.className)
};
return classNames(classes);
}
getFieldProps() {
let props = _.extend({}, this.props.fieldProps, {
disabled: this.context.loading,
errored: !!this.props.error,
name: this.props.name,
placeholder: this.props.placeholder,
key: 'nativeField',
onChange: this.onChange.bind(this),
onBlur: this.props.onBlur,
ref: 'nativeField',
required: this.props.required
});
if (this.props.field === 'select') {
props.selectedIndex = this.props.value;
} else {
props.value = this.props.value;
}
return props;
}
onChange(nativeEvent) {
let event = nativeEvent;
if (this.props.field === 'checkbox') {
event = {
target: {
value: event.target.checked
}
};
}
if (this.props.field === 'select') {
event = {
target: {
value: event.index
}
};
}
if (this.props.onChange) {
this.props.onChange(event)
}
}
focus() {
if (this.refs.nativeField) {
this.refs.nativeField.focus();
}
}
}
export default FormField;

View File

@ -0,0 +1,42 @@
@import "../scss/vars";
.form-field {
display: block;
margin-bottom: 20px;
&__label {
color: $primary-black;
font-size: 15px;
display: block;
padding: 3px 0;
text-align: left;
}
&_errored {
.form-field__error {
color: $primary-red;
font-size: $font-size--sm;
display: block;
position: absolute;
}
}
&_checkbox {
display: inline-block;
margin-bottom: 0;
.form-field__label {
display: inline-block;
margin-left: 10px;
font-size: 14px;
user-select: none;
}
}
&_select {
.form-field__label {
padding-bottom: 10px;
}
}
}

View File

@ -1,12 +1,13 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import {EditorState} from 'draft-js';
import {stateToHTML} from 'draft-js-export-html';
import {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs';
import ValidationFactory from 'lib-app/validations/validations-factory';
import Input from 'core-components/input';
import Checkbox from 'core-components/checkbox';
import FormField from 'core-components/form-field';
class Form extends React.Component {
@ -75,14 +76,14 @@ class Form extends React.Component {
getFieldProps({props, type}) {
let additionalProps = {};
if (type === Input || type === Checkbox) {
if (this.isValidField({type})) {
let fieldName = props.name;
additionalProps = {
ref: fieldName,
value: this.state.form[fieldName] || props.value,
error: this.getFieldError(fieldName),
onChange: this.handleFieldChange.bind(this, fieldName, type),
onChange: this.handleFieldChange.bind(this, fieldName),
onBlur: this.validateField.bind(this, fieldName)
}
}
@ -138,13 +139,8 @@ class Form extends React.Component {
reactDFS(this.props.children, (child) => {
if (this.isValidFieldType(child)) {
if (child.type === Input) {
form[child.props.name] = child.props.value || '';
}
else if (child.type === Checkbox) {
form[child.props.name] = child.props.checked || false;
}
if (this.isValidField(child)) {
form[child.props.name] = child.props.value || FormField.getDefaultValue(child.props.field);
if (child.props.required) {
validations[child.props.name] = ValidationFactory.getValidator(child.props.validation || 'DEFAULT');
@ -161,29 +157,33 @@ class Form extends React.Component {
handleSubmit(event) {
event.preventDefault();
const form = _.mapValues(this.state.form, (field) => {
if (field instanceof EditorState) {
return stateToHTML(field.getCurrentContent());
} else {
return field;
}
});
if (this.hasFormErrors()) {
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this));
} else if (this.props.onSubmit) {
this.props.onSubmit(this.state.form);
this.props.onSubmit(form);
}
}
handleFieldChange(fieldName, type, event) {
handleFieldChange(fieldName, event) {
let form = _.clone(this.state.form);
form[fieldName] = event.target.value;
if (type === Checkbox) {
form[fieldName] = event.target.checked || false;
}
this.setState({
form: form
});
}
isValidFieldType(child) {
return child.type === Input || child.type === Checkbox;
isValidField(node) {
return node.type === FormField;
}
hasFormErrors() {

View File

@ -1,5 +0,0 @@
.form {
.input {
margin-bottom: 20px;
}
}

View File

@ -0,0 +1,27 @@
import React from 'react';
class Header extends React.Component {
static propTypes = {
title: React.PropTypes.string.isRequired,
description: React.PropTypes.string
};
render() {
return (
<div className="header">
<h2 className="header__title">{this.props.title}</h2>
{(this.props.description) ? this.renderDescription() : null}
</div>
)
}
renderDescription() {
return (
<div className="header__description">
{this.props.description}
</div>
)
}
}
export default Header;

View File

@ -0,0 +1,17 @@
@import '../scss/vars';
.header {
margin-bottom: 30px;
text-align: left;
&__title {
margin: 5px 0 14px;
font-size: 24px;
}
&__description {
font-size: 13px;
color: $dark-grey;
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
}

View File

@ -14,7 +14,7 @@ class Input extends React.Component {
value: React.PropTypes.string,
validation: React.PropTypes.string,
onChange: React.PropTypes.func,
inputType: React.PropTypes.string,
size: React.PropTypes.oneOf(['small', 'medium', 'large']),
password: React.PropTypes.bool,
required: React.PropTypes.bool,
icon: React.PropTypes.string,
@ -22,30 +22,18 @@ class Input extends React.Component {
};
static defaultProps = {
inputType: 'primary'
size: 'small'
};
render() {
return (
<label className={this.getClass()}>
<span className="input__label">{this.props.label}</span>
<span className={this.getClass()}>
{this.renderIcon()}
<input {...this.getInputProps()} className="input__text" />
{this.renderError()}
</label>
</span>
);
}
renderError() {
let error = null;
if (this.props.error){
error = <span className="input__error"> {this.props.error} </span>;
}
return error;
}
renderIcon() {
let icon = null;
@ -62,12 +50,12 @@ class Input extends React.Component {
props['aria-required'] = this.props.required;
props.type = (this.props.password) ? 'password' : 'text';
props.ref = 'nativeInput';
props.disabled = this.context.loading;
delete props.errored;
delete props.required;
delete props.validation;
delete props.inputType;
delete props.error;
delete props.errored;
delete props.password;
return props;
@ -77,8 +65,8 @@ class Input extends React.Component {
let classes = {
'input': true,
'input_with-icon': (this.props.icon),
'input_with-error': (this.props.error),
['input_' + this.props.inputType]: true,
'input_errored': (this.props.errored),
['input_' + this.props.size]: true,
[this.props.className]: (this.props.className)
};

View File

@ -19,22 +19,18 @@
}
}
&__label {
color: $primary-black;
font-size: 15px;
display: block;
padding: 3px 0;
text-align: left;
}
&_primary {
&_small {
width: 200px;
}
&_secondary {
&_medium {
width: 250px;
}
&_large {
width: 350px;
}
&_with-icon {
position: relative;
@ -52,16 +48,9 @@
}
}
&_with-error {
.input__error {
color: $primary-red;
font-size: $font-size--sm;
display: block;
position: absolute;
}
&_errored {
.input__text {
border: 1px solid $primary-red;
}
}
}

View File

@ -0,0 +1,118 @@
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import _ from 'lodash';
import {Editor, EditorState, RichUtils} from 'draft-js';
import Button from 'core-components/button';
class TextEditor extends React.Component {
static propTypes = {
errored: React.PropTypes.bool,
onChange: React.PropTypes.func,
value: React.PropTypes.object
};
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
focused: false
};
}
render() {
return (
<div className={this.getClass()}>
{this.renderEditOptions()}
<div className="text-editor__editor" onClick={this.focus.bind(this)} onMouseDown={(event) => event.preventDefault()}>
<span onMouseDown={(event) => event.stopPropagation()}>
<Editor {...this.getEditorProps()} />
</span>
</div>
</div>
);
}
renderEditOptions() {
const onBoldClick = (event) => {
event.preventDefault();
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
};
const onItalicsClick = (event) => {
event.preventDefault();
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'ITALIC'));
};
const onUnderlineClick = (event) => {
event.preventDefault();
this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'UNDERLINE'));
};
return (
<div className="text-editor__options">
<Button type="primary-icon" iconName="bold" onClick={onBoldClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary-icon" iconName="italic" onClick={onItalicsClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
<Button type="primary-icon" iconName="underline" onClick={onUnderlineClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
</div>
)
}
getClass() {
let classes = {
'text-editor': true,
'text-editor_errored': (this.props.errored),
'text-editor_focused': (this.state.focused),
[this.props.className]: (this.props.className)
};
return classNames(classes);
}
getEditorProps() {
return {
editorState: this.props.value || this.state.editorState,
ref: 'editor',
onChange: this.onEditorChange.bind(this),
onFocus: this.onEditorFocus.bind(this),
onBlur: this.onBlur.bind(this)
};
}
onEditorChange(editorState) {
this.setState({editorState});
if (this.props.onChange) {
this.props.onChange({
target: {
value: editorState
}
});
}
}
onEditorFocus(event) {
this.setState({focused: true});
if(this.props.onFocus) {
this.props.onFocus(event)
}
}
onBlur(event) {
this.setState({focused: false});
if(this.props.onBlur) {
this.props.onBlur(event)
}
}
focus() {
if (this.refs.editor) {
this.refs.editor.focus();
}
}
}
export default TextEditor;

View File

@ -0,0 +1,39 @@
@import "../scss/vars";
.text-editor {
&__editor {
border: 1px solid $grey;
border-radius: 3px;
padding: 8px;
width: 100%;
height: 200px;
text-align: left;
overflow: auto;
&:hover {
border-color: $medium-grey;
}
}
&__options {
text-align: left;
margin-bottom: 5px;
.button {
margin-right: 3px;
}
}
&_focused {
.text-editor__editor {
border-color: $primary-blue;
}
}
&_errored {
.text-editor__editor {
border: 1px solid $primary-red;
}
}
}

View File

@ -0,0 +1,25 @@
module.exports = [
{
path: '/ticket/create',
time: 2000,
response: function (data) {
let response;
if (data.title !== 'error') {
response = {
status: 'success',
data: {
'ticketNumber': 121444
}
};
} else {
response = {
status: 'fail',
message: 'Ticket could not be created'
};
}
return response;
}
}
];

View File

@ -9,23 +9,29 @@ export default {
'NEW_PASSWORD': 'New password',
'REPEAT_NEW_PASSWORD': 'Repeat new password',
'BACK_LOGIN_FORM': 'Back to login form',
'TICKET_LIST': 'Ticket List',
'CREATE_TICKET': 'Create Ticket',
'VIEW_ARTICLES': 'View Articles',
'EDIT_PROFILE': 'Edit Profile',
'CLOSE_SESSION': 'Close session',
'CREATE_TICKET': 'Create Ticket',
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
'TICKET_LIST': 'Ticket List',
'TICKET_LIST_DESCRIPTION': 'Here you can find a list of all tickets you have sent to our support team.',
//ERRORS
'EMAIL_NOT_EXIST': 'Email does not exist',
'ERROR_EMPTY': 'Invalid value',
'ERROR_PASSWORD': 'Invalid password',
'ERROR_NAME': 'Invalid name',
'ERROR_TITLE': 'Invalid title',
'ERROR_EMAIL': 'Invalid email',
'ERROR_CONTENT_SHORT': 'Content too short',
'PASSWORD_NOT_MATCH': 'Password does not match',
'INVALID_RECOVER': 'Invalid recover data',
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
'TICKET_SENT': 'Ticket has been created successfully.',
'VALID_RECOVER': 'Password recovered successfully',
'EMAIL_EXISTS': 'Email already exists, please try to log in or recover password'
};

View File

@ -7,6 +7,13 @@ import ConfigActions from 'actions/config-actions';
import routes from 'app/Routes';
import store from 'app/store';
Array.prototype.swap = function (x,y) {
var b = this[x];
this[x] = this[y];
this[y] = b;
return this;
};
if ( process.env.NODE_ENV !== 'production' ) {
// Enable React devtools
window.React = React;

View File

@ -17,6 +17,7 @@ let fixtures = (function () {
// FIXTURES
fixtures.add(require('data/fixtures/user-fixtures'));
fixtures.add(require('data/fixtures/ticket-fixtures'));
fixtures.add(require('data/fixtures/system-fixtures'));
_.each(fixtures.getAll(), function (fixture) {

View File

@ -1,3 +1,5 @@
import {EditorState} from 'draft-js';
import Validator from 'lib-app/validations/validator';
class LengthValidator extends Validator {
@ -9,6 +11,10 @@ class LengthValidator extends Validator {
}
validate(value, form) {
if (value instanceof EditorState) {
value = value.getCurrentContent().getPlainText();
}
if (value.length < this.minlength) return this.getError(this.errorKey);
}
}

View File

@ -7,7 +7,9 @@ import LengthValidator from 'lib-app/validations/length-validator';
let validators = {
'DEFAULT': new Validator(),
'NAME': new AlphaNumericValidator('ERROR_NAME', new LengthValidator(2, 'ERROR_NAME')),
'TITLE': new AlphaNumericValidator('ERROR_TITLE', new LengthValidator(2, 'ERROR_TITLE')),
'EMAIL': new EmailValidator(),
'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'),
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
'REPEAT_PASSWORD': new RepeatPasswordValidator()
};

View File

@ -1,4 +1,6 @@
const i18n = require('lib-app/i18n');
import {EditorState} from 'draft-js';
import i18n from 'lib-app/i18n';
class Validator {
constructor(validator = null) {
@ -18,7 +20,11 @@ class Validator {
}
validate(value, form) {
if (!value.length) return this.getError('ERROR_EMPTY');
if (value instanceof EditorState) {
value = value.getCurrentContent().getPlainText()
}
if (value.length === 0) return this.getError('ERROR_EMPTY');
}
getError(errorKey) {

View File

@ -34,4 +34,11 @@ global.reRenderIntoDocument = (function () {
})();
global.ReduxMock = {
connect: stub().returns(stub().returnsArg(0))
};
};
Array.prototype.swap = function (x,y) {
var b = this[x];
this[x] = this[y];
this[y] = b;
return this;
};