#!/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"])