#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: 2026-02-19
# File: tests/python/test_claim.py
"""Tests for scitex_writer claim feature.
Tests CRUD operations and LaTeX rendering via public API (scitex_writer.claim).
All file I/O uses tmp_path to avoid touching actual project files.
"""
import json
from pathlib import Path
def _make_project(tmp_path: Path) -> Path:
"""Create minimal project structure for claim tests."""
shared = tmp_path / "00_shared"
shared.mkdir(parents=True)
return str(tmp_path)
class TestClaimAdd:
"""Test adding claims."""
def test_add_statistic(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.add(
project_dir=project_dir,
claim_id="group_comparison",
claim_type="statistic",
value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
context="Group A vs B",
)
assert result["success"]
assert result["claim_id"] == "group_comparison"
def test_add_value(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.add(
project_dir=project_dir,
claim_id="rt_mean",
claim_type="value",
value={"value": 42.3, "unit": "ms"},
)
assert result["success"]
def test_add_citation(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.add(
project_dir=project_dir,
claim_id="key_finding",
claim_type="citation",
value={"text": "as previously reported"},
)
assert result["success"]
def test_add_creates_claims_json(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="c1",
claim_type="citation",
value={"text": "test"},
)
claims_file = Path(project_dir) / "00_shared" / "claims.json"
assert claims_file.exists()
data = json.loads(claims_file.read_text())
assert "c1" in data["claims"]
def test_add_preserves_existing(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="c1",
claim_type="citation",
value={"text": "first"},
)
sc.add(
project_dir=project_dir,
claim_id="c2",
claim_type="citation",
value={"text": "second"},
)
result = sc.list(project_dir=project_dir)
assert result["count"] == 2
class TestClaimList:
"""Test listing claims."""
def test_list_empty(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.list(project_dir=project_dir)
assert result["success"]
assert result["count"] == 0
assert result["claims"] == []
def test_list_after_add(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="stat1",
claim_type="statistic",
value={"t": 2.1, "df": 20, "p": 0.05, "d": 0.4},
)
result = sc.list(project_dir=project_dir)
assert result["success"]
assert result["count"] == 1
assert result["claims"][0]["claim_id"] == "stat1"
assert result["claims"][0]["type"] == "statistic"
def test_list_includes_preview(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 3.5, "df": 10, "p": 0.006, "d": 0.9},
)
result = sc.list(project_dir=project_dir)
claim = result["claims"][0]
assert "preview_nature" in claim
class TestClaimGet:
"""Test getting individual claims."""
def test_get_existing(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="my_claim",
claim_type="citation",
value={"text": "test text"},
)
result = sc.get(project_dir=project_dir, claim_id="my_claim")
assert result["success"]
assert result["claim"]["type"] == "citation"
def test_get_missing(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.get(project_dir=project_dir, claim_id="nonexistent")
assert not result["success"]
class TestClaimRemove:
"""Test removing claims."""
def test_remove_existing(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="to_delete",
claim_type="citation",
value={"text": "delete me"},
)
result = sc.remove(project_dir=project_dir, claim_id="to_delete")
assert result["success"]
assert sc.list(project_dir=project_dir)["count"] == 0
def test_remove_missing(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.remove(project_dir=project_dir, claim_id="ghost")
assert not result["success"]
class TestClaimFormat:
"""Test formatting claims into rendered strings."""
def test_format_statistic_nature(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
)
result = sc.format(project_dir=project_dir, claim_id="s1", style="nature")
assert result["success"]
assert "t" in result["rendered"]
assert "4.23" in result["rendered"]
def test_format_statistic_apa(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
)
result = sc.format(project_dir=project_dir, claim_id="s1", style="apa")
assert result["success"]
assert "Cohen" in result["rendered"]
def test_format_statistic_plain(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
)
result = sc.format(project_dir=project_dir, claim_id="s1", style="plain")
assert result["success"]
assert "$" not in result["rendered"]
def test_format_p_small(self, tmp_path):
"""p < 0.001 should render as < 0.001, not 0.000."""
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 5.0, "df": 100, "p": 0.0005, "d": 1.0},
)
result = sc.format(project_dir=project_dir, claim_id="s1", style="nature")
assert result["success"]
assert "0.001" in result["rendered"] # < 0.001
def test_format_citation(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="c1",
claim_type="citation",
value={"text": "as shown previously"},
)
result = sc.format(project_dir=project_dir, claim_id="c1", style="nature")
assert result["success"]
assert "as shown previously" in result["rendered"]
def test_format_value(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="v1",
claim_type="value",
value={"value": 42.3, "unit": "ms"},
)
result = sc.format(project_dir=project_dir, claim_id="v1", style="nature")
assert result["success"]
assert "42.3" in result["rendered"]
assert "ms" in result["rendered"]
class TestClaimRender:
"""Test rendering all claims to claims_rendered.tex."""
def test_render_creates_tex(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="grp",
claim_type="statistic",
value={"t": 4.23, "df": 34, "p": 0.00032, "d": 0.87},
)
result = sc.render(project_dir=project_dir)
assert result["success"]
tex_path = Path(project_dir) / "00_shared" / "claims_rendered.tex"
assert tex_path.exists()
def test_render_tex_has_macro(self, tmp_path):
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="my_stat",
claim_type="statistic",
value={"t": 2.0, "df": 10, "p": 0.03, "d": 0.5},
)
sc.render(project_dir=project_dir)
tex = (Path(project_dir) / "00_shared" / "claims_rendered.tex").read_text()
assert "\\stxclaim" in tex
assert "mystat" in tex # sanitized ID (underscores removed)
def test_render_all_styles(self, tmp_path):
"""Rendered .tex defines nature, apa, and plain macros for each claim."""
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
sc.add(
project_dir=project_dir,
claim_id="s1",
claim_type="statistic",
value={"t": 3.0, "df": 20, "p": 0.005, "d": 0.7},
)
sc.render(project_dir=project_dir)
tex = (Path(project_dir) / "00_shared" / "claims_rendered.tex").read_text()
assert "@nature" in tex
assert "@apa" in tex
assert "@plain" in tex
def test_render_empty(self, tmp_path):
"""Rendering with no claims still creates a valid .tex file."""
import scitex_writer.claim as sc
project_dir = _make_project(tmp_path)
result = sc.render(project_dir=project_dir)
assert result["success"]
assert result["claims_count"] == 0
tex_path = Path(project_dir) / "00_shared" / "claims_rendered.tex"
assert tex_path.exists()
class TestClaimPublicApi:
"""Test that scitex_writer.claim exposes the correct public interface."""
def test_public_functions_exist(self):
import scitex_writer.claim as sc
for name in ["add", "list", "get", "remove", "format", "render"]:
assert hasattr(sc, name), f"Missing: claim.{name}"
assert callable(getattr(sc, name))
def test_claim_not_in_top_level_all(self):
"""Internal dataclasses should not appear in scitex_writer.__all__."""
import scitex_writer as sw
for name in [
"CompilationResult",
"ManuscriptTree",
"RevisionTree",
"SupplementaryTree",
]:
assert name not in sw.__all__, f"{name} should be hidden from __all__"
def test_claim_in_top_level_all(self):
"""claim module should be in scitex_writer.__all__."""
import scitex_writer as sw
assert "claim" in sw.__all__