6881d31650
When displayValue:true, the input shows the selected option's value (e.g. a
state code "TX") while the dropdown stays label-searchable ("Texas") — for a
field searched by name but displayed compactly. Backward-compatible
(default false). 20/20 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
94 lines
3.0 KiB
JavaScript
94 lines
3.0 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { comboboxData } from './combobox.js';
|
|
|
|
test('free-typing combobox commits typed text as the value (SEV-392)', () => {
|
|
const c = comboboxData({ options: ['BMW', 'Porsche', 'Tesla'] });
|
|
c.init();
|
|
c.query = 'Devin Sportscar'; // off-list classic
|
|
c.onInput();
|
|
assert.equal(c.value, 'Devin Sportscar'); // accepted as-is, never blocked
|
|
});
|
|
|
|
test('fuzzy filter matches prefix, substring + abbreviation subsequences (SEV-392)', () => {
|
|
const c = comboboxData({ options: ['BMW', 'Mercedes-Benz', 'Porsche'] });
|
|
// prefix
|
|
c.query = 'merc';
|
|
assert.ok(c.filtered.map((o) => o.label).includes('Mercedes-Benz'));
|
|
// abbreviation subsequence (M…B)
|
|
c.query = 'mb';
|
|
assert.ok(c.filtered.map((o) => o.label).includes('Mercedes-Benz'));
|
|
// and it excludes non-matches
|
|
c.query = 'xyz';
|
|
assert.equal(c.filtered.length, 0);
|
|
});
|
|
|
|
test('choose() commits an option value + label; supports {value,label} (SEV-392)', () => {
|
|
const c = comboboxData({
|
|
options: [{ value: 'CA', label: 'California' }, { value: 'OR', label: 'Oregon' }],
|
|
allowFree: false,
|
|
});
|
|
c.init();
|
|
c.choose({ value: 'OR', label: 'Oregon' });
|
|
assert.equal(c.value, 'OR');
|
|
assert.equal(c.query, 'Oregon');
|
|
assert.equal(c.isOpen, false);
|
|
});
|
|
|
|
test('closed-list blur snaps back to the committed label (SEV-392)', () => {
|
|
const c = comboboxData({
|
|
options: [{ value: 'CA', label: 'California' }],
|
|
allowFree: false,
|
|
value: 'CA',
|
|
});
|
|
c.init();
|
|
assert.equal(c.query, 'California'); // init syncs query from value
|
|
c.query = 'typing garbage';
|
|
c.onBlur();
|
|
assert.equal(c.value, 'CA'); // unchanged
|
|
assert.equal(c.query, 'California'); // reverted
|
|
});
|
|
|
|
test('empty query lists all options (capped) (SEV-392)', () => {
|
|
const c = comboboxData({ options: ['a', 'b', 'c'] });
|
|
c.query = '';
|
|
assert.equal(c.filtered.length, 3);
|
|
});
|
|
|
|
test('setOptions feeds reactive option lists (SEV-392)', () => {
|
|
const c = comboboxData({ allowFree: true });
|
|
c.setOptions([]); // async source not loaded yet
|
|
assert.equal(c.filtered.length, 0);
|
|
c.setOptions(['BMW', 'Tesla']); // makes arrive
|
|
assert.equal(c.filtered.length, 2);
|
|
c.query = 'tes';
|
|
assert.deepEqual(c.filtered.map((o) => o.value), ['Tesla']);
|
|
});
|
|
|
|
test('displayValue shows the code in the input, searches by label (SEV-408)', () => {
|
|
const c = comboboxData({
|
|
options: [{ value: 'CA', label: 'California' }, { value: 'TX', label: 'Texas' }],
|
|
allowFree: false,
|
|
displayValue: true,
|
|
});
|
|
c.init();
|
|
// search by name
|
|
c.query = 'tex';
|
|
assert.deepEqual(c.filtered.map((o) => o.value), ['TX']);
|
|
// choosing shows the CODE, not the name
|
|
c.choose({ value: 'TX', label: 'Texas' });
|
|
assert.equal(c.value, 'TX');
|
|
assert.equal(c.query, 'TX');
|
|
});
|
|
|
|
test('displayValue syncs the code from an external value (SEV-408)', () => {
|
|
const c = comboboxData({
|
|
options: [{ value: 'OR', label: 'Oregon' }],
|
|
allowFree: false,
|
|
displayValue: true,
|
|
value: 'OR',
|
|
});
|
|
c.init();
|
|
assert.equal(c.query, 'OR'); // shows code, not "Oregon"
|
|
});
|