import { describe, test, expect } from 'vitest'; import { ButtonLink } from './ButtonLink'; const toHtml = (ButtonLink as any).toHtml; describe('ButtonLink.toHtml href sanitization (attacker-controlled `href` prop)', () => { test('a javascript: URL is neutralized', () => { const { html } = toHtml({ href: 'javascript:alert(1)', text: 'Click' }, ''); expect(html).not.toContain('javascript:alert'); }); test('a quote-breakout href does not escape the href attribute', () => { const malicious = '">'; const { html } = toHtml({ href: malicious, text: 'Click' }, ''); expect(html).not.toContain(''); }); test('a normal href still renders correctly', () => { const { html } = toHtml({ href: 'https://example.com', text: 'Click' }, ''); expect(html).toContain('href="https://example.com"'); }); }); describe('ButtonLink.toHtml target (boolean-gated, not raw interpolation)', () => { test('an attribute-breakout value for target does not reach the output raw', () => { const malicious = '_blank" onmouseover="alert(1)' as any; const { html } = toHtml({ href: '#', text: 'x', target: malicious }, ''); expect(html).not.toContain('onmouseover'); }); test('target="_blank" still adds rel=noopener noreferrer', () => { const { html } = toHtml({ href: '#', text: 'x', target: '_blank' }, ''); expect(html).toContain('target="_blank"'); expect(html).toContain('rel="noopener noreferrer"'); }); }); describe('ButtonLink.toHtml text escaping (attacker-controlled `text` prop)', () => { test('a tag-breakout attempt in text is neutralized (no injected element)', () => { const { html } = toHtml({ href: '#', text: '' }, ''); expect(html).not.toContain(' { const { html } = toHtml({ href: '#', text: 'Tom & Jerry' }, ''); expect(html).toContain('Tom & Jerry'); }); test('a normal text value still renders unchanged', () => { const { html } = toHtml({ href: '#', text: 'Click Me' }, ''); expect(html).toContain('>Click Me'); }); }); describe('ButtonLink.toHtml hover state (scoped '; const { html } = toHtml({ href: '#', text: 'x', hoverBg: malicious }, '', 'node-1'); expect(html).not.toContain(''); }); test('a rule-breakout attempt in hoverColor cannot inject a second selector/rule', () => { const malicious = 'red;}body{background:red'; const { html } = toHtml({ href: '#', text: 'x', hoverColor: malicious }, '', 'node-1'); expect(html).not.toContain('}body{'); expect(html).not.toContain(';}'); // The whole export is still exactly one