ankigen / tests /test_ui_logic.py
brickfrog's picture
Upload folder using huggingface_hub
7505a0b verified
import pytest
import pandas as pd
from ankigen.models import Card, CardFront, CardBack
from ankigen.ui_logic import (
cards_to_dataframe,
dataframe_to_cards,
update_mode_visibility,
)
def test_cards_to_dataframe_empty():
"""Test cards_to_dataframe with an empty list of cards."""
df = cards_to_dataframe([])
assert isinstance(df, pd.DataFrame)
assert df.empty
assert list(df.columns) == [
"ID",
"Topic",
"Front",
"Back",
"Tags",
"Card Type",
"Explanation",
"Example",
"Source_URL",
]
def test_cards_to_dataframe_single():
"""Test cards_to_dataframe with a single card."""
card = Card(
front=CardFront(question="What is Python?"),
back=CardBack(answer="A programming language", explanation="E", example="Ex"),
metadata={
"topic": "Programming",
"tags": ["python", "coding"],
"source_url": "http://python.org",
},
card_type="Basic",
)
df = cards_to_dataframe([card])
assert len(df) == 1
assert df.iloc[0]["ID"] == 1
assert df.iloc[0]["Topic"] == "Programming"
assert df.iloc[0]["Front"] == "What is Python?"
assert df.iloc[0]["Back"] == "A programming language"
assert df.iloc[0]["Tags"] == "python, coding"
assert df.iloc[0]["Card Type"] == "Basic"
assert df.iloc[0]["Source_URL"] == "http://python.org"
def test_cards_to_dataframe_multiple():
"""Test cards_to_dataframe with multiple cards having varying metadata/tags."""
cards = [
Card(
front=CardFront(question="Q1"),
back=CardBack(answer="A1", explanation="E1", example="Ex1"),
metadata={"topic": "T1", "tags": ["tag1"]},
),
Card(
front=CardFront(question="Q2"),
back=CardBack(answer="A2", explanation="E2", example="Ex2"),
metadata={"tags": ["tag2", "tag3"]},
),
Card(
front=CardFront(question="Q3"),
back=CardBack(answer="A3", explanation="E3", example="Ex3"),
metadata={},
),
]
df = cards_to_dataframe(cards)
assert len(df) == 3
assert df.iloc[0]["Topic"] == "T1"
assert df.iloc[0]["Tags"] == "tag1"
assert df.iloc[1]["Topic"] == "N/A"
assert df.iloc[1]["Tags"] == "tag2, tag3"
assert df.iloc[2]["Topic"] == "N/A"
assert df.iloc[2]["Tags"] == ""
def test_cards_to_dataframe_no_metadata():
"""Test cards_to_dataframe with cards that have no metadata."""
card = Card(
front=CardFront(question="Q"),
back=CardBack(answer="A", explanation="E", example="Ex"),
metadata=None,
)
df = cards_to_dataframe([card])
assert len(df) == 1
assert df.iloc[0]["Topic"] == "N/A"
assert df.iloc[0]["Tags"] == ""
assert df.iloc[0]["Source_URL"] == ""
def test_dataframe_to_cards_empty():
"""Test dataframe_to_cards with empty dataframe and/or empty cards."""
# Both empty
assert dataframe_to_cards(pd.DataFrame(), []) == []
# Empty DF, non-empty cards
original_cards = [
Card(
front=CardFront(question="Q"),
back=CardBack(answer="A", explanation="E", example="Ex"),
)
]
assert dataframe_to_cards(pd.DataFrame(), original_cards) == []
def test_dataframe_to_cards_round_trip():
"""Test a normal round-trip from cards to dataframe and back with edits."""
original_cards = [
Card(
front=CardFront(question="Original Q"),
back=CardBack(answer="Original A", explanation="E", example="Ex"),
metadata={"topic": "Original T", "tags": ["tag1"]},
)
]
df = cards_to_dataframe(original_cards)
# Edit the dataframe
df.at[0, "Front"] = "Updated Q"
df.at[0, "Back"] = "Updated A"
df.at[0, "Tags"] = "tag1, tag2"
df.at[0, "Topic"] = "Updated T"
df.at[0, "Card Type"] = "Cloze"
updated_cards = dataframe_to_cards(df, original_cards)
assert len(updated_cards) == 1
assert updated_cards[0].front.question == "Updated Q"
assert updated_cards[0].back.answer == "Updated A"
assert updated_cards[0].metadata["tags"] == ["tag1", "tag2"]
assert updated_cards[0].metadata["topic"] == "Updated T"
assert updated_cards[0].card_type == "Cloze"
def test_dataframe_to_cards_out_of_bounds_id(mocker):
"""Test handling of IDs that are out of bounds for the original cards list."""
mock_logger = mocker.patch("ankigen.ui_logic.logger")
original_cards = [
Card(
front=CardFront(question="Q1"),
back=CardBack(answer="A1", explanation="E1", example="Ex1"),
)
]
# ID 2 is out of bounds (only 1 card exists)
df = pd.DataFrame({"ID": [2], "Front": ["Q2"]})
result = dataframe_to_cards(df, original_cards)
assert result == []
mock_logger.warning.assert_called_with(
"Card ID 2 from DataFrame is out of bounds for original_cards list."
)
def test_dataframe_to_cards_error_handling(mocker):
"""Test handling of rows that cause errors during processing."""
mock_logger = mocker.patch("ankigen.ui_logic.logger")
original_cards = [
Card(
front=CardFront(question="Q1"),
back=CardBack(answer="A1", explanation="E1", example="Ex1"),
)
]
# Case 1: Missing ID column (KeyError)
# Desired behavior: the function should handle this gracefully, log an error,
# and not raise an UnboundLocalError.
df_missing_id = pd.DataFrame({"Front": ["Q1"]})
result = dataframe_to_cards(df_missing_id, original_cards)
# Expect that no cards are returned for rows missing a valid ID
assert result == []
mock_logger.error.assert_called()
def test_dataframe_to_cards_error_handling_recovery(mocker):
"""Test that it recovers gracefully if an error occurs after ID is parsed."""
mock_logger = mocker.patch("ankigen.ui_logic.logger")
# Patch the class method instead of instance because Pydantic blocks instance patching of methods
mocker.patch(
"ankigen.models.CardFront.copy", side_effect=AttributeError("Mock Error")
)
card = Card(
front=CardFront(question="Q1"),
back=CardBack(answer="A1", explanation="E1", example="Ex1"),
)
original_cards = [card]
df = pd.DataFrame({"ID": [1], "Front": ["New Q"]})
result = dataframe_to_cards(df, original_cards)
# It should catch the error, log it, and use the original card
assert len(result) == 1
assert result[0] is card
assert mock_logger.error.call_count >= 1
def test_update_mode_visibility(mocker):
"""Verify update_mode_visibility returns the expected 5-tuple of gr.update() calls."""
# Mock gr.update to return its arguments for easy verification
mocker.patch("gradio.update", side_effect=lambda **kwargs: kwargs)
result = update_mode_visibility("subject", "Biology")
assert isinstance(result, tuple)
assert len(result) == 5
# Verify each update call
assert result[0] == {"visible": True}
assert result[1] == {"visible": True}
assert result[2] == {"value": "Biology"}
# result[3] should have a value that is an empty DataFrame with specific columns
assert isinstance(result[3]["value"], pd.DataFrame)
assert list(result[3]["value"].columns) == [
"Index",
"Topic",
"Card_Type",
"Question",
"Answer",
"Explanation",
"Example",
"Prerequisites",
"Learning_Outcomes",
"Difficulty",
]
# result[4] is total_cards_html
assert "total-cards-count" in result[4]["value"]
assert result[4]["visible"] is False