Files
2025-etpgrf/tests/test_hanging.py

157 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tests/test_hanging.py
# Тесты для модуля висячей пунктуации (HangingPunctuationProcessor).
import pytest
from bs4 import BeautifulSoup
from etpgrf.hanging import HangingPunctuationProcessor
from etpgrf.config import (
CHAR_RU_QUOT1_OPEN, CHAR_RU_QUOT1_CLOSE,
CHAR_EN_QUOT1_OPEN, CHAR_EN_QUOT1_CLOSE,
CHAR_LPAR, CHAR_LSQB, CHAR_LCUB,
CHAR_RPAR, CHAR_RSQB, CHAR_RCUB,
CHAR_SHY, CHAR_THIN_SP, CHAR_HAIR_SP,
CHAR_MED_SP, CHAR_EN_SP, CHAR_EM_SP,
CHAR_NULL_SP, CHAR_THIN_NBSP, CHAR_NBSP,
CHAR_ZWNJ
)
# Вспомогательная функция для создания soup
def make_soup(html_str):
return BeautifulSoup(html_str, 'html.parser')
# Набор тестовых случаев в формате:
# (режим, входной_html, ожидаемый_html)
HANGING_TEST_CASES = [
# --- Режим 'left' (только левая висячая пунктуация) ---
('left', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</span></p>'),
('left', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE} вначале текста</p>',
f'<p><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</span> вначале текста</p>'),
('left', f'<p>А вот это {CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE} внутри текста</p>',
f'<p>А вот <span class="etp-sp-laquo">это </span><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</span> внутри текста</p>'),
('left', f'<p>А вот это {CHAR_RU_QUOT1_OPEN}Длинная цитата{CHAR_RU_QUOT1_CLOSE} внутри текста</p>',
f'<p>А вот <span class="etp-sp-laquo">это </span><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Длинная</span> цитата{CHAR_RU_QUOT1_CLOSE} внутри текста</p>'),
('left', f'<p>А вот это <b>{CHAR_RU_QUOT1_OPEN}Длинная цитата{CHAR_RU_QUOT1_CLOSE}</b> внутри текста</p>',
f'<p>А вот <span class="etp-sp-laquo">это </span><b><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Длинная</span> цитата{CHAR_RU_QUOT1_CLOSE}</b> внутри текста</p>'),\
('left', f'<p>А вот это <b><i>{CHAR_RU_QUOT1_OPEN}Длинная цитата{CHAR_RU_QUOT1_CLOSE}</i></b> внутри текста</p>',
f'<p>А вот <span class="etp-sp-laquo">это </span><b><i><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Длинная</span> цитата{CHAR_RU_QUOT1_CLOSE}</i></b> внутри текста</p>'),
('left', f'<p>А вот это {CHAR_RU_QUOT1_OPEN}<b>Длинная цитата</b>{CHAR_RU_QUOT1_CLOSE} внутри текста</p>',
f'<p>А вот <span class="etp-sp-laquo">это </span><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span><b>Длинная цитата</b>{CHAR_RU_QUOT1_CLOSE} внутри текста</p>'),
('left', f'<p>Неразрывный пробел перед{CHAR_NBSP}{CHAR_RU_QUOT1_OPEN}Цитатой{CHAR_RU_QUOT1_CLOSE} внутри текста</p>',
f'<p>Неразрывный пробел перед{CHAR_NBSP}{CHAR_RU_QUOT1_OPEN}Цитатой{CHAR_RU_QUOT1_CLOSE} внутри текста</p>'),
('left', '<p>(Скобки)</p>', '<p><span class="etp-lpar">(Скобки)</span></p>'),
('left', '<p>Текст.</p>', '<p>Текст.</p>'),
# --- Режим 'right' (только правая пунктуация) ---
#('right', f'<p>{CHAR_RU_QUOT1_OPEN}Цитата{CHAR_RU_QUOT1_CLOSE}</p>',
# f'<p>{CHAR_RU_QUOT1_OPEN}Цитата<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></p>'),
#('right', '<p>Текст.</p>', '<p>Текст<span class="etp-r-dot">.</span></p>'),
#('right', '<p>(Скобки)</p>', '<p>(Скобки<span class="etp-rpar">)</span></p>'),
#('right', '<p>3.14</p>', '<p>3.14</p>'),
#('right', '<p>End.</p>', '<p>End<span class="etp-r-dot">.</span></p>'),
# --- Режим None / False (отключено) ---
(None, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
(False, f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>',
f'<p>{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'),
]
@pytest.mark.parametrize("mode, input_html, expected_html", HANGING_TEST_CASES)
def test_hanging_punctuation_processor(mode, input_html, expected_html):
"""
Проверяет работу HangingPunctuationProcessor в различных режимах.
"""
# Arrange
processor = HangingPunctuationProcessor(mode=mode)
soup = make_soup(input_html)
# Act
processor.process(soup)
actual_html = str(soup)
# Assert
assert actual_html == expected_html
def test_hanging_punctuation_target_tags():
"""
Отдельный тест для проверки работы со списком целевых тегов.
"""
mode = ['blockquote', 'h1']
input_html = (f'<div>{CHAR_RU_QUOT1_OPEN}Игнор{CHAR_RU_QUOT1_CLOSE}</div>'
f'<blockquote>{CHAR_RU_QUOT1_OPEN}Обработка{CHAR_RU_QUOT1_CLOSE}</blockquote>'
f'<h1>{CHAR_RU_QUOT1_OPEN}Заголовок{CHAR_RU_QUOT1_CLOSE}</h1>')
expected_html = (f'<div>{CHAR_RU_QUOT1_OPEN}Игнор{CHAR_RU_QUOT1_CLOSE}</div>'
f'<blockquote><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Обработка<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></blockquote>'
f'<h1><span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}</span>Заголовок<span class="etp-raquo">{CHAR_RU_QUOT1_CLOSE}</span></h1>')
processor = HangingPunctuationProcessor(mode=mode)
soup = make_soup(input_html)
processor.process(soup)
assert str(soup) == expected_html
LEFT_FULL_SYMBOLS = [
(CHAR_RU_QUOT1_OPEN, CHAR_RU_QUOT1_CLOSE, 'etp-laquo'),
(CHAR_EN_QUOT1_OPEN, CHAR_EN_QUOT1_CLOSE, 'etp-ldquo'),
(CHAR_LPAR, CHAR_RPAR, 'etp-lpar'),
(CHAR_LSQB, CHAR_RSQB, 'etp-lsqb'),
(CHAR_LCUB, CHAR_RCUB, 'etp-lcub'),
]
@pytest.mark.parametrize("open_char, close_char, cls", LEFT_FULL_SYMBOLS)
def test_hanging_left_mode_wraps_symbol_pairs(open_char, close_char, cls):
"""
Убедимся, что разные висячие символы полностью оборачиваются в левом режиме.
open_char: символ, открывающий висячий знак.
close_char: символ, закрывающий висячий знак.
cls: CSS класс для обёртки висячих знаков.
"""
input_html = f'<p>{open_char}Текст{close_char}</p>'
expected_html = f'<p><span class="{cls}">{open_char}Текст{close_char}</span></p>'
processor = HangingPunctuationProcessor(mode='left')
soup = make_soup(input_html)
processor.process(soup)
assert str(soup) == expected_html
SPACE_VARIANTS = [
' ', CHAR_SHY, CHAR_THIN_SP, CHAR_HAIR_SP,
CHAR_MED_SP, CHAR_EN_SP, CHAR_EM_SP, CHAR_NULL_SP,
]
@pytest.mark.parametrize("space", SPACE_VARIANTS)
def test_hanging_left_mode_compensates_different_spaces(space):
"""Проверяем компенсацию для разных типов разрывных пробелов."""
text = f'<p>Проба{space}{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'
expected_html = (f'<p><span class="etp-sp-laquo">Проба{space}</span>'
f'<span class="etp-laquo">{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</span></p>')
processor = HangingPunctuationProcessor(mode='left')
soup = make_soup(text)
processor.process(soup)
assert str(soup) == expected_html
@pytest.mark.parametrize("separator", [CHAR_NBSP, CHAR_THIN_NBSP, CHAR_ZWNJ])
def test_hanging_left_mode_honors_cancellation(separator):
"""Символы, отменяющие перенос, остаются без обёрток."""
input_html = f'<p>Неразрывный{separator}{CHAR_RU_QUOT1_OPEN}Текст{CHAR_RU_QUOT1_CLOSE}</p>'
processor = HangingPunctuationProcessor(mode='left')
soup = make_soup(input_html)
processor.process(soup)
assert str(soup) == input_html