feat(ui): dim hidden sessions in Manage View with CSS badge (Phase 5)
Hidden sessions in the Manage View panel now render at 55% opacity with a "(hidden)" pseudo-element badge driven by CSS. renderManageViewList uses the isHidden() Phase 1 helper for the conditional class — no inline hidden-checks remain in the rendering path. 4 new tests verify the class is applied/removed correctly across renders.
This commit is contained in:
@@ -2613,7 +2613,6 @@ function renderManageViewList() {
|
|||||||
if (!listEl) return;
|
if (!listEl) return;
|
||||||
|
|
||||||
var views = (_serverSettings && _serverSettings.views) || [];
|
var views = (_serverSettings && _serverSettings.views) || [];
|
||||||
var hidden = (_serverSettings && _serverSettings.hidden_sessions) || [];
|
|
||||||
|
|
||||||
// Find the active view's session list
|
// Find the active view's session list
|
||||||
var activeViewObj = null;
|
var activeViewObj = null;
|
||||||
@@ -2665,17 +2664,18 @@ function renderManageViewList() {
|
|||||||
var s = sorted[j];
|
var s = sorted[j];
|
||||||
var key = s.sessionKey || s.name;
|
var key = s.sessionKey || s.name;
|
||||||
var isInView = viewSessions.indexOf(key) !== -1 || viewSessions.indexOf(s.name) !== -1;
|
var isInView = viewSessions.indexOf(key) !== -1 || viewSessions.indexOf(s.name) !== -1;
|
||||||
var isHidden = hidden.indexOf(key) !== -1 || hidden.indexOf(s.name) !== -1;
|
// Phase 5: use the isHidden() helper (Phase 1) — do not inline a hidden check here.
|
||||||
|
// The manage-view-item--hidden class triggers opacity + CSS ::after "(hidden)" badge.
|
||||||
|
var sessionIsHidden = isHidden(key, _serverSettings);
|
||||||
var escapedName = escapeHtml(s.name || '');
|
var escapedName = escapeHtml(s.name || '');
|
||||||
var deviceName = escapeHtml(_getDeviceDisplayName(s) || '');
|
var deviceName = escapeHtml(_getDeviceDisplayName(s) || '');
|
||||||
|
|
||||||
html += '<label class="manage-view-item' + (isHidden ? ' manage-view-item--hidden' : '') + '">';
|
html += '<label class="manage-view-item' + (sessionIsHidden ? ' manage-view-item--hidden' : '') + '">';
|
||||||
html += '<input type="checkbox" class="manage-view-item__checkbox" data-session-key="' + escapeHtml(key) + '"' + (isInView ? ' checked' : '') + (isHidden ? ' data-is-hidden="1"' : '') + ' />';
|
html += '<input type="checkbox" class="manage-view-item__checkbox" data-session-key="' + escapeHtml(key) + '"' + (isInView ? ' checked' : '') + (sessionIsHidden ? ' data-is-hidden="1"' : '') + ' />';
|
||||||
html += '<span class="manage-view-item__name">' + escapedName + '</span>';
|
html += '<span class="manage-view-item__name">' + escapedName + '</span>';
|
||||||
if (deviceName) html += '<span class="manage-view-item__device">' + deviceName + '</span>';
|
if (deviceName) html += '<span class="manage-view-item__device">' + deviceName + '</span>';
|
||||||
if (isHidden) html += '<span class="manage-view-item__badge">hidden</span>';
|
|
||||||
html += '</label>';
|
html += '</label>';
|
||||||
if (isHidden) {
|
if (sessionIsHidden) {
|
||||||
html += '<div class="manage-view-item__disclosure">Adding will unhide this session</div>';
|
html += '<div class="manage-view-item__disclosure">Adding will unhide this session</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1907,7 +1907,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.manage-view-item--hidden {
|
.manage-view-item--hidden {
|
||||||
opacity: 0.5;
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Phase 5: "(hidden)" badge via CSS pseudo-element on the name span.
|
||||||
|
CSS pseudo-elements cannot be read from JSDOM — test coverage verifies
|
||||||
|
the manage-view-item--hidden class is applied (which triggers this rule). */
|
||||||
|
.manage-view-item--hidden .manage-view-item__name::after {
|
||||||
|
content: " (hidden)";
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.manage-view-item__checkbox {
|
.manage-view-item__checkbox {
|
||||||
|
|||||||
@@ -5349,3 +5349,141 @@ test('removeSessionFromViewOp does not mutate its settings argument', () => {
|
|||||||
app.removeSessionFromViewOp(settings, 'Work', 'dev1:a');
|
app.removeSessionFromViewOp(settings, 'Work', 'dev1:a');
|
||||||
assert.strictEqual(JSON.stringify(settings), before, 'settings must not be mutated');
|
assert.strictEqual(JSON.stringify(settings), before, 'settings must not be mutated');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Phase 5 dim styling ────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// renderManageViewList applies manage-view-item--hidden to rows for sessions
|
||||||
|
// that are in settings.hidden_sessions. The CSS class triggers:
|
||||||
|
// opacity: 0.55
|
||||||
|
// .manage-view-item__name::after { content: " (hidden)"; ... }
|
||||||
|
//
|
||||||
|
// The ::after pseudo-element is not readable from JSDOM, so tests verify that
|
||||||
|
// the class is applied (which in a browser triggers the badge via CSS).
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Helper: mock document.getElementById for renderManageViewList and return
|
||||||
|
// the fake list element so callers can inspect innerHTML.
|
||||||
|
function _withManageViewDOM(fn) {
|
||||||
|
const fakeList = { innerHTML: '', onchange: null };
|
||||||
|
const fakeSummary = { textContent: '' };
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'manage-view-list') return fakeList;
|
||||||
|
if (id === 'manage-view-summary') return fakeSummary;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
fn(fakeList, fakeSummary);
|
||||||
|
} finally {
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Phase 5: hidden session row gets manage-view-item--hidden class', () => {
|
||||||
|
// Session dev1:hidden-sess is in the Work view AND in hidden_sessions.
|
||||||
|
const settings = {
|
||||||
|
hidden_sessions: ['dev1:hidden-sess'],
|
||||||
|
views: [{ name: 'Work', sessions: ['dev1:hidden-sess'] }],
|
||||||
|
};
|
||||||
|
app._setServerSettings(settings);
|
||||||
|
app._setCurrentSessions([{ name: 'hidden-sess', sessionKey: 'dev1:hidden-sess' }]);
|
||||||
|
app._setActiveView('Work');
|
||||||
|
|
||||||
|
let renderedHTML = '';
|
||||||
|
_withManageViewDOM((fakeList) => {
|
||||||
|
app.renderManageViewList();
|
||||||
|
renderedHTML = fakeList.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
app._setServerSettings(null);
|
||||||
|
app._setCurrentSessions([]);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
renderedHTML.includes('manage-view-item--hidden'),
|
||||||
|
'hidden session label must have manage-view-item--hidden class; got: ' + renderedHTML
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Phase 5: non-hidden session row does NOT get manage-view-item--hidden class', () => {
|
||||||
|
// Session dev1:visible-sess is in the Work view and NOT hidden.
|
||||||
|
const settings = {
|
||||||
|
hidden_sessions: [],
|
||||||
|
views: [{ name: 'Work', sessions: ['dev1:visible-sess'] }],
|
||||||
|
};
|
||||||
|
app._setServerSettings(settings);
|
||||||
|
app._setCurrentSessions([{ name: 'visible-sess', sessionKey: 'dev1:visible-sess' }]);
|
||||||
|
app._setActiveView('Work');
|
||||||
|
|
||||||
|
let renderedHTML = '';
|
||||||
|
_withManageViewDOM((fakeList) => {
|
||||||
|
app.renderManageViewList();
|
||||||
|
renderedHTML = fakeList.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
app._setServerSettings(null);
|
||||||
|
app._setCurrentSessions([]);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!renderedHTML.includes('manage-view-item--hidden'),
|
||||||
|
'visible session label must NOT have manage-view-item--hidden class; got: ' + renderedHTML
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Phase 5: dim class is absent after session is unhidden (re-render)', () => {
|
||||||
|
// Render once with hidden, once without — verify the class toggles.
|
||||||
|
const sessionKey = 'dev1:toggled-sess';
|
||||||
|
const session = [{ name: 'toggled-sess', sessionKey }];
|
||||||
|
|
||||||
|
// First render: session is hidden.
|
||||||
|
app._setServerSettings({
|
||||||
|
hidden_sessions: [sessionKey],
|
||||||
|
views: [{ name: 'Work', sessions: [sessionKey] }],
|
||||||
|
});
|
||||||
|
app._setCurrentSessions(session);
|
||||||
|
app._setActiveView('Work');
|
||||||
|
|
||||||
|
let htmlWhenHidden = '';
|
||||||
|
_withManageViewDOM((fakeList) => {
|
||||||
|
app.renderManageViewList();
|
||||||
|
htmlWhenHidden = fakeList.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second render: unhide the session (empty hidden_sessions).
|
||||||
|
app._setServerSettings({
|
||||||
|
hidden_sessions: [],
|
||||||
|
views: [{ name: 'Work', sessions: [sessionKey] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
let htmlWhenVisible = '';
|
||||||
|
_withManageViewDOM((fakeList) => {
|
||||||
|
app.renderManageViewList();
|
||||||
|
htmlWhenVisible = fakeList.innerHTML;
|
||||||
|
});
|
||||||
|
|
||||||
|
app._setServerSettings(null);
|
||||||
|
app._setCurrentSessions([]);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
htmlWhenHidden.includes('manage-view-item--hidden'),
|
||||||
|
'row must be dimmed when session is hidden'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!htmlWhenVisible.includes('manage-view-item--hidden'),
|
||||||
|
'row must NOT be dimmed after session is unhidden'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Phase 5: isHidden() helper (not inline check) drives the dim class', () => {
|
||||||
|
// Verify that isHidden() correctly reports hidden state, confirming the
|
||||||
|
// helper is the source of truth used by renderManageViewList.
|
||||||
|
// (The CSS ::after "(hidden)" badge cannot be verified from JSDOM — see
|
||||||
|
// comment at top of Phase 5 section. Class presence is sufficient.)
|
||||||
|
const settings = { hidden_sessions: ['dev1:sess-a'] };
|
||||||
|
|
||||||
|
assert.strictEqual(app.isHidden('dev1:sess-a', settings), true,
|
||||||
|
'isHidden must return true for a key in hidden_sessions');
|
||||||
|
assert.strictEqual(app.isHidden('dev1:sess-b', settings), false,
|
||||||
|
'isHidden must return false for a key not in hidden_sessions');
|
||||||
|
assert.strictEqual(app.isHidden('dev1:sess-a', null), false,
|
||||||
|
'isHidden must return false when settings is null');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user