Skip to content

Displays label properly on <SLDSInput> when both label and assistiveText are defined #337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/forms/input/check-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
/* eslint-disable import/no-mutable-exports */

import oneOfRequiredProperty from '../../../utilities/warning/one-of-required-property';
import onlyOneOfProperties from '../../../utilities/warning/only-one-of-properties';

let checkProps = function () {};

Expand All @@ -21,6 +22,10 @@ if (process.env.NODE_ENV !== 'production') {
assistiveText: props.assistiveText,
label: props.label
});
onlyOneOfProperties(COMPONENT, {
assistiveText: props.assistiveText,
label: props.label
});
/* eslint-enable max-len */
};
}
Expand Down
17 changes: 11 additions & 6 deletions components/forms/input/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const Input = React.createClass({
const hasIcon = iconCategory && iconName;

// One of these is required to pass accessibility tests
const labelText = assistiveText || label;
const labelText = label || assistiveText;

return (
<div
Expand All @@ -201,14 +201,19 @@ const Input = React.createClass({
},
className)}
>
{labelText && (!readOnly
? <label className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText })} htmlFor={id}>
{labelText && (readOnly
? <span
className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText && !label })}
>
{labelText}
</span>
: <label
className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText && !label })} htmlFor={id}
>
{required && <abbr className="slds-required" title="required">*</abbr>}
{labelText}
</label>
: <span className={classNames('slds-form-element__label', { 'slds-assistive-text': assistiveText })}>
{labelText}
</span>)}
)}
<div
className={classNames('slds-form-element__control', hasIcon && [
'slds-input-has-icon',
Expand Down
1 change: 1 addition & 0 deletions stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export default from './button';
export default from './context-bar';
export default from './data-table';
export default from './icon';
export default from './input';
53 changes: 53 additions & 0 deletions stories/input/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';

import { FORMS_INPUT } from '../../utilities/constants';
import Input from '../../components/forms/input';

const iconClicked = action;

storiesOf(FORMS_INPUT, module)
.addDecorator(getStory => <div className="slds-p-around--medium">{getStory()}</div>)
.add('Standard Input', () => (
<Input
id="unique-id-1"
label="Input Label"
placeholder="Placeholder Text"
/>
))
.add('Assistive Text, No Label', () => (
<Input
id="unique-id-1"
assistiveText="Assistive Text"
placeholder="Placeholder Text"
/>
))
.add('Input with Clickable Icon', () => (
<Input
id="unique-id-2"
label="Input Label"
iconName="close"
iconCategory="utility"
iconPosition="right"
onIconClick={iconClicked('Clear icon clicked')}
placeholder="Placeholder Text"
/>
))
.add('Read Only Input', () => (
<Input
id="unique-id-3"
label="Input Label"
readOnly
value="Read Only Value"
/>
))
.add('Required Input in Error State', () => (
<Input
id="unique-id-4"
label="Input Label"
required
errorText="Error Message"
placeholder="Placeholder Text"
/>
));

172 changes: 172 additions & 0 deletions tests/input/input.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { expect } from 'chai';
import assign from 'lodash.assign';
import TestUtils from 'react-addons-test-utils';

import { SLDSInput } from '../../components';
const {
// Simulate,
findRenderedDOMComponentWithTag,
scryRenderedDOMComponentsWithTag,
findRenderedDOMComponentWithClass
} = TestUtils;

describe('SLDS INPUT **************************************************', () => {
const defaultProps = {
id: 'unique-id-2',
placeholder: 'Placeholder Text'
};

let body;

const renderInput = instance => {
body = document.createElement('div');
document.body.appendChild(body);
return ReactDOM.render(instance, body);
};

function removeInput () {
ReactDOM.unmountComponentAtNode(body);
document.body.removeChild(body);
}

const createInput = (props) => React.createElement(SLDSInput, assign({}, defaultProps, props));
const getInput = (props) => renderInput(createInput(props));

describe('Standard Input with Label', () => {
let component;
let wrapper;
let input;
let label;

beforeEach(() => {
component = getInput({ label: 'Input Label' });
wrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element');
input = findRenderedDOMComponentWithTag(component, 'input');
label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
});

afterEach(() => {
removeInput();
});

it('is wrapped in div with class "slds-form-element"', () => {
expect(wrapper.className).to.include('slds-form-element');
});

it('renders label', () => {
expect(label.textContent).to.equal('Input Label');
});

it('renders input element with class "slds-input"', () => {
expect(input.className).to.include('slds-input');
});

it('has an id', () => {
expect(input.getAttribute('id')).to.be.ok;
});

it('renders placeholder text', () => {
expect(input.getAttribute('placeholder')).to.equal('Placeholder Text');
});
});

describe('Input without Label', () => {
let component;
let label;

beforeEach(() => {
component = getInput({ assistiveText: 'Assistive Label' });
label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
});

afterEach(() => {
removeInput();
});

it('renders label (assitive)', () => {
expect(label.textContent).to.equal('Assistive Label');
});

it('label has class "slds-assistive-text"', () => {
expect(label.className).to.include('slds-assistive-text');
});
});

describe('Read Only Input', () => {
let component;
let label;
let input;

beforeEach(() => {
component = getInput({ label: 'Input Label', readOnly: true });
label = scryRenderedDOMComponentsWithTag(component, 'span')[0];
input = scryRenderedDOMComponentsWithTag(component, 'span')[1];
});

afterEach(() => {
removeInput();
});

it('label is a span and has class "slds-form-element__label"', () => {
expect(label.className).to.include('slds-form-element__label');
});

it('input is a span and has class "slds-form-element__static"', () => {
expect(input.className).to.include('slds-form-element__static');
});
});

describe('Required Input in Error State', () => {
let component;
let wrapper;
let error;

beforeEach(() => {
component = getInput({ label: 'Input Label', required: true, errorText: 'Error Message' });
wrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element');
error = findRenderedDOMComponentWithClass(component, 'slds-form-element__help');
});

afterEach(() => {
removeInput();
});

it('input wrapper has class "is-required"', () => {
expect(wrapper.className).to.include('is-required');
});

it('input wrapper has class "slds-has-error"', () => {
expect(wrapper.className).to.include('slds-has-error');
});

it('renders error message', () => {
expect(error.textContent).to.equal('Error Message');
});
});


// describe('Input with Clickable Icon', () => {
// let component;
// let input;
// let label;
// let icon;

// beforeEach(() => {
// component = getInput({ label: 'Input Label' });
// input = findRenderedDOMComponentWithTag(component, 'input');
// inputWrapper = findRenderedDOMComponentWithClass(component, 'slds-form-element__control');
// label = findRenderedDOMComponentWithClass(component, 'slds-form-element__label');
// icon = findRenderedDOMComponentWithTag(component, 'svg');
// });

// afterEach(() => {
// removeInput();
// });

// // input wrapper has class "slds-input-has-icon"
// // icon has class "slds-input__icon"
// // icon can be clicked (Simulate)
// });
});
35 changes: 35 additions & 0 deletions utilities/warning/only-one-of-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright (c) 2015, salesforce.com, inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* eslint-disable indent, import/no-mutable-exports */

// This function will deliver an warning message to the browser console if extraneous properties are defined (falsey).
import warning from 'warning';

let onlyOneOf = function () {};

if (process.env.NODE_ENV !== 'production') {
const hasWarned = {};

onlyOneOf = function (control, selectedProps, comment) {
const additionalComment = comment ? ` ${comment}` : '';
let keys = Object.keys(selectedProps);
keys = keys.filter((key) => selectedProps[key]);

if (!hasWarned[control]) {
/* eslint-disable max-len */
warning((keys.length === 1), `[Design System React] Only one of the following props is needed by ${control}: [${keys.join()}].${additionalComment}`);
/* eslint-enable max-len */
hasWarned[control] = !!selectedProps;
}
};
}

export default onlyOneOf;