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" "src/**/**/**/**/**/*.js"
], ],
"exclude": [ "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 { export default class Container {
constructor(instance, element, classNames) { constructor(instance, element, classNames) {
this.parentInstance = instance; this.parentInstance = instance;
@ -55,26 +57,15 @@ export default class Container {
* @param {Number} dropdownPos * @param {Number} dropdownPos
* @returns * @returns
*/ */
shouldFlip(dropdownPos) { shouldFlip(dropdownPos, windowHeight = getWindowHeight()) {
if (dropdownPos === undefined) { if (dropdownPos === undefined) {
return false; 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 // If flip is enabled and the dropdown bottom position is
// greater than the window height flip the dropdown. // greater than the window height flip the dropdown.
let shouldFlip = false; let shouldFlip = false;
if (this.config.position === 'auto') { if (this.config.position === 'auto') {
shouldFlip = dropdownPos >= winHeight; shouldFlip = dropdownPos >= windowHeight;
} else if (this.config.position === 'top') { } else if (this.config.position === 'top') {
shouldFlip = true; shouldFlip = true;
} }

View file

@ -18,16 +18,18 @@ describe('components/container', () => {
instance = new Container(choicesInstance, choicesElement, DEFAULT_CLASSNAMES); instance = new Container(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
}); });
it('assigns choices instance to class', () => { describe('constructor', () => {
expect(instance.parentInstance).to.eql(choicesInstance); it('assigns choices instance to class', () => {
}); expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
it('assigns classnames to class', () => { it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES); expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
}); });
describe('getElement', () => { 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', () => { describe('setActiveDescendant', () => {
it('sets element\'s aria-activedescendant attribute with passed descendant ID', () => { 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); instance = new Dropdown(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
}); });
it('assigns choices instance to instance', () => { describe('constructor', () => {
expect(instance.parentInstance).to.eql(choicesInstance); it('assigns choices instance to instance', () => {
}); expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to instance', () => { it('assigns choices element to instance', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
it('assigns classnames to instance', () => { it('assigns classnames to instance', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES); expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
}); });
describe('getElement', () => { 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', () => { describe('enable', () => {
beforeEach(() => { beforeEach(() => {
instance.element.setAttribute('disabled', ''); instance.element.setAttribute('disabled', '');
@ -219,10 +195,49 @@ describe('components/input', () => {
focusStub.restore(); focusStub.restore();
}); });
it('focuses element if isFocussed flag is set to false', () => { describe('when element is not focussed', () => {
instance.isFocussed = false; it('focuses element if isFocussed flag is set to false', () => {
instance.focus(); instance.isFocussed = true;
expect(focusStub.callCount).to.equal(1); 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); instance = new List(choicesInstance, choicesElement, DEFAULT_CLASSNAMES);
}); });
it('assigns choices instance to class', () => { describe('constructor', () => {
expect(instance.parentInstance).to.eql(choicesInstance); it('assigns choices instance to class', () => {
}); expect(instance.parentInstance).to.eql(choicesInstance);
});
it('assigns choices element to class', () => { it('assigns choices element to class', () => {
expect(instance.element).to.eql(choicesElement); expect(instance.element).to.eql(choicesElement);
}); });
it('assigns classnames to class', () => { it('assigns classnames to class', () => {
expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES); expect(instance.classNames).to.eql(DEFAULT_CLASSNAMES);
});
}); });
describe('getElement', () => { describe('getElement', () => {
@ -46,11 +48,21 @@ describe('components/list', () => {
}); });
describe('scrollTo', () => { describe('scrollTo', () => {
it('scrolls element to passed position', () => { describe('passing position', () => {
const scrollPosition = 20; it('scrolls element to passed position', () => {
expect(instance.element.scrollTop).to.equal(0); const scrollPosition = 20;
instance.scrollTo(scrollPosition); expect(instance.element.scrollTop).to.equal(0);
expect(instance.element.scrollTop).to.equal(scrollPosition); 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 = { export const DEFAULT_CONFIG = {
items: [],
choices: [],
silent: false, silent: false,
renderChoiceLimit: -1, renderChoiceLimit: -1,
maxItemCount: -1, maxItemCount: -1,
@ -59,9 +61,9 @@ export const DEFAULT_CONFIG = {
noResultsText: 'No results found', noResultsText: 'No results found',
noChoicesText: 'No choices to choose from', noChoicesText: 'No choices to choose from',
itemSelectText: 'Press to select', itemSelectText: 'Press to select',
uniqueItemText: 'Only unique values can be added.',
addItemText: value => `Press Enter to add <b>"${value}"</b>`, addItemText: value => `Press Enter to add <b>"${value}"</b>`,
maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`, maxItemText: maxItemCount => `Only ${maxItemCount} values can be added.`,
uniqueItemText: 'Only unique values can be added.',
fuseOptions: { fuseOptions: {
includeScore: true, 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'); const expression = new RegExp(regex.source, 'i');
return expression.test(value); 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('when choices do not exist', () => {
describe('ADD_CHOICE', () => { describe('ADD_CHOICE', () => {
it('adds choice', () => { const value = 'test';
const value = 'test'; const label = 'test';
const label = 'test'; const id = 'test';
const id = 'test'; const groupId = 'test';
const groupId = 'test'; const disabled = false;
const disabled = false; const elementId = 'test';
const elementId = 'test'; const customProperties = 'test';
const customProperties = 'test'; const placeholder = '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, value,
label, label,
id, id,
@ -28,26 +46,82 @@ describe('reducers/choices', () => {
elementId, elementId,
customProperties, customProperties,
placeholder, placeholder,
selected: false, });
active: true,
score: 9999,
keyCode: null,
},
];
const actualResponse = choices(undefined, { expect(actualResponse).to.eql(expectedResponse);
type: 'ADD_CHOICE', });
value, });
label,
id, describe('fallback values', () => {
groupId, describe('passing no label', () => {
disabled, it('adds choice using value as label', () => {
elementId, const expectedResponse = [
customProperties, {
placeholder, 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) => { return newState.map((obj) => {
const item = obj; const item = obj;
if (item.highlighted) { item.highlighted = false;
item.highlighted = false;
}
return item; return item;
}); });
} }

View file

@ -19,34 +19,83 @@ describe('reducers/items', () => {
const placeholder = 'This is a placeholder'; const placeholder = 'This is a placeholder';
const keyCode = 10; const keyCode = 10;
const expectedResponse = [ describe('passing expected values', () => {
{ let actualResponse;
id,
choiceId,
groupId,
value,
label,
active: true,
highlighted: false,
customProperties,
placeholder,
keyCode: null,
},
];
const actualResponse = items(undefined, { beforeEach(() => {
type: 'ADD_ITEM', actualResponse = items(undefined, {
value, type: 'ADD_ITEM',
label, value,
id, label,
choiceId, id,
groupId, choiceId,
customProperties, groupId,
placeholder, customProperties,
keyCode, 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 * Get items from store reduced to just their values
* @return {Array} Item objects * @return {Array} Item objects
*/ */
getItemsReducedToValues(items = this.getItems()) { getItemsReducedToValues(items) {
const values = items.reduce((prev, current) => { const values = items.reduce((prev, current) => {
prev.push(current.value); prev.push(current.value);
return prev; return prev;

View file

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