import { describe, expect, it } from 'vitest'; import { toPreviewHTML, toggleLinePrefix, toggleMarker, stripFormatting, isMarkerActive, WHATSAPP_MARKERS, } from './whatsapp-markup'; describe('toPreviewHTML — inline formatting', () => { it('renders bold', () => { expect(toPreviewHTML('*bold*')).toBe('bold'); }); it('renders italic', () => { expect(toPreviewHTML('_italic_')).toBe('italic'); }); it('renders strikethrough', () => { expect(toPreviewHTML('~strike~')).toBe('strike'); }); it('renders inline monospace', () => { expect(toPreviewHTML('`code`')).toBe('code'); }); it('renders a single-character marker', () => { expect(toPreviewHTML('*a*')).toBe('a'); }); it('formats markers mid-sentence', () => { expect(toPreviewHTML('this is *bold* text')).toBe('this is bold text'); }); it('nests bold + italic', () => { expect(toPreviewHTML('*_x_*')).toBe('x'); }); it('does not format markers padded with spaces', () => { // Mid-sentence so the leading "* " is not parsed as a bullet list. expect(toPreviewHTML('a * not bold * b')).toBe('a * not bold * b'); }); it('leaves unmatched markers literal', () => { expect(toPreviewHTML('a * b')).toBe('a * b'); }); }); describe('toPreviewHTML — code spans are literal', () => { it('does not format markers inside inline code', () => { expect(toPreviewHTML('`*not bold*`')).toBe('*not bold*'); }); it('renders fenced code blocks', () => { expect(toPreviewHTML('```\nline1\nline2\n```')).toBe('
line1\nline2
'); }); it('escapes html inside code', () => { expect(toPreviewHTML('`&`')).toBe('<b>&</b>'); }); }); describe('toPreviewHTML — blocks', () => { it('renders a blockquote', () => { expect(toPreviewHTML('> quoted')).toBe('
quoted
'); }); it('renders an unordered list', () => { expect(toPreviewHTML('- one\n- two')).toBe(''); }); it('renders an ordered list', () => { expect(toPreviewHTML('1. one\n2. two')).toBe('
  1. one
  2. two
'); }); it('applies inline formatting within list items', () => { expect(toPreviewHTML('- *bold*')).toBe(''); }); it('joins plain lines with
', () => { expect(toPreviewHTML('a\nb')).toBe('a
b'); }); }); describe('toPreviewHTML — safety', () => { it('escapes html in plain text', () => { expect(toPreviewHTML('')).toBe( '<script>alert(1)</script>', ); }); it('returns empty string for empty input', () => { expect(toPreviewHTML('')).toBe(''); }); }); describe('toggleMarker', () => { const bold = WHATSAPP_MARKERS.bold; it('wraps a selection', () => { const r = toggleMarker('hello world', 6, 11, bold); expect(r.value).toBe('hello *world*'); expect(r.value.slice(r.selectionStart, r.selectionEnd)).toBe('world'); }); it('wraps an empty selection and places caret between markers', () => { const r = toggleMarker('hi ', 3, 3, bold); expect(r.value).toBe('hi **'); expect(r.selectionStart).toBe(4); expect(r.selectionEnd).toBe(4); }); it('unwraps when the selection includes the markers', () => { const r = toggleMarker('*world*', 0, 7, bold); expect(r.value).toBe('world'); expect(r.value.slice(r.selectionStart, r.selectionEnd)).toBe('world'); }); it('unwraps when markers sit just outside the selection', () => { const r = toggleMarker('*world*', 1, 6, bold); expect(r.value).toBe('world'); expect(r.value.slice(r.selectionStart, r.selectionEnd)).toBe('world'); }); it('round-trips wrap then unwrap', () => { const wrapped = toggleMarker('abc', 0, 3, bold); expect(wrapped.value).toBe('*abc*'); const unwrapped = toggleMarker( wrapped.value, wrapped.selectionStart, wrapped.selectionEnd, bold, ); expect(unwrapped.value).toBe('abc'); }); }); describe('toggleLinePrefix', () => { it('adds a blockquote prefix to a single line', () => { const r = toggleLinePrefix('hello', 0, 0, 'blockquote'); expect(r.value).toBe('> hello'); }); it('removes the blockquote prefix when toggled again', () => { const r = toggleLinePrefix('> hello', 0, 0, 'blockquote'); expect(r.value).toBe('hello'); }); it('adds a bullet prefix', () => { const r = toggleLinePrefix('milk', 0, 0, 'bullet'); expect(r.value).toBe('- milk'); }); it('numbers an ordered list across multiple selected lines', () => { const text = 'one\ntwo\nthree'; const r = toggleLinePrefix(text, 0, text.length, 'ordered'); expect(r.value).toBe('1. one\n2. two\n3. three'); }); it('toggles an ordered list off', () => { const text = '1. one\n2. two'; const r = toggleLinePrefix(text, 0, text.length, 'ordered'); expect(r.value).toBe('one\ntwo'); }); it('applies bullets to every line spanned by the selection', () => { const text = 'a\nb'; const r = toggleLinePrefix(text, 0, text.length, 'bullet'); expect(r.value).toBe('- a\n- b'); }); it('only affects the line under the caret, not the whole text', () => { const text = 'first\nsecond'; // caret inside "second" (index 8) const r = toggleLinePrefix(text, 8, 8, 'blockquote'); expect(r.value).toBe('first\n> second'); }); it('switches family from bullet to ordered without stacking', () => { const r = toggleLinePrefix('- a', 0, 3, 'ordered'); expect(r.value).toBe('1. a'); }); }); describe('stripFormatting', () => { it('removes inline markers', () => { expect(stripFormatting('*bold* _italic_ ~strike~ `mono`')).toBe('bold italic strike mono'); }); it('removes nested markers', () => { expect(stripFormatting('*_x_*')).toBe('x'); }); it('unwraps fenced code blocks', () => { expect(stripFormatting('```\ncode\n```')).toBe('code'); }); it('strips line-prefix blocks', () => { expect(stripFormatting('> quote\n- item\n1. first')).toBe('quote\nitem\nfirst'); }); it('leaves plain text untouched', () => { expect(stripFormatting('nothing here')).toBe('nothing here'); }); it('is idempotent', () => { const once = stripFormatting('*a* _b_'); expect(stripFormatting(once)).toBe(once); }); }); describe('isMarkerActive', () => { const BOLD = WHATSAPP_MARKERS.bold; const ITALIC = WHATSAPP_MARKERS.italic; it('is false for a collapsed selection', () => { expect(isMarkerActive('*hi*', 2, 2, BOLD)).toBe(false); }); it('detects when the selection itself is wrapped (case A)', () => { expect(isMarkerActive('*hi*', 0, 4, BOLD)).toBe(true); }); it('detects when markers sit just outside the selection (case B)', () => { // "hi" selected inside *hi* expect(isMarkerActive('*hi*', 1, 3, BOLD)).toBe(true); }); it('is false for an unformatted selection', () => { expect(isMarkerActive('hi there', 0, 2, BOLD)).toBe(false); }); it('is marker-specific', () => { expect(isMarkerActive('_hi_', 0, 4, BOLD)).toBe(false); expect(isMarkerActive('_hi_', 0, 4, ITALIC)).toBe(true); }); });