More coverage

This commit is contained in:
Josh Johnson 2017-12-10 18:00:57 +00:00
parent 43417510cd
commit e79699facd
14 changed files with 526 additions and 144 deletions

View file

@ -87,7 +87,8 @@
"src/**/**/**/**/**/*.js"
],
"exclude": [
"src/**/**/**/**/**/*.test.js"
"src/**/**/**/**/**/*.test.js",
"src/scripts/src/lib/polyfills.js"
]
}
}

View file

@ -1,3 +1,5 @@
import { getWindowHeight } from '../lib/utils';
export default class Container {
constructor(instance, element, classNames) {
this.parentInstance = instance;
@ -55,26 +57,15 @@ export default class Container {
* @param {Number} dropdownPos
* @returns
*/
shouldFlip(dropdownPos) {
shouldFlip(dropdownPos, windowHeight = getWindowHeight()) {
if (dropdownPos === undefined) {
return false;
}
const body = document.body;
const html = document.documentElement;
const winHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
);
// If flip is enabled and the dropdown bottom position is
// greater than the window height flip the dropdown.
let shouldFlip = false;
if (this.config.position === 'auto') {
shouldFlip = dropdownPos >= winHeight;
shouldFlip = dropdownPos >= windowHeight;
} else if (this.config.position === 'top') {
shouldFlip = true;
}

View file

@ -18,16 +18,18 @@ describe('components/container', () => {
instance = new Container(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
});
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
});
describe('getElement', () => {
@ -90,7 +92,43 @@ describe('components/container', () => {
});
});
// describe('shouldFlip', () => {});
describe('shouldFlip', () => {
describe('not passing dropdownPos', () => {
it('returns false', () => {
expect(instance.shouldFlip()).to.equal(false);
});
});
describe('passing dropdownPos', () => {
describe('position config option set to "auto"', () => {
beforeEach(() => {
instance.config.position = 'auto';
});
describe('dropdownPos is greater than window height', () => {
it('returns false', () => {
expect(instance.shouldFlip(100, 1000)).to.equal(false);
});
});
describe('dropdownPos is less than window height', () => {
it('returns true', () => {
expect(instance.shouldFlip(100, 50)).to.equal(true);
});
});
});
describe('position config option set to "top"', () => {
beforeEach(() => {
instance.config.position = 'top';
});
it('returns true', () => {
expect(instance.shouldFlip(100)).to.equal(true);
});
});
});
});
describe('setActiveDescendant', () => {
it('sets element\'s aria-activedescendant attribute with passed descendant ID', () => {

View file

@ -20,16 +20,18 @@ describe('components/dropdown', () => {
instance = new Dropdown(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
});
it('assigns choices instance to instance', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
describe('constructor', () => {
it('assigns choices instance to instance', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to instance', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns choices element to instance', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns classnames to instance', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
it('assigns classnames to instance', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
});
describe('getElement', () => {

View file

@ -152,30 +152,6 @@ describe('components/input', () => {
});
});
// describe('activate', () => {
// describe('when passed focusInput argument is true, canSearch is true and current element is not in focus', () => {
// let focusSpy;
// beforeEach(() => {
// instance.parentInstance.canSearch = true;
// focusSpy = spy(instance.element, 'focus');
// });
// afterEach(() => {
// focusSpy.restore();
// });
// it('focuses element', () => {
// expect(focusSpy.callCount).to.equal(0);
// instance.activate(true);
// expect(focusSpy.callCount).to.equal(1);
// });
// });
// });
describe('deactivate', () => {
});
describe('enable', () => {
beforeEach(() => {
instance.element.setAttribute('disabled', '');
@ -219,10 +195,49 @@ describe('components/input', () => {
focusStub.restore();
});
it('focuses element if isFocussed flag is set to false', () => {
instance.isFocussed = false;
instance.focus();
expect(focusStub.callCount).to.equal(1);
describe('when element is not focussed', () => {
it('focuses element if isFocussed flag is set to false', () => {
instance.isFocussed = true;
instance.focus();
expect(focusStub.callCount).to.equal(0);
});
});
describe('when element is focussed', () => {
it('focuses element if isFocussed flag is set to false', () => {
instance.isFocussed = false;
instance.focus();
expect(focusStub.callCount).to.equal(1);
});
});
});
describe('blur', () => {
let blurStub;
beforeEach(() => {
blurStub = stub(instance.element, 'blur');
});
afterEach(() => {
blurStub.restore();
});
describe('when element is not focussed', () => {
it('doesn\'t blur element', () => {
instance.isFocussed = false;
instance.blur();
expect(blurStub.callCount).to.equal(0);
});
});
describe('when element is focussed', () => {
it('blurs element', () => {
instance.isFocussed = true;
instance.blur();
expect(blurStub.callCount).to.equal(1);
});
});
});

View file

@ -17,16 +17,18 @@ describe('components/list', () => {
instance = new List(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
});
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
describe('constructor', () => {
it('assigns choices instance to class', () => {
expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement);
});
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
});
describe('getElement', () => {
@ -46,11 +48,21 @@ describe('components/list', () => {
});
describe('scrollTo', () => {
it('scrolls element to passed position', () => {
const scrollPosition = 20;
expect(instance.element.scrollTop).to.equal(0);
instance.scrollTo(scrollPosition);
expect(instance.element.scrollTop).to.equal(scrollPosition);
describe('passing position', () => {
it('scrolls element to passed position', () => {
const scrollPosition = 20;
expect(instance.element.scrollTop).to.equal(0);
instance.scrollTo(scrollPosition);
expect(instance.element.scrollTop).to.equal(scrollPosition);
});
});
describe('not passing position', () => {
it('scrolls element to default position', () => {
expect(instance.element.scrollTop).to.equal(0);
instance.scrollTo();
expect(instance.element.scrollTop).to.equal(0);
});
});
});

View file

@ -29,6 +29,8 @@ export const DEFAULT_CLASSNAMES = {
};
export const DEFAULT_CONFIG = {
items: [],
choices: [],
silent: false,
renderChoiceLimit: -1,
maxItemCount: -1,
@ -59,9 +61,9 @@ export const DEFAULT_CONFIG = {
noResultsText: 'No results found',
noChoicesText: 'No choices to choose from',
itemSelectText: 'Press to select',
uniqueItemText: 'Only unique values can be added.',
addItemText: value => `Press Enter to add <b>"${value}"</b>`,
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`,
uniqueItemText: 'Only unique values can be added.',
fuseOptions: {
includeScore: true,
},

View file

@ -0,0 +1,155 @@
import { expect } from 'chai';
import {
DEFAULT_CLASSNAMES,
DEFAULT_CONFIG,
EVENTS,
ACTION_TYPES,
KEY_CODES,
SCROLLING_SPEED,
} from './constants';
describe('constants', () => {
describe('type checks', () => {
describe('DEFAULT_CLASSNAMES', () => {
it('exports as an object with expected keys', () => {
expect(DEFAULT_CLASSNAMES).to.be.an('object');
expect(Object.keys(DEFAULT_CLASSNAMES)).to.eql([
'containerOuter',
'containerInner',
'input',
'inputCloned',
'list',
'listItems',
'listSingle',
'listDropdown',
'item',
'itemSelectable',
'itemDisabled',
'itemChoice',
'placeholder',
'group',
'groupHeading',
'button',
'activeState',
'focusState',
'openState',
'disabledState',
'highlightedState',
'hiddenState',
'flippedState',
'loadingState',
'noResults',
'noChoices',
]);
});
});
describe('DEFAULT_CONFIG', () => {
it('exports as an object', () => {
expect(DEFAULT_CONFIG).to.be.an('object');
});
it('has expected config options', () => {
expect(DEFAULT_CONFIG.items).to.be.an('array');
expect(DEFAULT_CONFIG.choices).to.be.an('array');
expect(DEFAULT_CONFIG.silent).to.be.a('boolean');
expect(DEFAULT_CONFIG.renderChoiceLimit).to.be.a('number');
expect(DEFAULT_CONFIG.maxItemCount).to.be.a('number');
expect(DEFAULT_CONFIG.addItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.removeItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.removeItemButton).to.be.a('boolean');
expect(DEFAULT_CONFIG.editItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.duplicateItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.delimiter).to.be.a('string');
expect(DEFAULT_CONFIG.paste).to.be.a('boolean');
expect(DEFAULT_CONFIG.searchEnabled).to.be.a('boolean');
expect(DEFAULT_CONFIG.searchChoices).to.be.a('boolean');
expect(DEFAULT_CONFIG.searchFloor).to.be.a('number');
expect(DEFAULT_CONFIG.searchResultLimit).to.be.a('number');
expect(DEFAULT_CONFIG.searchFields).to.be.an('array');
expect(DEFAULT_CONFIG.position).to.be.a('string');
expect(DEFAULT_CONFIG.regexFilter).to.equal(null);
expect(DEFAULT_CONFIG.shouldSort).to.be.a('boolean');
expect(DEFAULT_CONFIG.shouldSortItems).to.be.a('boolean');
expect(DEFAULT_CONFIG.placeholder).to.be.a('boolean');
expect(DEFAULT_CONFIG.placeholderValue).to.equal(null);
expect(DEFAULT_CONFIG.searchPlaceholderValue).to.equal(null);
expect(DEFAULT_CONFIG.prependValue).to.equal(null);
expect(DEFAULT_CONFIG.appendValue).to.equal(null);
expect(DEFAULT_CONFIG.renderSelectedChoices).to.be.a('string');
expect(DEFAULT_CONFIG.loadingText).to.be.a('string');
expect(DEFAULT_CONFIG.noResultsText).to.be.a('string');
expect(DEFAULT_CONFIG.noChoicesText).to.be.a('string');
expect(DEFAULT_CONFIG.itemSelectText).to.be.a('string');
expect(DEFAULT_CONFIG.uniqueItemText).to.be.a('string');
expect(DEFAULT_CONFIG.addItemText).to.be.a('function');
expect(DEFAULT_CONFIG.maxItemText).to.be.a('function');
expect(DEFAULT_CONFIG.fuseOptions).to.be.an('object');
expect(DEFAULT_CONFIG.callbackOnInit).to.equal(null);
expect(DEFAULT_CONFIG.callbackOnCreateTemplates).to.equal(null);
});
});
describe('EVENTS', () => {
it('exports as an object with expected keys', () => {
expect(EVENTS).to.be.an('object');
expect(Object.keys(EVENTS)).to.eql([
'showDropdown',
'hideDropdown',
'change',
'choice',
'search',
'addItem',
'removeItem',
'highlightItem',
]);
});
});
describe('ACTION_TYPES', () => {
it('exports as an object with expected keys', () => {
expect(ACTION_TYPES).to.be.an('object');
expect(Object.keys(ACTION_TYPES)).to.eql([
'ADD_CHOICE',
'FILTER_CHOICES',
'ACTIVATE_CHOICES',
'CLEAR_CHOICES',
'ADD_GROUP',
'ADD_ITEM',
'REMOVE_ITEM',
'HIGHLIGHT_ITEM',
'CLEAR_ALL',
]);
});
});
describe('KEY_CODES', () => {
it('exports as an object with expected keys', () => {
expect(KEY_CODES).to.be.an('object');
expect(Object.keys(KEY_CODES)).to.eql([
'BACK_KEY',
'DELETE_KEY',
'ENTER_KEY',
'A_KEY',
'ESC_KEY',
'UP_KEY',
'DOWN_KEY',
'PAGE_UP_KEY',
'PAGE_DOWN_KEY',
]);
});
it('exports each value as a number', () => {
Object.keys(KEY_CODES).forEach((key) => {
expect(KEY_CODES[key]).to.be.a('number');
});
});
});
describe('SCROLLING_SPEED', () => {
it('exports as an number', () => {
expect(SCROLLING_SPEED).to.be.a('number');
});
});
});
});

View file

@ -582,3 +582,15 @@ export const regexFilter = (value, regex) => {
const expression = new RegExp(regex.source, 'i');
return expression.test(value);
};
export const getWindowHeight = () => {
const body = document.body;
const html = document.documentElement;
return Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
);
};

View file

@ -8,18 +8,36 @@ describe('reducers/choices', () => {
describe('when choices do not exist', () => {
describe('ADD_CHOICE', () => {
it('adds choice', () => {
const value = 'test';
const label = 'test';
const id = 'test';
const groupId = 'test';
const disabled = false;
const elementId = 'test';
const customProperties = 'test';
const placeholder = 'test';
const value = 'test';
const label = 'test';
const id = 'test';
const groupId = 'test';
const disabled = false;
const elementId = 'test';
const customProperties = 'test';
const placeholder = 'test';
const expectedResponse = [
{
describe('passing expected values', () => {
it('adds choice', () => {
const expectedResponse = [
{
value,
label,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
selected: false,
active: true,
score: 9999,
keyCode: null,
},
];
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label,
id,
@ -28,26 +46,82 @@ describe('reducers/choices', () => {
elementId,
customProperties,
placeholder,
selected: false,
active: true,
score: 9999,
keyCode: null,
},
];
});
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
expect(actualResponse).to.eql(expectedResponse);
});
});
describe('fallback values', () => {
describe('passing no label', () => {
it('adds choice using value as label', () => {
const expectedResponse = [
{
value,
label: value,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
selected: false,
active: true,
score: 9999,
keyCode: null,
},
];
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label: null,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder,
});
expect(actualResponse).to.eql(expectedResponse);
});
});
expect(actualResponse).to.eql(expectedResponse);
describe('passing no placeholder value', () => {
it('adds choice with placeholder set to false', () => {
const expectedResponse = [
{
value,
label: value,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder: false,
selected: false,
active: true,
score: 9999,
keyCode: null,
},
];
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label: null,
id,
groupId,
disabled,
elementId,
customProperties,
placeholder: undefined,
});
expect(actualResponse).to.eql(expectedResponse);
});
});
});
});
});

View file

@ -19,9 +19,7 @@ export default function items(state = defaultState, action) {
return newState.map((obj) => {
const item = obj;
if (item.highlighted) {
item.highlighted = false;
}
item.highlighted = false;
return item;
});
}

View file

@ -19,34 +19,83 @@ describe('reducers/items', () => {
const placeholder = 'This is a placeholder';
const keyCode = 10;
const expectedResponse = [
{
id,
choiceId,
groupId,
value,
label,
active: true,
highlighted: false,
customProperties,
placeholder,
keyCode: null,
},
];
describe('passing expected values', () => {
let actualResponse;
const actualResponse = items(undefined, {
type: 'ADD_ITEM',
value,
label,
id,
choiceId,
groupId,
customProperties,
placeholder,
keyCode,
beforeEach(() => {
actualResponse = items(undefined, {
type: 'ADD_ITEM',
value,
label,
id,
choiceId,
groupId,
customProperties,
placeholder,
keyCode,
});
});
it('adds item', () => {
const expectedResponse = [
{
id,
choiceId,
groupId,
value,
label,
active: true,
highlighted: false,
customProperties,
placeholder,
keyCode: null,
},
];
expect(actualResponse).to.eql(expectedResponse);
});
it('unhighlights all highlighted items', () => {
actualResponse.forEach((item) => {
expect(item.highlighted).to.equal(false);
});
});
});
expect(actualResponse).to.eql(expectedResponse);
describe('fallback values', () => {
describe('passing no placeholder value', () => {
it('adds item with placeholder set to false', () => {
const expectedResponse = [
{
id,
choiceId,
groupId,
value,
label,
active: true,
highlighted: false,
customProperties,
placeholder: false,
keyCode: null,
},
];
const actualResponse = items(undefined, {
type: 'ADD_ITEM',
value,
label,
id,
choiceId,
groupId,
customProperties,
placeholder: undefined,
keyCode,
});
expect(actualResponse).to.eql(expectedResponse);
});
});
});
});
});

View file

@ -72,7 +72,7 @@ export default class Store {
* Get items from store reduced to just their values
* @return {Array} Item objects
*/
getItemsReducedToValues(items = this.getItems()) {
getItemsReducedToValues(items) {
const values = items.reduce((prev, current) => {
prev.push(current.value);
return prev;

View file

@ -21,14 +21,18 @@ describe('reducers/store', () => {
getStateStub.restore();
});
it('creates redux store on construction', () => {
expect(instance.store).to.contain.keys([
'subscribe',
'dispatch',
'getState',
]);
describe('constructor', () => {
it('creates redux store', () => {
expect(instance.store).to.contain.keys([
'subscribe',
'dispatch',
'getState',
]);
});
});
describe('subscribe', () => {
it('wraps redux subscribe method', () => {
const onChange = () => {};
@ -79,14 +83,26 @@ describe('reducers/store', () => {
id: 2,
choiceId: 2,
groupId: -1,
value: 'Item one',
label: 'Item one',
value: 'Item two',
label: 'Item two',
active: true,
highlighted: false,
customProperties: null,
placeholder: false,
keyCode: null,
},
{
id: 3,
choiceId: 3,
groupId: -1,
value: 'Item three',
label: 'Item three',
active: true,
highlighted: true,
customProperties: null,
placeholder: false,
keyCode: null,
},
],
choices: [
{
@ -153,13 +169,21 @@ describe('reducers/store', () => {
});
});
describe('getItemsFilteredByHighlighted', () => {
it('returns items that are active and highlighted', () => {
const expectedResponse = state.items.filter((item => item.highlighted && item.active));
const actualResponse = instance.getItemsFilteredByHighlighted();
expect(actualResponse).to.eql(expectedResponse);
});
});
describe('getItemsReducedToValues', () => {
it('returns an array of item values', () => {
const expectedResponse = state.items.reduce((items, item) => {
items.push(item.value);
return items;
}, []);
const actualResponse = instance.getItemsReducedToValues();
const actualResponse = instance.getItemsReducedToValues(state.items);
expect(actualResponse).to.eql(expectedResponse);
});
});
@ -197,11 +221,20 @@ describe('reducers/store', () => {
});
describe('getChoiceById', () => {
it('returns active choice by id', () => {
const id = '1';
const expectedResponse = state.choices.find((choice => choice.id === parseInt(id, 10)));
const actualResponse = instance.getChoiceById(id);
expect(actualResponse).to.eql(expectedResponse);
describe('passing id', () => {
it('returns active choice by passed id', () => {
const id = '1';
const expectedResponse = state.choices.find((choice => choice.id === parseInt(id, 10)));
const actualResponse = instance.getChoiceById(id);
expect(actualResponse).to.eql(expectedResponse);
});
});
describe('passing no id', () => {
it('returns false', () => {
const actualResponse = instance.getChoiceById();
expect(actualResponse).to.equal(false);
});
});
});