diff --git a/.github/workflows/deploy_sphinx.yaml b/.github/workflows/deploy_sphinx.yaml index aec883b..04e29f5 100644 --- a/.github/workflows/deploy_sphinx.yaml +++ b/.github/workflows/deploy_sphinx.yaml @@ -44,5 +44,3 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/build/html - publish_branch: gh-pages - keep_files: false # remove old files diff --git a/.github/workflows/ossf_scorecard.yml b/.github/workflows/ossf_scorecard.yml index 58a9012..5e95ebd 100644 --- a/.github/workflows/ossf_scorecard.yml +++ b/.github/workflows/ossf_scorecard.yml @@ -3,7 +3,7 @@ on: push: branches: - main - pull_request: + pull_request_target: branches: - main workflow_dispatch: diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..4be9750 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,28 @@ +# Examples + +## Running Locally: One-click Install + +In this directory, there is an `install.bat` and `install.sh` file. These files will install the required dependencies and run the program, including python 3.10.12 (or higher), pip, and mastermind-ai. + +- If you're using a windows machine, download and double click to run `install.bat`. +- Otherwise, download and run `install.sh` in your terminal by: + + ```bash + cd "" + chmod +x install.sh + ./install.sh + ``` + + Replace `` with the actual path to the directory containing `install.sh`, or ignore it if you already opened your terminal in the directory. + +## Running Remotely: Google Colab + +- Open [this link](https://colab.research.google.com/github/FlysonBot/Mastermind/blob/main/examples/mastermind_in_colab.ipynb) to open the program in Google Colab. + +## Update the Local Version + +To update the local version, simply run the installation script again. + +## Uninstall Local Version + +There is an `uninstall.bat` and `uninstall.sh` file in this directory. These files will uninstall mastermind-ai, but will not uninstall the Python and the other dependencies. diff --git a/examples/install.bat b/examples/install.bat new file mode 100644 index 0000000..4a946e8 --- /dev/null +++ b/examples/install.bat @@ -0,0 +1,63 @@ +@echo off +setlocal + +:check_python +rem Check for Python installation +python --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo Python is not installed. + set /p install_python="Do you want to install Python 3.10.12? (y/n): " + if /i "%install_python%"=="y" ( + call :install_python + ) else ( + echo Exiting. + exit /b + ) +) else ( + for /f "tokens=2 delims=." %%a in ('python --version') do ( + if %%a lss 10 ( + echo Python version is less than 3.10. + set /p update_python="Do you want to update to Python 3.10.12? (y/n): " + if /i "%update_python%"=="y" ( + call :install_python + ) else ( + echo Exiting. + exit /b + ) + ) else ( + call :upgrade_pip + ) + ) +) + +:install_python +echo Installing Python 3.10.12 using winget... +start /wait winget install Python.Python.3.10 --silent +goto :upgrade_pip + +:upgrade_pip +rem Upgrade pip +python -m pip install --upgrade pip +goto :check_mastermind_ai + +:check_mastermind_ai +rem Check if mastermind-ai is installed +pip show mastermind-ai >nul 2>&1 +if %errorlevel% neq 0 ( + echo mastermind-ai is not installed. Installing... + pip install mastermind-ai +) else ( + echo mastermind-ai is already installed. + python -m pip list --outdated | findstr mastermind-ai >nul + if %errorlevel% == 0 ( + set /p update_package="An update for mastermind-ai is available. Do you want to update it? (y/n): " + if /i "%update_package%"=="y" ( + pip install --upgrade mastermind-ai + ) + ) +) + +rem Clear the screen and run mastermind +cls +mastermind +endlocal diff --git a/examples/install.sh b/examples/install.sh new file mode 100755 index 0000000..65dd191 --- /dev/null +++ b/examples/install.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +check_python() { + # Check for Python installation + if ! command -v python3 &>/dev/null; then + echo "Python is not installed." + read -p "Do you want to install Python 3.10.12? (y/n): " install_python + if [[ "$install_python" == "y" ]]; then + install_python + else + echo "Exiting." + exit 1 + fi + else + PYTHON_VERSION=$(python3 --version | grep -oP '\d+\.\d+') + if (( $(echo "$PYTHON_VERSION < 3.10" | bc -l) )); then + echo "Python version is less than 3.10." + read -p "Do you want to update to Python 3.10.12? (y/n): " update_python + if [[ "$update_python" == "y" ]]; then + install_python + else + echo "Exiting." + exit 1 + fi + fi + fi +} + +install_python() { + echo "Installing Python 3.10.12..." + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + sudo apt update + sudo apt install -y python3.10 python3-pip + elif [[ "$OSTYPE" == "darwin"* ]]; then + brew install python@3.10 + else + echo "Unsupported OS for automatic Python installation." + exit 1 + fi +} + +upgrade_pip() { + # Upgrade pip + python3 -m pip install --upgrade pip +} + +check_mastermind_ai() { + # Check if mastermind-ai is installed + if ! pip show mastermind-ai &>/dev/null; then + echo "mastermind-ai is not installed. Installing..." + pip install mastermind-ai + else + echo "mastermind-ai is already installed." + if pip list --outdated | grep mastermind-ai &>/dev/null; then + read -p "An update for mastermind-ai is available. Do you want to update it? (y/n): " update_package + if [[ "$update_package" == "y" ]]; then + pip install --upgrade mastermind-ai + fi + fi + fi +} + +# Main execution +check_python +upgrade_pip # Call to upgrade pip +check_mastermind_ai # Call to check mastermind-ai installation + +# Clear the screen and run mastermind +clear +mastermind diff --git a/examples/uninstall.bat b/examples/uninstall.bat new file mode 100644 index 0000000..903726e --- /dev/null +++ b/examples/uninstall.bat @@ -0,0 +1 @@ +pip uninstall mastermind-ai diff --git a/examples/uninstall.sh b/examples/uninstall.sh new file mode 100644 index 0000000..e773de2 --- /dev/null +++ b/examples/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pip uninstall mastermind-ai diff --git a/src/mastermind/main/main.py b/src/mastermind/main/main.py index 0f52af0..d585b8c 100644 --- a/src/mastermind/main/main.py +++ b/src/mastermind/main/main.py @@ -15,11 +15,6 @@ class MainUI: """Class to handle the user menu interface.""" - def __new__(cls): - if not hasattr(cls, "instance"): - cls.instance = super(MainUI, cls).__new__(cls) - return cls.instance - def main_menu(self) -> bool: """ Display the main menu and handle user input. @@ -83,7 +78,7 @@ def run(self): while self.main_menu(): pass # keep calling self.main_menu() until it return False print("Thank you for playing!") - userdata._save_data() + userdata.save_data() def main(): diff --git a/src/mastermind/ui/menu/data_menu.py b/src/mastermind/ui/menu/data_menu.py index 5a0290e..ae99d1f 100644 --- a/src/mastermind/ui/menu/data_menu.py +++ b/src/mastermind/ui/menu/data_menu.py @@ -1,10 +1,10 @@ -from abc import abstractmethod +from abc import ABC, abstractmethod from typing import Any, Optional from mastermind.ui.menu.base_menu import BaseMenu -class DataDisplayMenu(BaseMenu): +class DataDisplayMenu(BaseMenu, ABC): """ An abstract base class for menus that display data. @@ -19,7 +19,7 @@ def _print_content(self) -> None: if data is not None: self._render_data(data) else: - print(self._empty_message()) + print(self._empty_message) @abstractmethod def _fetch_data(self) -> Optional[Any]: diff --git a/src/mastermind/ui/menu/game_history_menu.py b/src/mastermind/ui/menu/game_history_menu.py index 84fa502..48e0aa7 100644 --- a/src/mastermind/ui/menu/game_history_menu.py +++ b/src/mastermind/ui/menu/game_history_menu.py @@ -14,6 +14,7 @@ class GameHistoryMenu(DataDisplayMenu): name = "Game History" width = 25 + _empty_message = "No game history found." def _fetch_data(self) -> Optional[pd.DataFrame]: """ @@ -27,12 +28,6 @@ def _render_data(self, data: pd.DataFrame) -> None: """ render_dataframe(data) - def _empty_message(self) -> str: - """ - Returns the message to display when there is no game history. - """ - return "No game history found." - def display(self) -> None: """ Displays the game history menu and waits for user input to continue. diff --git a/tests/main/test_game_controller.py b/tests/main/test_game_controller.py new file mode 100644 index 0000000..ea0448a --- /dev/null +++ b/tests/main/test_game_controller.py @@ -0,0 +1,105 @@ +import unittest +from io import StringIO +from unittest.mock import call, create_autospec, patch + +from mastermind.game.game import Game +from mastermind.main.game_controller import GameController +from mastermind.storage.user_data import UserDataManager + + +class TestGameController(unittest.TestCase): + """Unit tests for the GameController class""" + + @patch("builtins.input", side_effect=["6", "4", "10", "q"]) + @patch("mastermind.main.game_controller.GameHistoryManager.save_game") + def test_start_new_game_classic(self, mock_save_game, mock_input): + """Test starting a new Classic game""" + with patch("sys.stdout", new=StringIO()): + GameController.start_new_game("HvAI") + + mock_input.assert_has_calls( + [ + call("\nEnter the number of colors (2-10): "), + call("\nEnter the number of dots (2-10): "), + call("\nEnter the maximum number of attempts: "), + ] + ) + mock_save_game.assert_called_once() + + @patch("builtins.input", side_effect=["8", "5", "15", "q"]) + @patch("mastermind.main.game_controller.GameHistoryManager.save_game") + def test_start_new_game_custom(self, mock_save_game, mock_input): + """Test starting a new Custom game""" + with patch("sys.stdout", new=StringIO()): + GameController.start_new_game("HvAI") + + mock_input.assert_has_calls( + [ + call("\nEnter the number of colors (2-10): "), + call("\nEnter the number of dots (2-10): "), + call("\nEnter the maximum number of attempts: "), + ] + ) + mock_save_game.assert_called_once() + + def test_resume_game_discard(self): + """Test resuming a game and discarding it""" + game_mock = create_autospec(Game, instance=True) + game_mock.resume_game.return_value = "d" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = [ + { + "game": game_mock, + "game_mode": "HvH", + "number_of_dots": 4, + "number_of_colors": 6, + "amount_attempted": 8, + "amount_allowed": 10, + "win_status": None, + "guesses": ["1234", "4561", "2312"], + "feedback": [(4, 0), (3, 1), (2, 2)], + } + ] + + with patch( + "mastermind.main.game_controller.userdata", + new=mock_user_data_manager, + ): + GameController.resume_game(0) + + self.assertEqual(mock_user_data_manager.saved_games, []) + + @patch("builtins.input", side_effect=["q"]) + def test_resume_game_update_saved(self, mock_input): + """Test resuming a game and updating the saved game""" + game = Game(6, 4, 10, "HvAI") + game._state.game_started = True + game._player_logic.initialize_players() + + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = [ + { + "game": game, + "game_mode": "HvAI", + "number_of_dots": 4, + "number_of_colors": 6, + "amount_attempted": 3, + "amount_allowed": 10, + "win_status": None, + "guesses": ["1234", "4561", "2312"], + "feedback": [(4, 0), (3, 1), (2, 2)], + } + ] + + with patch( + "mastermind.main.game_controller.userdata", new=mock_user_data_manager + ): + with patch("sys.stdout", new=StringIO()): + GameController.resume_game(0) + + self.assertEqual(len(mock_user_data_manager.saved_games), 1) + self.assertIsNone(mock_user_data_manager.saved_games[0]["win_status"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/main/test_game_history.py b/tests/main/test_game_history.py new file mode 100644 index 0000000..cd22e8e --- /dev/null +++ b/tests/main/test_game_history.py @@ -0,0 +1,114 @@ +import unittest +from unittest.mock import create_autospec, patch + +import pandas as pd + +from mastermind.game.game import Game +from mastermind.main.game_history import GameHistoryManager, game_list_to_pandas +from mastermind.storage.user_data import UserDataManager + + +class TestGameHistoryManager(unittest.TestCase): + """Unit tests for the GameHistoryManager class""" + + def setUp(self): + self.sample_games = [ + { + "game_mode": "HvH", + "number_of_dots": 4, + "number_of_colors": 6, + "amount_attempted": 8, + "amount_allowed": 10, + "win_status": True, + "guesses": ["1234", "4561", "2312"], + "feedback": [(4, 0), (3, 1), (2, 2)], + }, + { + "game_mode": "HvAI", + "number_of_dots": 5, + "number_of_colors": 8, + "amount_attempted": 12, + "amount_allowed": 15, + "win_status": False, + "guesses": ["12345", "45612", "34567"], + "feedback": [(3, 2), (2, 3), (0, 5)], + }, + { + "game_mode": "AIvH", + "number_of_dots": 4, + "number_of_colors": 6, + "amount_attempted": 5, + "amount_allowed": 10, + "win_status": None, + "guesses": ["1234", "4561"], + "feedback": [(4, 0), (3, 1)], + }, + ] + + def test_save_game(self): + """Test the save_game method""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = [] + + with patch("mastermind.main.game_history.userdata", new=mock_user_data_manager): + self._set_up_game_and_test_saved(mock_user_data_manager) + + def test_save_game_with_empty_list(self): + """Test the save_game method when the saved_games list is empty""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = None + + with patch("mastermind.main.game_history.userdata", new=mock_user_data_manager): + self._set_up_game_and_test_saved(mock_user_data_manager) + + def _set_up_game_and_test_saved(self, mock_user_data_manager): + game = Game(4, 6, 10, "HvH") + game_metadata = GameHistoryManager.generate_meta_data(game) + GameHistoryManager.save_game(game) + self.assertIn(game_metadata, mock_user_data_manager.saved_games) + + def test_retrieve_game_history_with_no_games(self): + """Test the retrieve_game_history method when there are no games""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = None + + with patch("mastermind.main.game_storage.userdata", new=mock_user_data_manager): + game_history = GameHistoryManager.retrieve_game_history() + self.assertIsNone(game_history) + + def test_retrieve_continuable_games_with_no_games(self): + """Test the retrieve_continuable_games method when there are no games""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = None + + with patch("mastermind.main.game_storage.userdata", new=mock_user_data_manager): + continuable_games = GameHistoryManager.retrieve_continuable_games() + self.assertIsNone(continuable_games) + + @patch("mastermind.main.game_storage.retrieve_stored_games", return_value=None) + def test_game_list_to_pandas_with_no_games(self, mock_retrieve_stored_games): + """Test the game_list_to_pandas function when there are no games""" + dataframe = game_list_to_pandas([]) + self.assertIsNone(dataframe) + + def test_game_list_to_pandas(self): + """Test the game_list_to_pandas function""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = self.sample_games + + with patch("mastermind.main.game_history.userdata", new=mock_user_data_manager): + dataframe = game_list_to_pandas(self.sample_games) + self.assertIsInstance(dataframe, pd.DataFrame) + self.assertEqual(len(dataframe), 3) + self.assertListEqual( + list(dataframe.columns), ["Mode", "Dimension", "Attempts"] + ) + self.assertListEqual(list(dataframe["Mode"]), ["HvH", "HvAI", "AIvH"]) + self.assertListEqual(list(dataframe["Dimension"]), ["6x4", "8x5", "6x4"]) + self.assertListEqual( + list(dataframe["Attempts"]), ["W 8/10", "L 12/15", " 5/10"] + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/main/test_game_storage.py b/tests/main/test_game_storage.py new file mode 100644 index 0000000..1a58d9b --- /dev/null +++ b/tests/main/test_game_storage.py @@ -0,0 +1,70 @@ +import unittest +from unittest.mock import create_autospec, patch + +from mastermind.main.game_storage import ( + list_continuable_games, + list_continuable_games_index, + retrieve_stored_games, +) +from mastermind.storage.user_data import UserDataManager + + +class TestMastermindStorage(unittest.TestCase): + """Unit tests for the mastermind.storage module""" + + def setUp(self): + self.sample_games = [ + {"game_id": 1, "win_status": None}, + {"game_id": 2, "win_status": True}, + {"game_id": 3, "win_status": False}, + {"game_id": 4, "win_status": None}, + ] # Note: For testing only! Actual data doesn't looks like this! + + def test_retrieve_stored_games(self): + """Test the retrieve_stored_games function""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = self.sample_games + + with patch("mastermind.main.game_storage.userdata", new=mock_user_data_manager): + games = retrieve_stored_games() + self.assertEqual(games, self.sample_games) + + def test_retrieve_empty_games(self): + """Test the retrieve_stored_games function""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = [] + + with patch("mastermind.main.game_storage.userdata", new=mock_user_data_manager): + games = retrieve_stored_games() + self.assertEqual(games, []) + + def test_retrieve_none_games(self): + """Test the retrieve_stored_games function with None saved_games""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.saved_games = None + + with patch("mastermind.main.game_storage.userdata", new=mock_user_data_manager): + games = retrieve_stored_games() + self.assertEqual(games, []) + + def test_list_continuable_games_index(self): + """Test the list_continuable_games_index function""" + continuable_indexes = list_continuable_games_index(self.sample_games) + self.assertEqual(continuable_indexes, [0, 3]) + + continuable_indexes = list_continuable_games_index([]) + self.assertEqual(continuable_indexes, []) + + def test_list_continuable_games(self): + """Test the list_continuable_games function""" + continuable_games = list_continuable_games(self.sample_games) + self.assertEqual( + continuable_games, [self.sample_games[0], self.sample_games[3]] + ) + + continuable_games = list_continuable_games([]) + self.assertEqual(continuable_games, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/main/test_main.py b/tests/main/test_main.py index e69de29..120b2b1 100644 --- a/tests/main/test_main.py +++ b/tests/main/test_main.py @@ -0,0 +1,134 @@ +import unittest +from unittest.mock import MagicMock, call, create_autospec, patch + +from mastermind.main.main import MainUI +from mastermind.storage.user_data import UserDataManager + + +class TestMainUI(unittest.TestCase): + """Unit tests for the MainUI class""" + + @patch("mastermind.ui.menu.MainMenu.get_option", return_value="Start New Game") + @patch("mastermind.main.main.MainUI.new_game_menu", return_value=True) + def test_main_menu_start_new_game(self, mock_new_game_menu, mock_get_option): + """Test the main menu when user chooses to start a new game""" + main_ui = MainUI() + self.assertTrue(main_ui.main_menu()) + mock_get_option.assert_called_once() + mock_new_game_menu.assert_called_once() + + @patch("mastermind.ui.menu.MainMenu.get_option", return_value="Load Saved Game") + @patch("mastermind.main.main.MainUI.saved_game_menu", return_value=True) + def test_main_menu_load_saved_game(self, mock_saved_game_menu, mock_get_option): + """Test the main menu when user chooses to load a saved game""" + main_ui = MainUI() + self.assertTrue(main_ui.main_menu()) + mock_get_option.assert_called_once() + mock_saved_game_menu.assert_called_once() + + @patch("mastermind.ui.menu.MainMenu.get_option", return_value="Game History") + @patch("mastermind.ui.menu.GameHistoryMenu.display") + def test_main_menu_game_history(self, mock_display, mock_get_option): + """Test the main menu when user chooses to view game history""" + main_ui = MainUI() + self.assertTrue(main_ui.main_menu()) + mock_get_option.assert_called_once() + mock_display.assert_called_once() + + @patch("mastermind.ui.menu.MainMenu.get_option", return_value="Save and Exit") + def test_main_menu_save_and_exit(self, mock_get_option): + """Test the main menu when user chooses to save and exit""" + main_ui = MainUI() + self.assertFalse(main_ui.main_menu()) + mock_get_option.assert_called_once() + + @patch( + "mastermind.ui.menu.NewGameMenu.get_option", return_value="You vs Someone Else" + ) + @patch("mastermind.main.game_controller.GameController.start_new_game") + def test_new_game_menu_human_vs_human(self, mock_start_new_game, mock_get_option): + """Test the new game menu when user chooses to play against another human""" + main_ui = MainUI() + self.assertTrue(main_ui.new_game_menu()) + mock_get_option.assert_called_once() + mock_start_new_game.assert_called_once_with("HvH") + + @patch("mastermind.ui.menu.NewGameMenu.get_option", return_value="You vs AI") + @patch("mastermind.main.game_controller.GameController.start_new_game") + def test_new_game_menu_human_vs_ai(self, mock_start_new_game, mock_get_option): + """Test the new game menu when user chooses to play against the AI""" + main_ui = MainUI() + self.assertTrue(main_ui.new_game_menu()) + mock_get_option.assert_called_once() + mock_start_new_game.assert_called_once_with("HvAI") + + @patch("mastermind.ui.menu.NewGameMenu.get_option", return_value="AI vs You") + @patch("builtins.print") + def test_new_game_menu_ai_vs_human(self, mock_print, mock_get_option): + """Test the new game menu when user chooses the AI vs Human mode (not implemented)""" + main_ui = MainUI() + self.assertTrue(main_ui.new_game_menu()) + mock_get_option.assert_called_once() + mock_print.assert_called_once_with("This feature is not implemented yet.") + + @patch( + "mastermind.ui.menu.NewGameMenu.get_option", return_value="Return to Main Menu" + ) + def test_new_game_menu_return_to_main(self, mock_get_option): + """Test the new game menu when user chooses to return to the main menu""" + main_ui = MainUI() + self.assertFalse(main_ui.new_game_menu()) + mock_get_option.assert_called_once() + + @patch("mastermind.ui.menu.NewGameMenu.get_option", return_value="Invalid Choice") + def test_new_game_menu_invalid_choice(self, mock_get_option): + """Test the new game menu when user makes an invalid choice""" + main_ui = MainUI() + with self.assertRaises(AssertionError): + main_ui.new_game_menu() + mock_get_option.assert_called_once() + + @patch("mastermind.ui.menu.ResumeGameMenu.get_option", return_value=0) + def test_saved_game_menu_return_to_main(self, mock_get_option): + """Test the saved game menu when user chooses to return to the main menu""" + main_ui = MainUI() + self.assertFalse(main_ui.saved_game_menu()) + mock_get_option.assert_called_once() + + @patch( + "mastermind.main.main.list_continuable_games_index", + return_value=[0], + ) + @patch("mastermind.main.game_controller.GameController.resume_game") + @patch("mastermind.ui.menu.ResumeGameMenu.get_option", return_value=1) + def test_saved_game_menu_resume_game( + self, mock_get_option, mock_resume_game, mock_list_continuable_games_index + ): + """Test the saved game menu when user chooses to resume a game""" + main_ui = MainUI() + self.assertTrue(main_ui.saved_game_menu()) + mock_get_option.assert_called_once() + mock_list_continuable_games_index.assert_called_once() + mock_resume_game.assert_called_once_with(0) # index 0 for option 1 + + @patch("builtins.print") + def test_run_main_loop(self, mock_print): + """Test the main loop of the MainUI""" + mock_user_data_manager = create_autospec(UserDataManager, instance=True) + mock_user_data_manager.save_data = MagicMock() + + with patch("mastermind.main.main.userdata", new=mock_user_data_manager): + with patch( + "mastermind.main.main.MainUI.main_menu", side_effect=[True, True, False] + ): + main_ui = MainUI() + main_ui.run() + + mock_print.assert_has_calls( + [call("Welcome to Mastermind!"), call("Thank you for playing!")] + ) + mock_user_data_manager.save_data.assert_called_once() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/ui/menu/test_data_menu.py b/tests/ui/menu/test_data_menu.py index b81480f..8aff41c 100644 --- a/tests/ui/menu/test_data_menu.py +++ b/tests/ui/menu/test_data_menu.py @@ -10,7 +10,7 @@ class ConcreteDataDisplayMenu(DataDisplayMenu): name = "Data Menu" _fetch_data = None _render_data = None - _empty_message = None + _empty_message = "No data available" def setUp(self): self.data_menu = self.ConcreteDataDisplayMenu() @@ -26,12 +26,7 @@ def test_print_content_with_data(self, mock_render_data, mock_fetch_data): @patch.object(ConcreteDataDisplayMenu, "_fetch_data", return_value=None) @patch.object(ConcreteDataDisplayMenu, "_render_data") - @patch.object( - ConcreteDataDisplayMenu, "_empty_message", return_value="No data available" - ) - def test_print_content_without_data( - self, mock_empty_message, mock_render_data, mock_fetch_data - ): + def test_print_content_without_data(self, mock_render_data, mock_fetch_data): with patch("builtins.print") as mock_print: self.data_menu._print_content() mock_fetch_data.assert_called()