# tests/test_typograph.py # Тестирует основной класс Typographer и его конвейер обработки. import pytest from etpgrf import Typographer from etpgrf.config import CHAR_NBSP, CHAR_THIN_SP, CHAR_NDASH, CHAR_MDASH, SANITIZE_ETPGRF, SANITIZE_ALL_HTML TYPOGRAPHER_HTML_TEST_CASES = [ # --- Базовая обработка без HTML --- ('mnemonic', 'Простой текст с "кавычками".', f'Простой текст с «кавычками».'), ('mixed', 'Простой текст с "кавычками".', f'Простой текст с «кавычками».'), ('unicode', 'Простой текст с "кавычками".', f'Простой текст с{CHAR_NBSP}«кавычками».'), # --- Базовая обработка с HTML --- ('mnemonic', '
Простой параграф с «кавычками».
', 'Простой параграф с «кавычками».
'), ('mixed', 'Простой параграф с "кавычками".
', 'Простой параграф с «кавычками».
'), ('unicode', 'Простой параграф с "кавычками".
', f'Простой параграф с{CHAR_NBSP}«кавычками».
'), # --- Рекурсивный обход --- ('mnemonic', 'Текст, а внутри для проверки "жирный" текст.
Текст, а внутри для проверки «жирный» текст.
Текст, а внутри для проверки "жирный" текст.
Текст, а внутри для проверки «жирный» текст.
Текст, а внутри для проверки "жирный" текст.
Текст, а{CHAR_NBSP}внутри для{CHAR_NBSP}проверки «жирный» текст.
Текст с предлогом в доме.
Текст с предлогом в доме.
Текст с предлогом в доме.
Текст с предлогом в доме.
Текст с предлогом в доме.
Текст с{CHAR_NBSP}предлогом в{CHAR_NBSP}доме.
Союз и слово и еще один союз а текст.
', 'Союз и слово и еще один союз а текст.
'), ('mixed', 'Союз и слово и еще один союз а текст.
', 'Союз и слово и еще один союз а текст.
'), ('unicode', 'Союз и слово и еще один союз а текст.
', f'Союз и{CHAR_NBSP}слово и{CHAR_NBSP}еще один союз а{CHAR_NBSP}текст.
'), # --- Проверка тегов ', 'Текст «до».
'), ('mixed', 'Текст "до".
', 'Текст «до».
'), ('mixed', 'Текст "до".
Ctrl + C', 'Текст «до».
Ctrl + C'), ('mixed', 'Текст "до".
Sample "text"', 'Текст «до».
Sample "text"'), ('mixed', 'Текст "до".
', 'Текст «до».
'), # --- Проверка тегов с атрибутами --- ('mixed', 'Текст "снаружи"', 'Текст «снаружи»'), ('mixed', 'Текст "снаружи"', 'Текст «снаружи»'), ('mixed', 'Текст "снаружи"', 'Текст «снаружи»'), ('mnemonic', 'Текст "снаружи"', 'Текст «снаружи»'), # --- Комплексный интеграционный тест --- ('mnemonic', 'Он сказал: "В 1941-1945 гг. -- было 100 тыс. руб. и т. д."
', 'Он сказал: «В 1941–1945 гг. – было 100 тыс. руб.' ' и т. д.»
'), ('mixed', 'Он сказал: "В 1941-1945 гг. -- было 100 тыс. руб. и т. д."
', 'Он сказал: «В 1941–1945 гг. – было 100 тыс. руб.' ' и т. д.»
'), ('unicode', 'Он сказал: "В 1941-1945 гг. -- было 100 тыс. руб. и т. д."
', f'Он{CHAR_NBSP}сказал: «В{CHAR_NBSP}1941{CHAR_NDASH}1945{CHAR_NBSP}гг.{CHAR_NBSP}{CHAR_NDASH} было' f' 100{CHAR_NBSP}тыс.{CHAR_THIN_SP}руб. и{CHAR_NBSP}т.{CHAR_THIN_SP}д.»
'), # --- Теги внутри кавычек --- ('mnemonic', '"Почему", "зачем" и "кому это выгодно" -- вопросы требующие ответа.
', '«Почему», «зачем» и «кому это выгодно' '» – вопросы требующие ответа.
'), ('mixed', '"Почему", "зачем" и "кому это выгодно" -- вопросы требующие ответа.
', '«Почему», «зачем» и «кому это выгодно» – вопросы требующие ответа.
'), ('unicode', '"Почему", "зачем" и "кому это выгодно" -- вопросы требующие ответа.
', f'«Почему», «зачем» и{CHAR_NBSP}«кому это выгодно»{CHAR_NBSP}{CHAR_NDASH} вопросы требующие ответа.
'), # --- Проверка пустого текста и узлов с пробелами --- ('mnemonic', '
Слово
', '
Слово
'), ('mixed', '
Слово
', '
Слово
'), ('unicode', '
Слово
', '
Слово
'), # --- Самозакрывающиеся теги и теги с атрибутами --- # ВАЖНО: 1. Порядок атрибутов в типографированном тексте может быть произвольным # 2. Любое число пробельных символов внутри "пустых" тегов будут редуцированы до одного пробела или # перевода строки. # 3. Самозакрывающиеся теги будут приведены к единому виду с косой чертой в конце. ТипаТекст с картинкой
и текстом.
Текст с картинкой
и текстом.
Текст с <br>
А это новая строка.
Текст с <br>
А это новая строка.
Текст с картинкой
и текстом.
Текст с картинкой
и текстом.
Текст с <br>
А это новая строка.
Текст с <br>
А это новая строка.
Текст с картинкой
и текстом.
Текст с{CHAR_NBSP}картинкой
и{CHAR_NBSP}текстом.
Текст с <br>
А это новая строка.
Текст с{CHAR_NBSP}<br>
А{CHAR_NBSP}это новая строка.
Текст с тире --- после закрытого тега.
', 'Текст с тире — после закрытого тега.
'), ('mixed', 'Целых 100 т веса.
', 'Целых 100 т веса.
'), ] @pytest.mark.parametrize("mode, input_html, expected_html", TYPOGRAPHER_HTML_TEST_CASES) def test_typographer_html_processing(mode, input_html, expected_html): """ Проверяет полный конвейер Typographer при обработке HTML. """ typo = Typographer(langs='ru', process_html=True, mode=mode) actual_html = typo.process(input_html) assert actual_html == expected_html def test_typographer_plain_text_processing(): """ Проверяет, что в режиме process_html=False типограф маскирует HTML-теги и обрабатывает весь текст. """ typo = Typographer(langs='ru', process_html=False) input_text = 'Текст "без" HTML, но с предлогом в доме.' expected_text = '<i>Текст «без» <b>HTML</b>, но с предлогом в доме.</i>' actual_text = typo.process(input_text) assert actual_text == expected_text def test_typographer_sanitizer_etpgrf_integration(): """ Интеграционный тест: проверяет, что Typographer вызывает Sanitizer для очистки ETP-разметки. """ input_html = 'Текст со "старой" разметкой.
' # Ожидаем, что "старая" разметка будет удалена, а "новая" (кавычки-елочки) будет добавлена. expected_html = 'Текст со «старой» разметкой.
' typo = Typographer(langs='ru', process_html=True, sanitizer=SANITIZE_ETPGRF, mode='mixed') actual_html = typo.process(input_html) assert actual_html == expected_html def test_typographer_sanitizer_all_html_integration(): """ Интеграционный тест: проверяет, что Typographer вызывает Sanitizer для полной очистки HTML. """ input_html = 'Текст с "кавычками" и жирным текстом.
' # Ожидаем, что все теги будут удалены, а к чистому тексту применится типографика. expected_text = 'Текст с «кавычками» и жирным текстом.' typo = Typographer(langs='ru', process_html=True, sanitizer=SANITIZE_ALL_HTML, mode='mixed') actual_text = typo.process(input_html) assert actual_text == expected_text # --- Новые тесты на структуру HTML (проверка отсутствия лишних оберток) --- HTML_STRUCTURE_TEST_CASES = [ # 1. Фрагмент HTML (без html/body) -> должен остаться фрагментом ('Текст
', 'Текст
'), # 2. Голый текст -> должен остаться голым текстом (без, ,
) ('Текст без тегов', 'Текст без тегов'), # Исправлено: ожидаем nbsp ('Текст с тегом внутри', 'Текст с тегом внутри'), # 3. Полноценный html-документ -> должен сохранить структуру ('Текст
', 'Текст
'), # Используем валидный HTML для теста с DOCTYPE ('Текст
', '\nТекст
'), # 4. Кривой html -> будет "починен" ('Текст', '
Текст
'), ('Текст жирный курсив', 'Текст жирный курсив'), # 5. Тест на защищенные теги с "битым" HTML внутри (BS их закроет) ('<html> и <body> при обработке фрагментов HTML.<html> и <body> при обработке фрагментов HTML.++ и при обработке фрагментов HTML.++ и при обработке фрагментов HTML.Текст с < и > и & внутри.
', 'Текст с < и > и & внутри.
'), ('Текст с < и > и & внутри.
', 'Текст с < и > и & внутри.
'), ('Мнемоника превратится в неразрывный пробел
Мнемоника превратится в неразрывный пробел
Текст
' in actual_html else: assert actual_html == expected_html