Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/lexicards-ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,27 @@ jobs:
name: Run Tests with Pytest
runs-on: ubuntu-latest
needs: [flake8, black, isort, bandit]
if: false
env:
PYTHONPATH: ${{ github.workspace }}/src

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: |
pip install -r requirements.txt
pip install pytest
- run: python -m pytest tests --maxfail=5 --disable-warnings -q

- name: Run Audio Tests
run: pytest tests/audio/test_audio_service.py

- name: Run Data Services Tests
run: pytest tests/data/test_data_service.py

- name: Run Manager Tests
run: pytest tests/manager/test_manager.py

build:
runs-on: windows-latest
needs: [pytest]
steps:
- uses: actions/checkout@v3

Expand Down
16 changes: 5 additions & 11 deletions lexicards/controllers/lexical_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,19 @@ def start_first_word(self) -> None:
def handle_known_word(self) -> None:
"""
Handle the 'Known' button click.

Displays the meaning of the current word for 2 seconds,
then generates a new random word. Marks the word as known.
Mark the current word as known and update storage.
"""
self._display_meaning()
self.ui.run_after(2000, self.handle_next_word)
self.manager.mark_as_known()

def handle_unknown_word(self) -> None:
"""
Handle the 'Unknown' button click.

Displays the meaning of the current word for 2 seconds,
then generates a new random word. Marks the word as unknown.
Mark the current word as unknown and update storage.
"""
self._display_meaning()
self.ui.run_after(2000, self.handle_next_word)
self.manager.mark_as_unknown()

def handle_next_word(self) -> None:
"""Generate and display the next random word."""
"""Display the next random word and show its meaning after three seconds."""
try:
word, meaning = self.manager.get_random_word()
self.current_word = word
Expand All @@ -70,6 +62,8 @@ def handle_next_word(self) -> None:
self.ui.update_title(self.manager.foreign_language)
self.ui.update_word_display(word)

self.ui.run_after(3000, self._display_meaning)

except ValueError:
self.ui.update_word_display("No data loaded.")

Expand Down
4 changes: 2 additions & 2 deletions lexicards/interfaces/controller/i_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def handle_unknown_word(self):

@abstractmethod
def handle_next_word(self):
"""Called when the user clicks the 'Next' button.
Moves to the next random word, regardless of known/unknown state."""
"""Called when the user clicks the 'next' button.
Display the next random word and show its meaning after three seconds."""
pass

@abstractmethod
Expand Down
Empty file added tests/__init__.py
Empty file.
Empty file added tests/audio/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions tests/audio/test_audio_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest
from unittest.mock import MagicMock

from lexicards.controllers.lexical_controller import LexicalController
from lexicards.interfaces.audio.i_audio_servcie import IAudioService


class TestAudioServices(unittest.TestCase):
def test_handle_speak_calls_audio(self):
audio_mock: IAudioService = MagicMock(spec=IAudioService)
ui_mock = MagicMock()
manager_mock = MagicMock()
manager_mock.foreign_language = "ja"

controller = LexicalController(ui_mock, manager_mock, audio_mock)
controller.current_word = "こんにちは"

controller.handle_speak()

# Assert that speak_async was called on the interface
audio_mock.speak_async.assert_called_once_with("こんにちは", "ja")


if __name__ == "__main__":
unittest.main()
Empty file added tests/data/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions tests/data/test_data_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
from unittest.mock import MagicMock

from lexicards.interfaces.data.i_data_retriever import IDataRetriever
from lexicards.interfaces.data.i_data_saver import IDataSaver


class TestDataServices(unittest.TestCase):
def test_data_retriever_load_data(self):
mock_retriever = MagicMock(spec=IDataRetriever)
mock_retriever.load_data.return_value = [["こんにちは", "Hello"], ["川", "River"]]

data = mock_retriever.load_data()

self.assertIsInstance(data, list)
self.assertEqual(data[0], ["こんにちは", "Hello"])

def test_data_saver_save_data(self):
mock_saver = MagicMock(spec=IDataSaver)
mock_saver.save_data("川", "River")

mock_saver.save_data.assert_called_once_with("川", "River")


if __name__ == "__main__":
unittest.main()
Empty file added tests/manager/__init__.py
Empty file.
79 changes: 79 additions & 0 deletions tests/manager/test_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import unittest
from unittest.mock import MagicMock

from lexicards.data.data_remover import DataRemoverFactory
from lexicards.data.data_retriever import DataRetrieverFactory
from lexicards.data.data_saver import DataSaverFactory
from lexicards.interfaces.data.i_data_retriever import IDataRetriever
from lexicards.manager.word_manager import WordManager


class TestWordManager(unittest.TestCase):
def setUp(self):

self.mock_loader_factory = MagicMock(spec=DataRetrieverFactory)
self.mock_retriever = MagicMock(spec=IDataRetriever)
self.mock_loader_factory.create_data_retriever.return_value = (
self.mock_retriever
)

self.mock_saver_factory = MagicMock(spec=DataSaverFactory)
self.mock_saver = MagicMock()
self.mock_saver_factory.create_data_saver.return_value = self.mock_saver

self.mock_remover_factory = MagicMock(spec=DataRemoverFactory)
self.mock_remover = MagicMock()
self.mock_remover_factory.create_data_remover.return_value = self.mock_remover

self.mock_retriever.load_data.return_value = [
["Japanese", "English"],
["川", "River"],
["山", "Mountain"],
]

self.manager = WordManager(
loader_factory=self.mock_loader_factory,
saver_factory=self.mock_saver_factory,
data_remover=self.mock_remover_factory,
source_file="mock.csv",
)

def test_get_random_word(self):
current_word, current_meaning = self.manager.get_random_word()
self.assertIn([current_word, current_meaning], self.manager.words)

def test_mark_as_known(self):
self.manager.current_word = "川"
self.manager.current_meaning = "River"

self.manager.mark_as_known()
self.mock_remover.mark_for_removal.assert_called_once_with(
self.manager.current_word
)

def test_mark_as_unknown_called(self):
self.manager.current_word = "川"
self.manager.current_meaning = "River"
self.manager.unknown_file = "mock_unknown.csv"

self.manager.mark_as_unknown()

self.manager.saver_factory.create_data_saver(self.manager.unknown_file)
self.mock_saver.save_data.assert_called_once_with("川", "River")

def test_properties(self):
self.assertEqual(self.manager.foreign_language, "Japanese")
self.assertEqual(self.manager.native_language, "English")

def test_load_words(self):
words = self.manager.words # Already loaded in __init__
self.assertEqual(words[1], ["川", "River"])
self.assertEqual(words[2], ["山", "Mountain"])
self.mock_loader_factory.create_data_retriever.assert_called_once_with(
"mock.csv"
)
self.mock_retriever.load_data.assert_called_once()


if __name__ == "__main__":
unittest.main()