fix(ext/tests): update settings.test.ts for left-nav settings + revamp

Tests were written against the pre-Stream B flat settings page. After
the left-nav restructure (bd6a301) and the management-surfaces revamp,
the Display section's IDs are only in the DOM once the user navigates
there, and renderSettings makes additional sendMessage calls (is_unlocked,
per-section data) that the original mocks didn't cover.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-05-30 21:14:36 -04:00
parent 797709b441
commit c9802ef392

View File

@@ -24,18 +24,35 @@ function mockChromeStorage(initial: Record<string, unknown> = {}) {
set: vi.fn((kv: Record<string, unknown>) => { Object.assign(store, kv); return Promise.resolve(); }), set: vi.fn((kv: Record<string, unknown>) => { Object.assign(store, kv); return Promise.resolve(); }),
remove: vi.fn((key: string) => { delete store[key]; return Promise.resolve(); }), remove: vi.fn((key: string) => { delete store[key]; return Promise.resolve(); }),
}, },
local: {
get: vi.fn(() => Promise.resolve({})),
set: vi.fn(() => Promise.resolve()),
},
}, },
}; };
return store; return store;
} }
function settingsResponses() { // After the Stream B left-nav restructure (bd6a301) and the management-surfaces
// Two parallel calls in renderSettings: get_settings + get_blacklist. // revamp, renderSettings makes these calls in this order:
// 1. is_unlocked (gates vault-only sections)
// 2. get_settings + get_blacklist (parallel) (Autofill is the default section)
function mockDefaultLanding(opts: { unlocked?: boolean } = {}) {
const unlocked = opts.unlocked ?? false;
(sendMessage as ReturnType<typeof vi.fn>) (sendMessage as ReturnType<typeof vi.fn>)
.mockResolvedValueOnce({ ok: true, data: { unlocked } })
.mockResolvedValueOnce({ ok: true, data: { settings: { captureEnabled: false, captureStyle: 'bar' } } }) .mockResolvedValueOnce({ ok: true, data: { settings: { captureEnabled: false, captureStyle: 'bar' } } })
.mockResolvedValueOnce({ ok: true, data: { blacklist: [] } }); .mockResolvedValueOnce({ ok: true, data: { blacklist: [] } });
} }
async function navigateToDisplay(app: HTMLElement): Promise<void> {
const btn = app.querySelector<HTMLButtonElement>('[data-section="display"]')!;
btn.click();
// Allow renderDisplaySection's async loadColorScheme + DOM writes to settle.
await new Promise((r) => setTimeout(r, 0));
await new Promise((r) => setTimeout(r, 0));
}
describe('settings view', () => { describe('settings view', () => {
let app: HTMLElement; let app: HTMLElement;
@@ -45,42 +62,45 @@ describe('settings view', () => {
(sendMessage as ReturnType<typeof vi.fn>).mockReset(); (sendMessage as ReturnType<typeof vi.fn>).mockReset();
}); });
it('renders a Sync now button', async () => { it('renders the left-nav with the seven sections', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
expect(app.querySelector('#sync-now-btn')).not.toBeNull(); const sections = ['autofill', 'display', 'security', 'generator', 'retention', 'backup', 'import'];
for (const s of sections) {
expect(app.querySelector(`[data-section="${s}"]`)).not.toBeNull();
}
}); });
it('clicking Sync now sends a sync message and shows feedback on success', async () => { it('lands on the Autofill section by default and renders the capture toggle', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app);
expect(sendMessage).toHaveBeenCalledWith({ type: 'is_unlocked' });
expect(sendMessage).toHaveBeenCalledWith({ type: 'get_settings' });
expect(sendMessage).toHaveBeenCalledWith({ type: 'get_blacklist' });
expect(app.querySelector<HTMLInputElement>('#capture-enabled')).not.toBeNull();
});
it('toggling capture-enabled sends an update_settings message', async () => {
mockChromeStorage();
mockDefaultLanding();
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: true }); (sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: true });
await renderSettings(app); await renderSettings(app);
app.querySelector<HTMLButtonElement>('#sync-now-btn')!.click(); const cb = app.querySelector<HTMLInputElement>('#capture-enabled')!;
await new Promise((r) => setTimeout(r, 0)); cb.checked = true;
cb.dispatchEvent(new Event('change'));
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
expect(sendMessage).toHaveBeenCalledWith({ type: 'sync' }); expect(sendMessage).toHaveBeenCalledWith({
const status = app.querySelector('#sync-status')!; type: 'update_settings',
expect(status.textContent).toMatch(/synced/i); settings: { captureEnabled: true },
}); });
it('shows the error when sync fails', async () => {
mockChromeStorage();
settingsResponses();
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: false, error: 'remote_unreachable' });
await renderSettings(app);
app.querySelector<HTMLButtonElement>('#sync-now-btn')!.click();
await new Promise((r) => setTimeout(r, 0));
await new Promise((r) => setTimeout(r, 0));
const status = app.querySelector('#sync-status')!;
expect(status.textContent).toMatch(/remote_unreachable/);
}); });
}); });
@@ -95,12 +115,13 @@ describe('settings Display section', () => {
it('renders digit and symbol color pickers with default values when storage is empty', async () => { it('renders digit and symbol color pickers with default values when storage is empty', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
const digitInput = app.querySelector<HTMLInputElement>('#display-digit-color'); const digitInput = app.querySelector<HTMLInputElement>('#digit-color');
const symbolInput = app.querySelector<HTMLInputElement>('#display-symbol-color'); const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color');
expect(digitInput).not.toBeNull(); expect(digitInput).not.toBeNull();
expect(symbolInput).not.toBeNull(); expect(symbolInput).not.toBeNull();
expect(digitInput!.value).toBe(DEFAULT_DIGIT_COLOR); expect(digitInput!.value).toBe(DEFAULT_DIGIT_COLOR);
@@ -111,32 +132,35 @@ describe('settings Display section', () => {
mockChromeStorage({ mockChromeStorage({
password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' }, password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' },
}); });
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
const digitInput = app.querySelector<HTMLInputElement>('#display-digit-color'); const digitInput = app.querySelector<HTMLInputElement>('#digit-color');
const symbolInput = app.querySelector<HTMLInputElement>('#display-symbol-color'); const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color');
expect(digitInput!.value).toBe('#112233'); expect(digitInput!.value).toBe('#112233');
expect(symbolInput!.value).toBe('#aabbcc'); expect(symbolInput!.value).toBe('#aabbcc');
}); });
it('renders a color-preview-swatch element', async () => { it('renders a color-preview swatch element', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
expect(app.querySelector('#display-swatch')).not.toBeNull(); expect(app.querySelector('#color-preview')).not.toBeNull();
}); });
it('changing digit color calls saveColorScheme with updated scheme', async () => { it('changing digit color calls saveColorScheme with updated scheme', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
const digitInput = app.querySelector<HTMLInputElement>('#display-digit-color')!; const digitInput = app.querySelector<HTMLInputElement>('#digit-color')!;
digitInput.value = '#ff0000'; digitInput.value = '#ff0000';
digitInput.dispatchEvent(new Event('change')); digitInput.dispatchEvent(new Event('change'));
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
@@ -151,11 +175,12 @@ describe('settings Display section', () => {
it('changing symbol color calls saveColorScheme with updated scheme', async () => { it('changing symbol color calls saveColorScheme with updated scheme', async () => {
mockChromeStorage(); mockChromeStorage();
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
const symbolInput = app.querySelector<HTMLInputElement>('#display-symbol-color')!; const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color')!;
symbolInput.value = '#00ff00'; symbolInput.value = '#00ff00';
symbolInput.dispatchEvent(new Event('change')); symbolInput.dispatchEvent(new Event('change'));
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
@@ -172,19 +197,21 @@ describe('settings Display section', () => {
mockChromeStorage({ mockChromeStorage({
password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' }, password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' },
}); });
settingsResponses(); mockDefaultLanding();
await renderSettings(app); await renderSettings(app);
await navigateToDisplay(app);
const resetBtn = app.querySelector<HTMLButtonElement>('#display-reset')!; const resetBtn = app.querySelector<HTMLButtonElement>('#reset-colors')!;
resetBtn.click(); resetBtn.click();
await new Promise((r) => setTimeout(r, 0)); await new Promise((r) => setTimeout(r, 0));
await new Promise((r) => setTimeout(r, 0));
const syncRemove = (global as any).chrome.storage.sync.remove as ReturnType<typeof vi.fn>; const syncRemove = (global as any).chrome.storage.sync.remove as ReturnType<typeof vi.fn>;
expect(syncRemove).toHaveBeenCalledWith('password_display_scheme'); expect(syncRemove).toHaveBeenCalledWith('password_display_scheme');
const digitInput = app.querySelector<HTMLInputElement>('#display-digit-color')!; const digitInput = app.querySelector<HTMLInputElement>('#digit-color')!;
const symbolInput = app.querySelector<HTMLInputElement>('#display-symbol-color')!; const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color')!;
expect(digitInput.value).toBe(DEFAULT_DIGIT_COLOR); expect(digitInput.value).toBe(DEFAULT_DIGIT_COLOR);
expect(symbolInput.value).toBe(DEFAULT_SYMBOL_COLOR); expect(symbolInput.value).toBe(DEFAULT_SYMBOL_COLOR);
}); });