feat(ext/popup): renderSections helper for custom-field detail rendering
This commit is contained in:
104
extension/src/popup/components/__tests__/sections-render.test.ts
Normal file
104
extension/src/popup/components/__tests__/sections-render.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { renderSections } from '../fields';
|
||||
import type { Item } from '../../../shared/types';
|
||||
|
||||
function itemWithSections(sections: Item['sections']): Item {
|
||||
return {
|
||||
id: 'aaaaaaaaaaaaaaaa',
|
||||
title: 'test',
|
||||
type: 'login',
|
||||
tags: [], favorite: false,
|
||||
created: 0, modified: 0,
|
||||
core: { type: 'login' },
|
||||
sections,
|
||||
attachments: [],
|
||||
field_history: {},
|
||||
};
|
||||
}
|
||||
|
||||
describe('renderSections', () => {
|
||||
it('returns empty string when item has no sections', () => {
|
||||
const html = renderSections(itemWithSections([]), 'login');
|
||||
expect(html).toBe('');
|
||||
});
|
||||
|
||||
it('skips sections with zero fields', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{ name: 'empty', fields: [] },
|
||||
]), 'login');
|
||||
expect(html).not.toContain('empty');
|
||||
});
|
||||
|
||||
it('renders a named section header + field rows', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{
|
||||
name: 'recovery codes',
|
||||
fields: [
|
||||
{ id: 'f0000001', label: 'code 1', kind: 'text',
|
||||
value: { kind: 'text', value: 'abc-123' }, hidden_by_default: false },
|
||||
],
|
||||
},
|
||||
]), 'login');
|
||||
expect(html).toContain('recovery codes');
|
||||
expect(html).toContain('code 1');
|
||||
expect(html).toContain('abc-123');
|
||||
});
|
||||
|
||||
it('renders concealed password fields with unique ids', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{
|
||||
name: 'backup',
|
||||
fields: [
|
||||
{ id: 'f0000002', label: 'pin', kind: 'password',
|
||||
value: { kind: 'password', value: 'hunter2' }, hidden_by_default: true },
|
||||
],
|
||||
},
|
||||
]), 'login');
|
||||
expect(html).toContain('data-field-id="login-s0-f0"');
|
||||
expect(html).toContain('data-revealed="false"');
|
||||
expect(html).not.toMatch(/>hunter2</);
|
||||
});
|
||||
|
||||
it('renders anonymous section with separator not header', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{
|
||||
fields: [
|
||||
{ id: 'f0000003', label: 'extra', kind: 'text',
|
||||
value: { kind: 'text', value: 'note' }, hidden_by_default: false },
|
||||
],
|
||||
},
|
||||
]), 'login');
|
||||
expect(html).toContain('section-separator');
|
||||
expect(html).not.toContain('section-header');
|
||||
});
|
||||
|
||||
it('silently skips unsupported field kinds', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{
|
||||
fields: [
|
||||
{ id: 'f0000004', label: 'link', kind: 'url' as any,
|
||||
value: { kind: 'url', value: 'https://example.com' } as any,
|
||||
hidden_by_default: false },
|
||||
{ id: 'f0000005', label: 'note', kind: 'text',
|
||||
value: { kind: 'text', value: 'kept' }, hidden_by_default: false },
|
||||
],
|
||||
},
|
||||
]), 'login');
|
||||
expect(html).not.toContain('https://example.com');
|
||||
expect(html).toContain('kept');
|
||||
});
|
||||
|
||||
it('renders concealed fields for the concealed kind too', () => {
|
||||
const html = renderSections(itemWithSections([
|
||||
{
|
||||
fields: [
|
||||
{ id: 'f0000006', label: 'secret', kind: 'concealed',
|
||||
value: { kind: 'concealed', value: 'shhh' }, hidden_by_default: true },
|
||||
],
|
||||
},
|
||||
]), 'login');
|
||||
expect(html).toContain('data-field-id="login-s0-f0"');
|
||||
expect(html).toContain('secret');
|
||||
expect(html).not.toMatch(/>shhh</);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user