test(ext/vault): handler→renderer status integration + indicator CSS (Plan C Phase 6)

Pins the 6.1↔6.2 contract: handleGetVaultStatus output feeds straight into
renderStatusIndicator. Adds minimal self-contained .vault-status CSS. Stays
out of vault-sidebar.ts — the footer wiring (Task 6.3) is Dev-B's boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-05-31 18:09:08 -04:00
parent 61275574d4
commit 5efc3a5491
2 changed files with 77 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import { describe, expect, it } from 'vitest';
import { handleGetVaultStatus } from '../../service-worker/vault';
import { renderStatusIndicator } from '../vault-status';
import type { Manifest, ManifestEntry } from '../../shared/types';
// Integration seam: the get_vault_status SW handler (Phase 6 Task 6.1) produces
// the exact data shape the sidebar renderer (Task 6.2) consumes. This pins the
// contract between the two so a future change to either side can't silently
// drift the keys apart. It does NOT touch vault-sidebar.ts — the wiring layer
// (Task 6.3) is Dev-B's boundary and lands separately.
const cache = (lastSyncAt: number | null, ahead = 0, behind = 0) =>
({ lastSyncAt, ahead, behind });
function manifestWith(activeCount: number, trashedCount = 0): Manifest {
const items: Record<string, ManifestEntry> = {};
for (let i = 0; i < activeCount; i++) {
items[`a${i}`] = { trashed_at: undefined } as ManifestEntry;
}
for (let i = 0; i < trashedCount; i++) {
items[`t${i}`] = { trashed_at: 1000 } as ManifestEntry;
}
return { items } as Manifest;
}
describe('vault status: handler → renderer integration', () => {
it('renders "in sync" from a freshly-synced, no-pending handler response', () => {
const resp = handleGetVaultStatus({ gitHost: cache(1700000000), manifest: manifestWith(0) });
expect(resp.ok).toBe(true);
if (!resp.ok) return;
const el = document.createElement('div');
renderStatusIndicator(el, resp.data);
expect(el.textContent).toMatch(/in sync/i);
expect(el.textContent).toMatch(/last sync/i);
});
it('surfaces the handler\'s active-item count as "N pending" in the DOM', () => {
const resp = handleGetVaultStatus({ gitHost: cache(1700000000), manifest: manifestWith(7, 3) });
expect(resp.ok).toBe(true);
if (!resp.ok) return;
expect(resp.data.pendingItems).toBe(7);
const el = document.createElement('div');
renderStatusIndicator(el, resp.data);
expect(el.textContent).toMatch(/7 pending/i);
});
it('surfaces cached ahead/behind from the handler in the DOM', () => {
const resp = handleGetVaultStatus({ gitHost: cache(1700000000, 2, 1), manifest: manifestWith(0) });
expect(resp.ok).toBe(true);
if (!resp.ok) return;
const el = document.createElement('div');
renderStatusIndicator(el, resp.data);
expect(el.textContent).toMatch(/2 ahead/i);
expect(el.textContent).toMatch(/1 behind/i);
});
it('renders "never synced" when the handler reports a null lastSyncAt', () => {
const resp = handleGetVaultStatus({ gitHost: cache(null), manifest: manifestWith(0) });
expect(resp.ok).toBe(true);
if (!resp.ok) return;
const el = document.createElement('div');
renderStatusIndicator(el, resp.data);
expect(el.textContent).toMatch(/never synced/i);
});
});

View File

@@ -2113,3 +2113,15 @@ textarea {
.history-index-row__info { flex: 1; display: flex; flex-direction: column; }
.history-index-row__title { color: var(--text); }
.history-index-row__meta { font-size: 11px; }
/* Sidebar-footer vault status indicator (Plan C Phase 6, vault-status.ts).
The footer slot + refresh button are wired by vault-sidebar.ts in Task 6.3. */
.vault-status {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 11px;
line-height: 1.4;
}
.vault-status__state { color: var(--text-dim); }
.vault-status__ts { color: var(--text-muted); }