Fix remaning type issues

This commit is contained in:
Josh Johnson 2019-12-21 11:49:15 +00:00
parent 44666936a9
commit a80d42f5f6
25 changed files with 251 additions and 222 deletions

View file

@ -14,9 +14,14 @@ export interface AddChoiceAction {
keyCode: number;
}
export interface Result<T> {
item: T;
score: number;
}
export interface FilterChoicesAction {
type: typeof ACTION_TYPES.FILTER_CHOICES;
results: Choice[];
results: Result<Choice>[];
}
export interface ActivateChoicesAction {
@ -51,7 +56,9 @@ export const addChoice = ({
keyCode,
});
export const filterChoices = (results: Choice[]): FilterChoicesAction => ({
export const filterChoices = (
results: Result<Choice>[],
): FilterChoicesAction => ({
type: ACTION_TYPES.FILTER_CHOICES,
results,
});

View file

@ -5,7 +5,7 @@ describe('actions/groups', () => {
describe('addGroup action', () => {
it('returns ADD_GROUP action', () => {
const value = 'test';
const id = 'test';
const id = 1;
const active = true;
const disabled = false;
const expectedAction = {

View file

@ -6,9 +6,9 @@ describe('actions/items', () => {
it('returns ADD_ITEM action', () => {
const value = 'test';
const label = 'test';
const id = '1234';
const choiceId = '1234';
const groupId = 'test';
const id = 1;
const choiceId = 1;
const groupId = 1;
const customProperties = { test: true };
const placeholder = true;
const keyCode = 10;
@ -42,8 +42,8 @@ describe('actions/items', () => {
describe('removeItem action', () => {
it('returns REMOVE_ITEM action', () => {
const id = '1234';
const choiceId = '1';
const id = 1;
const choiceId = 1;
const expectedAction = {
type: 'REMOVE_ITEM',
id,
@ -56,7 +56,7 @@ describe('actions/items', () => {
describe('highlightItem action', () => {
it('returns HIGHLIGHT_ITEM action', () => {
const id = '1234';
const id = 1;
const highlighted = true;
const expectedAction = {

View file

@ -14,7 +14,14 @@ describe('actions/misc', () => {
describe('resetTo action', () => {
it('returns RESET_TO action', () => {
const state = { test: true };
const state = {
choices: [],
items: [],
groups: [],
general: {
loading: false,
},
};
const expectedAction = {
type: 'RESET_TO',
state,

View file

@ -3,9 +3,11 @@ import { spy, stub } from 'sinon';
import sinonChai from 'sinon-chai';
import Choices from './choices';
import { EVENTS, ACTION_TYPES, DEFAULT_CONFIG, KEY_CODES } from './constants';
import { WrappedSelect, WrappedInput } from './components/index';
import { removeItem } from './actions/items';
import { Item, Choice, Group } from './interfaces';
chai.use(sinonChai);
@ -28,12 +30,6 @@ describe('choices', () => {
instance = null;
});
const returnsInstance = () => {
it('returns this', () => {
expect(output).to.eql(instance);
});
};
describe('constructor', () => {
describe('config', () => {
describe('not passing config options', () => {
@ -88,7 +84,7 @@ describe('choices', () => {
`;
instance = new Choices('[data-choice]', {
renderSelectedChoices: 'test',
renderSelectedChoices: 'test' as any,
});
expect(instance.config.renderSelectedChoices).to.equal('auto');
@ -211,7 +207,7 @@ describe('choices', () => {
<input data-choice type="text" id="input-1" />
`;
instance = new Choices(document.querySelector('[data-choice]'));
instance = new Choices('[data-choice]');
expect(instance.passedElement).to.be.an.instanceOf(WrappedInput);
});
@ -223,7 +219,7 @@ describe('choices', () => {
<select data-choice id="select-1"></select>
`;
instance = new Choices(document.querySelector('[data-choice]'));
instance = new Choices('[data-choice]');
expect(instance.passedElement).to.be.an.instanceOf(WrappedSelect);
});
@ -423,7 +419,9 @@ describe('choices', () => {
output = instance.enable();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(passedElementEnableSpy.called).to.equal(false);
@ -481,7 +479,9 @@ describe('choices', () => {
output = instance.disable();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(removeEventListenersSpy.called).to.equal(false);
@ -638,7 +638,9 @@ describe('choices', () => {
output = instance.hideDropdown();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(containerOuterCloseSpy.called).to.equal(false);
@ -735,7 +737,9 @@ describe('choices', () => {
output = instance.highlightItem();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
@ -745,7 +749,7 @@ describe('choices', () => {
});
describe('item passed', () => {
const item = {
const item: Item = {
id: 1234,
value: 'Test',
label: 'Test',
@ -756,7 +760,9 @@ describe('choices', () => {
output = instance.highlightItem(item, true);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('dispatches highlightItem action with correct arguments', () => {
expect(storeDispatchSpy.called).to.equal(true);
@ -817,7 +823,9 @@ describe('choices', () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
});
});
});
@ -850,7 +858,9 @@ describe('choices', () => {
output = instance.unhighlightItem();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
@ -860,7 +870,7 @@ describe('choices', () => {
});
describe('item passed', () => {
const item = {
const item: Item = {
id: 1234,
value: 'Test',
label: 'Test',
@ -871,7 +881,9 @@ describe('choices', () => {
output = instance.unhighlightItem(item, true);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('dispatches highlightItem action with correct arguments', () => {
expect(storeDispatchSpy.called).to.equal(true);
@ -932,7 +944,9 @@ describe('choices', () => {
expect(passedElementTriggerEventStub.called).to.equal(false);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
});
});
});
@ -966,7 +980,9 @@ describe('choices', () => {
storeGetItemsStub.reset();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('highlights each item in store', () => {
expect(highlightItemStub.callCount).to.equal(items.length);
@ -1004,7 +1020,9 @@ describe('choices', () => {
storeGetItemsStub.reset();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('unhighlights each item in store', () => {
expect(unhighlightItemStub.callCount).to.equal(items.length);
@ -1027,7 +1045,9 @@ describe('choices', () => {
instance._store.dispatch.reset();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('dispatches clearChoices action', () => {
expect(storeDispatchStub.lastCall.args[0]).to.eql({
@ -1050,7 +1070,9 @@ describe('choices', () => {
instance._store.dispatch.reset();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('dispatches clearAll action', () => {
expect(storeDispatchStub.lastCall.args[0]).to.eql({
@ -1075,7 +1097,9 @@ describe('choices', () => {
instance._store.dispatch.reset();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
describe('text element', () => {
beforeEach(() => {
@ -1164,14 +1188,14 @@ describe('choices', () => {
const handleLoadingStateSpy = spy(choice, '_handleLoadingState');
let fetcherCalled = false;
const fetcher = async inst => {
const fetcher = async (inst): Promise<Choice[]> => {
expect(inst).to.eq(choice);
fetcherCalled = true;
await new Promise(resolve => setTimeout(resolve, 800));
return [
{ label: 'l1', value: 'v1', customProperties: 'prop1' },
{ label: 'l2', value: 'v2', customProperties: 'prop2' },
{ label: 'l1', value: 'v1', customProperties: { prop1: true } },
{ label: 'l2', value: 'v2', customProperties: { prop2: false } },
];
};
expect(choice._store.choices.length).to.equal(0);
@ -1211,7 +1235,9 @@ describe('choices', () => {
output = instance.setValue(values);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(setChoiceOrItemStub.called).to.equal(false);
@ -1224,7 +1250,9 @@ describe('choices', () => {
output = instance.setValue(values);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('sets each value', () => {
expect(setChoiceOrItemStub.callCount).to.equal(2);
@ -1252,7 +1280,9 @@ describe('choices', () => {
output = instance.setChoiceByValue([]);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('returns early', () => {
expect(findAndSelectChoiceByValueStub.called).to.equal(false);
@ -1272,7 +1302,9 @@ describe('choices', () => {
output = instance.setChoiceByValue(value);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('sets each choice with same value', () => {
expect(findAndSelectChoiceByValueStub.called).to.equal(true);
@ -1289,7 +1321,9 @@ describe('choices', () => {
output = instance.setChoiceByValue(values);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('sets each choice with same value', () => {
expect(findAndSelectChoiceByValueStub.callCount).to.equal(2);
@ -1509,7 +1543,9 @@ describe('choices', () => {
output = instance.removeHighlightedItems();
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('removes each highlighted item in store', () => {
expect(removeItemStub.callCount).to.equal(2);
@ -1521,7 +1557,9 @@ describe('choices', () => {
output = instance.removeHighlightedItems(true);
});
returnsInstance(output);
it('returns this', () => {
expect(output).to.eql(instance);
});
it('triggers event with item value', () => {
expect(triggerChangeStub.callCount).to.equal(2);
@ -1538,7 +1576,7 @@ describe('choices', () => {
let containerOuterRemoveLoadingStateStub;
const value = 'value';
const label = 'label';
const choices = [
const choices: Choice[] = [
{
id: 1,
value: '1',
@ -1550,7 +1588,7 @@ describe('choices', () => {
label: 'Test 2',
},
];
const groups = [
const groups: Group[] = [
{
...choices[0],
choices,
@ -1679,7 +1717,7 @@ describe('choices', () => {
describe('private methods', () => {
describe('_createGroupsFragment', () => {
let _createChoicesFragmentStub;
const choices = [
const choices: Choice[] = [
{
id: 1,
selected: true,
@ -1703,7 +1741,7 @@ describe('choices', () => {
},
];
const groups = [
const groups: Group[] = [
{
id: 2,
value: 'Group 2',

View file

@ -19,12 +19,13 @@ import {
SELECT_ONE_TYPE,
SELECT_MULTIPLE_TYPE,
} from './constants';
import { TEMPLATES } from './templates';
import templates from './templates';
import {
addChoice,
filterChoices,
activateChoices,
clearChoices,
Result,
} from './actions/choices';
import { addItem, removeItem, highlightItem } from './actions/items';
import { addGroup } from './actions/groups';
@ -41,7 +42,6 @@ import {
diff,
} from './lib/utils';
import {
Templates,
Options,
Choice,
Item,
@ -67,14 +67,14 @@ const USER_DEFAULTS: Partial<Options> = {};
class Choices {
static get defaults(): {
options: Partial<Options>;
templates: Templates;
templates: typeof templates;
} {
return Object.preventExtensions({
get options(): Partial<Options> {
return USER_DEFAULTS;
},
get templates(): Templates {
return TEMPLATES;
get templates(): typeof templates {
return templates;
},
});
}
@ -94,7 +94,7 @@ class Choices {
_isSelectMultipleElement: boolean;
_isSelectElement: boolean;
_store: Store;
_templates: Templates;
_templates: typeof templates;
_initialState: State;
_currentState: State;
_prevState: State;
@ -116,7 +116,11 @@ class Choices {
_presetItems: Item[] | string[];
constructor(
element: string | HTMLInputElement | HTMLSelectElement = '[data-choice]',
element:
| string
| Element
| HTMLInputElement
| HTMLSelectElement = '[data-choice]',
userConfig: Partial<Options> = {},
) {
this.config = merge.all<Options>(
@ -183,7 +187,7 @@ class Choices {
this.passedElement = new WrappedSelect({
element: passedElement as HTMLSelectElement,
classNames: this.config.classNames,
template: (data: object): HTMLOptionElement =>
template: (data: Item): HTMLOptionElement =>
this._templates.option(data),
});
}
@ -337,7 +341,7 @@ class Choices {
(this.passedElement as WrappedSelect).options = this._presetOptions;
}
this._templates = TEMPLATES;
this._templates = templates;
this.initialised = false;
}
@ -627,7 +631,7 @@ class Choices {
if (typeof Promise === 'function' && fetcher instanceof Promise) {
// that's a promise
// eslint-disable-next-line compat/compat
return new Promise(resolve => requestAnimationFrame(resolve))
return new Promise(resolve => requestAnimationFrame(resolve)) // eslint-disable-line compat/compat
.then(() => this._handleLoadingState(true))
.then(() => fetcher)
.then((data: Choice[]) =>
@ -1294,9 +1298,12 @@ class Choices {
const haystack = this._store.searchableChoices;
const needle = newValue;
const keys = [...this.config.searchFields];
const options = Object.assign(this.config.fuseOptions, { keys });
const options = Object.assign(this.config.fuseOptions, {
keys,
includeMatches: true,
});
const fuse = new Fuse(haystack, options);
const results = fuse.search(needle);
const results: Result<Choice>[] = fuse.search(needle) as any[]; // see https://github.com/krisk/Fuse/issues/303
this._currentValue = newValue;
this._highlightPosition = 0;
@ -2091,7 +2098,7 @@ class Choices {
}
}
_getTemplate<K extends keyof Templates>(template: K, ...args: any): any {
_getTemplate(template: string, ...args: any): any {
const { classNames } = this.config;
return this._templates[template].call(this, classNames, ...args);
@ -2108,7 +2115,7 @@ class Choices {
userTemplates = callbackOnCreateTemplates.call(this, strToEl);
}
this._templates = merge(TEMPLATES, userTemplates);
this._templates = merge(templates, userTemplates);
}
_createElements(): void {

View file

@ -13,7 +13,7 @@ describe('components/container', () => {
document.body.appendChild(element);
instance = new Container({
element: document.getElementById('container'),
element: document.getElementById('container') as HTMLElement,
classNames: DEFAULT_CLASSNAMES,
position: 'auto',
type: 'text',
@ -383,7 +383,7 @@ describe('components/container', () => {
});
afterEach(() => {
document.getElementById('wrap-test').remove();
document.getElementById('wrap-test')!.remove();
});
it('wraps passed element inside element', () => {
@ -406,7 +406,7 @@ describe('components/container', () => {
});
afterEach(() => {
document.body.removeChild(document.getElementById('unwrap-test'));
document.body.removeChild(document.getElementById('unwrap-test') as Node);
});
it('moves wrapped element outside of element', () => {

View file

@ -13,7 +13,6 @@ describe('components/input', () => {
element: choicesElement,
type: 'text',
classNames: DEFAULT_CLASSNAMES,
placeholderValue: null,
preventPaste: false,
});
});
@ -49,7 +48,7 @@ describe('components/input', () => {
expect(['input', 'paste', 'focus', 'blur']).to.have.members(
Array.from(
{ length: addEventListenerStub.callCount },
(v, i) => addEventListenerStub.getCall(i).args[0],
(_, i) => addEventListenerStub.getCall(i).args[0],
),
);
});

View file

@ -31,7 +31,7 @@ export default class List {
this.element.scrollTop = 0;
}
scrollToChildElement(element: Element, direction: 1 | -1): void {
scrollToChildElement(element: HTMLElement, direction: 1 | -1): void {
if (!element) {
return;
}

View file

@ -34,10 +34,12 @@ describe('components/wrappedInput', () => {
});
describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
methods.forEach(method => {
describe(method, () => {
beforeEach(() => {
stub(WrappedElement.prototype, method);
stub(WrappedElement.prototype, method as keyof WrappedElement);
});
afterEach(() => {

View file

@ -1,5 +1,5 @@
import WrappedElement from './wrapped-element';
import { ClassNames, Item } from '../interfaces';
import { ClassNames } from '../interfaces';
export default class WrappedInput extends WrappedElement {
element: HTMLInputElement;

View file

@ -32,7 +32,7 @@ describe('components/wrappedSelect', () => {
document.body.appendChild(element);
instance = new WrappedSelect({
element: document.getElementById('target'),
element: document.getElementById('target') as HTMLSelectElement,
classNames: DEFAULT_CLASSNAMES,
template: spy(Templates.option),
});
@ -54,9 +54,11 @@ describe('components/wrappedSelect', () => {
});
describe('inherited methods', () => {
['conceal', 'reveal', 'enable', 'disable'].forEach(method => {
const methods: string[] = ['conceal', 'reveal', 'enable', 'disable'];
methods.forEach(method => {
beforeEach(() => {
stub(WrappedElement.prototype, method);
stub(WrappedElement.prototype, method as keyof WrappedElement);
});
afterEach(() => {

View file

@ -1,5 +1,5 @@
import WrappedElement from './wrapped-element';
import { ClassNames, Item, Choice } from '../interfaces';
import { ClassNames, Item } from '../interfaces';
export default class WrappedSelect extends WrappedElement {
element: HTMLSelectElement;
@ -35,7 +35,7 @@ export default class WrappedSelect extends WrappedElement {
return Array.from(this.element.options);
}
set options(options: Item[] | Choice[]): void {
set options(options: Item[] | HTMLOptionElement[]) {
const fragment = document.createDocumentFragment();
const addOptionToFragment = (data): void => {
// Create a standard select option

View file

@ -21,7 +21,7 @@ export interface Choice {
customProperties?: Record<string, any>;
disabled?: boolean;
active?: boolean;
elementId?: string;
elementId?: number;
groupId?: number;
keyCode?: number;
label: string;
@ -183,64 +183,6 @@ export type ActionType =
| 'RESET_TO'
| 'SET_IS_LOADING';
export interface Templates {
containerOuter: (
this: Choices,
classNames: ClassNames,
direction: HTMLElement['dir'],
isSelectElement: boolean,
isSelectOneElement: boolean,
searchEnabled: boolean,
passedElementType: PassedElement['type'],
) => HTMLElement;
containerInner: (this: Choices, classNames: ClassNames) => HTMLElement;
itemList: (
this: Choices,
classNames: ClassNames,
isSelectOneElement: boolean,
) => HTMLElement;
placeholder: (
this: Choices,
classNames: ClassNames,
value: string,
) => HTMLElement;
item: (
this: Choices,
classNames: ClassNames,
data: Choice,
removeItemButton: boolean,
) => HTMLElement;
choiceList: (
this: Choices,
classNames: ClassNames,
isSelectOneElement: boolean,
) => HTMLElement;
choiceGroup: (
this: Choices,
classNames: ClassNames,
data: Choice,
) => HTMLElement;
choice: (
this: Choices,
classNames: ClassNames,
data: Choice,
selectText: string,
) => HTMLElement;
input: (
this: Choices,
classNames: ClassNames,
placeholderValue: string,
) => HTMLInputElement;
dropdown: (this: Choices, classNames: ClassNames) => HTMLElement;
notice: (
this: Choices,
classNames: ClassNames,
label: string,
type: '' | 'no-results' | 'no-choices',
) => HTMLElement;
option: (data: object) => HTMLOptionElement;
}
/** Classes added to HTML generated by By default classnames follow the BEM notation. */
export interface ClassNames {
/** @default 'choices' */
@ -795,9 +737,7 @@ export interface Options {
*
* @default null
*/
callbackOnCreateTemplates:
| ((template: Types.strToEl) => Partial<Templates>)
| null;
callbackOnCreateTemplates: ((template: Types.strToEl) => void) | null;
}
export interface KeyDownAction {
@ -815,9 +755,9 @@ export interface Notice {
}
export interface State {
choices: object[];
groups: object[];
items: object[];
choices: Choice[];
groups: Group[];
items: Item[];
general: {
loading: boolean;
};

View file

@ -1,3 +1,4 @@
/* eslint-disable no-new-wrappers */
import { expect } from 'chai';
import { stub } from 'sinon';
import {
@ -140,19 +141,19 @@ describe('utils', () => {
it('sorts by label alphabetically', () => {
const values = [
{ label: 'The Strokes' },
{ label: 'Arctic Monkeys' },
{ label: 'Oasis' },
{ label: 'Tame Impala' },
{ value: '0', label: 'The Strokes' },
{ value: '0', label: 'Arctic Monkeys' },
{ value: '0', label: 'Oasis' },
{ value: '0', label: 'Tame Impala' },
];
const output = values.sort(sortByAlpha);
expect(output).to.eql([
{ label: 'Arctic Monkeys' },
{ label: 'Oasis' },
{ label: 'Tame Impala' },
{ label: 'The Strokes' },
{ value: '0', label: 'Arctic Monkeys' },
{ value: '0', label: 'Oasis' },
{ value: '0', label: 'Tame Impala' },
{ value: '0', label: 'The Strokes' },
]);
});
});
@ -185,12 +186,12 @@ describe('utils', () => {
const fakeElement = {
dispatchEvent: stub(),
};
const eventType = 'testEvent';
const eventType = 'addItem';
const customArgs = {
testing: true,
};
dispatchEvent(fakeElement, eventType, customArgs);
dispatchEvent(fakeElement as any, eventType, customArgs);
expect(fakeElement.dispatchEvent.called).to.equal(true);
const event = fakeElement.dispatchEvent.lastCall.args[0];

View file

@ -126,7 +126,10 @@ export const sortByAlpha = (
numeric: true,
});
export const sortByScore = (a: Choice, b: Choice): number => {
export const sortByScore = (
a: Pick<Choice, 'score'>,
b: Pick<Choice, 'score'>,
): number => {
const { score: scoreA = 0 } = a;
const { score: scoreB = 0 } = b;

View file

@ -1,21 +1,22 @@
import { expect } from 'chai';
import choices, { defaultState } from './choices';
import { Choice } from '../interfaces';
describe('reducers/choices', () => {
it('should return same state when no action matches', () => {
expect(choices(defaultState, {})).to.equal(defaultState);
expect(choices(defaultState, {} as any)).to.equal(defaultState);
});
describe('when choices do not exist', () => {
describe('ADD_CHOICE', () => {
const value = 'test';
const label = 'test';
const id = 'test';
const groupId = 'test';
const id = 1;
const groupId = 1;
const disabled = false;
const elementId = 'test';
const customProperties = 'test';
const placeholder = 'test';
const elementId = 1;
const customProperties = { test: true };
const placeholder = true;
describe('passing expected values', () => {
it('adds choice', () => {
@ -75,7 +76,7 @@ describe('reducers/choices', () => {
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label: null,
label: undefined,
id,
groupId,
disabled,
@ -110,7 +111,7 @@ describe('reducers/choices', () => {
const actualResponse = choices(undefined, {
type: 'ADD_CHOICE',
value,
label: null,
label: undefined,
id,
groupId,
disabled,
@ -178,9 +179,7 @@ describe('reducers/choices', () => {
type: 'FILTER_CHOICES',
results: [
{
item: {
id,
},
item: { id } as Choice,
score,
},
],
@ -248,11 +247,10 @@ describe('reducers/choices', () => {
expect(actualResponse).to.eql(expectedResponse);
});
it('activates all choices if activateOptions flag passed', () => {
it('activates all choices if active flag passed', () => {
const clonedState = state.slice(0);
const actualResponse = choices(clonedState, {
type: 'ADD_ITEM',
activateOptions: true,
active: true,
});
@ -265,7 +263,6 @@ describe('reducers/choices', () => {
const clonedState = state.slice(0);
const actualResponse = choices(clonedState, {
type: 'ADD_ITEM',
activateOptions: false,
choiceId: undefined,
});

View file

@ -1,4 +1,4 @@
import { Choice, State } from '../interfaces';
import { Choice } from '../interfaces';
import {
AddChoiceAction,
FilterChoicesAction,
@ -9,11 +9,6 @@ import { AddItemAction, RemoveItemAction } from '../actions/items';
export const defaultState = [];
interface Result {
item: Choice;
score: number;
}
type ActionTypes =
| AddChoiceAction
| FilterChoicesAction
@ -25,7 +20,7 @@ type ActionTypes =
export default function choices(
state: Choice[] = defaultState,
action: ActionTypes,
): State['choices'] {
): Choice[] {
switch (action.type) {
case 'ADD_CHOICE': {
const addChoiceAction = action as AddChoiceAction;
@ -41,7 +36,6 @@ export default function choices(
score: 9999,
customProperties: addChoiceAction.customProperties,
placeholder: addChoiceAction.placeholder || false,
keyCode: null,
};
/*
@ -49,7 +43,7 @@ export default function choices(
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return [...state, choice];
return [...state, choice as Choice];
}
case 'ADD_ITEM': {
@ -97,17 +91,15 @@ export default function choices(
const choice = obj;
// Set active state based on whether choice is
// within filtered results
choice.active = filterChoicesAction.results.some(
({ item, score }: Result) => {
if (item.id === choice.id) {
choice.score = score;
choice.active = filterChoicesAction.results.some(({ item, score }) => {
if (item.id === choice.id) {
choice.score = score;
return true;
}
return true;
}
return false;
},
);
return false;
});
return choice;
});

View file

@ -3,7 +3,7 @@ import general, { defaultState } from './general';
describe('reducers/general', () => {
it('should return same state when no action matches', () => {
expect(general(defaultState, {})).to.equal(defaultState);
expect(general(defaultState, {} as any)).to.equal(defaultState);
});
describe('SET_IS_LOADING', () => {

View file

@ -3,13 +3,13 @@ import groups, { defaultState } from './groups';
describe('reducers/groups', () => {
it('should return same state when no action matches', () => {
expect(groups(defaultState, {})).to.equal(defaultState);
expect(groups(defaultState, {} as any)).to.equal(defaultState);
});
describe('when groups do not exist', () => {
describe('ADD_GROUP', () => {
it('adds group', () => {
const id = '1';
const id = 1;
const value = 'Group one';
const active = true;
const disabled = false;

View file

@ -11,9 +11,9 @@ describe('reducers/rootReducer', () => {
it('returns expected reducers', () => {
const state = store.getState();
expect(state.groups).to.equal(groups(undefined, {}));
expect(state.choices).to.equal(choices(undefined, {}));
expect(state.items).to.equal(items(undefined, {}));
expect(state.groups).to.equal(groups(undefined, {} as any));
expect(state.choices).to.equal(choices(undefined, {} as any));
expect(state.items).to.equal(items(undefined, {} as any));
});
describe('CLEAR_ALL', () => {

View file

@ -1,9 +1,10 @@
import { expect } from 'chai';
import items, { defaultState } from './items';
import { RemoveItemAction } from '../actions/items';
describe('reducers/items', () => {
it('should return same state when no action matches', () => {
expect(items(defaultState, {})).to.equal(defaultState);
expect(items(defaultState, {} as any)).to.equal(defaultState);
});
describe('when items do not exist', () => {
@ -148,7 +149,7 @@ describe('reducers/items', () => {
const actualResponse = items(clonedState, {
type: 'REMOVE_ITEM',
id,
});
} as RemoveItemAction);
expect(actualResponse).to.eql(expectedResponse);
});

View file

@ -33,7 +33,7 @@ describe('reducers/store', () => {
describe('subscribe', () => {
it('wraps redux subscribe method', () => {
const onChange = () => {};
const onChange = (): void => {};
expect(subscribeStub.callCount).to.equal(0);
instance.subscribe(onChange);
expect(subscribeStub.callCount).to.equal(1);

View file

@ -7,7 +7,7 @@ import { strToEl } from './lib/utils';
* @param {HTMLElement} element1
* @param {HTMLElement} element2
*/
function expectEqualElements(element1, element2) {
function expectEqualElements(element1, element2): void {
expect(element1.tagName).to.equal(element2.tagName);
expect(element1.attributes.length).to.equal(element2.attributes.length);
expect(Object.keys(element1.dataset)).to.have.members(
@ -516,11 +516,10 @@ describe('templates', () => {
};
it('returns expected html', () => {
const value = 'test';
const expectedOutput = strToEl(
`<div class="${classes.list} ${classes.listDropdown}" aria-expanded="false"></div>`,
);
const actualOutput = templates.dropdown(classes, value);
const actualOutput = templates.dropdown(classes);
expectEqualElements(actualOutput, expectedOutput);
});

View file

@ -1,26 +1,19 @@
import {
Templates,
ClassNames,
Item,
Choice,
Group,
PassedElement,
} from './interfaces';
import { ClassNames, Item, Choice, Group, PassedElement } from './interfaces';
/**
* Helpers to create HTML elements used by Choices
* Can be overridden by providing `callbackOnCreateTemplates` option
*/
export const TEMPLATES: Templates = {
const templates = {
containerOuter(
{ containerOuter }: ClassNames,
{ containerOuter }: Pick<ClassNames, 'containerOuter'>,
dir: HTMLElement['dir'],
isSelectElement: boolean,
isSelectOneElement: boolean,
searchEnabled: boolean,
passedElementType: PassedElement['type'],
) {
): HTMLDivElement {
const div = Object.assign(document.createElement('div'), {
className: containerOuter,
});
@ -48,22 +41,31 @@ export const TEMPLATES: Templates = {
return div;
},
containerInner({ containerInner }: ClassNames) {
containerInner({
containerInner,
}: Pick<ClassNames, 'containerInner'>): HTMLDivElement {
return Object.assign(document.createElement('div'), {
className: containerInner,
});
},
itemList(
{ list, listSingle, listItems }: ClassNames,
{
list,
listSingle,
listItems,
}: Pick<ClassNames, 'list' | 'listSingle' | 'listItems'>,
isSelectOneElement: boolean,
) {
): HTMLDivElement {
return Object.assign(document.createElement('div'), {
className: `${list} ${isSelectOneElement ? listSingle : listItems}`,
});
},
placeholder({ placeholder }: ClassNames, value: string) {
placeholder(
{ placeholder }: Pick<ClassNames, 'placeholder'>,
value: string,
): HTMLDivElement {
return Object.assign(document.createElement('div'), {
className: placeholder,
innerHTML: value,
@ -71,7 +73,16 @@ export const TEMPLATES: Templates = {
},
item(
{ item, button, highlightedState, itemSelectable, placeholder }: ClassNames,
{
item,
button,
highlightedState,
itemSelectable,
placeholder,
}: Pick<
ClassNames,
'item' | 'button' | 'highlightedState' | 'itemSelectable' | 'placeholder'
>,
{
id,
value,
@ -83,7 +94,7 @@ export const TEMPLATES: Templates = {
placeholder: isPlaceholder,
}: Item,
removeItemButton: boolean,
) {
): HTMLDivElement {
const div = Object.assign(document.createElement('div'), {
className: item,
innerHTML: label,
@ -133,7 +144,10 @@ export const TEMPLATES: Templates = {
return div;
},
choiceList({ list }: ClassNames, isSelectOneElement: boolean) {
choiceList(
{ list }: Pick<ClassNames, 'list'>,
isSelectOneElement: boolean,
): HTMLDivElement {
const div = Object.assign(document.createElement('div'), {
className: list,
});
@ -147,9 +161,13 @@ export const TEMPLATES: Templates = {
},
choiceGroup(
{ group, groupHeading, itemDisabled }: ClassNames,
{
group,
groupHeading,
itemDisabled,
}: Pick<ClassNames, 'group' | 'groupHeading' | 'itemDisabled'>,
{ id, value, disabled }: Group,
) {
): HTMLDivElement {
const div = Object.assign(document.createElement('div'), {
className: `${group} ${disabled ? itemDisabled : ''}`,
});
@ -184,7 +202,15 @@ export const TEMPLATES: Templates = {
selectedState,
itemDisabled,
placeholder,
}: ClassNames,
}: Pick<
ClassNames,
| 'item'
| 'itemChoice'
| 'itemSelectable'
| 'selectedState'
| 'itemDisabled'
| 'placeholder'
>,
{
id,
value,
@ -233,7 +259,7 @@ export const TEMPLATES: Templates = {
},
input(
{ input, inputCloned }: ClassNames,
{ input, inputCloned }: Pick<ClassNames, 'input' | 'inputCloned'>,
placeholderValue: string,
): HTMLInputElement {
const inp = Object.assign(document.createElement('input'), {
@ -251,7 +277,10 @@ export const TEMPLATES: Templates = {
return inp;
},
dropdown({ list, listDropdown }: ClassNames): HTMLDivElement {
dropdown({
list,
listDropdown,
}: Pick<ClassNames, 'list' | 'listDropdown'>): HTMLDivElement {
const div = document.createElement('div');
div.classList.add(list, listDropdown);
@ -261,7 +290,12 @@ export const TEMPLATES: Templates = {
},
notice(
{ item, itemChoice, noResults, noChoices }: ClassNames,
{
item,
itemChoice,
noResults,
noChoices,
}: Pick<ClassNames, 'item' | 'itemChoice' | 'noResults' | 'noChoices'>,
innerHTML: string,
type: 'no-choices' | 'no-results' | '' = '',
): HTMLDivElement {
@ -298,4 +332,4 @@ export const TEMPLATES: Templates = {
},
};
export default TEMPLATES;
export default templates;