Loading...
No commits yet
Not committed History
Blame
test_csv_to_latex.py • 11.4 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Test file for: csv_to_latex.py

import os
import sys
from pathlib import Path

import pytest

# Add scripts/python to path for imports
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(ROOT_DIR / "scripts" / "python"))

# Try to import pandas and the functions
try:
    import pandas as pd  # noqa: F401
    from csv_to_latex import csv_to_latex, escape_latex, format_number

    HAS_DEPS = True
except ImportError:
    HAS_DEPS = False

# Skip all tests if pandas not available
pytestmark = pytest.mark.skipif(not HAS_DEPS, reason="pandas not installed")


class TestEscapeLatex:
    """Test LaTeX special character escaping."""

    def test_escape_latex_ampersand(self):
        """Should escape ampersand."""
        result = escape_latex("A & B")
        assert result == r"A \& B"

    def test_escape_latex_percent(self):
        """Should escape percent sign."""
        result = escape_latex("50%")
        assert result == r"50\%"

    def test_escape_latex_underscore(self):
        """Should escape underscore."""
        result = escape_latex("a_b")
        assert result == r"a\_b"

    def test_escape_latex_hash(self):
        """Should escape hash/pound sign."""
        result = escape_latex("#1")
        assert result == r"\#1"

    def test_escape_latex_dollar(self):
        """Should escape dollar sign."""
        result = escape_latex("$100")
        assert result == r"\$100"

    def test_escape_latex_backslash(self):
        """Should escape backslash."""
        result = escape_latex("a\\b")
        # Braces in \textbackslash{} also get escaped
        assert result == r"a\textbackslash\{\}b"

    def test_escape_latex_braces(self):
        """Should escape curly braces."""
        result = escape_latex("a{b}c")
        assert result == r"a\{b\}c"

    def test_escape_latex_tilde(self):
        """Should escape tilde."""
        result = escape_latex("a~b")
        assert result == r"a\textasciitilde{}b"

    def test_escape_latex_caret(self):
        """Should escape caret."""
        result = escape_latex("a^b")
        assert result == r"a\textasciicircum{}b"

    def test_escape_latex_nan(self):
        """Should handle pandas NaN values."""
        result = escape_latex(float("nan"))
        assert result == ""

    def test_escape_latex_none(self):
        """Should handle None values."""
        result = escape_latex(None)
        assert result == ""

    def test_escape_latex_multiple_chars(self):
        """Should escape multiple special characters."""
        result = escape_latex("A & B: 50% #1")
        assert result == r"A \& B: 50\% \#1"

    def test_escape_latex_numeric_input(self):
        """Should convert numbers to strings and escape."""
        result = escape_latex(42)
        assert result == "42"

    def test_escape_latex_empty_string(self):
        """Should handle empty strings."""
        result = escape_latex("")
        assert result == ""


class TestFormatNumber:
    """Test number formatting."""

    def test_format_number_integer(self):
        """Should format integers without decimals."""
        result = format_number("42.0")
        assert result == "42"

    def test_format_number_decimal(self):
        """Should format decimals with up to 3 places."""
        result = format_number("3.14159")
        assert result == "3.142"

    def test_format_number_small_scientific(self):
        """Should use scientific notation for very small numbers."""
        result = format_number("0.001")
        # Should be in scientific notation
        assert "e" in result.lower()

    def test_format_number_zero(self):
        """Should format zero correctly."""
        result = format_number("0.0")
        assert result == "0"

    def test_format_number_negative(self):
        """Should handle negative numbers."""
        result = format_number("-3.14")
        assert result == "-3.14"

    def test_format_number_text(self):
        """Should return non-numeric text as-is."""
        result = format_number("hello")
        assert result == "hello"

    def test_format_number_strips_trailing_zeros(self):
        """Should strip trailing zeros."""
        result = format_number("3.100")
        assert result == "3.1"

    def test_format_number_strips_trailing_dot(self):
        """Should strip trailing decimal point."""
        result = format_number("3.000")
        assert result == "3"

    def test_format_number_large_number(self):
        """Should format large numbers correctly."""
        result = format_number("123456.789")
        assert result == "123456.789"

    def test_format_number_very_small_nonzero(self):
        """Should use scientific notation for numbers < 0.01."""
        result = format_number("0.005")
        assert "e" in result.lower()

    def test_format_number_boundary_0_01(self):
        """Should use scientific notation at 0.01 boundary."""
        result = format_number("0.009")
        assert "e" in result.lower()


class TestCsvToLatex:
    """Test full CSV to LaTeX conversion."""

    def test_csv_to_latex_creates_file(self, tmp_path):
        """Should create output LaTeX file."""
        # Create test CSV
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Name,Age\nAlice,30\nBob,25")

        output_file = tmp_path / "output.tex"

        result = csv_to_latex(csv_file, output_file)

        assert result is True
        assert output_file.exists()

    def test_csv_to_latex_contains_tabular(self, tmp_path):
        """Output should contain tabular environment."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A,B\n1,2")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        assert r"\begin{tabular}" in content
        assert r"\end{tabular}" in content

    def test_csv_to_latex_contains_headers(self, tmp_path):
        """Output should contain bold headers."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Name,Age\nAlice,30")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        # Headers should be bold and title-cased
        assert r"\textbf{Name}" in content
        assert r"\textbf{Age}" in content

    def test_csv_to_latex_contains_data(self, tmp_path):
        """Output should contain table data."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Name,Value\nTest,42")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        assert "Test" in content
        assert "42" in content

    def test_csv_to_latex_escapes_special_chars(self, tmp_path):
        """Should escape LaTeX special characters in data."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Text\nA & B")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        # Ampersand should be escaped
        assert r"\&" in content

    def test_csv_to_latex_contains_table_env(self, tmp_path):
        """Output should contain table environment."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A\n1")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        assert r"\begin{table}" in content
        assert r"\end{table}" in content

    def test_csv_to_latex_contains_caption(self, tmp_path):
        """Output should contain caption."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A\n1")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        assert r"\caption{" in content

    def test_csv_to_latex_custom_caption(self, tmp_path):
        """Should use custom caption if provided."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A\n1")

        output_file = tmp_path / "output.tex"
        custom_caption = "My Custom Caption"
        csv_to_latex(csv_file, output_file, caption=custom_caption)

        content = output_file.read_text()
        assert custom_caption in content

    def test_csv_to_latex_custom_label(self, tmp_path):
        """Should use custom label if provided."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A\n1")

        output_file = tmp_path / "output.tex"
        custom_label = "tab:mylabel"
        csv_to_latex(csv_file, output_file, label=custom_label)

        content = output_file.read_text()
        assert r"\label{tab:mylabel}" in content

    def test_csv_to_latex_invalid_csv(self, tmp_path):
        """Should return False for invalid CSV."""
        csv_file = tmp_path / "nonexistent.csv"
        output_file = tmp_path / "output.tex"

        result = csv_to_latex(csv_file, output_file)

        assert result is False
        assert not output_file.exists()

    def test_csv_to_latex_contains_booktabs(self, tmp_path):
        """Should use booktabs rules (toprule, midrule, bottomrule)."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A,B\n1,2")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        assert r"\toprule" in content
        assert r"\midrule" in content
        assert r"\bottomrule" in content

    def test_csv_to_latex_number_formatting(self, tmp_path):
        """Should format numbers appropriately."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Value\n3.14159\n42.0")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        # Float should be formatted
        assert "3.142" in content
        # Integer should not have decimals
        assert "42 " in content or "42\\\\" in content

    def test_csv_to_latex_column_alignment(self, tmp_path):
        """Should align numeric columns right."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("Name,Age\nAlice,30")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        # Should have tabular with alignment spec (l for text, r for numbers)
        assert r"\begin{tabular}{" in content

    def test_csv_to_latex_handles_missing_values(self, tmp_path):
        """Should handle missing/NaN values."""
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("A,B\n1,\n,3")

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file)

        content = output_file.read_text()
        # Missing values should be represented (as --)
        assert "--" in content

    def test_csv_to_latex_truncation_message(self, tmp_path):
        """Should add truncation message for large tables."""
        # Create CSV with many rows
        lines = ["Value"] + [str(i) for i in range(50)]
        csv_file = tmp_path / "test.csv"
        csv_file.write_text("\n".join(lines))

        output_file = tmp_path / "output.tex"
        csv_to_latex(csv_file, output_file, max_rows=10)

        content = output_file.read_text()
        # Should mention truncation
        assert "truncated" in content.lower() or "omitted" in content.lower()


if __name__ == "__main__":
    pytest.main([os.path.abspath(__file__), "-v"])