fix: renderGrid shows status tiles when no sessions; _previewClickHandler forwards sourceUrl
- renderGrid early-return now builds status tiles for auth_required and unreachable sources even when visible.length === 0. Hides empty-state when status tiles are present, shows it only when truly nothing to display. - _previewClickHandler now looks up the session in _currentSessions to recover its sourceUrl before calling openSession, ensuring remote sessions opened via hover-preview connect to the correct instance instead of falling back to the local API path. Tests added (3 new, 214 → 217 total): - renderGrid shows auth tile and hides empty-state when no sessions but source is auth_required - renderGrid shows offline tile and hides empty-state when no sessions but source is unreachable - _previewClickHandler looks up sourceUrl from _currentSessions before calling openSession Closes code-review Important issues #1 and #2. Co-authored-by: Amplifier <amplifier@anthropic.com>
This commit is contained in:
+18
-3
@@ -839,8 +839,20 @@ function renderGrid(sessions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (visible.length === 0) {
|
if (visible.length === 0) {
|
||||||
if (grid) grid.innerHTML = '';
|
// Build status tiles for non-authenticated sources even when no sessions exist
|
||||||
if (emptyState) emptyState.classList.remove('hidden');
|
var statusTilesHtml = '';
|
||||||
|
if (typeof _sources !== 'undefined' && _sources) {
|
||||||
|
_sources.forEach(function(source) {
|
||||||
|
if (source.status === 'auth_required') statusTilesHtml += buildAuthTileHTML(source);
|
||||||
|
else if (source.status === 'unreachable') statusTilesHtml += buildOfflineTileHTML(source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (grid) grid.innerHTML = statusTilesHtml;
|
||||||
|
// Only show empty-state when there are truly no tiles at all
|
||||||
|
if (emptyState) {
|
||||||
|
if (statusTilesHtml) emptyState.classList.add('hidden');
|
||||||
|
else emptyState.classList.remove('hidden');
|
||||||
|
}
|
||||||
// Show filter bar even when filtered to empty (so user can switch back)
|
// Show filter bar even when filtered to empty (so user can switch back)
|
||||||
if (filterBar) {
|
if (filterBar) {
|
||||||
if (_gridViewMode === 'filtered') {
|
if (_gridViewMode === 'filtered') {
|
||||||
@@ -920,7 +932,10 @@ function _previewClickHandler(e) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var name = _previewSessionName;
|
var name = _previewSessionName;
|
||||||
hidePreview();
|
hidePreview();
|
||||||
if (name) openSession(name);
|
if (name) {
|
||||||
|
var session = _currentSessions && _currentSessions.find(function(s) { return s.name === name; });
|
||||||
|
openSession(name, { sourceUrl: session && session.sourceUrl || '' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreview(name) {
|
function showPreview(name) {
|
||||||
|
|||||||
@@ -606,6 +606,69 @@ test('renderGrid includes offline tile HTML when a source is unreachable', () =>
|
|||||||
app._setSources([]);
|
app._setSources([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('renderGrid shows auth tile and hides empty-state when no sessions but source is auth_required', () => {
|
||||||
|
const addedClasses = [];
|
||||||
|
const removedClasses = [];
|
||||||
|
const mockGrid = { innerHTML: '' };
|
||||||
|
const mockEmpty = { style: {}, classList: { add: (c) => addedClasses.push(c), remove: (c) => removedClasses.push(c) } };
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'session-grid') return mockGrid;
|
||||||
|
if (id === 'empty-state') return mockEmpty;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setSources([
|
||||||
|
{ url: 'http://workstation:8088', name: 'Workstation', status: 'auth_required' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// No sessions — early-return path
|
||||||
|
app.renderGrid([]);
|
||||||
|
|
||||||
|
assert.ok(mockGrid.innerHTML.includes('source-tile--auth'), 'grid should show auth tile even when sessions list is empty');
|
||||||
|
assert.ok(addedClasses.includes('hidden'), 'empty-state should be hidden when status tiles are present');
|
||||||
|
assert.ok(!removedClasses.includes('hidden'), 'empty-state hidden class should NOT be removed when status tiles are present');
|
||||||
|
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
app._setSources([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderGrid shows offline tile and hides empty-state when no sessions but source is unreachable', () => {
|
||||||
|
const addedClasses = [];
|
||||||
|
const mockGrid = { innerHTML: '' };
|
||||||
|
const mockEmpty = { style: {}, classList: { add: (c) => addedClasses.push(c), remove: () => {} } };
|
||||||
|
const origGetById = globalThis.document.getElementById;
|
||||||
|
globalThis.document.getElementById = (id) => {
|
||||||
|
if (id === 'session-grid') return mockGrid;
|
||||||
|
if (id === 'empty-state') return mockEmpty;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
app._setSources([
|
||||||
|
{ url: 'http://devbox:8088', name: 'Dev Box', status: 'unreachable', lastSeenAt: Date.now() - 120000 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// No sessions — early-return path
|
||||||
|
app.renderGrid([]);
|
||||||
|
|
||||||
|
assert.ok(mockGrid.innerHTML.includes('source-tile--offline'), 'grid should show offline tile even when sessions list is empty');
|
||||||
|
assert.ok(addedClasses.includes('hidden'), 'empty-state should be hidden when status tiles are present');
|
||||||
|
|
||||||
|
globalThis.document.getElementById = origGetById;
|
||||||
|
app._setSources([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('_previewClickHandler looks up sourceUrl from _currentSessions before calling openSession', () => {
|
||||||
|
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||||
|
const handlerIdx = source.indexOf('function _previewClickHandler');
|
||||||
|
assert.ok(handlerIdx >= 0, '_previewClickHandler must exist');
|
||||||
|
// Extract from function declaration to its closing brace
|
||||||
|
const handlerEnd = source.indexOf('\n}', handlerIdx) + 2;
|
||||||
|
const handlerBody = source.slice(handlerIdx, handlerEnd);
|
||||||
|
assert.ok(handlerBody.includes('_currentSessions'), '_previewClickHandler must look up session from _currentSessions to recover sourceUrl');
|
||||||
|
assert.ok(handlerBody.includes('sourceUrl'), '_previewClickHandler must forward sourceUrl when calling openSession');
|
||||||
|
});
|
||||||
|
|
||||||
// --- requestNotificationPermission ---
|
// --- requestNotificationPermission ---
|
||||||
|
|
||||||
test('requestNotificationPermission is exported', () => {
|
test('requestNotificationPermission is exported', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user