0 ? 'role="treeitem"' : 'role="option"'}>
+ *
* ★ ${data.label}
*
* `);
diff --git a/src/scripts/interfaces/passed-element-type.ts b/src/scripts/interfaces/passed-element-type.ts
index 49225822..d2325d86 100644
--- a/src/scripts/interfaces/passed-element-type.ts
+++ b/src/scripts/interfaces/passed-element-type.ts
@@ -1 +1,9 @@
-export type PassedElementType = 'text' | 'select-one' | 'select-multiple';
+import { Types } from './types';
+
+export const PassedElementTypes = {
+ Text: 'text',
+ SelectOne: 'select-one',
+ SelectMultiple: 'select-multiple',
+} as const;
+
+export type PassedElementType = Types.ValueOf
;
diff --git a/src/scripts/interfaces/store.ts b/src/scripts/interfaces/store.ts
index 40c6d3ec..1c63a5b5 100644
--- a/src/scripts/interfaces/store.ts
+++ b/src/scripts/interfaces/store.ts
@@ -12,7 +12,7 @@ export interface StateUpdate {
state: T;
}
-export type Reducer = (state: T, action: AnyAction) => StateUpdate;
+export type Reducer = (state: T, action: AnyAction, context?: unknown) => StateUpdate;
export type StoreListener = (changes: StateChangeSet) => void;
diff --git a/src/scripts/interfaces/templates.ts b/src/scripts/interfaces/templates.ts
index 36f4596b..657069cc 100644
--- a/src/scripts/interfaces/templates.ts
+++ b/src/scripts/interfaces/templates.ts
@@ -4,6 +4,7 @@ import { ChoiceFull } from './choice-full';
import { GroupFull } from './group-full';
import { Options } from './options';
+import { Types } from './types';
export type TemplateOptions = Pick<
Options,
@@ -22,7 +23,7 @@ export const NoticeTypes = {
addChoice: 'add-choice',
generic: '',
} as const;
-export type NoticeType = (typeof NoticeTypes)[keyof typeof NoticeTypes];
+export type NoticeType = Types.ValueOf;
export interface Templates {
containerOuter(
@@ -47,7 +48,7 @@ export interface Templates {
choiceGroup(options: TemplateOptions, group: GroupFull): HTMLDivElement;
- choice(options: TemplateOptions, choice: ChoiceFull, selectText: string): HTMLDivElement;
+ choice(options: TemplateOptions, choice: ChoiceFull, selectText: string, groupText?: string): HTMLDivElement;
input(options: TemplateOptions, placeholderValue: string | null): HTMLInputElement;
diff --git a/src/scripts/interfaces/types.ts b/src/scripts/interfaces/types.ts
index 21fc6c38..04f8080a 100644
--- a/src/scripts/interfaces/types.ts
+++ b/src/scripts/interfaces/types.ts
@@ -14,4 +14,5 @@ export namespace Types {
value?: StringUntrusted | string;
label?: StringUntrusted | string;
}
+ export type ValueOf = T[keyof T];
}
diff --git a/src/scripts/lib/utils.ts b/src/scripts/lib/utils.ts
index 50b525a1..b56a3dce 100644
--- a/src/scripts/lib/utils.ts
+++ b/src/scripts/lib/utils.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { EventType } from '../interfaces/event-type';
+import { EventTypes } from '../interfaces/event-type';
import { StringUntrusted } from '../interfaces/string-untrusted';
import { StringPreEscaped } from '../interfaces/string-pre-escaped';
import { ChoiceFull } from '../interfaces/choice-full';
@@ -164,7 +164,7 @@ export const sortByRank = (a: Pick, b: Pick {
+export const dispatchEvent = (element: HTMLElement, type: EventTypes, customArgs: object | null = null): boolean => {
const event = new CustomEvent(type, {
detail: customArgs,
bubbles: true,
@@ -213,3 +213,11 @@ export const parseCustomProperties = (customProperties?: string): object | strin
return {};
};
+
+export const updateClassList = (item: ChoiceFull, add: string | string[], remove: string | string[]): void => {
+ const { itemEl } = item;
+ if (itemEl) {
+ itemEl.classList.remove(...getClassNames(remove));
+ itemEl.classList.add(...getClassNames(add));
+ }
+};
diff --git a/src/scripts/reducers/choices.ts b/src/scripts/reducers/choices.ts
index 1b521146..24ca4158 100644
--- a/src/scripts/reducers/choices.ts
+++ b/src/scripts/reducers/choices.ts
@@ -1,5 +1,5 @@
/* eslint-disable */
-import { ActionType, State } from '../interfaces';
+import { ActionType, Options, State } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
import { ChoiceActions } from '../actions/choices';
import { ItemActions } from '../actions/items';
@@ -9,7 +9,7 @@ import { ChoiceFull } from '../interfaces/choice-full';
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['choices'];
-export default function choices(s: StateType, action: ActionTypes): StateUpdate {
+export default function choices(s: StateType, action: ActionTypes, context?: Options): StateUpdate {
let state = s;
let update = true;
@@ -25,12 +25,15 @@ export default function choices(s: StateType, action: ActionTypes): StateUpdate<
}
case ActionType.REMOVE_CHOICE: {
+ action.choice.choiceEl = undefined;
+
state = state.filter((obj) => obj.id !== action.choice.id);
break;
}
case ActionType.ADD_ITEM:
case ActionType.REMOVE_ITEM: {
+ action.item.choiceEl = undefined;
break;
}
@@ -52,6 +55,9 @@ export default function choices(s: StateType, action: ActionTypes): StateUpdate<
choice.rank = 0;
choice.active = false;
}
+ if (context && context.appendGroupInSearch) {
+ choice.choiceEl = undefined;
+ }
});
break;
@@ -60,6 +66,9 @@ export default function choices(s: StateType, action: ActionTypes): StateUpdate<
case ActionType.ACTIVATE_CHOICES: {
state.forEach((choice) => {
choice.active = action.active;
+ if (context && context.appendGroupInSearch) {
+ choice.choiceEl = undefined;
+ }
});
break;
}
diff --git a/src/scripts/reducers/items.ts b/src/scripts/reducers/items.ts
index 2e23082c..203f9ba0 100644
--- a/src/scripts/reducers/items.ts
+++ b/src/scripts/reducers/items.ts
@@ -1,64 +1,78 @@
import { ItemActions } from '../actions/items';
import { State } from '../interfaces/state';
import { ChoiceActions } from '../actions/choices';
-import { ActionType } from '../interfaces';
+import { ActionType, Options, PassedElementTypes } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
import { isHtmlSelectElement } from '../lib/html-guard-statements';
-import { SELECT_ONE_TYPE } from '../constants';
+import { ChoiceFull } from '../interfaces/choice-full';
+import { updateClassList } from '../lib/utils';
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['items'];
-export default function items(s: StateType, action: ActionTypes): StateUpdate {
+const removeItem = (item: ChoiceFull): void => {
+ const { itemEl } = item;
+ if (itemEl) {
+ itemEl.remove();
+ item.itemEl = undefined;
+ }
+};
+
+export default function items(s: StateType, action: ActionTypes, context?: Options): StateUpdate {
let state = s;
let update = true;
switch (action.type) {
case ActionType.ADD_ITEM: {
- const { item } = action;
- item.selected = true;
- const el = item.element as HTMLOptionElement | undefined;
+ action.item.selected = true;
+ const el = action.item.element as HTMLOptionElement | undefined;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
- state.push(item);
- state.forEach((choice) => {
- choice.highlighted = false;
- });
+ state.push(action.item);
break;
}
case ActionType.REMOVE_ITEM: {
- const { item } = action;
- item.selected = false;
- const el = item.element as HTMLOptionElement | undefined;
+ action.item.selected = false;
+ const el = action.item.element as HTMLOptionElement | undefined;
if (el) {
el.selected = false;
el.removeAttribute('selected');
// For a select-one, if all options are deselected, the first item is selected. To set a black value, select.value needs to be set
const select = el.parentElement;
- if (select && isHtmlSelectElement(select) && select.type === SELECT_ONE_TYPE) {
+ if (select && isHtmlSelectElement(select) && select.type === PassedElementTypes.SelectOne) {
select.value = '';
}
}
- state = state.filter((choice) => choice.id !== item.id);
+ // this is mixing concerns, but this is *so much faster*
+ removeItem(action.item);
+ state = state.filter((choice) => choice.id !== action.item.id);
break;
}
case ActionType.REMOVE_CHOICE: {
state = state.filter((item) => item.id !== action.choice.id);
+ removeItem(action.choice);
break;
}
case ActionType.HIGHLIGHT_ITEM: {
- const highlightItemAction = action;
- state.forEach((choice) => {
- if (choice.id === highlightItemAction.item.id) {
- choice.highlighted = highlightItemAction.highlighted;
+ const { highlighted } = action;
+ const item = state.find((obj) => obj.id === action.item.id);
+ if (item && item.highlighted !== highlighted) {
+ item.highlighted = highlighted;
+ if (context) {
+ updateClassList(
+ item,
+ highlighted ? context.classNames.highlightedState : context.classNames.selectedState,
+ highlighted ? context.classNames.selectedState : context.classNames.highlightedState,
+ );
}
- });
+ }
+
break;
}
diff --git a/src/scripts/store/store.ts b/src/scripts/store/store.ts
index ab0a0e88..68acd2e4 100644
--- a/src/scripts/store/store.ts
+++ b/src/scripts/store/store.ts
@@ -14,7 +14,7 @@ const reducers: ReducerList = {
choices,
} as const;
-export default class Store implements IStore {
+export default class Store implements IStore {
_state: State = this.defaultState;
_listeners: StoreListener[] = [];
@@ -23,6 +23,12 @@ export default class Store implements IStore {
_changeSet?: StateChangeSet;
+ _context: T;
+
+ constructor(context: T) {
+ this._context = context;
+ }
+
// eslint-disable-next-line class-methods-use-this
get defaultState(): State {
return {
@@ -61,7 +67,7 @@ export default class Store implements IStore {
const changes = this._changeSet || this.changeSet(false);
Object.keys(reducers).forEach((key: string) => {
- const stateUpdate = (reducers[key] as Reducer)(state[key], action);
+ const stateUpdate = (reducers[key] as Reducer)(state[key], action, this._context);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
diff --git a/src/scripts/templates.ts b/src/scripts/templates.ts
index 7c44b0a8..e3d5a14e 100644
--- a/src/scripts/templates.ts
+++ b/src/scripts/templates.ts
@@ -8,8 +8,15 @@ import { ChoiceFull } from './interfaces/choice-full';
import { GroupFull } from './interfaces/group-full';
import { PassedElementType } from './interfaces/passed-element-type';
import { StringPreEscaped } from './interfaces/string-pre-escaped';
-import { getClassNames, unwrapStringForRaw, resolveNoticeFunction, setElementHtml } from './lib/utils';
+import {
+ getClassNames,
+ unwrapStringForRaw,
+ resolveNoticeFunction,
+ setElementHtml,
+ escapeForTemplate,
+} from './lib/utils';
import { NoticeType, NoticeTypes, TemplateOptions, Templates as TemplatesInterface } from './interfaces/templates';
+import { StringUntrusted } from './interfaces/string-untrusted';
const isEmptyObject = (obj: object): boolean => {
// eslint-disable-next-line no-restricted-syntax
@@ -136,28 +143,26 @@ const templates: TemplatesInterface = {
choice: ChoiceFull,
removeItemButton: boolean,
): HTMLDivElement {
- const { labelClass, label, disabled, value } = choice;
- const rawValue = unwrapStringForRaw(value);
+ const rawValue = unwrapStringForRaw(choice.value);
const div = document.createElement('div');
div.className = getClassNames(item).join(' ');
- if (labelClass) {
+ if (choice.labelClass) {
const spanLabel = document.createElement('span');
- setElementHtml(spanLabel, allowHTML, label);
- spanLabel.className = getClassNames(labelClass).join(' ');
+ setElementHtml(spanLabel, allowHTML, choice.label);
+ spanLabel.className = getClassNames(choice.labelClass).join(' ');
div.appendChild(spanLabel);
} else {
- setElementHtml(div, allowHTML, label);
+ setElementHtml(div, allowHTML, choice.label);
}
- const { dataset } = div;
- dataset.item = '';
- dataset.id = choice.id as unknown as string;
- dataset.value = rawValue;
+ div.dataset.item = '';
+ div.dataset.id = choice.id as unknown as string;
+ div.dataset.value = rawValue;
assignCustomProperties(div, choice, true);
- if (disabled || this.containerOuter.isDisabled) {
+ if (choice.disabled || this.containerOuter.isDisabled) {
div.setAttribute('aria-disabled', 'true');
}
if (this._isSelectElement) {
@@ -167,23 +172,23 @@ const templates: TemplatesInterface = {
if (choice.placeholder) {
div.classList.add(...getClassNames(placeholder));
- dataset.placeholder = '';
+ div.dataset.placeholder = '';
}
div.classList.add(...(choice.highlighted ? getClassNames(highlightedState) : getClassNames(itemSelectable)));
if (removeItemButton) {
- if (disabled) {
+ if (choice.disabled) {
div.classList.remove(...getClassNames(itemSelectable));
}
- dataset.deletable = '';
+ div.dataset.deletable = '';
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = getClassNames(button).join(' ');
- setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, value));
+ setElementHtml(removeButton, true, resolveNoticeFunction(removeItemIconText, choice.value));
- const REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, value);
+ const REMOVE_ITEM_LABEL = resolveNoticeFunction(removeItemLabelText, choice.value);
if (REMOVE_ITEM_LABEL) {
removeButton.setAttribute('aria-label', REMOVE_ITEM_LABEL);
}
@@ -220,10 +225,9 @@ const templates: TemplatesInterface = {
div.setAttribute('role', 'group');
- const { dataset } = div;
- dataset.group = '';
- dataset.id = id as unknown as string;
- dataset.value = rawLabel;
+ div.dataset.group = '';
+ div.dataset.id = id as unknown as string;
+ div.dataset.value = rawLabel;
if (disabled) {
div.setAttribute('aria-disabled', 'true');
@@ -231,7 +235,7 @@ const templates: TemplatesInterface = {
const heading = document.createElement('div');
heading.className = getClassNames(groupHeading).join(' ');
- setElementHtml(heading, allowHTML, label);
+ setElementHtml(heading, allowHTML, label || '');
div.appendChild(heading);
return div;
@@ -244,29 +248,38 @@ const templates: TemplatesInterface = {
}: TemplateOptions,
choice: ChoiceFull,
selectText: string,
+ groupName?: string,
): HTMLDivElement {
- const { value, elementId, groupId, label, labelClass, labelDescription } = choice;
- const rawValue = unwrapStringForRaw(value);
+ // eslint-disable-next-line prefer-destructuring
+ let label: string | StringUntrusted | StringPreEscaped = choice.label;
+ const rawValue = unwrapStringForRaw(choice.value);
const div = document.createElement('div');
- div.id = elementId as string;
+ div.id = choice.elementId as string;
div.className = `${getClassNames(item).join(' ')} ${getClassNames(itemChoice).join(' ')}`;
+ if (groupName && typeof label === 'string') {
+ label = escapeForTemplate(allowHTML, label);
+ label += ` (${groupName})`;
+ label = { trusted: label };
+ div.dataset.groupId = `${choice.groupId}`;
+ }
+
let describedBy: HTMLElement = div;
- if (labelClass) {
+ if (choice.labelClass) {
const spanLabel = document.createElement('span');
setElementHtml(spanLabel, allowHTML, label);
- spanLabel.className = getClassNames(labelClass).join(' ');
+ spanLabel.className = getClassNames(choice.labelClass).join(' ');
describedBy = spanLabel;
div.appendChild(spanLabel);
} else {
setElementHtml(div, allowHTML, label);
}
- if (labelDescription) {
- const descId = `${elementId}-description`;
+ if (choice.labelDescription) {
+ const descId = `${choice.elementId}-description`;
describedBy.setAttribute('aria-describedby', descId);
const spanDesc = document.createElement('span');
- setElementHtml(spanDesc, allowHTML, labelDescription);
+ setElementHtml(spanDesc, allowHTML, choice.labelDescription);
spanDesc.id = descId;
spanDesc.classList.add(...getClassNames(description));
div.appendChild(spanDesc);
@@ -280,29 +293,24 @@ const templates: TemplatesInterface = {
div.classList.add(...getClassNames(placeholder));
}
- const { dataset } = div;
- const showGroupId = groupId && groupId > 0;
- div.setAttribute('role', showGroupId ? 'treeitem' : 'option');
- if (showGroupId) {
- dataset.groupId = `${groupId}`;
- }
+ div.setAttribute('role', choice.groupId ? 'treeitem' : 'option');
- dataset.choice = '';
- dataset.id = choice.id as unknown as string;
- dataset.value = rawValue;
+ div.dataset.choice = '';
+ div.dataset.id = choice.id as unknown as string;
+ div.dataset.value = rawValue;
if (selectText) {
- dataset.selectText = selectText;
+ div.dataset.selectText = selectText;
}
assignCustomProperties(div, choice, false);
if (choice.disabled) {
div.classList.add(...getClassNames(itemDisabled));
- dataset.choiceDisabled = '';
+ div.dataset.choiceDisabled = '';
div.setAttribute('aria-disabled', 'true');
} else {
div.classList.add(...getClassNames(itemSelectable));
- dataset.choiceSelectable = '';
+ div.dataset.choiceSelectable = '';
}
return div;
@@ -365,9 +373,8 @@ const templates: TemplatesInterface = {
notice.className = classes.join(' ');
if (type === NoticeTypes.addChoice) {
- const { dataset } = notice;
- dataset.choiceSelectable = '';
- dataset.choice = '';
+ notice.dataset.choiceSelectable = '';
+ notice.dataset.choice = '';
}
return notice;
diff --git a/src/styles/choices.scss b/src/styles/choices.scss
index f31fdf6c..9d2c62a4 100644
--- a/src/styles/choices.scss
+++ b/src/styles/choices.scss
@@ -250,7 +250,7 @@ $choices-z-index: 1 !default;
}
%choices-dropdown {
- visibility: hidden;
+ display: none;
z-index: $choices-z-index;
position: absolute;
width: 100%;
@@ -262,10 +262,9 @@ $choices-z-index: 1 !default;
border-bottom-right-radius: $choices-border-radius;
overflow: hidden;
word-break: break-all;
- will-change: visibility;
&.is-active {
- visibility: visible;
+ display: block;
}
.is-open & {
diff --git a/test-e2e/test-suit.ts b/test-e2e/test-suit.ts
index 50ed0704..b8331c7b 100644
--- a/test-e2e/test-suit.ts
+++ b/test-e2e/test-suit.ts
@@ -34,6 +34,13 @@ export class TestSuit {
this.dropdown = this.group.locator('.choices__list.choices__list--dropdown');
}
+ logConsole(): void {
+ this.page.on('console', (msg) => {
+ // eslint-disable-next-line no-console
+ console.log(msg);
+ });
+ }
+
async start(textInput?: string): Promise {
if (this.choicesBundle) {
await this.page.route('/assets/scripts/choices.js', (route) => route.continue({ url: this.choicesBundle }));
diff --git a/test-e2e/tests/select-multiple-performance.spec.ts b/test-e2e/tests/select-multiple-performance.spec.ts
new file mode 100644
index 00000000..aa321470
--- /dev/null
+++ b/test-e2e/tests/select-multiple-performance.spec.ts
@@ -0,0 +1,204 @@
+import { expect } from '@playwright/test';
+import { test } from '../bundle-test';
+import { SelectTestSuit } from '../select-test-suit';
+
+const { describe } = test;
+
+const testUrl = '/test/select-multiple/index-performance.html';
+
+describe(`Choices - select multiple (performance tests)`, () => {
+ // test.setTimeout(30000);
+
+ describe('scenarios', () => {
+ describe('basic', () => {
+ const testId = 'basic';
+ const inputValue = 'test';
+
+ describe('focusing on container', () => {
+ describe('pressing enter key', () => {
+ test('toggles the dropdown', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.start();
+ await suite.wrapper.focus();
+ await suite.enterKey();
+ await suite.expectVisibleDropdown();
+ await suite.escapeKey();
+ await suite.expectHiddenDropdown();
+ });
+ });
+
+ describe('pressing an alpha-numeric key', () => {
+ test('opens the dropdown and the input value', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.start();
+ await suite.selectByKeyPress(inputValue);
+ await expect(suite.input).toHaveValue(inputValue);
+ });
+ });
+ });
+
+ describe('selecting choices', () => {
+ const selectedChoiceText = 'Choice 1$';
+
+ test('allows selecting choices from dropdown', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.choices.first().click();
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
+ });
+
+ test('remove selected choice from dropdown list', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.choices.first().click();
+ await expect(suite.choices.first()).not.toHaveText(selectedChoiceText);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
+ });
+
+ test('multiple choices', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.expectedItemCount(1000);
+ await suite.expectChoiceCount(1000);
+ await suite.expectVisibleDropdown();
+
+ await suite.getChoiceWithText('Choice 1$').click();
+ await suite.expectedItemCount(1001);
+ await suite.expectChoiceCount(999);
+ await suite.expectVisibleDropdown();
+
+ await suite.getChoiceWithText('Choice 3$').click();
+ await suite.expectedItemCount(1002);
+ await suite.expectChoiceCount(998);
+ await suite.expectVisibleDropdown();
+ });
+
+ /* This test is unreasonably slow due to selecting over a thousand items...
+ describe('slowly', () => {
+ test.setTimeout(60000);
+ test('all available choices', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ const itemCount = await suite.items.count();
+ const count = await suite.choices.count();
+
+ for (let i = 1; i < count + 1; i++) {
+ await suite.expectVisibleDropdown();
+ await suite.getChoiceWithText(`Choice ${i * 2 - 1}$`).click();
+ await suite.advanceClock();
+ await suite.expectedItemCount(itemCount + i);
+ await expect(suite.selectableChoices).toHaveCount(count - i);
+ }
+
+ await suite.expectVisibleNoticeHtml('No choices to choose from');
+ });
+ });
+ */
+ });
+
+ describe('keys for choice', () => {
+ test('up/down arrows for selection', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.input.press('ArrowDown');
+ await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/);
+ await expect(suite.choices.nth(1)).toHaveClass(/is-highlighted/);
+
+ await suite.input.press('ArrowUp');
+ await expect(suite.choices.first()).toHaveClass(/is-highlighted/);
+ await expect(suite.choices.nth(1)).not.toHaveClass(/is-highlighted/);
+ });
+
+ test('page-up/page-down arrows for selection', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.input.press('PageDown');
+ await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/);
+ await expect(suite.choices.last()).toHaveClass(/is-highlighted/);
+
+ await suite.input.press('PageUp');
+ await expect(suite.choices.first()).toHaveClass(/is-highlighted/);
+ await expect(suite.choices.last()).not.toHaveClass(/is-highlighted/);
+ });
+ });
+
+ describe('searching choices', () => {
+ describe('on input', () => {
+ describe('searching by label', () => {
+ test('displays choices filtered by inputted value', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+ await suite.typeText('item2');
+
+ await suite.expectVisibleDropdownWithItem('Choice 2');
+ });
+ });
+
+ describe('searching by value', () => {
+ test('displays choices filtered by inputted value - by character', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ const searchTerm = 'Choice 3';
+ await suite.startWithClick();
+ for (const t of [...searchTerm]) {
+ await suite.typeText(t);
+ }
+ // await suite.typeText(searchTerm);
+
+ await suite.expectVisibleDropdownWithItem(searchTerm);
+ });
+
+ test('displays choices filtered by inputted value - by phrase', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ const searchTerm = 'Choice 3';
+ await suite.startWithClick();
+ await suite.typeText(searchTerm);
+
+ await suite.expectVisibleDropdownWithItem(searchTerm);
+ });
+ });
+
+ describe('no results found', () => {
+ test('displays "no results found" prompt', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+ await suite.typeText('faergge');
+
+ await suite.expectVisibleNoticeHtml('No results found');
+ });
+ });
+ });
+ });
+
+ describe('disabling', () => {
+ describe('on disable', () => {
+ test('disables the search input', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+ await suite.group.locator('button.disable').click();
+ await expect(suite.wrapper).toBeDisabled();
+ await expect(suite.input).toBeDisabled();
+ });
+ });
+ });
+
+ describe('enabling', () => {
+ describe('on enable', () => {
+ test('enables the search input', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+ await suite.group.locator('button.disable').click();
+ await suite.group.locator('button.enable').click();
+ await expect(suite.wrapper).toBeEnabled();
+ await expect(suite.input).toBeEnabled();
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/test-e2e/tests/select-multiple.spec.ts b/test-e2e/tests/select-multiple.spec.ts
index 37296279..3e61bb9d 100644
--- a/test-e2e/tests/select-multiple.spec.ts
+++ b/test-e2e/tests/select-multiple.spec.ts
@@ -44,7 +44,7 @@ describe(`Choices - select multiple`, () => {
await suite.startWithClick();
await suite.choices.first().click();
- await expect(suite.itemList.last()).toHaveText(selectedChoiceText);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
});
test('remove selected choice from dropdown list', async ({ page, bundle }) => {
@@ -53,10 +53,10 @@ describe(`Choices - select multiple`, () => {
await suite.choices.first().click();
await expect(suite.choices.first()).not.toHaveText(selectedChoiceText);
- await expect(suite.itemList.last()).toHaveText(selectedChoiceText);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
});
- test('selecting multiple choices', async ({ page, bundle }) => {
+ test('multiple choices', async ({ page, bundle }) => {
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
await suite.startWithClick();
@@ -75,7 +75,7 @@ describe(`Choices - select multiple`, () => {
await suite.expectVisibleDropdown();
});
- test('selecting all available choices', async ({ page, bundle }) => {
+ test('all available choices', async ({ page, bundle }) => {
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
await suite.startWithClick();
@@ -207,6 +207,86 @@ describe(`Choices - select multiple`, () => {
});
});
+ describe('render selected choices', () => {
+ const testId = 'render-selected-choices';
+ describe('selecting choices', () => {
+ const selectedChoiceText = 'Choice 1';
+
+ test('not removing selected choice from dropdown list', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await expect(suite.choices.first()).toHaveText(selectedChoiceText);
+ await expect(suite.choices.first()).toHaveClass(/is-selected/);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
+ });
+
+ test('multiple choices', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await expect(suite.getChoiceWithText(selectedChoiceText)).toHaveClass(/is-selected/);
+ await suite.expectedItemCount(1);
+ await suite.expectChoiceCount(4);
+ await suite.expectVisibleDropdown();
+
+ await suite.getChoiceWithText('Choice 2').click();
+ await expect(suite.getChoiceWithText('Choice 2')).toHaveClass(/is-selected/);
+ await suite.expectedItemCount(2);
+ await suite.expectChoiceCount(4);
+ await suite.expectVisibleDropdown();
+ });
+
+ test('all available choices', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ const count = await suite.choices.count();
+
+ for (let i = 1; i < count + 1; i++) {
+ await suite.expectVisibleDropdown();
+ await suite.getChoiceWithText(`Choice ${i}`).click();
+ await suite.advanceClock();
+ await suite.expectedItemCount(i);
+ if (i < count) {
+ await expect(suite.getChoiceWithText(`Choice ${i}`)).toHaveClass(/is-selected/);
+ await expect(suite.selectableChoices).toHaveCount(count);
+ } else {
+ await suite.expectVisibleNoticeHtml('No choices to choose from');
+ }
+ }
+ });
+ });
+
+ describe('keys for choice', () => {
+ test('up/down arrows for selection', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.input.press('ArrowDown');
+ await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/);
+ await expect(suite.choices.nth(1)).toHaveClass(/is-highlighted/);
+
+ await suite.input.press('ArrowUp');
+ await expect(suite.choices.first()).toHaveClass(/is-highlighted/);
+ await expect(suite.choices.nth(1)).not.toHaveClass(/is-highlighted/);
+ });
+
+ test('page-up/page-down arrows for selection', async ({ page, bundle }) => {
+ const suite = new SelectTestSuit(page, bundle, testUrl, testId);
+ await suite.startWithClick();
+
+ await suite.input.press('PageDown');
+ await expect(suite.choices.first()).not.toHaveClass(/is-highlighted/);
+ await expect(suite.choices.last()).toHaveClass(/is-highlighted/);
+
+ await suite.input.press('PageUp');
+ await expect(suite.choices.first()).toHaveClass(/is-highlighted/);
+ await expect(suite.choices.last()).not.toHaveClass(/is-highlighted/);
+ });
+ });
+ });
+
describe('remove button', () => {
const testId = 'remove-button';
describe('on click', () => {
diff --git a/test-e2e/tests/select-one.spec.ts b/test-e2e/tests/select-one.spec.ts
index 5f07a497..7e7b9fb5 100644
--- a/test-e2e/tests/select-one.spec.ts
+++ b/test-e2e/tests/select-one.spec.ts
@@ -44,7 +44,7 @@ describe(`Choices - select one`, () => {
await suite.startWithClick();
await suite.choices.first().click();
- await expect(suite.itemList.last()).toHaveText(selectedChoiceText);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
});
test('does not remove selected choice from dropdown list', async ({ page, bundle }) => {
@@ -53,7 +53,7 @@ describe(`Choices - select one`, () => {
await suite.choices.first().click();
await expect(suite.choices.first()).toHaveText(selectedChoiceText);
- await expect(suite.itemList.last()).toHaveText(selectedChoiceText);
+ await expect(suite.items.last()).toHaveText(selectedChoiceText);
});
});
diff --git a/test-e2e/tsconfig.json b/test-e2e/tsconfig.json
index 1dda5bf4..a5d27148 100644
--- a/test-e2e/tsconfig.json
+++ b/test-e2e/tsconfig.json
@@ -2,7 +2,7 @@
"compilerOptions": {
"module": "es6",
"lib": ["es2017", "dom"],
- "target": "es5",
+ "target": "ES2020",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
diff --git a/test/scripts/choices.test.ts b/test/scripts/choices.test.ts
index 0cd08817..3fbd401b 100644
--- a/test/scripts/choices.test.ts
+++ b/test/scripts/choices.test.ts
@@ -7,7 +7,6 @@ import { WrappedSelect, WrappedInput } from '../../src/scripts/components/index'
import { removeItem } from '../../src/scripts/actions/items';
import templates from '../../src/scripts/templates';
import { ChoiceFull } from '../../src/scripts/interfaces/choice-full';
-import { GroupFull } from '../../src/scripts/interfaces/group-full';
import { SearchByFuse } from '../../src/scripts/search/fuse';
import { SearchByPrefixFilter } from '../../src/scripts/search/prefix-filter';
@@ -46,6 +45,7 @@ describe('choices', () => {
...DEFAULT_CONFIG,
searchEnabled: false,
closeDropdownOnSelect: true,
+ renderSelectedChoices: false,
});
});
});
@@ -66,6 +66,7 @@ describe('choices', () => {
...DEFAULT_CONFIG,
searchEnabled: false,
closeDropdownOnSelect: true,
+ renderSelectedChoices: false,
...config,
});
});
@@ -110,7 +111,7 @@ describe('choices', () => {
renderSelectedChoices: 'test' as any,
});
- expect(instance.config.renderSelectedChoices).to.equal('auto');
+ expect(instance.config.renderSelectedChoices).to.equal(false);
});
});
});
@@ -752,9 +753,10 @@ describe('choices', () => {
let storeDispatchSpy;
let storeGetGroupByIdStub;
let choicesStub;
+ let itemsStub;
const groupIdValue = 'Test';
const item: ChoiceFull = {
- groupId: 0,
+ groupId: 1,
highlighted: false,
active: false,
disabled: false,
@@ -769,6 +771,7 @@ describe('choices', () => {
beforeEach(() => {
choicesStub = stub(instance._store, 'choices').get(() => [item]);
+ itemsStub = stub(instance._store, 'items').get(() => [item]);
passedElementTriggerEventStub = stub();
storeGetGroupByIdStub = stub().returns({
id: 4321,
@@ -782,6 +785,7 @@ describe('choices', () => {
afterEach(() => {
choicesStub.reset();
+ itemsStub.reset();
storeDispatchSpy.restore();
instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset();
@@ -825,7 +829,7 @@ describe('choices', () => {
describe('item with negative groupId', () => {
beforeEach(() => {
- item.groupId = -1;
+ item.groupId = 0;
output = instance.highlightItem(item);
});
@@ -877,12 +881,13 @@ describe('choices', () => {
describe('unhighlightItem', () => {
let choicesStub;
+ let itemsStub;
let passedElementTriggerEventStub;
let storeDispatchSpy;
let storeGetGroupByIdStub;
const groupIdValue = 'Test';
const item: ChoiceFull = {
- groupId: 0,
+ groupId: 1,
highlighted: true,
active: false,
disabled: false,
@@ -897,6 +902,7 @@ describe('choices', () => {
beforeEach(() => {
choicesStub = stub(instance._store, 'choices').get(() => [item]);
+ itemsStub = stub(instance._store, 'items').get(() => [item]);
passedElementTriggerEventStub = stub();
storeGetGroupByIdStub = stub().returns({
id: 4321,
@@ -910,6 +916,7 @@ describe('choices', () => {
afterEach(() => {
choicesStub.reset();
+ itemsStub.reset();
storeDispatchSpy.restore();
instance._store.getGroupById.reset();
instance.passedElement.triggerEvent.reset();
@@ -953,7 +960,7 @@ describe('choices', () => {
describe('item with negative groupId', () => {
beforeEach(() => {
- item.groupId = -1;
+ item.groupId = 0;
output = instance.unhighlightItem(item);
});
@@ -1002,32 +1009,51 @@ describe('choices', () => {
});
describe('highlightAll', () => {
- let storeGetItemsStub;
- let highlightItemStub;
+ let choicesStub;
+ let itemsStub;
+ let storeDispatchSpy;
- const items = [
+ const items: ChoiceFull[] = [
{
id: 1,
value: 'Test 1',
+ highlighted: false,
+ disabled: false,
+ active: false,
+ groupId: 0,
+ label: '',
+ placeholder: false,
+ selected: false,
+ score: 0,
+ rank: 0,
},
{
id: 2,
value: 'Test 2',
+ highlighted: false,
+ disabled: false,
+ active: false,
+ groupId: 0,
+ label: '',
+ placeholder: false,
+ selected: false,
+ score: 0,
+ rank: 0,
},
];
beforeEach(() => {
- storeGetItemsStub = stub(instance._store, 'items').get(() => items);
- highlightItemStub = stub();
-
- instance.highlightItem = highlightItemStub;
+ choicesStub = stub(instance._store, 'choices').get(() => items);
+ itemsStub = stub(instance._store, 'items').get(() => items);
+ storeDispatchSpy = spy(instance._store, 'dispatch');
output = instance.highlightAll();
});
afterEach(() => {
- highlightItemStub.reset();
- storeGetItemsStub.reset();
+ storeDispatchSpy.restore();
+ choicesStub.reset();
+ itemsStub.reset();
});
it('returns this', () => {
@@ -1035,39 +1061,66 @@ describe('choices', () => {
});
it('highlights each item in store', () => {
- expect(highlightItemStub.callCount).to.equal(items.length);
- expect(highlightItemStub.firstCall.args[0]).to.equal(items[0]);
- expect(highlightItemStub.lastCall.args[0]).to.equal(items[1]);
+ expect(storeDispatchSpy.callCount).to.equal(items.length);
+ expect(storeDispatchSpy.firstCall.args[0]).to.deep.contains({
+ type: ActionType.HIGHLIGHT_ITEM,
+ item: items[0],
+ highlighted: true,
+ });
+ expect(storeDispatchSpy.lastCall.args[0]).to.deep.contains({
+ type: ActionType.HIGHLIGHT_ITEM,
+ item: items[1],
+ highlighted: true,
+ });
});
});
describe('unhighlightAll', () => {
- let storeGetItemsStub;
- let unhighlightItemStub;
+ let choicesStub;
+ let itemsStub;
+ let storeDispatchSpy;
- const items = [
+ const items: ChoiceFull[] = [
{
id: 1,
value: 'Test 1',
+ highlighted: true,
+ disabled: false,
+ active: false,
+ groupId: 0,
+ label: '',
+ placeholder: false,
+ selected: false,
+ score: 0,
+ rank: 0,
},
{
id: 2,
value: 'Test 2',
+ highlighted: true,
+ disabled: false,
+ active: false,
+ groupId: 0,
+ label: '',
+ placeholder: false,
+ selected: false,
+ score: 0,
+ rank: 0,
},
];
beforeEach(() => {
- storeGetItemsStub = stub(instance._store, 'items').get(() => items);
- unhighlightItemStub = stub();
-
- instance.unhighlightItem = unhighlightItemStub;
+ choicesStub = stub(instance._store, 'choices').get(() => items);
+ itemsStub = stub(instance._store, 'items').get(() => items);
+ storeDispatchSpy = spy(instance._store, 'dispatch');
output = instance.unhighlightAll();
});
afterEach(() => {
- instance.unhighlightItem.reset();
- storeGetItemsStub.reset();
+ storeDispatchSpy.restore();
+ choicesStub.reset();
+ itemsStub.reset();
});
it('returns this', () => {
@@ -1075,9 +1128,17 @@ describe('choices', () => {
});
it('unhighlights each item in store', () => {
- expect(unhighlightItemStub.callCount).to.equal(items.length);
- expect(unhighlightItemStub.firstCall.args[0]).to.equal(items[0]);
- expect(unhighlightItemStub.lastCall.args[0]).to.equal(items[1]);
+ expect(storeDispatchSpy.callCount).to.equal(items.length);
+ expect(storeDispatchSpy.firstCall.args[0]).to.deep.contains({
+ type: ActionType.HIGHLIGHT_ITEM,
+ item: items[0],
+ highlighted: false,
+ });
+ expect(storeDispatchSpy.lastCall.args[0]).to.deep.contains({
+ type: ActionType.HIGHLIGHT_ITEM,
+ item: items[1],
+ highlighted: false,
+ });
});
});
@@ -1986,229 +2047,6 @@ describe('choices', () => {
});
describe('private methods', () => {
- describe('_createGroupsFragment', () => {
- let _createChoicesFragmentStub;
- const choices: ChoiceFull[] = [
- {
- id: 1,
- selected: true,
- groupId: 1,
- value: 'Choice 1',
- label: 'Choice 1',
- disabled: false,
- active: false,
- placeholder: false,
- highlighted: false,
- score: 0,
- rank: 0,
- },
- {
- id: 2,
- selected: false,
- groupId: 2,
- value: 'Choice 2',
- label: 'Choice 2',
- disabled: false,
- active: false,
- placeholder: false,
- highlighted: false,
- score: 0,
- rank: 0,
- },
- {
- id: 3,
- selected: false,
- groupId: 1,
- value: 'Choice 3',
- label: 'Choice 3',
- disabled: false,
- active: false,
- placeholder: false,
- highlighted: false,
- score: 0,
- rank: 0,
- },
- ];
-
- const groups: GroupFull[] = [
- {
- id: 2,
- label: 'Group 2',
- active: true,
- disabled: false,
- choices: [],
- },
- {
- id: 1,
- label: 'Group 1',
- active: true,
- disabled: false,
- choices: [],
- },
- ];
-
- beforeEach(() => {
- _createChoicesFragmentStub = stub();
- instance._createChoicesFragment = _createChoicesFragmentStub;
- });
-
- afterEach(() => {
- instance._createChoicesFragment.reset();
- });
-
- describe('returning a fragment of groups', () => {
- describe('passing fragment argument', () => {
- it('updates fragment with groups', () => {
- const fragment = document.createDocumentFragment();
- const childElement = document.createElement('div');
- fragment.appendChild(childElement);
-
- output = instance._createGroupsFragment(groups, choices, fragment);
- const elementToWrapFragment = document.createElement('div');
- elementToWrapFragment.appendChild(output);
-
- expect(output).to.be.instanceOf(DocumentFragment);
- expect(elementToWrapFragment.children[0]).to.deep.equal(childElement);
- expect(elementToWrapFragment.querySelectorAll('[data-group]').length).to.equal(2);
- });
- });
-
- describe('not passing fragment argument', () => {
- it('returns new groups fragment', () => {
- output = instance._createGroupsFragment(groups, choices);
- const elementToWrapFragment = document.createElement('div');
- elementToWrapFragment.appendChild(output);
-
- expect(output).to.be.instanceOf(DocumentFragment);
- expect(elementToWrapFragment.querySelectorAll('[data-group]').length).to.equal(2);
- });
- });
-
- describe('sorting groups', () => {
- let sortFnStub;
-
- beforeEach(() => {
- sortFnStub = stub();
- instance.config.sorter = sortFnStub;
- instance.config.shouldSort = true;
- });
-
- afterEach(() => {
- instance.config.sorter.reset();
- });
-
- it('sorts groups by config.sorter', () => {
- expect(sortFnStub.called).to.equal(false);
- instance._createGroupsFragment(groups, choices);
- expect(sortFnStub.called).to.equal(true);
- });
- });
-
- describe('not sorting groups', () => {
- let sortFnStub;
-
- beforeEach(() => {
- sortFnStub = stub();
- instance.config.sorter = sortFnStub;
- instance.config.shouldSort = false;
- });
-
- afterEach(() => {
- instance.config.sorter.reset();
- });
-
- it('does not sort groups', () => {
- instance._createGroupsFragment(groups, choices);
- expect(sortFnStub.called).to.equal(false);
- });
- });
-
- describe('select-one element', () => {
- beforeEach(() => {
- instance._isSelectOneElement = true;
- });
-
- it('calls _createChoicesFragment with choices that belong to each group', () => {
- expect(_createChoicesFragmentStub.called).to.equal(false);
- instance._createGroupsFragment(groups, choices);
- expect(_createChoicesFragmentStub.called).to.equal(true);
- expect(_createChoicesFragmentStub.firstCall.args[0][0]).to.contains({
- selected: true,
- groupId: 1,
- value: 'Choice 1',
- label: 'Choice 1',
- });
- expect(_createChoicesFragmentStub.firstCall.args[0][1]).to.contains({
- selected: false,
- groupId: 1,
- value: 'Choice 3',
- label: 'Choice 3',
- });
- });
- });
-
- describe('text/select-multiple element', () => {
- describe('renderSelectedChoices set to "always"', () => {
- beforeEach(() => {
- instance._isSelectOneElement = false;
- instance.config.renderSelectedChoices = 'always';
- });
-
- it('calls _createChoicesFragment with choices that belong to each group', () => {
- expect(_createChoicesFragmentStub.called).to.equal(false);
- instance._createGroupsFragment(groups, choices);
- expect(_createChoicesFragmentStub.called).to.equal(true);
- expect(_createChoicesFragmentStub.firstCall.args[0][0]).to.deep.contains({
- selected: true,
- groupId: 1,
- value: 'Choice 1',
- label: 'Choice 1',
- });
- expect(_createChoicesFragmentStub.firstCall.args[0][1]).to.deep.contains({
- selected: false,
- groupId: 1,
- value: 'Choice 3',
- label: 'Choice 3',
- });
- expect(_createChoicesFragmentStub.secondCall.args[0][0]).to.deep.contains({
- selected: false,
- groupId: 2,
- value: 'Choice 2',
- label: 'Choice 2',
- });
- });
- });
-
- describe('renderSelectedChoices not set to "always"', () => {
- beforeEach(() => {
- instance._isSelectOneElement = false;
- instance.config.renderSelectedChoices = false;
- });
-
- it('calls _createChoicesFragment with choices that belong to each group that are not already selected', () => {
- expect(_createChoicesFragmentStub.called).to.equal(false);
- instance._createGroupsFragment(groups, choices);
- expect(_createChoicesFragmentStub.called).to.equal(true);
- expect(_createChoicesFragmentStub.firstCall.args[0][0]).to.deep.contains({
- id: 3,
- selected: false,
- groupId: 1,
- value: 'Choice 3',
- label: 'Choice 3',
- });
- expect(_createChoicesFragmentStub.secondCall.args[0][0]).to.deep.contains({
- id: 2,
- selected: false,
- groupId: 2,
- value: 'Choice 2',
- label: 'Choice 2',
- });
- });
- });
- });
- });
- });
-
describe('_generatePlaceholderValue', () => {
describe('select element', () => {
describe('when a placeholder option is defined', () => {
diff --git a/test/scripts/components/container.test.ts b/test/scripts/components/container.test.ts
index 11754281..7c8f9341 100644
--- a/test/scripts/components/container.test.ts
+++ b/test/scripts/components/container.test.ts
@@ -44,7 +44,7 @@ describe('components/container', () => {
});
it('returns true', () => {
- expect(instance.shouldFlip(100)).to.equal(true);
+ expect(instance.shouldFlip(100, 100)).to.equal(true);
});
});
@@ -54,7 +54,7 @@ describe('components/container', () => {
});
it('returns false', () => {
- expect(instance.shouldFlip(100)).to.equal(false);
+ expect(instance.shouldFlip(100, 100)).to.equal(false);
});
});
});
diff --git a/test/scripts/components/dropdown.test.ts b/test/scripts/components/dropdown.test.ts
index 8c5ea520..2c09d906 100644
--- a/test/scripts/components/dropdown.test.ts
+++ b/test/scripts/components/dropdown.test.ts
@@ -1,5 +1,4 @@
import { expect } from 'chai';
-import sinon from 'sinon';
import { DEFAULT_CLASSNAMES } from '../../../src';
import Dropdown from '../../../src/scripts/components/dropdown';
import { getClassNames } from '../../../src/scripts/lib/utils';
@@ -41,45 +40,6 @@ describe('components/dropdown', () => {
});
});
- describe('distanceFromTopWindow', () => {
- let top: number;
- let dimensions;
- let getBoundingClientRectStub;
-
- beforeEach(() => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
-
- top = 100;
- dimensions = {
- bottom: 121,
- height: 0,
- left: 0,
- right: 0,
- top,
- width: 0,
- };
-
- getBoundingClientRectStub = sinon.stub(instance.element, 'getBoundingClientRect').returns(dimensions);
- });
-
- afterEach(() => {
- getBoundingClientRectStub.restore();
- });
-
- it('determines how far the top of our element is from the top of the viewport', () => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
- const expectedResponse = dimensions.bottom;
- const actualResponse = instance.distanceFromTopWindow;
- expect(actualResponse).to.equal(expectedResponse);
- });
- });
-
describe('show', () => {
let actualResponse;
diff --git a/test/scripts/components/list.test.ts b/test/scripts/components/list.test.ts
index edd54ef9..23fc1b10 100644
--- a/test/scripts/components/list.test.ts
+++ b/test/scripts/components/list.test.ts
@@ -1,5 +1,4 @@
import { expect } from 'chai';
-import { getClassNames } from '../../../src/scripts/lib/utils';
import List from '../../../src/scripts/components/list';
describe('components/list', () => {
@@ -36,62 +35,6 @@ describe('components/list', () => {
});
});
- describe('clear', () => {
- it("clears element's inner HTML", () => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
- const innerHTML = 'test';
- instance.element.innerHTML = innerHTML;
- expect(instance.element.innerHTML).to.equal(innerHTML);
- instance.clear();
- expect(instance.element.innerHTML).to.equal('');
- });
- });
-
- describe('append', () => {
- it('appends passed node to element', () => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
- const elementToAppend = document.createElement('span');
- const childClass = 'test-element';
- elementToAppend.classList.add(...getClassNames(childClass));
- expect(instance.element.querySelector(`.${childClass}`)).to.equal(null);
- instance.element.append(elementToAppend);
- expect(instance.element.querySelector(`.${childClass}`)).to.equal(elementToAppend);
- });
- });
-
- describe('hasChildren', () => {
- describe('when list has children', () => {
- it('returns true', () => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
- const childElement = document.createElement('span');
- instance.element.appendChild(childElement);
- const response = instance.element.hasChildNodes();
- expect(response).to.equal(true);
- });
- });
-
- describe('when list does not have children', () => {
- it('returns false', () => {
- expect(instance).to.not.be.null;
- if (!instance) {
- return;
- }
- instance.element.innerHTML = '';
- const response = instance.element.hasChildNodes();
- expect(response).to.equal(false);
- });
- });
- });
-
describe('scrollToTop', () => {
it("sets the position's scroll position to 0", () => {
expect(instance).to.not.be.null;
diff --git a/test/scripts/store/store.test.ts b/test/scripts/store/store.test.ts
index 766207dd..e3b06fb3 100644
--- a/test/scripts/store/store.test.ts
+++ b/test/scripts/store/store.test.ts
@@ -2,12 +2,18 @@ import { expect } from 'chai';
import sinon from 'sinon';
import { beforeEach } from 'vitest';
import Store from '../../../src/scripts/store/store';
-import { ActionType, State } from '../../../src';
+// eslint-disable-next-line import/no-named-default
+import { ActionType, State, default as Choices } from '../../../src';
import { cloneObject } from '../../../src/scripts/lib/utils';
import { AnyAction, StoreListener } from '../../../src/scripts/interfaces/store';
+import { Options } from '../../../src/scripts/interfaces/options';
+
+function shimStore() {
+ return new Store(Choices.defaults.allOptions);
+}
describe('reducers/store', () => {
- let instance: Store;
+ let instance: Store;
let subscribeStub: sinon.SinonStub<[listener: StoreListener], void>;
let dispatchStub: sinon.SinonStub<[action: AnyAction], void>;
let getStateStub: sinon.SinonStub;
@@ -15,7 +21,7 @@ describe('reducers/store', () => {
let state: State;
beforeEach(() => {
- instance = new Store();
+ instance = shimStore();
subscribeStub = sinon.stub(instance, 'subscribe');
dispatchStub = sinon.stub(instance, 'dispatch');
getStateStub = sinon.stub(instance, 'state');
diff --git a/test/scripts/templates.test.ts b/test/scripts/templates.test.ts
index b560b0a9..cc1d207f 100644
--- a/test/scripts/templates.test.ts
+++ b/test/scripts/templates.test.ts
@@ -11,13 +11,13 @@ import { NoticeTypes, Templates as TemplatesInterface } from '../../src/scripts/
*/
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(Object.keys(element2.dataset));
expect(element1.classList).to.include(element2.classList);
// compare attributes values
for (const attribute of Object.values(element1.attributes)) {
expect(element1.getAttribute(attribute)).to.equal(element2.getAttribute(attribute));
}
+ expect(element1.attributes.length).to.equal(element2.attributes.length);
}
function createOptionsWithPartialClasses(classNames: Partial, options: Partial = {}): Options {
@@ -563,7 +563,7 @@ describe('templates', () => {
${data.label}
`);
- const actualOutput = templates.choice(choiceOptions, data, itemSelectText);
+ const actualOutput = templates.choice(choiceOptions, data, itemSelectText, 'Group text');
expectEqualElements(actualOutput, expectedOutput);
});
diff --git a/test/tsconfig.json b/test/tsconfig.json
index 95360152..990eced2 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -2,7 +2,7 @@
"compilerOptions": {
"module": "es6",
"lib": ["es2017", "dom"],
- "target": "es5",
+ "target": "ES2020",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,