Files view: the jumped-to record stays selected (v2.6.2)
- focusMessage()/scrollToType() highlighted the target and removed the class after 1500 ms, so a Files-row (or map/category) jump looked selected for a moment and then lost it; on top of that any re-render (the 500 ms live poll, a filter/search change) rebuilt the list and dropped the highlight — even for a clicked entry - the selection is now persistent app state (STATE.selectedIdx): the render marks the entry (gap rows included), focusMessage records the index before rendering, showDetail records it for plain clicks, scrollToType persists it too (clearing the previous highlight), and a new session/capture resets it so a stale index cannot mark an unrelated APDU - tests: a stub-driven focusMessage case (state + detail updated, no render and no timer), the filter-clearing case, and source pins for renderMessages, focusMessage, scrollToType, showDetail, loadSession and startCapture; 527 Python + 58 Node tests - README/README_RUS: the jump leaves the record selected; PWA_VERSION/ pyproject/version.py 2.6.2, SW cache simtrace-analyser-v76
This commit is contained in:
1 parent
a0a2251522
commit
76c5647dd4
8 files changed
+98
-19
No files matched your search
@@ -13,6 +13,8 @@ let STATE = {};
|
||||
let API = {};
|
||||
let renders = [];
|
||||
let focused = [];
|
||||
let el = () => ({});
|
||||
let showDetail = () => {};
|
||||
function renderMessages(skipScroll, forceStick) {
|
||||
renders.push([skipScroll, forceStick]);
|
||||
}
|
||||
@@ -21,8 +23,11 @@ function focusMessage(idx) {
|
||||
}
|
||||
eval(
|
||||
extractFunc(src, 'jumpToMessage') + '\n' +
|
||||
extractFunc(src, 'shouldStickToBottom')
|
||||
extractFunc(src, 'shouldStickToBottom') + '\n'
|
||||
);
|
||||
// The real focusMessage, under an alias: the stub above stays in place for
|
||||
// the jumpToMessage tests.
|
||||
eval('var realFocusMessage = ' + extractFunc(src, 'focusMessage') + ';');
|
||||
|
||||
test('jumpToMessage pages, renders without stick-to-bottom, then focuses', async () => {
|
||||
renders = [];
|
||||
@@ -81,3 +86,64 @@ test('a new session/capture lands at the bottom (source pins)', () => {
|
||||
assert.match(extractFunc(src, 'loadSession'), /renderMessages\(false, true\);/);
|
||||
assert.match(extractFunc(src, 'startCapture'), /renderMessages\(false, true\);/);
|
||||
});
|
||||
|
||||
// ── the focused record stays selected ──────────────────────────────────
|
||||
|
||||
test('focusMessage marks the record persistently and updates the detail', () => {
|
||||
renders = [];
|
||||
const classes = new Set();
|
||||
const entry = {
|
||||
dataset: { idx: '1' },
|
||||
classList: { add: c => classes.add(c), remove: c => classes.delete(c) },
|
||||
scrollIntoView: () => {},
|
||||
};
|
||||
STATE = { messages: [{ id: 'a' }, { id: 'b' }], selectedIdx: 0 };
|
||||
el = () => ({ value: '', querySelector: () => entry });
|
||||
let detailed = null;
|
||||
showDetail = (msg, e) => { detailed = [msg, e]; };
|
||||
realFocusMessage(1);
|
||||
assert.equal(STATE.selectedIdx, 1);
|
||||
assert.deepEqual(detailed, [{ id: 'b' }, entry]);
|
||||
// No render is needed when no filter is active — and no flash timeout
|
||||
// is scheduled: the highlight is owned by showDetail/the state.
|
||||
assert.deepEqual(renders, []);
|
||||
});
|
||||
|
||||
test('focusMessage clears a filter before focusing', () => {
|
||||
renders = [];
|
||||
const entry = {
|
||||
dataset: { idx: '3' },
|
||||
classList: { add: () => {}, remove: () => {} },
|
||||
scrollIntoView: () => {},
|
||||
};
|
||||
STATE = { messages: {}, selectedIdx: null };
|
||||
el = () => ({ value: 'x', querySelector: () => entry });
|
||||
showDetail = () => {};
|
||||
realFocusMessage(3);
|
||||
assert.deepEqual(renders, [[true, undefined]]);
|
||||
assert.equal(STATE.selectedIdx, 3);
|
||||
});
|
||||
|
||||
test('the selection persists across renders (source pins)', () => {
|
||||
const render = extractFunc(src, 'renderMessages');
|
||||
assert.match(render, /const selectedIdx = STATE\.selectedIdx;/);
|
||||
assert.match(render, /idx === selectedIdx \? ' selected' : ''/);
|
||||
assert.match(render, /apdu-entry apdu-gap\$\{selCls\}/);
|
||||
assert.match(render, /apdu-entry\$\{pairCls\}\$\{selCls\}/);
|
||||
|
||||
const focus = extractFunc(src, 'focusMessage');
|
||||
assert.match(focus, /STATE\.selectedIdx = idx;/);
|
||||
assert.doesNotMatch(focus, /setTimeout/);
|
||||
|
||||
const type = extractFunc(src, 'scrollToType');
|
||||
assert.match(type, /STATE\.selectedIdx = parseInt\(targetEl\.dataset\.idx\);/);
|
||||
assert.doesNotMatch(type, /setTimeout/);
|
||||
|
||||
const detail = extractFunc(src, 'showDetail');
|
||||
assert.match(detail, /entry\.classList\.add\('selected'\)/);
|
||||
assert.match(detail, /STATE\.selectedIdx = idx;/);
|
||||
|
||||
// A new session/capture must not keep a stale index selected.
|
||||
assert.match(extractFunc(src, 'loadSession'), /STATE\.selectedIdx = null;/);
|
||||
assert.match(extractFunc(src, 'startCapture'), /STATE\.selectedIdx = null;/);
|
||||
});
|
||||
Reference in new issue
Block a user