feat: add renderSidebar — populates sidebar list from session data
This commit is contained in:
@@ -339,6 +339,35 @@ function buildSidebarHTML(session, currentSession) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the session sidebar list. Only renders in fullscreen view.
|
||||||
|
* Shows empty state when no sessions exist.
|
||||||
|
* Binds click handlers on each sidebar-item to switch sessions.
|
||||||
|
* @param {object[]} sessions
|
||||||
|
* @param {string|null} currentSession - name of the currently active session
|
||||||
|
*/
|
||||||
|
function renderSidebar(sessions, currentSession) {
|
||||||
|
if (_viewMode !== 'fullscreen') return;
|
||||||
|
|
||||||
|
const list = $('sidebar-list');
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
list.innerHTML = '<div class="sidebar-empty">No sessions</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = sessions.map((session) => buildSidebarHTML(session, currentSession)).join('');
|
||||||
|
|
||||||
|
// Bind click handlers on each sidebar item
|
||||||
|
list.querySelectorAll('.sidebar-item').forEach((item) => {
|
||||||
|
const name = item.dataset.session;
|
||||||
|
on(item, 'click', () => {
|
||||||
|
if (name !== currentSession) openSession(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the session grid. Shows empty state when no sessions exist.
|
* Render the session grid. Shows empty state when no sessions exist.
|
||||||
* On mobile, sorts sessions by priority before rendering.
|
* On mobile, sorts sessions by priority before rendering.
|
||||||
@@ -937,6 +966,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|||||||
escapeHtml,
|
escapeHtml,
|
||||||
buildTileHTML,
|
buildTileHTML,
|
||||||
buildSidebarHTML,
|
buildSidebarHTML,
|
||||||
|
renderSidebar,
|
||||||
renderGrid,
|
renderGrid,
|
||||||
requestNotificationPermission,
|
requestNotificationPermission,
|
||||||
handleBellTransitions,
|
handleBellTransitions,
|
||||||
|
|||||||
@@ -1692,3 +1692,77 @@ test('buildSidebarHTML includes snapshot preview in a pre element', () => {
|
|||||||
const html = app.buildSidebarHTML(session, '');
|
const html = app.buildSidebarHTML(session, '');
|
||||||
assert.ok(/<pre>[\s\S]*line1[\s\S]*<\/pre>/.test(html), 'snapshot content should be inside a <pre> element');
|
assert.ok(/<pre>[\s\S]*line1[\s\S]*<\/pre>/.test(html), 'snapshot content should be inside a <pre> element');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- renderSidebar ---
|
||||||
|
|
||||||
|
test('renderSidebar populates sidebar-list innerHTML when viewMode is fullscreen', () => {
|
||||||
|
let capturedHTML = '';
|
||||||
|
const mockList = {
|
||||||
|
get innerHTML() { return capturedHTML; },
|
||||||
|
set innerHTML(v) { capturedHTML = v; },
|
||||||
|
querySelectorAll: () => [],
|
||||||
|
};
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'sidebar-list') return mockList;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setViewMode('fullscreen');
|
||||||
|
const sessions = [
|
||||||
|
{ name: 'session-a', snapshot: '', bell: { unseen_count: 0 } },
|
||||||
|
{ name: 'session-b', snapshot: '', bell: { unseen_count: 0 } },
|
||||||
|
];
|
||||||
|
app.renderSidebar(sessions, 'session-a');
|
||||||
|
|
||||||
|
assert.ok(capturedHTML.includes('sidebar-item'), 'innerHTML should contain sidebar-item');
|
||||||
|
assert.ok(capturedHTML.includes('sidebar-item--active'), 'innerHTML should contain sidebar-item--active for active session');
|
||||||
|
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
app._setViewMode('grid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderSidebar renders empty message when sessions array is empty', () => {
|
||||||
|
let capturedHTML = '';
|
||||||
|
const mockList = {
|
||||||
|
get innerHTML() { return capturedHTML; },
|
||||||
|
set innerHTML(v) { capturedHTML = v; },
|
||||||
|
querySelectorAll: () => [],
|
||||||
|
};
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'sidebar-list') return mockList;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setViewMode('fullscreen');
|
||||||
|
app.renderSidebar([], null);
|
||||||
|
|
||||||
|
assert.ok(capturedHTML.includes('sidebar-empty'), 'innerHTML should contain sidebar-empty class');
|
||||||
|
assert.ok(capturedHTML.includes('No sessions'), 'innerHTML should contain "No sessions" text');
|
||||||
|
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
app._setViewMode('grid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderSidebar does nothing when view is not fullscreen', () => {
|
||||||
|
let innerHTMLSet = false;
|
||||||
|
const mockList = {
|
||||||
|
get innerHTML() { return ''; },
|
||||||
|
set innerHTML(v) { innerHTMLSet = true; },
|
||||||
|
querySelectorAll: () => [],
|
||||||
|
};
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'sidebar-list') return mockList;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setViewMode('grid');
|
||||||
|
const sessions = [{ name: 'session-a', snapshot: '', bell: { unseen_count: 0 } }];
|
||||||
|
app.renderSidebar(sessions, null);
|
||||||
|
|
||||||
|
assert.strictEqual(innerHTMLSet, false, 'innerHTML setter should never be called when not in fullscreen');
|
||||||
|
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user