brickfrog commited on
Commit
7505a0b
·
verified ·
1 Parent(s): 1f40585

Upload folder using huggingface_hub

Browse files
tests/test_card_generator.py CHANGED
@@ -1,4 +1,4 @@
1
- from ankigen_core.card_generator import (
2
  _parse_model_selection,
3
  _map_generation_mode_to_subject,
4
  _build_generation_context,
@@ -6,7 +6,7 @@ from ankigen_core.card_generator import (
6
  get_dataframe_columns,
7
  generate_token_usage_html,
8
  )
9
- from ankigen_core.models import Card, CardFront, CardBack
10
 
11
  # --- _parse_model_selection Tests ---
12
 
@@ -16,7 +16,7 @@ def test_parse_model_selection():
16
  assert _parse_model_selection("gpt-5.2-instant") == ("gpt-5.2", "none")
17
  assert _parse_model_selection("gpt-5.2-thinking") == ("gpt-5.2", "high")
18
  assert _parse_model_selection("custom-model") == ("custom-model", None)
19
- assert _parse_model_selection("") == ("gpt-5.2", None)
20
 
21
 
22
  # --- _map_generation_mode_to_subject Tests ---
@@ -112,14 +112,14 @@ def test_generate_token_usage_html():
112
 
113
 
114
  def test_available_models_constant():
115
- from ankigen_core.card_generator import AVAILABLE_MODELS
116
 
117
  assert len(AVAILABLE_MODELS) >= 3
118
  assert AVAILABLE_MODELS[0]["value"] == "gpt-5.2-auto"
119
 
120
 
121
  def test_generation_modes_constant():
122
- from ankigen_core.card_generator import GENERATION_MODES
123
 
124
  assert len(GENERATION_MODES) >= 1
125
  assert GENERATION_MODES[0]["value"] == "subject"
 
1
+ from ankigen.card_generator import (
2
  _parse_model_selection,
3
  _map_generation_mode_to_subject,
4
  _build_generation_context,
 
6
  get_dataframe_columns,
7
  generate_token_usage_html,
8
  )
9
+ from ankigen.models import Card, CardFront, CardBack
10
 
11
  # --- _parse_model_selection Tests ---
12
 
 
16
  assert _parse_model_selection("gpt-5.2-instant") == ("gpt-5.2", "none")
17
  assert _parse_model_selection("gpt-5.2-thinking") == ("gpt-5.2", "high")
18
  assert _parse_model_selection("custom-model") == ("custom-model", None)
19
+ assert _parse_model_selection("") == ("gpt-5.4", None)
20
 
21
 
22
  # --- _map_generation_mode_to_subject Tests ---
 
112
 
113
 
114
  def test_available_models_constant():
115
+ from ankigen.card_generator import AVAILABLE_MODELS
116
 
117
  assert len(AVAILABLE_MODELS) >= 3
118
  assert AVAILABLE_MODELS[0]["value"] == "gpt-5.2-auto"
119
 
120
 
121
  def test_generation_modes_constant():
122
+ from ankigen.card_generator import GENERATION_MODES
123
 
124
  assert len(GENERATION_MODES) >= 1
125
  assert GENERATION_MODES[0]["value"] == "subject"
tests/test_exporters.py CHANGED
@@ -3,7 +3,7 @@ import pandas as pd
3
  import os
4
  import gradio as gr
5
  from unittest.mock import patch
6
- from ankigen_core.exporters import (
7
  _format_field_as_string,
8
  _generate_timestamped_filename,
9
  _validate_non_empty_data,
@@ -123,14 +123,14 @@ def test_export_cards_to_apkg_zero_valid_notes():
123
  # --- export_cards_from_crawled_content Tests ---
124
 
125
 
126
- @patch("ankigen_core.exporters.export_cards_to_csv")
127
  def test_export_cards_from_crawled_content_csv(mock_csv):
128
  cards = [{"front": "Q", "back": "A"}]
129
  export_cards_from_crawled_content(cards, export_format="csv")
130
  mock_csv.assert_called_once()
131
 
132
 
133
- @patch("ankigen_core.exporters.export_cards_to_apkg")
134
  def test_export_cards_from_crawled_content_apkg(mock_apkg):
135
  cards = [{"front": "Q", "back": "A"}]
136
  export_cards_from_crawled_content(cards, export_format="apkg")
@@ -161,7 +161,7 @@ def test_export_dataframe_to_csv_empty():
161
  export_dataframe_to_csv(None)
162
 
163
 
164
- @patch("ankigen_core.exporters.export_cards_to_apkg")
165
  def test_export_dataframe_to_apkg_success(mock_apkg):
166
  df = pd.DataFrame({"Question": ["Q1"], "Answer": ["A1"], "Card_Type": ["Basic"]})
167
  export_dataframe_to_apkg(df, output_path="test.apkg", deck_name="Test Deck")
 
3
  import os
4
  import gradio as gr
5
  from unittest.mock import patch
6
+ from ankigen.exporters import (
7
  _format_field_as_string,
8
  _generate_timestamped_filename,
9
  _validate_non_empty_data,
 
123
  # --- export_cards_from_crawled_content Tests ---
124
 
125
 
126
+ @patch("ankigen.exporters.export_cards_to_csv")
127
  def test_export_cards_from_crawled_content_csv(mock_csv):
128
  cards = [{"front": "Q", "back": "A"}]
129
  export_cards_from_crawled_content(cards, export_format="csv")
130
  mock_csv.assert_called_once()
131
 
132
 
133
+ @patch("ankigen.exporters.export_cards_to_apkg")
134
  def test_export_cards_from_crawled_content_apkg(mock_apkg):
135
  cards = [{"front": "Q", "back": "A"}]
136
  export_cards_from_crawled_content(cards, export_format="apkg")
 
161
  export_dataframe_to_csv(None)
162
 
163
 
164
+ @patch("ankigen.exporters.export_cards_to_apkg")
165
  def test_export_dataframe_to_apkg_success(mock_apkg):
166
  df = pd.DataFrame({"Question": ["Q1"], "Answer": ["A1"], "Card_Type": ["Basic"]})
167
  export_dataframe_to_apkg(df, output_path="test.apkg", deck_name="Test Deck")
tests/test_models.py CHANGED
@@ -1,6 +1,6 @@
1
  import pytest
2
  from pydantic import ValidationError
3
- from ankigen_core.models import (
4
  Step,
5
  Subtopics,
6
  Topics,
 
1
  import pytest
2
  from pydantic import ValidationError
3
+ from ankigen.models import (
4
  Step,
5
  Subtopics,
6
  Topics,
tests/test_ui_logic.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import pandas as pd
3
+ from ankigen.models import Card, CardFront, CardBack
4
+ from ankigen.ui_logic import (
5
+ cards_to_dataframe,
6
+ dataframe_to_cards,
7
+ update_mode_visibility,
8
+ )
9
+
10
+
11
+ def test_cards_to_dataframe_empty():
12
+ """Test cards_to_dataframe with an empty list of cards."""
13
+ df = cards_to_dataframe([])
14
+ assert isinstance(df, pd.DataFrame)
15
+ assert df.empty
16
+ assert list(df.columns) == [
17
+ "ID",
18
+ "Topic",
19
+ "Front",
20
+ "Back",
21
+ "Tags",
22
+ "Card Type",
23
+ "Explanation",
24
+ "Example",
25
+ "Source_URL",
26
+ ]
27
+
28
+
29
+ def test_cards_to_dataframe_single():
30
+ """Test cards_to_dataframe with a single card."""
31
+ card = Card(
32
+ front=CardFront(question="What is Python?"),
33
+ back=CardBack(answer="A programming language", explanation="E", example="Ex"),
34
+ metadata={
35
+ "topic": "Programming",
36
+ "tags": ["python", "coding"],
37
+ "source_url": "http://python.org",
38
+ },
39
+ card_type="Basic",
40
+ )
41
+ df = cards_to_dataframe([card])
42
+ assert len(df) == 1
43
+ assert df.iloc[0]["ID"] == 1
44
+ assert df.iloc[0]["Topic"] == "Programming"
45
+ assert df.iloc[0]["Front"] == "What is Python?"
46
+ assert df.iloc[0]["Back"] == "A programming language"
47
+ assert df.iloc[0]["Tags"] == "python, coding"
48
+ assert df.iloc[0]["Card Type"] == "Basic"
49
+ assert df.iloc[0]["Source_URL"] == "http://python.org"
50
+
51
+
52
+ def test_cards_to_dataframe_multiple():
53
+ """Test cards_to_dataframe with multiple cards having varying metadata/tags."""
54
+ cards = [
55
+ Card(
56
+ front=CardFront(question="Q1"),
57
+ back=CardBack(answer="A1", explanation="E1", example="Ex1"),
58
+ metadata={"topic": "T1", "tags": ["tag1"]},
59
+ ),
60
+ Card(
61
+ front=CardFront(question="Q2"),
62
+ back=CardBack(answer="A2", explanation="E2", example="Ex2"),
63
+ metadata={"tags": ["tag2", "tag3"]},
64
+ ),
65
+ Card(
66
+ front=CardFront(question="Q3"),
67
+ back=CardBack(answer="A3", explanation="E3", example="Ex3"),
68
+ metadata={},
69
+ ),
70
+ ]
71
+ df = cards_to_dataframe(cards)
72
+ assert len(df) == 3
73
+ assert df.iloc[0]["Topic"] == "T1"
74
+ assert df.iloc[0]["Tags"] == "tag1"
75
+ assert df.iloc[1]["Topic"] == "N/A"
76
+ assert df.iloc[1]["Tags"] == "tag2, tag3"
77
+ assert df.iloc[2]["Topic"] == "N/A"
78
+ assert df.iloc[2]["Tags"] == ""
79
+
80
+
81
+ def test_cards_to_dataframe_no_metadata():
82
+ """Test cards_to_dataframe with cards that have no metadata."""
83
+ card = Card(
84
+ front=CardFront(question="Q"),
85
+ back=CardBack(answer="A", explanation="E", example="Ex"),
86
+ metadata=None,
87
+ )
88
+ df = cards_to_dataframe([card])
89
+ assert len(df) == 1
90
+ assert df.iloc[0]["Topic"] == "N/A"
91
+ assert df.iloc[0]["Tags"] == ""
92
+ assert df.iloc[0]["Source_URL"] == ""
93
+
94
+
95
+ def test_dataframe_to_cards_empty():
96
+ """Test dataframe_to_cards with empty dataframe and/or empty cards."""
97
+ # Both empty
98
+ assert dataframe_to_cards(pd.DataFrame(), []) == []
99
+
100
+ # Empty DF, non-empty cards
101
+ original_cards = [
102
+ Card(
103
+ front=CardFront(question="Q"),
104
+ back=CardBack(answer="A", explanation="E", example="Ex"),
105
+ )
106
+ ]
107
+ assert dataframe_to_cards(pd.DataFrame(), original_cards) == []
108
+
109
+
110
+ def test_dataframe_to_cards_round_trip():
111
+ """Test a normal round-trip from cards to dataframe and back with edits."""
112
+ original_cards = [
113
+ Card(
114
+ front=CardFront(question="Original Q"),
115
+ back=CardBack(answer="Original A", explanation="E", example="Ex"),
116
+ metadata={"topic": "Original T", "tags": ["tag1"]},
117
+ )
118
+ ]
119
+ df = cards_to_dataframe(original_cards)
120
+
121
+ # Edit the dataframe
122
+ df.at[0, "Front"] = "Updated Q"
123
+ df.at[0, "Back"] = "Updated A"
124
+ df.at[0, "Tags"] = "tag1, tag2"
125
+ df.at[0, "Topic"] = "Updated T"
126
+ df.at[0, "Card Type"] = "Cloze"
127
+
128
+ updated_cards = dataframe_to_cards(df, original_cards)
129
+
130
+ assert len(updated_cards) == 1
131
+ assert updated_cards[0].front.question == "Updated Q"
132
+ assert updated_cards[0].back.answer == "Updated A"
133
+ assert updated_cards[0].metadata["tags"] == ["tag1", "tag2"]
134
+ assert updated_cards[0].metadata["topic"] == "Updated T"
135
+ assert updated_cards[0].card_type == "Cloze"
136
+
137
+
138
+ def test_dataframe_to_cards_out_of_bounds_id(mocker):
139
+ """Test handling of IDs that are out of bounds for the original cards list."""
140
+ mock_logger = mocker.patch("ankigen.ui_logic.logger")
141
+ original_cards = [
142
+ Card(
143
+ front=CardFront(question="Q1"),
144
+ back=CardBack(answer="A1", explanation="E1", example="Ex1"),
145
+ )
146
+ ]
147
+
148
+ # ID 2 is out of bounds (only 1 card exists)
149
+ df = pd.DataFrame({"ID": [2], "Front": ["Q2"]})
150
+
151
+ result = dataframe_to_cards(df, original_cards)
152
+ assert result == []
153
+ mock_logger.warning.assert_called_with(
154
+ "Card ID 2 from DataFrame is out of bounds for original_cards list."
155
+ )
156
+
157
+
158
+ def test_dataframe_to_cards_error_handling(mocker):
159
+ """Test handling of rows that cause errors during processing."""
160
+ mock_logger = mocker.patch("ankigen.ui_logic.logger")
161
+ original_cards = [
162
+ Card(
163
+ front=CardFront(question="Q1"),
164
+ back=CardBack(answer="A1", explanation="E1", example="Ex1"),
165
+ )
166
+ ]
167
+
168
+ # Case 1: Missing ID column (KeyError)
169
+ # Desired behavior: the function should handle this gracefully, log an error,
170
+ # and not raise an UnboundLocalError.
171
+ df_missing_id = pd.DataFrame({"Front": ["Q1"]})
172
+
173
+ result = dataframe_to_cards(df_missing_id, original_cards)
174
+
175
+ # Expect that no cards are returned for rows missing a valid ID
176
+ assert result == []
177
+ mock_logger.error.assert_called()
178
+
179
+
180
+ def test_dataframe_to_cards_error_handling_recovery(mocker):
181
+ """Test that it recovers gracefully if an error occurs after ID is parsed."""
182
+ mock_logger = mocker.patch("ankigen.ui_logic.logger")
183
+
184
+ # Patch the class method instead of instance because Pydantic blocks instance patching of methods
185
+ mocker.patch(
186
+ "ankigen.models.CardFront.copy", side_effect=AttributeError("Mock Error")
187
+ )
188
+
189
+ card = Card(
190
+ front=CardFront(question="Q1"),
191
+ back=CardBack(answer="A1", explanation="E1", example="Ex1"),
192
+ )
193
+ original_cards = [card]
194
+ df = pd.DataFrame({"ID": [1], "Front": ["New Q"]})
195
+
196
+ result = dataframe_to_cards(df, original_cards)
197
+
198
+ # It should catch the error, log it, and use the original card
199
+ assert len(result) == 1
200
+ assert result[0] is card
201
+ assert mock_logger.error.call_count >= 1
202
+
203
+
204
+ def test_update_mode_visibility(mocker):
205
+ """Verify update_mode_visibility returns the expected 5-tuple of gr.update() calls."""
206
+ # Mock gr.update to return its arguments for easy verification
207
+ mocker.patch("gradio.update", side_effect=lambda **kwargs: kwargs)
208
+
209
+ result = update_mode_visibility("subject", "Biology")
210
+ assert isinstance(result, tuple)
211
+ assert len(result) == 5
212
+
213
+ # Verify each update call
214
+ assert result[0] == {"visible": True}
215
+ assert result[1] == {"visible": True}
216
+ assert result[2] == {"value": "Biology"}
217
+
218
+ # result[3] should have a value that is an empty DataFrame with specific columns
219
+ assert isinstance(result[3]["value"], pd.DataFrame)
220
+ assert list(result[3]["value"].columns) == [
221
+ "Index",
222
+ "Topic",
223
+ "Card_Type",
224
+ "Question",
225
+ "Answer",
226
+ "Explanation",
227
+ "Example",
228
+ "Prerequisites",
229
+ "Learning_Outcomes",
230
+ "Difficulty",
231
+ ]
232
+
233
+ # result[4] is total_cards_html
234
+ assert "total-cards-count" in result[4]["value"]
235
+ assert result[4]["visible"] is False
tests/test_utils.py CHANGED
@@ -1,7 +1,7 @@
1
  import pytest
2
  import hashlib
3
  from unittest.mock import MagicMock
4
- from ankigen_core.utils import (
5
  ResponseCache,
6
  RateLimiter,
7
  strip_html_tags,
 
1
  import pytest
2
  import hashlib
3
  from unittest.mock import MagicMock
4
+ from ankigen.utils import (
5
  ResponseCache,
6
  RateLimiter,
7
  strip_html_tags,