toContain('🍺'); expect($result)->not()->toContain(':beer'); }); test('convert multiple smileys in one string', function () { $result = LegacySmileyMapper::convert(':clap great art :love it'); expect($result)->toContain('👏'); expect($result)->toContain('❤️'); }); test('does not replace smiley codes embedded in words', function () { // ":lol" should only be replaced when it stands alone $result = LegacySmileyMapper::convert('something:lol mixed'); // The code is embedded in a word, so it should NOT be replaced expect($result)->toContain(':lol'); }); test('detect returns found codes', function () { $found = LegacySmileyMapper::detect('nice :beer and :lol'); expect($found)->toContain(':beer'); expect($found)->toContain(':lol'); }); test('detect returns empty array for clean text', function () { $found = LegacySmileyMapper::detect('hello world, no smileys here'); expect($found)->toBeEmpty(); }); test('convert returns original string when no codes present', function () { $input = 'This has no smiley codes.'; $result = LegacySmileyMapper::convert($input); expect($result)->toBe($input); }); test('getMap returns non-empty array', function () { $map = LegacySmileyMapper::getMap(); expect($map)->toBeArray()->not()->toBeEmpty(); expect($map[':beer'])->toBe('🍺'); }); test('convert handles empty string', function () { expect(LegacySmileyMapper::convert(''))->toBe(''); }); // ── collapseFlood ───────────────────────────────────────────────────────────── test('collapseFlood leaves short runs untouched', function () { // 5 beer mugs with spaces — at or below the maxRun=5 default, no collapse. $input = '\ud83c\udf7a \ud83c\udf7a \ud83c\udf7a \ud83c\udf7a \ud83c\udf7a'; expect(LegacySmileyMapper::collapseFlood($input))->toBe($input); }); test('collapseFlood collapses a run-together flood', function () { // 8 fire emoji in a row (no spaces) — should produce 5 + ×8 $input = str_repeat('🔥', 8); $result = LegacySmileyMapper::collapseFlood($input); expect($result)->toContain('×8'); // output has at most maxRun (5) copies of the emoji $count = substr_count($result, '🔥'); expect($count)->toBeLessThanOrEqual(5); }); test('collapseFlood collapses a space-separated flood', function () { // 10 clap emoji separated by single spaces $input = implode(' ', array_fill(0, 10, '👏')); $result = LegacySmileyMapper::collapseFlood($input); expect($result)->toContain('×10'); $count = substr_count($result, '👏'); expect($count)->toBeLessThanOrEqual(5); }); test('collapseFlood respects custom maxRun', function () { $input = implode(' ', array_fill(0, 8, '❤️')); $result = LegacySmileyMapper::collapseFlood($input, maxRun: 3); expect($result)->toContain('×8'); $count = substr_count($result, '❤️'); expect($count)->toBeLessThanOrEqual(3); }); test('collapseFlood does not affect regular text or mixed content', function () { $input = 'Nice work \ud83d\udc4d really cool \ud83d\udd25'; $result = LegacySmileyMapper::collapseFlood($input); expect($result)->toBe($input); // nothing to collapse }); test('collapseFlood handles empty string', function () { expect(LegacySmileyMapper::collapseFlood(''))->toBe(''); }); // ── detect() colon-lookahead regression ────────────────────────────────────── test('detect does not match smiley code with trailing colon', function () { // ':sad:' used to partially match ':sad' leaving a stray ':'. // After the fix, ':' is not in the lookahead, so ':sad:' should be // detected only if followed by whitespace / punctuation (not ':'). // A bare ':sad:' surrounded by spaces must still be detected. $found = LegacySmileyMapper::detect(':sad and more text'); expect($found)->toContain(':sad'); // ':sad:' where the colon immediately follows should NOT be detected // because ':' is no longer in [.,!?;] lookahead. $foundColon = LegacySmileyMapper::detect('text:sad: more'); expect($foundColon)->not()->toContain(':sad'); });