Cover remaining uncovered lines in bcda (client, store, log, pipe/cclf, flatten), bib (sync, spider, translate, ingest, item, store, format, ui), cms (express wrappers, log edge cases), api (base, gitea, rustfs, woodpecker, zotero), bls (table import), and pfs (pragma on race guard). 37,687 statements, 0 missed — 11,061 tests passing.
216 lines
6.6 KiB
Python
216 lines
6.6 KiB
Python
"""Tests for bib.ui — marimo widgets for bibliography."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from unittest.mock import MagicMock
|
|
|
|
from bib.item import Rule
|
|
from bib.store import Store
|
|
|
|
|
|
def _install_mock_marimo() -> MagicMock:
|
|
"""Install a mock marimo module into sys.modules."""
|
|
mo = MagicMock()
|
|
|
|
# mo.Html returns an object with the html stored
|
|
class MockHtml:
|
|
def __init__(self, html: str) -> None:
|
|
self.text = html
|
|
|
|
mo.Html = MockHtml
|
|
mo.ui.text.return_value = MagicMock(value="")
|
|
mo.ui.table.return_value = MagicMock()
|
|
mo.accordion.return_value = MagicMock()
|
|
mo.md.return_value = MagicMock()
|
|
mo.tabs.return_value = MagicMock()
|
|
mo.vstack.return_value = MagicMock()
|
|
|
|
sys.modules["marimo"] = mo
|
|
return mo
|
|
|
|
|
|
def _remove_mock_marimo() -> None:
|
|
"""Remove mock marimo from sys.modules."""
|
|
sys.modules.pop("marimo", None)
|
|
# Also remove cached ui submodule imports
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
|
|
# ── ui.__init__ ─────────────────────────────────────────────────────
|
|
|
|
|
|
class TestUiInit:
|
|
def test_imports(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
# Force reimport
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
import bib.ui
|
|
|
|
assert hasattr(bib.ui, "library_browser")
|
|
assert hasattr(bib.ui, "cite")
|
|
assert hasattr(bib.ui, "bibliography")
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
|
|
# ── library_browser ─────────────────────────────────────────────────
|
|
|
|
|
|
class TestLibraryBrowser:
|
|
def test_returns_widget(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
# Force reimport
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.browser import library_browser
|
|
|
|
s = Store(":memory:")
|
|
s.create(Rule(title="Test Rule"), tags=["module:pfs"])
|
|
s.ensure_collections({"Rules": {}})
|
|
|
|
result = library_browser(s)
|
|
assert result is not None
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
def test_with_collection_filter(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.browser import library_browser
|
|
|
|
s = Store(":memory:")
|
|
coll_map = s.ensure_collections({"Rules": {}})
|
|
s.create(
|
|
Rule(title="R1"),
|
|
collection=coll_map["Rules"],
|
|
)
|
|
|
|
result = library_browser(s, collection=coll_map["Rules"])
|
|
assert result is not None
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
def test_with_search_value(self) -> None:
|
|
"""Line 72: search.value is non-empty."""
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
# Set search mock to return non-empty value
|
|
mo = sys.modules["marimo"]
|
|
search_mock = MagicMock()
|
|
search_mock.value = "test query"
|
|
mo.ui.text.return_value = search_mock
|
|
|
|
from bib.ui.browser import library_browser
|
|
|
|
s = Store(":memory:")
|
|
s.create(Rule(title="Test Rule"))
|
|
result = library_browser(s)
|
|
assert result is not None
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
def test_empty_store(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.browser import library_browser
|
|
|
|
s = Store(":memory:")
|
|
result = library_browser(s)
|
|
assert result is not None
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
|
|
# ── cite ────────────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCite:
|
|
def test_returns_html(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.cite import cite
|
|
|
|
s = Store(":memory:")
|
|
key = s.create(Rule(title="Test Rule", date_published="2025-01-01"))
|
|
|
|
result = cite(s, key)
|
|
assert result is not None
|
|
assert hasattr(result, "text")
|
|
assert "bib-cite" in result.text
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
|
|
# ── bibliography ────────────────────────────────────────────────────
|
|
|
|
|
|
class TestBibliography:
|
|
def test_returns_html(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.cite import bibliography
|
|
|
|
s = Store(":memory:")
|
|
k1 = s.create(Rule(title="Rule A", date_published="2025-01-01"))
|
|
k2 = s.create(Rule(title="Rule B", date_published="2025-06-01"))
|
|
|
|
result = bibliography(s, [k1, k2])
|
|
assert result is not None
|
|
assert hasattr(result, "text")
|
|
assert "bib-bibliography" in result.text
|
|
assert "<ol>" in result.text
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|
|
|
|
def test_empty_keys(self) -> None:
|
|
_install_mock_marimo()
|
|
try:
|
|
for key in list(sys.modules):
|
|
if key.startswith("bib.ui"):
|
|
sys.modules.pop(key, None)
|
|
|
|
from bib.ui.cite import bibliography
|
|
|
|
s = Store(":memory:")
|
|
result = bibliography(s, [])
|
|
assert result is not None
|
|
s.close()
|
|
finally:
|
|
_remove_mock_marimo()
|