summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/parser_unittest.py73
-rw-r--r--tests/scheduler_brutal_unittest.py88
-rw-r--r--tests/session_integrationtest.py47
-rw-r--r--tests/state_json_unittest.py14
4 files changed, 222 insertions, 0 deletions
diff --git a/tests/parser_unittest.py b/tests/parser_unittest.py
new file mode 100644
index 0000000..8076369
--- /dev/null
+++ b/tests/parser_unittest.py
@@ -0,0 +1,73 @@
+import pytest
+from pathlib import Path
+
+from flashcards import parser
+
+# Happy path
+def test_validFile(tmp_path):
+ file_contents = """
+
+FRONT
+
+
+Foo
+
+Bar
+
+BACK
+
+
+Fizz
+
+Buzz
+
+
+
+FRONT
+
+Another card
+
+BACK
+
+Another back
+
+
+ """
+ expected = {
+ ("Foo\n\nBar", "Fizz\n\nBuzz"),
+ ("Another card", "Another back")
+ }
+
+ path = tmp_path / "valid_file.fcard"
+ with open(path, "w") as f:
+ f.write(file_contents)
+
+ cards = parser.parseFile(path)
+
+ assert expected == set(cards.values())
+
+# Edge cases
+def test_emptyFile(tmp_path):
+ path = tmp_path / "empty.fcard"
+ with open(path, "w") as f:
+ f.write("")
+
+ cards = parser.parseFile(path)
+ assert cards == {}
+
+def checkException(tmp_path, file_contents):
+ path = tmp_path / "invalid_file.fcard"
+ with open(path, "w") as f:
+ f.write(file_contents)
+
+ with pytest.raises(Exception):
+ cards = parser.parseFile(path)
+
+def test_doesNotStartWithFront(tmp_path):
+ checkException(tmp_path, "BACK\noops")
+
+def test_frontTwiceInARow(tmp_path):
+ checkException(tmp_path, "FRONT\noops\nFRONT\nbad")
+
+def test_doesNotEndWithBack(tmp_path):
+ checkException(tmp_path, "FRONT\ntest\nBACK\ntest\nFRONT\noops")
diff --git a/tests/scheduler_brutal_unittest.py b/tests/scheduler_brutal_unittest.py
new file mode 100644
index 0000000..a87e5e9
--- /dev/null
+++ b/tests/scheduler_brutal_unittest.py
@@ -0,0 +1,88 @@
+import pytest
+
+from flashcards import scheduler_brutal
+from flashcards.scheduler_brutal import SchedulerBrutal as UUT
+from flashcards.card import Card
+
+# Force HISTORY_DEPTH to simplify testing
+scheduler_brutal.HISTORY_DEPTH = 3
+
+#--------------------------------------------------------------------------
+# Scheduling behaviour
+#--------------------------------------------------------------------------
+def test_scheduling():
+ cards = {str(id): Card("", "") for id in range(0, 10)}
+ state = {
+ "0": [1, 1, 1],
+ "1": [0, 0, 0],
+ "2": [0, 0, 1],
+ "3": [1, 0, 0],
+
+ "4": [None, None, 1 ],
+ "5": [None, 1, None],
+ "6": [1, None, None],
+ "7": [None, None, 0 ],
+ "8": [0, 0, None],
+ "9": [None, None, None],
+ }
+
+ expected_priority = ["9", "6", "5", "7", "8", "4", "1", "3", "2", "0"]
+
+ uut = UUT(cards, state)
+
+ for i in range(0, len(expected_priority)):
+ assert set(uut.practice(i + 1)) == set(expected_priority[0:i + 1])
+
+#--------------------------------------------------------------------------
+# State update
+#--------------------------------------------------------------------------
+def test_stateUpdate():
+ cards = {"0": Card("f", "b"), "1": Card("a", "b"), "2": Card("c", "d")}
+ state = {"0": [1, 0, 1], "1": [1, 0, 0], "2": [0, 0, 1]}
+
+ uut = UUT(cards, state)
+
+ # Unknown IDs in the result are silently ignored
+ result = {"0": 1, "1": 0, "3": 0}
+ expected_state = {"0": [0, 1, 1], "1": [0, 0, 0], "2": [0, 1, None]}
+
+ uut.update(result)
+
+ assert uut.getState() == expected_state
+
+#--------------------------------------------------------------------------
+# State corrections
+#--------------------------------------------------------------------------
+def test_stateWhenCardsChanged():
+ cards = {"0": Card("f", "b"), "1": Card("a", "b")}
+
+ initial_state = {"0": [1, 0, 1], "2": [0, 0, 0]}
+ expected_state = {"0": [1, 0, 1], "1": [None, None, None]}
+
+ uut = UUT(cards, initial_state)
+
+ assert uut.getState() == expected_state
+
+def test_stateWhenHistoryDepthIncreased():
+ scheduler_brutal.HISTORY_DEPTH = 5
+
+ cards = {"0": Card("f", "b"), "1": Card("a", "b"), "2": Card("new", "new")}
+
+ initial_state = {"0": [1, 0, 1], "1": [0, 0, 0]}
+ expected_state = {"0": [None, None, 1, 0, 1], "1": [None, None, 0, 0, 0], "2": [None] * 5}
+
+ uut = UUT(cards, initial_state)
+
+ assert uut.getState() == expected_state
+
+def test_stateWhenHistoryDepthDecreased():
+ scheduler_brutal.HISTORY_DEPTH = 1
+
+ cards = {"0": Card("f", "b"), "1": Card("a", "b"), "2": Card("new", "new")}
+
+ initial_state = {"0": [1, 0, 0], "1": [0, 0, 1]}
+ expected_state = {"0": [0], "1": [1], "2": [None]}
+
+ uut = UUT(cards, initial_state)
+
+ assert uut.getState() == expected_state
diff --git a/tests/session_integrationtest.py b/tests/session_integrationtest.py
new file mode 100644
index 0000000..ddabff9
--- /dev/null
+++ b/tests/session_integrationtest.py
@@ -0,0 +1,47 @@
+import pytest
+import json
+
+from flashcards.session import Session
+
+@pytest.fixture
+def cardFiles(tmp_path):
+ card_files = [
+ tmp_path / "test1.fcard",
+ tmp_path / "test2.fcard",
+ ]
+
+ for i, c in enumerate(card_files):
+ with open(c, "w") as f:
+ for j in range(0, 3):
+ f.write(f"FRONT\nFile {i}, card {j} front\nBACK\nback\n")
+
+ return card_files
+
+@pytest.fixture
+def stateFile(tmp_path):
+ return tmp_path / "state.json"
+
+def test_practiceSession(cardFiles, stateFile):
+ session = Session("brutal", cardFiles, stateFile)
+
+ c = 0
+ for card in session.practice(5):
+ c += 1
+
+ assert c == 5
+
+def test_testSession(cardFiles, stateFile):
+ session = Session("brutal", cardFiles, stateFile)
+
+ c = 0
+ for i, (card, correct) in enumerate(session.test(5)):
+ c += 1
+ correct(i % 2 == 0)
+
+ assert c == 5
+
+ with open(stateFile, "r") as f:
+ state = json.load(f)
+ latest_scores = [history[-1] for id, history in state.items()]
+ assert latest_scores.count(0) == 2
+ assert latest_scores.count(1) == 3
diff --git a/tests/state_json_unittest.py b/tests/state_json_unittest.py
new file mode 100644
index 0000000..03ea555
--- /dev/null
+++ b/tests/state_json_unittest.py
@@ -0,0 +1,14 @@
+import pytest
+
+from flashcards import state_json
+
+def test_saveAndLoad(tmp_path):
+ file = tmp_path / "test.json"
+ state = {"key": [10, 20, None], "another_key": "value"}
+
+ state_json.save(file, state)
+
+ assert state_json.load(file) == state
+
+def test_missingFile(tmp_path):
+ assert state_json.load(tmp_path / "missing.json") == {}