295 lines
9.4 KiB
TypeScript
295 lines
9.4 KiB
TypeScript
import { suite, test, beforeEach, expect } from 'vitest';
|
||
import { matchSorter } from 'match-sorter';
|
||
|
||
import { Combobox, Option, State } from './combobox';
|
||
|
||
suite('Combobox', () => {
|
||
const options: Option[] =
|
||
'Fraises,Myrtilles,Framboises,Mûres,Canneberges,Groseilles,Baies de sureau,Mûres blanches,Baies de genièvre,Baies d’açaï'
|
||
.split(',')
|
||
.map((label) => ({ label, value: label }));
|
||
|
||
let combobox: Combobox;
|
||
let currentState: State;
|
||
|
||
suite('single select without custom value', () => {
|
||
suite('with default selection', () => {
|
||
beforeEach(() => {
|
||
combobox = new Combobox({
|
||
options,
|
||
selected: options.at(0) ?? null,
|
||
render: (state) => {
|
||
currentState = state;
|
||
}
|
||
});
|
||
combobox.init();
|
||
});
|
||
|
||
test('open select box and select option with click', () => {
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.loading).toBe(null);
|
||
expect(currentState.selection?.label).toBe('Fraises');
|
||
|
||
combobox.open();
|
||
expect(currentState.open).toBeTruthy();
|
||
|
||
combobox.select('Mûres');
|
||
expect(currentState.selection?.label).toBe('Mûres');
|
||
expect(currentState.open).toBeFalsy();
|
||
});
|
||
|
||
test('open select box and select option with enter', () => {
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection?.label).toBe('Fraises');
|
||
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection?.label).toBe('Fraises');
|
||
expect(currentState.focused?.label).toBe('Fraises');
|
||
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.selection?.label).toBe('Fraises');
|
||
expect(currentState.focused?.label).toBe('Myrtilles');
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.selection?.label).toBe('Myrtilles');
|
||
expect(currentState.open).toBeFalsy();
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.selection?.label).toBe('Myrtilles');
|
||
expect(currentState.open).toBeFalsy();
|
||
});
|
||
|
||
test('open select box and select option with tab', () => {
|
||
combobox.keyboard('ArrowDown');
|
||
combobox.keyboard('ArrowDown');
|
||
|
||
combobox.keyboard('Tab');
|
||
expect(currentState.selection?.label).toBe('Myrtilles');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.hint).toEqual({
|
||
type: 'selected',
|
||
label: 'Myrtilles'
|
||
});
|
||
});
|
||
|
||
test('do not open select box on focus', () => {
|
||
combobox.focus();
|
||
expect(currentState.open).toBeFalsy();
|
||
});
|
||
});
|
||
|
||
suite('empty', () => {
|
||
beforeEach(() => {
|
||
combobox = new Combobox({
|
||
options,
|
||
selected: null,
|
||
render: (state) => {
|
||
currentState = state;
|
||
}
|
||
});
|
||
combobox.init();
|
||
});
|
||
|
||
test('open select box on focus', () => {
|
||
combobox.focus();
|
||
expect(currentState.open).toBeTruthy();
|
||
});
|
||
|
||
suite('open', () => {
|
||
beforeEach(() => {
|
||
combobox.open();
|
||
});
|
||
|
||
test('if tab on empty input nothing is selected', () => {
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection).toBeNull();
|
||
combobox.keyboard('Tab');
|
||
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
});
|
||
|
||
test('if enter on empty input nothing is selected', () => {
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
});
|
||
});
|
||
|
||
suite('closed', () => {
|
||
test('if tab on empty input nothing is selected', () => {
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Tab');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
});
|
||
|
||
test('if enter on empty input nothing is selected', () => {
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
});
|
||
});
|
||
|
||
test('type exact match and press enter', () => {
|
||
combobox.input('Baies');
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.options.length).toEqual(3);
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection?.label).toBe('Baies d’açaï');
|
||
});
|
||
|
||
test('type exact match and press tab', () => {
|
||
combobox.input('Baies');
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Tab');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection?.label).toBe('Baies d’açaï');
|
||
expect(currentState.inputValue).toEqual('Baies d’açaï');
|
||
});
|
||
|
||
test('type non matching input and press enter', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('');
|
||
});
|
||
|
||
test('type non matching input and press tab', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Tab');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('');
|
||
});
|
||
|
||
test('type non matching input and close', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.close();
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('');
|
||
});
|
||
|
||
test('focus should circle', () => {
|
||
combobox.input('Baie');
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.options.map(({ label }) => label)).toEqual([
|
||
'Baies d’açaï',
|
||
'Baies de genièvre',
|
||
'Baies de sureau'
|
||
]);
|
||
expect(currentState.focused).toBeNull();
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.focused?.label).toBe('Baies d’açaï');
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.focused?.label).toBe('Baies de genièvre');
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.focused?.label).toBe('Baies de sureau');
|
||
combobox.keyboard('ArrowDown');
|
||
expect(currentState.focused?.label).toBe('Baies d’açaï');
|
||
combobox.keyboard('ArrowUp');
|
||
expect(currentState.focused?.label).toBe('Baies de sureau');
|
||
});
|
||
});
|
||
});
|
||
|
||
suite('single select with custom value', () => {
|
||
beforeEach(() => {
|
||
combobox = new Combobox({
|
||
options,
|
||
selected: null,
|
||
allowsCustomValue: true,
|
||
render: (state) => {
|
||
currentState = state;
|
||
}
|
||
});
|
||
combobox.init();
|
||
});
|
||
|
||
test('type non matching input and press enter', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Enter');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('toto');
|
||
});
|
||
|
||
test('type non matching input and press tab', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.keyboard('Tab');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('toto');
|
||
});
|
||
|
||
test('type non matching input and close', () => {
|
||
combobox.input('toto');
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
|
||
combobox.close();
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.inputValue).toEqual('toto');
|
||
});
|
||
});
|
||
|
||
suite('single select with fetcher', () => {
|
||
beforeEach(() => {
|
||
combobox = new Combobox({
|
||
options: (term: string) =>
|
||
Promise.resolve(matchSorter(options, term, { keys: ['value'] })),
|
||
selected: null,
|
||
render: (state) => {
|
||
currentState = state;
|
||
}
|
||
});
|
||
combobox.init();
|
||
});
|
||
|
||
test('type and get options from fetcher', async () => {
|
||
expect(currentState.open).toBeFalsy();
|
||
expect(currentState.loading).toBe(false);
|
||
|
||
const result = combobox.input('Baies');
|
||
|
||
expect(currentState.loading).toBe(true);
|
||
await result;
|
||
expect(currentState.loading).toBe(false);
|
||
expect(currentState.open).toBeTruthy();
|
||
expect(currentState.selection).toBeNull();
|
||
expect(currentState.options.length).toEqual(3);
|
||
});
|
||
});
|
||
});
|