feat(ext/popup): field history view — masked values with reveal toggle
Shows current + historical values for tracked fields (password/concealed). Click to reveal, copy button per entry (plaintext stored in a module-level Map, never embedded in the DOM). Grouped by field name if multiple tracked fields exist. Adds historyItemId to PopupState and 'field-history' to View. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { renderFieldHistory, teardown } from '../field-history';
|
||||
|
||||
// Mock popup module
|
||||
vi.mock('../../popup', () => ({
|
||||
getState: vi.fn(() => ({
|
||||
historyItemId: 'item123',
|
||||
selectedItem: { id: 'item123', title: 'Test Item', modified: 1000 },
|
||||
})),
|
||||
setState: vi.fn(),
|
||||
sendMessage: vi.fn(),
|
||||
navigate: vi.fn(),
|
||||
escapeHtml: (s: string) => s,
|
||||
}));
|
||||
|
||||
import { sendMessage, navigate } from '../../popup';
|
||||
|
||||
describe('field-history view', () => {
|
||||
let app: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
app = document.createElement('div');
|
||||
teardown();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders empty state when no history', async () => {
|
||||
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
data: { history: [] },
|
||||
});
|
||||
|
||||
await renderFieldHistory(app);
|
||||
|
||||
expect(app.innerHTML).toContain('No history available');
|
||||
});
|
||||
|
||||
it('renders history entries masked by default', async () => {
|
||||
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
data: {
|
||||
history: [{
|
||||
field_id: 'f1',
|
||||
field_name: 'password',
|
||||
current_value: 'secret123',
|
||||
entries: [{ value: 'oldpass', changed_at: 500 }],
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
await renderFieldHistory(app);
|
||||
|
||||
expect(app.innerHTML).toContain('••••••••••••');
|
||||
expect(app.innerHTML).not.toContain('secret123');
|
||||
expect(app.innerHTML).toContain('current');
|
||||
});
|
||||
|
||||
it('back button navigates to detail', async () => {
|
||||
(sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
data: { history: [] },
|
||||
});
|
||||
|
||||
await renderFieldHistory(app);
|
||||
app.querySelector<HTMLButtonElement>('#back-btn')?.click();
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith('detail');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user