@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
613 lines (516 loc) • 15.6 kB
JSX
/* eslint-disable max-lines */
/* eslint-disable react/no-render-return-value */
import React from 'react';
import ReactDOM from 'react-dom';
import { expect } from 'chai';
import assign from 'lodash.assign';
import TestUtils from 'react-dom/test-utils';
import Input from '../../input';
import Icon from '../../icon';
import InputIcon from '../../icon/input-icon';
import IconSettings from '../../icon-settings';
describe('SLDSInput', () => {
const defaultProps = {
placeholder: 'Placeholder Text',
};
let body;
const renderInput = (instance) => {
body = document.createElement('div');
document.body.appendChild(body);
/* deepscan-disable REACT_ASYNC_RENDER_RETURN_VALUE */
return ReactDOM.render(
<div>
<IconSettings iconPath="/assets/icons">{instance}</IconSettings>
</div>,
body
);
/* deepscan-enable REACT_ASYNC_RENDER_RETURN_VALUE */
};
function removeInput() {
ReactDOM.unmountComponentAtNode(body);
document.body.removeChild(body);
}
const createInput = (props) =>
React.createElement(Input, 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', id: 'custom-id' });
[wrapper] = component.getElementsByClassName('slds-form-element');
[input] = component.getElementsByTagName('input');
[label] = component.getElementsByClassName('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('can pass custom id', () => {
expect(input.getAttribute('id')).to.equal('custom-id');
});
it('renders placeholder text', () => {
expect(input.getAttribute('placeholder')).to.equal('Placeholder Text');
});
it('has associated label for tag pointing to id of input', () => {
const labelForTag = label.getAttribute('for');
const inputId = input.getAttribute('id');
expect(labelForTag).to.equal(inputId);
});
});
describe('Input without Assistive Text Label', () => {
let component;
let label;
let input;
beforeEach(() => {
component = getInput({ assistiveText: { label: 'Assistive Label' } });
[input] = component.getElementsByTagName('input');
[label] = component.getElementsByClassName('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');
});
it('has associated label for tag pointing to id of input', () => {
const labelForTag = label.getAttribute('for');
const inputId = input.getAttribute('id');
expect(labelForTag).to.equal(inputId);
});
});
describe('Read Only Input', () => {
let component;
let label;
let input;
beforeEach(() => {
component = getInput({ label: 'Input Label', readOnly: true });
[input] = component.getElementsByTagName('input');
[label] = component.getElementsByTagName('label');
});
afterEach(() => {
removeInput();
});
it('label has class "slds-form-element__label"', () => {
expect(label.className).to.include('slds-form-element__label');
});
it('input has attribute "readonly"', () => {
expect(input.getAttribute('readonly')).to.equal('');
});
});
describe('Static Input', () => {
let component;
let label;
let input;
beforeEach(() => {
component = getInput({ label: 'Input Label', isStatic: true });
[label] = component.getElementsByTagName('span');
// eslint-disable-next-line prefer-destructuring
input = component.getElementsByTagName('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('Disabled Input', () => {
let component;
let input;
beforeEach(() => {
component = getInput({ label: 'Input Label', disabled: true });
[input] = component.getElementsByTagName('input');
});
afterEach(() => {
removeInput();
});
it('input has attribute "disabled"', () => {
expect(input.getAttribute('disabled')).to.equal('');
});
});
describe('Multiple Inputs', () => {
let component1;
let component2;
let input1;
let input2;
beforeEach(() => {
component1 = getInput({
className: 'input-one',
label: 'Input One',
});
component2 = getInput({ className: 'input-two', label: 'Input Two' });
[input1] = component1.getElementsByTagName('input');
[input2] = component2.getElementsByTagName('input');
});
afterEach(() => {
const inputNodes = document.querySelectorAll('.slds-form-element');
inputNodes[0].parentNode.remove(inputNodes[0]);
inputNodes[1].parentNode.remove(inputNodes[1]);
});
it('each input has unique generated id', () => {
expect(input1.getAttribute('id')).to.not.equal(input2.getAttribute('id'));
});
});
describe('Required Input in Error State', () => {
let component;
let wrapper;
let error;
let input;
beforeEach(() => {
component = getInput({
label: 'Input Label',
required: true,
errorText: 'Error Message',
});
[wrapper] = component.getElementsByClassName('slds-form-element');
[input] = component.getElementsByTagName('input');
[error] = component.getElementsByClassName('slds-form-element__help');
});
afterEach(() => {
removeInput();
});
it('input wrapper contains an <abbr> that has class "slds-required"', () => {
expect(component.getElementsByTagName('abbr')[0].className).to.include(
'slds-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');
});
it('has associated aria-describedby pointing to id of error message', () => {
const inputDescribedby = input.getAttribute('aria-describedby');
const errorId = error.getAttribute('id');
expect(inputDescribedby).to.equal(errorId);
});
});
describe('Input with Left Clickable Icon', () => {
let component;
let elementControl;
let leftButton;
let iconAssistiveText;
const clickCallback = sinon.spy();
beforeEach(() => {
component = getInput({
iconLeft: (
<InputIcon
assistiveText={{ icon: 'Passed assistive text to icon' }}
name="search"
category="utility"
onClick={clickCallback}
/>
),
});
[leftButton] = component.getElementsByTagName('button');
[iconAssistiveText] = component.getElementsByClassName(
'slds-assistive-text'
);
[elementControl] = component.getElementsByClassName(
'slds-form-element__control'
);
});
afterEach(() => {
removeInput();
});
it('element control has class "slds-input-has-icon"', () => {
expect(elementControl.className).to.include('slds-input-has-icon');
});
it('passes "assitiveText" down to icon', () => {
expect(iconAssistiveText.textContent).to.equal(
'Passed assistive text to icon'
);
});
it('icon renders button BEFORE input in DOM', () => {
const render = elementControl.innerHTML;
expect(render.indexOf('<button')).to.be.below(render.indexOf('<input'));
});
it('icon can be clicked', () => {
TestUtils.Simulate.click(leftButton);
expect(clickCallback.calledOnce).to.be.true;
});
});
describe('Input with Right Clickable Icon', () => {
let component;
let elementControl;
let leftButton;
const clickCallback = sinon.spy();
beforeEach(() => {
component = getInput({
iconRight: (
<InputIcon
assistiveText={{ icon: 'Passed assistive text to icon' }}
name="search"
category="utility"
onClick={clickCallback}
/>
),
});
[leftButton] = component.getElementsByTagName('button');
[elementControl] = component.getElementsByClassName(
'slds-form-element__control'
);
});
afterEach(() => {
removeInput();
});
it('element control has class "slds-input-has-icon"', () => {
expect(elementControl.className).to.include('slds-input-has-icon');
});
it('icon renders button AFTER input in DOM', () => {
const render = elementControl.innerHTML;
expect(render.indexOf('<button')).to.be.above(render.indexOf('<input'));
});
it('icon can be clicked', () => {
TestUtils.Simulate.click(leftButton);
expect(clickCallback.calledOnce).to.be.true;
});
});
describe('Input with Non-Clickable Icon', () => {
let component;
let elementControl;
beforeEach(() => {
component = getInput({
iconRight: <Icon name="search" category="utility" />,
});
[elementControl] = component.getElementsByClassName(
'slds-form-element__control'
);
});
afterEach(() => {
removeInput();
});
it('button tag does not exist', () => {
expect(elementControl.getElementsByTagName('button')[0]).to.not.be.ok;
});
});
describe('Input with Loading Spinner Icon', () => {
let component;
let spinner;
let input;
beforeEach(() => {
component = getInput({
assistiveText: { label: 'Passed assistive text to icon' },
hasSpinner: true,
iconRight: (
<InputIcon
assistiveText={{ icon: 'Passed assistive text to icon' }}
name="search"
category="utility"
/>
),
id: 'unique-id-4',
label: 'Input Label',
});
[spinner] = component.getElementsByClassName('slds-spinner');
[input] = component.getElementsByTagName('input');
});
afterEach(() => {
removeInput();
});
it('renders loading spinner icon', () => {
expect(spinner).to.be.ok;
});
it('input aria-describedby points to id of spinner)', () => {
const spinnerId = spinner.getAttribute('id');
const inputDescribedby = input.getAttribute('aria-describedby');
expect(inputDescribedby).to.include(spinnerId);
});
it('input aria-describedby points to id of spinner AND id of error message)', () => {
const spinnerId = spinner.getAttribute('id');
const inputDescribedby = input.getAttribute('aria-describedby');
expect(inputDescribedby).to.include(spinnerId);
});
});
describe('Input with Loading Spinner Icon & Error', () => {
let component;
let spinner;
let input;
let error;
beforeEach(() => {
component = getInput({
assistiveText: { label: 'Passed assistive text to icon' },
required: true,
errorText: 'Error Message',
hasSpinner: true,
iconRight: (
<InputIcon
assistiveText={{ icon: 'Passed assistive text to icon' }}
name="search"
category="utility"
/>
),
id: 'unique-id-4',
label: 'Input Label',
});
[spinner] = component.getElementsByClassName('slds-spinner');
[input] = component.getElementsByTagName('input');
[error] = component.getElementsByClassName('slds-form-element__help');
});
afterEach(() => {
removeInput();
});
it('input aria-describedby points to id of spinner AND id of error message)', () => {
const errorId = error.getAttribute('id');
const spinnerId = spinner.getAttribute('id');
const inputDescribedby = input.getAttribute('aria-describedby');
expect(inputDescribedby).to.equal(`${spinnerId} ${errorId}`);
});
});
describe('Input with Fixed Left Text', () => {
let component;
let fixedTextLeft;
beforeEach(() => {
component = getInput({
fixedTextLeft: '$',
id: 'unique-id-5',
label: 'Input Label',
});
[fixedTextLeft] = component.getElementsByClassName(
'slds-form-element__addon'
);
});
afterEach(() => {
removeInput();
});
it('renders fixed text node', () => {
expect(fixedTextLeft).to.be.ok;
});
it('renders fixed text node content', () => {
expect(fixedTextLeft.textContent).to.equal('$');
});
});
describe('Counter Input', () => {
let changeOccurred;
let component;
let decrement;
let increment;
let onChangeData;
afterEach(() => {
removeInput();
});
it('increments and decrements as expected', () => {
component = getInput({
onChange: (event, data) => {
onChangeData = data;
},
value: 1,
variant: 'counter',
});
[decrement] = component.getElementsByClassName(
'slds-input__button_decrement'
);
[increment] = component.getElementsByClassName(
'slds-input__button_increment'
);
onChangeData = {};
TestUtils.Simulate.mouseDown(increment);
TestUtils.Simulate.mouseUp(increment);
expect(onChangeData.number === 2 && onChangeData.value === '2').to.be
.true;
onChangeData = {};
TestUtils.Simulate.keyDown(increment, {
key: 'Enter',
keyCode: 13,
which: 13,
});
TestUtils.Simulate.keyUp(increment, {
key: 'Enter',
keyCode: 13,
which: 13,
});
expect(onChangeData.number === 2 && onChangeData.value === '2').to.be
.true;
TestUtils.Simulate.mouseDown(decrement);
TestUtils.Simulate.mouseUp(decrement);
expect(onChangeData.number === 0 && onChangeData.value === '0').to.be
.true;
onChangeData = {};
TestUtils.Simulate.keyDown(decrement, {
key: 'Enter',
keyCode: 13,
which: 13,
});
TestUtils.Simulate.keyUp(decrement, {
key: 'Enter',
keyCode: 13,
which: 13,
});
expect(onChangeData.number === 0 && onChangeData.value === '0').to.be
.true;
});
it('respects min and max values', () => {
component = getInput({
maxValue: 1,
minValue: 1,
onChange: () => {
changeOccurred = true;
},
value: 1,
variant: 'counter',
});
[decrement] = component.getElementsByClassName(
'slds-input__button_decrement'
);
[increment] = component.getElementsByClassName(
'slds-input__button_increment'
);
changeOccurred = false;
TestUtils.Simulate.mouseDown(increment);
TestUtils.Simulate.mouseUp(increment);
expect(changeOccurred).to.be.false;
expect(increment.disabled).to.be.true;
changeOccurred = false;
TestUtils.Simulate.mouseDown(decrement);
TestUtils.Simulate.mouseUp(decrement);
expect(changeOccurred).to.be.false;
expect(decrement.disabled).to.be.true;
});
it('acknowledges custom step values', () => {
component = getInput({
onChange: (event, data) => {
onChangeData = data;
},
step: 0.1,
value: 1,
variant: 'counter',
});
[decrement] = component.getElementsByClassName(
'slds-input__button_decrement'
);
[increment] = component.getElementsByClassName(
'slds-input__button_increment'
);
onChangeData = {};
TestUtils.Simulate.mouseDown(increment);
TestUtils.Simulate.mouseUp(increment);
expect(onChangeData.number === 1.1 && onChangeData.value === '1.1').to.be
.true;
TestUtils.Simulate.mouseDown(decrement);
TestUtils.Simulate.mouseUp(decrement);
expect(onChangeData.number === 0.9 && onChangeData.value === '0.9').to.be
.true;
});
});
});