macronizer / tests /test_markup.py
Urdatorn's picture
rename
e051462
Raw
History Blame Contribute Delete
8.87 kB
"""
Test that plain output preserves input exactly except for markup.
Output should equal: input.replace("^", "").replace("_", "")
"""
import pytest
import sys
import html
import re
from pathlib import Path
# Add parent directory to path to import app module
sys.path.insert(0, str(Path(__file__).parent.parent))
from app import (
DEFAULT_MODEL_ID,
DEFAULT_MODEL_LABEL,
MODEL_OPTIONS,
_resolve_model_id,
_render_plain_line_per_word,
_render_styled_line_per_word,
macronize,
macronize_ui,
preprocess_and_syllabify,
)
def strip_markup(text: str) -> str:
"""Remove markup characters from text."""
return text.replace("^", "").replace("_", "")
def strip_color(text: str) -> str:
"""Remove pretty-output color markup."""
return html.unescape(re.sub(r"</?span[^>]*>", "", text))
# Test cases with Greek lines
TEST_CASES = [
# Basic simple word
"νεανίας",
# Multiple words with spaces
"νεανίας ἀάατός ἐστιν",
# With final sigma
"καὶ καλός",
# Multiple spaces
"καλὰ μὲν",
# Single letter
"ἢ",
# Word with punctuation preserved
"τυφλὸς ἤ",
# Multi-word with accents
"Ἀτρεΐδαι τε καὶ ἄλλοι",
# Longer passage
"νεανίας ἀάατός ἐστιν καὶ καλός",
# Expanded ξ and trailing comma must be restored exactly
"καλὰ μὲν ἠέξευ, καλὰ δ᾽ ἔτραφες, οὐράνιε Ζεῦ",
]
@pytest.mark.parametrize("input_line", TEST_CASES)
def test_plain_output_preserves_input_without_markup(input_line):
"""
Test that the plain output is identical to input after removing markup.
The output should be: input.replace("^", "").replace("_", "")
This ensures:
- All original characters are preserved
- Spaces are preserved exactly
- Final sigmas are preserved
- Only the markup (^ and _) are added
"""
# Get the rendered plain line
plain_output = _render_plain_line_per_word(input_line, DEFAULT_MODEL_ID)
# Strip markup from the output to get back the base text
output_without_markup = strip_markup(plain_output)
# The expected result: input with no markup
# (We don't normalize final sigma - we preserve exactly what was in the input)
input_expected = input_line
print(f"\nInput: {repr(input_line)}")
print(f"Output: {repr(plain_output)}")
print(f"Output without markup: {repr(output_without_markup)}")
print(f"Expected: {repr(input_expected)}")
# The core assertion: output without markup should match input
assert output_without_markup == input_expected, (
f"Output without markup doesn't match input.\n"
f"Expected: {repr(input_expected)}\n"
f"Got: {repr(output_without_markup)}"
)
def test_styled_output_uses_plain_formatting_with_colored_vowels(monkeypatch):
input_line = "νεανίας ἀάατός ἐστιν καὶ καλός. τὰ παῖδες τὰ καλά"
labels_by_word = {
"νεανίασ": [("νε", 0), ("α", 1), ("νί", 2), ("ασ", 0)],
"ἀάατόσ": [("ἀ", 2), ("ά", 1), ("α", 2), ("τόσ", 0)],
"ἐστιν": [("ἐσ", 0), ("τιν", 2)],
"καὶ": [("καὶ", 0)],
"καλόσ.": [("κα", 2), ("λό", 0), ("σ.", 0)],
"τὰ": [("τὰ", 2)],
"παῖδεσ": [("παῖ", 0), ("δεσ", 0)],
"καλά": [("κα", 1), ("λά", 1)],
}
def fake_classify_line(word, model_id):
return labels_by_word[word]
monkeypatch.setattr("app.classify_line", fake_classify_line)
styled_output = _render_styled_line_per_word(input_line, DEFAULT_MODEL_ID)
expected = (
'νε'
'<span class="vowel long">α</span>'
'ν<span class="vowel short">ί</span>'
'ας '
'<span class="vowel short">ἀ</span>'
'<span class="vowel long">ά</span>'
'<span class="vowel short">α</span>'
'τός '
'ἐστ<span class="vowel short">ι</span>ν '
'καὶ '
'κ<span class="vowel short">α</span>λός. '
'τ<span class="vowel short">ὰ</span> '
'παῖδες '
'τ<span class="vowel short">ὰ</span> '
'κ<span class="vowel long">α</span>λ<span class="vowel long">ά</span>'
)
assert styled_output == expected
assert "vowel clear" not in styled_output
@pytest.mark.parametrize("input_line", TEST_CASES)
def test_styled_output_preserves_input_without_color(monkeypatch, input_line):
def fake_classify_line(word, model_id):
return [
(syllable, 1 if idx % 2 == 0 else 2)
for idx, syllable in enumerate(preprocess_and_syllabify(word))
]
monkeypatch.setattr("app.classify_line", fake_classify_line)
styled_output = _render_styled_line_per_word(input_line, DEFAULT_MODEL_ID)
assert strip_color(styled_output) == input_line
def test_plain_output_box_contains_only_output_lines(monkeypatch):
monkeypatch.setattr("app._render_plain_line_per_word", lambda line, model_id: f"marked {line}")
monkeypatch.setattr("app._render_styled_line_per_word", lambda line, model_id: line)
html_output, plain_output = macronize_ui("alpha\nbeta", DEFAULT_MODEL_LABEL)
assert plain_output == "marked alpha\nmarked beta"
assert '<div class="source">' not in html_output
assert '<div class="line-output">alpha</div>' in html_output
def test_api_returns_only_plain_output(monkeypatch):
monkeypatch.setattr("app._render_plain_line_per_word", lambda line, model_id: f"marked {line}")
assert macronize("alpha\nbeta") == "marked alpha\nmarked beta"
@pytest.mark.parametrize(
("model_label", "expected_model_id"),
[
("modernbert", MODEL_OPTIONS["Pretrained ModernBERT"]),
("syllamobert", MODEL_OPTIONS["Pretrained ModernBERT"]),
("roberta", MODEL_OPTIONS["Fine-tuned RoBERTa"]),
("Pretrained ModernBERT", MODEL_OPTIONS["Pretrained ModernBERT"]),
("Fine-tuned RoBERTa", MODEL_OPTIONS["Fine-tuned RoBERTa"]),
],
)
def test_model_label_aliases(model_label, expected_model_id):
assert _resolve_model_id(model_label) == expected_model_id
def test_api_model_label_alias_is_used(monkeypatch):
model_ids = []
def fake_render_line(line, model_id):
model_ids.append(model_id)
return f"marked {line}"
monkeypatch.setattr("app._render_plain_line_per_word", fake_render_line)
assert macronize("alpha", model="roberta") == "marked alpha"
assert model_ids == [MODEL_OPTIONS["Fine-tuned RoBERTa"]]
def test_public_api_endpoint_is_plain_output_only():
import app
public_deps = [
dep
for dep in app.demo.config.get("dependencies", [])
if dep.get("api_name") == "macronize"
and dep.get("api_visibility") == "public"
]
assert len(public_deps) == 1
assert len(public_deps[0].get("inputs", [])) == 2
assert len(public_deps[0].get("outputs", [])) == 1
public_callable_deps = [
dep
for dep in app.demo.config.get("dependencies", [])
if dep.get("api_visibility") == "public"
and dep.get("api_name") not in (None, False)
]
assert [dep.get("api_name") for dep in public_callable_deps] == ["macronize"]
def test_mcp_tool_surface_exposes_only_plain_endpoint():
import app
assert set(app.demo.get_api_info()["named_endpoints"]) == {"/macronize"}
def test_public_api_text_example_is_greek():
import app
api_text_components = [
component
for component in app.demo.config.get("components", [])
if component.get("props", {}).get("label") == "text"
]
assert len(api_text_components) == 1
assert api_text_components[0]["props"]["value"] == "μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος"
def test_try_examples_preserve_visible_line_breaks():
import app
assert any("\n" in example for example in app.examples)
assert "#try-examples .examples" in app.CSS
assert "#try-examples .example-text" in app.CSS
assert "white-space: pre-wrap" in app.CSS
assert "overflow: visible" in app.CSS
assert "text-overflow: clip" in app.CSS
def test_python_api_instructions_omit_api_name():
import app
markdown_values = [
component.get("props", {}).get("value", "")
for component in app.demo.config.get("components", [])
if component.get("type") == "markdown"
]
api_markdown = next(value for value in markdown_values if "Python API" in value)
assert 'Client("Urdatorn/macronizer")' in api_markdown
assert 'model="roberta"' in api_markdown
assert 'api_name="/macronize"' in api_markdown
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])