diff --git a/src/scripts/actions/choices.ts b/src/scripts/actions/choices.ts index 7d6568d..526f4d4 100644 --- a/src/scripts/actions/choices.ts +++ b/src/scripts/actions/choices.ts @@ -14,9 +14,14 @@ export interface AddChoiceAction { keyCode: number; } +export interface Result { + item: T; + score: number; +} + export interface FilterChoicesAction { type: typeof ACTION_TYPES.FILTER_CHOICES; - results: Choice[]; + results: Result[]; } export interface ActivateChoicesAction { @@ -51,7 +56,9 @@ export const addChoice = ({ keyCode, }); -export const filterChoices = (results: Choice[]): FilterChoicesAction => ({ +export const filterChoices = ( + results: Result[], +): FilterChoicesAction => ({ type: ACTION_TYPES.FILTER_CHOICES, results, }); diff --git a/src/scripts/actions/groups.test.ts b/src/scripts/actions/groups.test.ts index 07d501d..0f5e299 100644 --- a/src/scripts/actions/groups.test.ts +++ b/src/scripts/actions/groups.test.ts @@ -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 = { diff --git a/src/scripts/actions/items.test.ts b/src/scripts/actions/items.test.ts index c637975..6c2528b 100644 --- a/src/scripts/actions/items.test.ts +++ b/src/scripts/actions/items.test.ts @@ -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 = { diff --git a/src/scripts/actions/misc.test.ts b/src/scripts/actions/misc.test.ts index 7842444..214232c 100644 --- a/src/scripts/actions/misc.test.ts +++ b/src/scripts/actions/misc.test.ts @@ -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, diff --git a/src/scripts/choices.test.ts b/src/scripts/choices.test.ts index f5c0bd1..2f9e663 100644 --- a/src/scripts/choices.test.ts +++ b/src/scripts/choices.test.ts @@ -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', () => { `; - 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', () => { `; - 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 => { 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', diff --git a/src/scripts/choices.ts b/src/scripts/choices.ts index eddda52..79a7f71 100644 --- a/src/scripts/choices.ts +++ b/src/scripts/choices.ts @@ -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 = {}; class Choices { static get defaults(): { options: Partial; - templates: Templates; + templates: typeof templates; } { return Object.preventExtensions({ get options(): Partial { 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 = {}, ) { this.config = merge.all( @@ -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[] = 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(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 { diff --git a/src/scripts/components/container.test.ts b/src/scripts/components/container.test.ts index a10cb97..065576e 100644 --- a/src/scripts/components/container.test.ts +++ b/src/scripts/components/container.test.ts @@ -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', () => { diff --git a/src/scripts/components/input.test.ts b/src/scripts/components/input.test.ts index 3ea3d71..9f3f394 100644 --- a/src/scripts/components/input.test.ts +++ b/src/scripts/components/input.test.ts @@ -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], ), ); }); diff --git a/src/scripts/components/list.ts b/src/scripts/components/list.ts index 94e5f6b..26facb0 100644 --- a/src/scripts/components/list.ts +++ b/src/scripts/components/list.ts @@ -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; } diff --git a/src/scripts/components/wrapped-input.test.ts b/src/scripts/components/wrapped-input.test.ts index 48adf84..2326169 100644 --- a/src/scripts/components/wrapped-input.test.ts +++ b/src/scripts/components/wrapped-input.test.ts @@ -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(() => { diff --git a/src/scripts/components/wrapped-input.ts b/src/scripts/components/wrapped-input.ts index 888d4a4..b5a1c98 100644 --- a/src/scripts/components/wrapped-input.ts +++ b/src/scripts/components/wrapped-input.ts @@ -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; diff --git a/src/scripts/components/wrapped-select.test.ts b/src/scripts/components/wrapped-select.test.ts index 58efd3d..4f723e6 100644 --- a/src/scripts/components/wrapped-select.test.ts +++ b/src/scripts/components/wrapped-select.test.ts @@ -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(() => { diff --git a/src/scripts/components/wrapped-select.ts b/src/scripts/components/wrapped-select.ts index 290282e..eb82eeb 100644 --- a/src/scripts/components/wrapped-select.ts +++ b/src/scripts/components/wrapped-select.ts @@ -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 diff --git a/src/scripts/interfaces.ts b/src/scripts/interfaces.ts index 7bbbc23..d76df12 100644 --- a/src/scripts/interfaces.ts +++ b/src/scripts/interfaces.ts @@ -21,7 +21,7 @@ export interface Choice { customProperties?: Record; 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) - | 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; }; diff --git a/src/scripts/lib/utils.test.ts b/src/scripts/lib/utils.test.ts index 2366f56..3cac1a1 100644 --- a/src/scripts/lib/utils.test.ts +++ b/src/scripts/lib/utils.test.ts @@ -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]; diff --git a/src/scripts/lib/utils.ts b/src/scripts/lib/utils.ts index 4972d28..1508e09 100644 --- a/src/scripts/lib/utils.ts +++ b/src/scripts/lib/utils.ts @@ -126,7 +126,10 @@ export const sortByAlpha = ( numeric: true, }); -export const sortByScore = (a: Choice, b: Choice): number => { +export const sortByScore = ( + a: Pick, + b: Pick, +): number => { const { score: scoreA = 0 } = a; const { score: scoreB = 0 } = b; diff --git a/src/scripts/reducers/choices.test.ts b/src/scripts/reducers/choices.test.ts index 9563f6c..200e069 100644 --- a/src/scripts/reducers/choices.test.ts +++ b/src/scripts/reducers/choices.test.ts @@ -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, }); diff --git a/src/scripts/reducers/choices.ts b/src/scripts/reducers/choices.ts index 9a666ab..bd73e19 100644 --- a/src/scripts/reducers/choices.ts +++ b/src/scripts/reducers/choices.ts @@ -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; }); diff --git a/src/scripts/reducers/general.test.ts b/src/scripts/reducers/general.test.ts index 01203dc..4589e8d 100644 --- a/src/scripts/reducers/general.test.ts +++ b/src/scripts/reducers/general.test.ts @@ -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', () => { diff --git a/src/scripts/reducers/groups.test.ts b/src/scripts/reducers/groups.test.ts index e67ee04..95008ef 100644 --- a/src/scripts/reducers/groups.test.ts +++ b/src/scripts/reducers/groups.test.ts @@ -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; diff --git a/src/scripts/reducers/index.test.ts b/src/scripts/reducers/index.test.ts index ae8a245..da229ee 100644 --- a/src/scripts/reducers/index.test.ts +++ b/src/scripts/reducers/index.test.ts @@ -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', () => { diff --git a/src/scripts/reducers/items.test.ts b/src/scripts/reducers/items.test.ts index c695e73..0a7262f 100644 --- a/src/scripts/reducers/items.test.ts +++ b/src/scripts/reducers/items.test.ts @@ -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); }); diff --git a/src/scripts/store/store.test.ts b/src/scripts/store/store.test.ts index 63ebcc5..44fcaac 100644 --- a/src/scripts/store/store.test.ts +++ b/src/scripts/store/store.test.ts @@ -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); diff --git a/src/scripts/templates.test.ts b/src/scripts/templates.test.ts index 2db8ed8..d41d7af 100644 --- a/src/scripts/templates.test.ts +++ b/src/scripts/templates.test.ts @@ -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( ``, ); - const actualOutput = templates.dropdown(classes, value); + const actualOutput = templates.dropdown(classes); expectEqualElements(actualOutput, expectedOutput); }); diff --git a/src/scripts/templates.ts b/src/scripts/templates.ts index b0c5746..cb56921 100644 --- a/src/scripts/templates.ts +++ b/src/scripts/templates.ts @@ -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, 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): HTMLDivElement { return Object.assign(document.createElement('div'), { className: containerInner, }); }, itemList( - { list, listSingle, listItems }: ClassNames, + { + list, + listSingle, + listItems, + }: Pick, isSelectOneElement: boolean, - ) { + ): HTMLDivElement { return Object.assign(document.createElement('div'), { className: `${list} ${isSelectOneElement ? listSingle : listItems}`, }); }, - placeholder({ placeholder }: ClassNames, value: string) { + placeholder( + { placeholder }: Pick, + 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, + 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, { 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, 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): 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, innerHTML: string, type: 'no-choices' | 'no-results' | '' = '', ): HTMLDivElement { @@ -298,4 +332,4 @@ export const TEMPLATES: Templates = { }, }; -export default TEMPLATES; +export default templates;