Files
Investidor-V2/tests/frontend/territory-search-select.test.mjs
T

164 lines
5.1 KiB
JavaScript

import { compileFunction } from 'node:vm';
import assert from 'node:assert/strict';
import test from 'node:test';
import fs from 'node:fs';
import { createRequire } from 'node:module';
import React from 'react';
import ts from 'typescript';
// Exercise the real component's event/state contract; this is not browser visual QA.
const require = createRequire(import.meta.url);
const source = fs.readFileSync(
new URL(
'../../resources/js/components/territory-search-select.tsx',
import.meta.url,
),
'utf8',
);
const compiled = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
}).outputText;
function harness(overrides = {}) {
const memory = [];
let cursor = 0;
const chosen = [];
const hooks = {
...React,
useState(initial) {
const i = cursor++;
if (!(i in memory)) memory[i] = initial;
return [
memory[i],
(value) => {
memory[i] =
typeof value === 'function' ? value(memory[i]) : value;
},
];
},
useRef: () => ({ current: null }),
useEffect: () => {},
};
const module = { exports: {} };
compileFunction(compiled, ['require', 'module', 'exports'])(
(name) => (name === 'react' ? hooks : require(name)),
module,
module.exports,
);
const options = [
{ id: 'province-a', name: 'Nampula' },
{ id: 'province-b', name: 'Maputo Província' },
];
const props = {
id: 'territory',
label: 'Província',
search: '',
options,
onSearch(query) {
props.search = query;
},
onChange(option) {
chosen.push(option);
props.selected = option;
},
...overrides,
};
let tree;
const flat = (node) =>
!node || typeof node !== 'object'
? []
: [
node,
...React.Children.toArray(node.props?.children).flatMap(flat),
];
const render = () => {
cursor = 0;
tree = module.exports.default(props);
return tree;
};
const find = (predicate) => flat(tree).find(predicate);
const input = () => find((node) => node.type === 'input');
const key = (value) =>
input().props.onKeyDown({ key: value, preventDefault() {} });
render();
return { props, chosen, render, find, input, key, options };
}
void test('one search input owns the dropdown; the separate select is absent', () => {
const h = harness();
assert.equal(
h.find((node) => node.type === 'select'),
undefined,
);
assert.equal(h.input().props.role, 'combobox');
assert.equal(h.input().props['aria-expanded'], false);
h.input().props.onFocus();
h.render();
assert.equal(h.input().props['aria-expanded'], true);
assert.ok(h.find((node) => node.props?.role === 'listbox'));
h.find(
(node) =>
node.props?.role === 'option' &&
node.props.id === 'territory-options-1',
).props.onClick();
h.render();
assert.deepEqual(h.chosen, [h.options[1]]);
assert.equal(h.input().props.value, 'Maputo Província');
assert.equal(h.input().props['aria-expanded'], false);
});
void test('typing and Escape preserve the existing selection until an option is explicitly chosen', () => {
const selected = {
id: 'saved',
name: 'Selecção guardada fora desta página',
};
const h = harness({ selected });
h.input().props.onFocus();
h.render();
h.input().props.onChange({ target: { value: 'Map' } });
h.render();
assert.equal(h.props.search, 'Map');
assert.deepEqual(h.chosen, []);
h.key('Escape');
h.render();
assert.equal(h.input().props.value, selected.name);
assert.deepEqual(h.chosen, []);
});
void test('arrow keys and Enter choose from the list; loading cannot commit an old result', () => {
const h = harness();
h.input().props.onFocus();
h.render();
h.key('ArrowDown');
h.render();
h.key('ArrowDown');
h.render();
assert.equal(
h.input().props['aria-activedescendant'],
'territory-options-1',
);
h.key('Enter');
h.render();
assert.deepEqual(h.chosen, [h.options[1]]);
const busy = harness({ loading: true });
busy.input().props.onFocus();
busy.render();
busy.key('Enter');
assert.deepEqual(busy.chosen, []);
});
void test('clear is explicit; disabled dependent levels cannot expose the dropdown', () => {
const h = harness({ selected: { id: 'saved', name: 'Nampula' } });
h.find(
(node) => node.props?.['aria-label'] === 'Limpar Província',
).props.onClick();
h.render();
assert.deepEqual(h.chosen, [undefined]);
assert.equal(h.input().props.value, '');
const disabled = harness({ disabled: true });
assert.equal(disabled.input().props.disabled, true);
assert.equal(
disabled.find((node) => node.props?.role === 'listbox'),
undefined,
);
});