diff --git a/README.md b/README.md index 77e0b02f2..832de52b6 100644 --- a/README.md +++ b/README.md @@ -824,12 +824,6 @@ choices.setValue(['Set value 1', 'Set value 2']); choices.disable(); ``` -### destroy(); - -**Input types affected:** `text`, `select-multiple`, `select-one` - -**Usage:** Kills the instance of Choices, removes all event listeners and returns passed input to its initial state. - ### init(); **Input types affected:** `text`, `select-multiple`, `select-one` @@ -838,6 +832,12 @@ choices.disable(); **Note:** This is called implicitly when a new instance of Choices is created. This would be used after a Choices instance had already been destroyed (using `destroy()`). +### destroy(); + +**Input types affected:** `text`, `select-multiple`, `select-one` + +**Usage:** Kills the instance of Choices, removes all event listeners and returns passed input to its initial state. + ### highlightAll(); **Input types affected:** `text`, `select-multiple` @@ -880,7 +880,7 @@ choices.disable(); **Usage:** Hide option list dropdown (only affects select inputs). -### setChoices(choices, value, label, replaceChoices); +### setChoices(choices[, value[, label[, replaceChoices]]]); **Input types affected:** `select-one`, `select-multiple` @@ -966,7 +966,13 @@ example.setChoices( **Input types affected:** `select-one`, `select-multiple` -**Usage:** Clear all choices from select +**Usage:** Clear all choices + +### clearItems(); + +**Input types affected:** `text`, `select-one`, `select-multiple` + +**Usage:** Clear all items ### getValue(valueOnly) diff --git a/src/scripts/actions/items.js b/src/scripts/actions/items.js index e28075925..424da3762 100644 --- a/src/scripts/actions/items.js +++ b/src/scripts/actions/items.js @@ -51,3 +51,10 @@ export const highlightItem = (id, highlighted) => ({ id, highlighted, }); + +/** + * @returns {Action} + */ +export const clearItems = () => ({ + type: ACTION_TYPES.CLEAR_ITEMS, +}); diff --git a/src/scripts/actions/items.test.js b/src/scripts/actions/items.test.js index c637975b3..a5f9310a9 100644 --- a/src/scripts/actions/items.test.js +++ b/src/scripts/actions/items.test.js @@ -68,4 +68,14 @@ describe('actions/items', () => { expect(actions.highlightItem(id, highlighted)).to.eql(expectedAction); }); }); + + describe('clearItems action', () => { + it('returns CLEAR_ITEMS action', () => { + const expectedAction = { + type: 'CLEAR_ITEMS', + }; + + expect(actions.clearItems()).to.eql(expectedAction); + }); + }); }); diff --git a/src/scripts/choices.js b/src/scripts/choices.js index a88982929..1f1fec2d9 100644 --- a/src/scripts/choices.js +++ b/src/scripts/choices.js @@ -25,7 +25,12 @@ import { activateChoices, clearChoices, } from './actions/choices'; -import { addItem, removeItem, highlightItem } from './actions/items'; +import { + addItem, + removeItem, + highlightItem, + clearItems, +} from './actions/items'; import { addGroup } from './actions/groups'; import { clearAll, resetTo, setIsLoading } from './actions/misc'; import { @@ -496,7 +501,7 @@ class Choices { * @param {T} [choicesArrayOrFetcher] * @param {string} [value = 'value'] - name of `value` field * @param {string} [label = 'label'] - name of 'label' field - * @param {boolean} [replaceChoices = false] - whether to replace of add choices + * @param {boolean} [replaceChoices = false] - whether to clear existing choices * @returns {this | Promise} * * @example @@ -574,9 +579,9 @@ class Choices { ); } - // Clear choices if needed if (replaceChoices) { this.clearChoices(); + this.clearItems(); } if (typeof choicesArrayOrFetcher === 'function') { @@ -651,6 +656,12 @@ class Choices { return this; } + clearItems() { + this._store.dispatch(clearItems()); + + return this; + } + clearStore() { this._store.dispatch(clearAll()); diff --git a/src/scripts/choices.test.js b/src/scripts/choices.test.js index f5c0bd117..e472ead03 100644 --- a/src/scripts/choices.test.js +++ b/src/scripts/choices.test.js @@ -28,12 +28,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,6 +82,7 @@ describe('choices', () => { `; instance = new Choices('[data-choice]', { + // @ts-ignore renderSelectedChoices: 'test', }); @@ -423,7 +418,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 +478,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 +637,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 +736,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); @@ -756,7 +759,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 +822,9 @@ describe('choices', () => { expect(passedElementTriggerEventStub.called).to.equal(false); }); - returnsInstance(output); + it('returns this', () => { + expect(output).to.eql(instance); + }); }); }); }); @@ -850,7 +857,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); @@ -871,7 +880,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 +943,9 @@ describe('choices', () => { expect(passedElementTriggerEventStub.called).to.equal(false); }); - returnsInstance(output); + it('returns this', () => { + expect(output).to.eql(instance); + }); }); }); }); @@ -966,7 +979,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 +1019,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 +1044,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({ @@ -1036,6 +1055,31 @@ describe('choices', () => { }); }); + describe('clearItems', () => { + let storeDispatchStub; + + beforeEach(() => { + storeDispatchStub = stub(); + instance._store.dispatch = storeDispatchStub; + + output = instance.clearItems(); + }); + + afterEach(() => { + instance._store.dispatch.reset(); + }); + + it('returns this', () => { + expect(output).to.eql(instance); + }); + + it('dispatches clearItems action', () => { + expect(storeDispatchStub.lastCall.args[0]).to.eql({ + type: ACTION_TYPES.CLEAR_ITEMS, + }); + }); + }); + describe('clearStore', () => { let storeDispatchStub; @@ -1050,7 +1094,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 +1121,9 @@ describe('choices', () => { instance._store.dispatch.reset(); }); - returnsInstance(output); + it('returns this', () => { + expect(output).to.eql(instance); + }); describe('text element', () => { beforeEach(() => { @@ -1125,8 +1173,8 @@ describe('choices', () => { instance.initialised = false; }); - it('should throw', () => { - expect(() => instance.setChoices(null)).Throw(ReferenceError); + it('throws a ReferenceError', () => { + expect(() => instance.setChoices(null)).to.throw(ReferenceError); }); }); @@ -1135,8 +1183,8 @@ describe('choices', () => { instance._isSelectElement = false; }); - it('should throw', () => { - expect(() => instance.setChoices(null)).Throw(TypeError); + it('throws a TypeError', () => { + expect(() => instance.setChoices([])).to.throw(TypeError); }); }); @@ -1145,15 +1193,22 @@ describe('choices', () => { instance._isSelectElement = true; }); - it('should throw on non function', () => { - expect(() => instance.setChoices(null)).Throw(TypeError, /Promise/i); + describe('passing a non-function', () => { + it('throws a TypeError', () => { + expect(() => instance.setChoices(null)).to.throw( + TypeError, + /Promise/i, + ); + }); }); - it(`should throw on function that doesn't return promise`, () => { - expect(() => instance.setChoices(() => 'boo')).to.throw( - TypeError, - /promise/i, - ); + describe('passing a function that does not return a promise', () => { + it('throws a TypeError', () => { + expect(() => instance.setChoices(() => 'boo')).to.throw( + TypeError, + /promise/i, + ); + }); }); }); @@ -1211,7 +1266,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 +1281,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 +1311,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 +1333,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 +1352,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 +1574,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 +1588,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); @@ -1533,6 +1602,7 @@ describe('choices', () => { describe('setChoices', () => { let clearChoicesStub; + let clearItemsStub; let addGroupStub; let addChoiceStub; let containerOuterRemoveLoadingStateStub; @@ -1560,11 +1630,13 @@ describe('choices', () => { beforeEach(() => { clearChoicesStub = stub(); + clearItemsStub = stub(); addGroupStub = stub(); addChoiceStub = stub(); containerOuterRemoveLoadingStateStub = stub(); instance.clearChoices = clearChoicesStub; + instance.clearItems = clearItemsStub; instance._addGroup = addGroupStub; instance._addChoice = addChoiceStub; instance.containerOuter.removeLoadingState = containerOuterRemoveLoadingStateStub; @@ -1595,7 +1667,7 @@ describe('choices', () => { instance._isSelectElement = true; }); - it('throws', () => { + it('throws a TypeError', () => { expect(() => instance.setChoices(choices, null, 'label', false), ).to.throw(TypeError, /value/i); @@ -1643,34 +1715,46 @@ describe('choices', () => { }); }); - describe('passing an empty array with a true replaceChoices flag', () => { - it('choices are cleared', () => { - instance._isSelectElement = true; - instance.setChoices([], value, label, true); - expect(clearChoicesStub.called).to.equal(true); - }); - }); - describe('passing an empty array with a false replaceChoices flag', () => { - it('choices stay the same', () => { - instance._isSelectElement = true; + beforeEach(() => { instance.setChoices([], value, label, false); + }); + + it('does not clear existing choices', () => { expect(clearChoicesStub.called).to.equal(false); }); + + it('does not clear existing items', () => { + expect(clearItemsStub.called).to.equal(false); + }); }); describe('passing true replaceChoices flag', () => { - it('choices are cleared', () => { + beforeEach(() => { instance.setChoices(choices, value, label, true); + }); + + it('clears existing choices', () => { expect(clearChoicesStub.called).to.equal(true); }); + + it('clears existing items', () => { + expect(clearItemsStub.called).to.equal(true); + }); }); describe('passing false replaceChoices flag', () => { - it('choices are not cleared', () => { + beforeEach(() => { instance.setChoices(choices, value, label, false); + }); + + it('does not clear existing choices', () => { expect(clearChoicesStub.called).to.equal(false); }); + + it('does not clear existing items', () => { + expect(clearItemsStub.called).to.equal(false); + }); }); }); }); diff --git a/src/scripts/constants.js b/src/scripts/constants.js index 5718c1c3f..3d6ace635 100644 --- a/src/scripts/constants.js +++ b/src/scripts/constants.js @@ -104,6 +104,7 @@ export const ACTION_TYPES = { ADD_ITEM: 'ADD_ITEM', REMOVE_ITEM: 'REMOVE_ITEM', HIGHLIGHT_ITEM: 'HIGHLIGHT_ITEM', + CLEAR_ITEMS: 'CLEAR_ITEMS', CLEAR_ALL: 'CLEAR_ALL', }; diff --git a/src/scripts/constants.test.js b/src/scripts/constants.test.js index 3579dcb81..83a78a550 100644 --- a/src/scripts/constants.test.js +++ b/src/scripts/constants.test.js @@ -120,6 +120,7 @@ describe('constants', () => { 'ADD_ITEM', 'REMOVE_ITEM', 'HIGHLIGHT_ITEM', + 'CLEAR_ITEMS', 'CLEAR_ALL', ]); }); diff --git a/src/scripts/reducers/items.js b/src/scripts/reducers/items.js index 2a520f9d2..79e2b00e1 100644 --- a/src/scripts/reducers/items.js +++ b/src/scripts/reducers/items.js @@ -51,6 +51,10 @@ export default function items(state = defaultState, action) { }); } + case 'CLEAR_ITEMS': { + return defaultState; + } + default: { return state; } diff --git a/src/scripts/reducers/items.test.js b/src/scripts/reducers/items.test.js index 4041a3b3e..00425e37e 100644 --- a/src/scripts/reducers/items.test.js +++ b/src/scripts/reducers/items.test.js @@ -177,5 +177,17 @@ describe('reducers/items', () => { expect(actualResponse).to.eql(expectedResponse); }); }); + + describe('CLEAR_ITEMS', () => { + it('restores to defaultState', () => { + const clonedState = state.slice(0); + const expectedResponse = defaultState; + const actualResponse = items(clonedState, { + type: 'CLEAR_ITEMS', + }); + + expect(actualResponse).to.eql(expectedResponse); + }); + }); }); }); diff --git a/types/index.d.ts b/types/index.d.ts index d3520bdfd..5e0f94e92 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -933,7 +933,7 @@ export default class Choices { * * @param {string} [value = 'value'] - name of `value` field * @param {string} [label = 'label'] - name of 'label' field - * @param {boolean} [replaceChoices = false] - whether to replace of add choices + * @param {boolean} [replaceChoices = false] - whether to clear existing choices * * @example * ```js