toContain('bold text'); }); test('render converts italic markdown to em tag', function () { $html = ContentSanitizer::render('*italic text*'); expect($html)->toContain('italic text'); }); test('render converts inline code to code tag', function () { $html = ContentSanitizer::render('use `code`'); expect($html)->toContain('code'); }); test('render auto-links URLs', function () { $html = ContentSanitizer::render('visit https://example.com for more'); expect($html)->toContain('toContain('href="https://example.com"'); }); test('render returns empty string for null input', function () { expect(ContentSanitizer::render(null))->toBe(''); }); test('render returns empty string for whitespace-only input', function () { expect(ContentSanitizer::render(' '))->toBe(''); }); // ── XSS Prevention ──────────────────────────────────────────────────────────── test('render strips script tags', function () { $html = ContentSanitizer::render('hello'); // The '); expect($errors)->not()->toBeEmpty(); expect(implode(' ', $errors))->toContain('HTML'); }); test('validate passes for clean markdown', function () { $errors = ContentSanitizer::validate('**bold** and *italic* and `code`'); expect($errors)->toBeEmpty(); }); test('validate returns error when content is too long', function () { $errors = ContentSanitizer::validate(str_repeat('a', 10_001)); expect($errors)->not()->toBeEmpty(); }); test('validate returns error when emoji density exceeds threshold', function () { // 10 fire emoji + 4 spaces = 14 chars; emoji count = 10; density = 10/14 ≈ 0.71 > 0.40 $floodContent = implode(' ', array_fill(0, 10, '🔥')); $errors = ContentSanitizer::validate($floodContent); expect($errors)->not()->toBeEmpty(); expect(implode(' ', $errors))->toContain('emoji'); }); test('validate accepts content with reasonable emoji usage', function () { // 3 emoji in a 50-char string — density ≈ 0.06, well below threshold $errors = ContentSanitizer::validate('Great work on this piece 🎨 love the colours ❤️ keep it up 👏'); expect($errors)->toBeEmpty(); }); // ── collapseFlood ───────────────────────────────────────────────────────────── test('collapseFlood delegates to LegacySmileyMapper and collapses runs', function () { $input = implode(' ', array_fill(0, 8, '🍺')); $result = ContentSanitizer::collapseFlood($input); expect($result)->toContain('×8'); expect(substr_count($result, '🍺'))->toBeLessThanOrEqual(5); }); test('collapseFlood returns unchanged string when no flood present', function () { $input = 'Nice art 🎨 love it ❤️'; expect(ContentSanitizer::collapseFlood($input))->toBe($input); });