# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Folding a typed command down to a comparable skeleton. Every non-ASCII literal here is an escape. These tests exist because two characters looked identical, so a reader must be able to see which codepoint each case actually means. """ from __future__ import annotations import pytest from signal_bot.folding import TABLE, fold # The regression itself: `!gecko` as it arrives from a Russian keyboard, with # U+0435 for the `e` and U+043E for the `o`. RUSSIAN_GECKO = "!g\u0435ck\u043e" def test_the_cyrillic_gecko_that_started_this() -> None: assert fold(RUSSIAN_GECKO) == "!gecko" assert RUSSIAN_GECKO != "!gecko" @pytest.mark.parametrize( ("typed", "expected"), [ # Cyrillic ("!\u043a\u043e\u0442", "!kot"), ("!\u0441\u0430\u0442", "!cat"), ("!d\u043eg", "!dog"), # Greek ("!d\u03bfg", "!dog"), ("!\u03bau\u03bf\u03c4", "!kuot"), # Latin small capitals ("!\u1d0b\u1d0f\u1d1b", "!kot"), # Latin letters that are not ASCII ("!d\u00f8g", "!dog"), ("!p\u0131es", "!pies"), # Leetspeak ("!g3ck0", "!gecko"), ("!d0g", "!dog"), ("!c4t", "!cat"), ("!p13s", "!pies"), ("!5zczek", "!szczek"), ("!ca7", "!cat"), # Compatibility forms NFKC already handles ("!\uff44\uff4f\uff47", "!dog"), ("!\U0001d5f8\U0001d5fc\U0001d601", "!kot"), # Diacritics decompose away ("!papie\u017c", "!papiez"), ("!k\u00f3t", "!kot"), # Invisible characters are dropped ("!d\u200bog", "!dog"), ("!d\u200dog", "!dog"), ("!dog\u00ad", "!dog"), ("!\ufeffdog", "!dog"), # Case and surrounding whitespace (" !DOG ", "!dog"), ("!Dog", "!dog"), (" !dog ", "!dog"), ], ) def test_fold_maps_lookalikes_onto_ascii(typed: str, expected: str) -> None: assert fold(typed) == expected def test_fold_keeps_internal_whitespace() -> None: """Argument support (0.0.6) splits on it, so folding must not eat it.""" assert fold("!suggest !gecko co ma robic") == "!suggest !gecko co ma robic" def test_fold_leaves_ordinary_text_alone() -> None: assert fold("!dog") == "!dog" assert fold("hello there") == "hello there" assert fold("") == "" def test_fold_is_idempotent() -> None: """Folding twice must equal folding once, or triggers drift from input.""" for typed in (RUSSIAN_GECKO, "!g3ck0", "!\u1d0b\u1d0f\u1d1b", "!papie\u017c"): assert fold(fold(typed)) == fold(typed) def test_fold_does_not_collapse_genuinely_different_commands() -> None: """The table must not be so eager that distinct commands become one.""" assert fold("!cat") != fold("!dog") assert fold("!kot") != fold("!kots") assert fold("!gekon") != fold("!geko") def test_every_mapping_target_is_ascii() -> None: """A mapping onto another non-ASCII character would need folding again.""" for replacement in TABLE.values(): assert replacement.isascii() def test_table_covers_both_alphabets_that_bit_us() -> None: assert TABLE[0x043E] == "o" assert TABLE[0x0435] == "e" assert TABLE[0x03BF] == "o"