Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion public/live.js
Original file line number Diff line number Diff line change
Expand Up @@ -1300,7 +1300,7 @@
<div class="live-toggles">
<div class="live-node-filter-wrap" style="position:relative">
<label class="live-node-filter-hitarea" style="display:inline-flex; align-items:center; min-height:44px; cursor:text;">
<input type="text" id="liveNodeFilterInput" placeholder="Filter by node…" autocomplete="off" class="live-node-filter-input" role="combobox" aria-expanded="false" aria-owns="liveNodeFilterDropdown" aria-autocomplete="list" aria-activedescendant="">
<input type="text" id="liveNodeFilterInput" placeholder="Filter by node…" autocomplete="off" class="live-node-filter-input" role="combobox" aria-expanded="false" aria-owns="liveNodeFilterDropdown" aria-autocomplete="list" aria-activedescendant="" disabled>
</label>
<div id="liveNodeFilterDropdown" class="live-node-filter-dropdown hidden" role="listbox"></div>
<button id="liveNodeFilterClear" class="vcr-btn" title="Clear node filter" style="display:none">×</button>
Expand Down Expand Up @@ -1903,6 +1903,8 @@
// Slight delay so click on a suggestion can register first.
setTimeout(hideDropdown, 150);
});
// Initial node loading yields before these handlers are installed (#2094).
nodeFilterInput.disabled = false;
}
if (nodeFilterClear) {
nodeFilterClear.addEventListener('click', () => {
Expand Down
47 changes: 47 additions & 0 deletions tests/e2e/test-1110-live-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,53 @@ async function test(name, fn) {
page.setDefaultTimeout(10000);
console.log(`#1110 Live filter E2E against ${BASE}`);

for (const abortInitialLoad of [false, true]) {
await test(`#2094 node filter becomes editable after initial nodes ${abortInitialLoad ? 'fail' : 'load'}`, async () => {
const response = await ctx.request.get(BASE + '/api/nodes?limit=1');
assert(response.ok(), 'fixture node request must succeed');
const { nodes } = await response.json();
assert(nodes && nodes[0] && nodes[0].public_key, 'fixture must contain a searchable node');
const key = nodes[0].public_key;
const loadingPage = await ctx.newPage();
loadingPage.setDefaultTimeout(10000);
let releaseNodes;
const heldNodes = new Promise(resolve => { releaseNodes = resolve; });
await loadingPage.route(url => url.pathname === '/api/nodes', async route => {
await heldNodes;
if (abortInitialLoad) await route.abort();
else await route.continue();
});
try {
const initialRequest = loadingPage.waitForRequest(req => new URL(req.url()).pathname === '/api/nodes');
await loadingPage.goto(BASE + '#/live', { waitUntil: 'domcontentloaded' });
await initialRequest;
const input = loadingPage.locator('#liveNodeFilterInput');
await input.waitFor({ state: 'visible' });
// A real keyboard cannot enter text into a control that is not ready.
await input.evaluate(el => el.focus());
await loadingPage.keyboard.type('pending');
assert(await input.inputValue() === '', 'node filter accepted text before its handlers were ready');
assert(await input.isDisabled(), 'node filter must be disabled while initial nodes are pending');
releaseNodes();
await loadingPage.waitForFunction(() => !document.getElementById('liveNodeFilterInput').disabled);
await input.fill(key.slice(0, -1));
const suggestion = loadingPage.locator(`#liveNodeFilterDropdown:not(.hidden) [data-key="${key}"]`);
await suggestion.waitFor({ state: 'visible' });
const optionKeys = await loadingPage.locator('#liveNodeFilterDropdown [data-key]')
.evaluateAll(options => options.map(option => option.getAttribute('data-key')));
for (let i = 0; i <= optionKeys.indexOf(key); i++) await input.press('ArrowDown');
await input.press('Enter');
const selected = await loadingPage.evaluate(() => window._liveGetNodeFilterKeys());
assert(selected.length === 1 && selected[0] === key, 'keyboard selection must apply the matching node filter');
assert(new URLSearchParams(loadingPage.url().split('?')[1]).get('node') === key,
'selected node must be preserved in the URL');
} finally {
releaseNodes();
await loadingPage.close();
}
});
}

await test('input matches toolbar styling (theme-aware bg, comparable height)', async () => {
await page.goto(BASE + '#/live', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#liveNodeFilterInput', { timeout: 10000 });
Expand Down
Loading