coaching-fence-attrs.test.ts1.6 KBView on GitHub
/**
 * The fence body arrives from a `data-*` attribute during ProseMirror's DOM parse, and the
 * `recording:` link arrives from agent-authored document content. Both are untrusted, and a throw
 * in the first takes down the whole editor rather than one node.
 */
import { decodeFenceAttr, safeHref } from '@/modules/documents/coaching/fence-attrs';

describe('decodeFenceAttr', () => {
  it('decodes a normal percent-encoded body', () => {
    expect(decodeFenceAttr(encodeURIComponent('deal: Marmon Mok\nverdict: bad'))).toBe(
      'deal: Marmon Mok\nverdict: bad',
    );
  });

  it('falls back to the raw value on a malformed sequence instead of throwing', () => {
    // `decodeURIComponent('%')` throws a URIError — inside parseHTML that kills the editor.
    expect(() => decodeFenceAttr('100%')).not.toThrow();
    expect(decodeFenceAttr('100%')).toBe('100%');
  });

  it('treats a missing attribute as an empty body', () => {
    expect(decodeFenceAttr(null)).toBe('');
  });
});

describe('safeHref', () => {
  it.each([
    'https://fathom.video/share/abc',
    'http://example.com/rec',
    'mailto:<email>',
    '/agent?conversationId=abc',
  ])('passes %s through', (url) => {
    expect(safeHref(url)).toBe(url);
  });

  it.each([
    'javascript:alert(1)',
    '  javascript:alert(1)',
    'JavaScript:alert(1)',
    'data:text/html,<script>alert(1)</script>',
  ])('drops %s', (url) => {
    expect(safeHref(url)).toBeUndefined();
  });

  it('drops an empty or missing link', () => {
    expect(safeHref(undefined)).toBeUndefined();
    expect(safeHref('')).toBeUndefined();
  });
});