mirror of
https://github.com/Benexl/FastAnime.git
synced 2026-01-02 07:50:04 -08:00
356 lines
14 KiB
Python
356 lines
14 KiB
Python
"""
|
|
Tests for the results menu functionality.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from fastanime.cli.interactive.menus.results import results
|
|
from fastanime.cli.interactive.state import ControlFlow, State, MediaApiState
|
|
from fastanime.libs.api.types import MediaItem, MediaSearchResult, PageInfo
|
|
|
|
|
|
class TestResultsMenu:
|
|
"""Test cases for the results menu."""
|
|
|
|
def test_results_menu_no_search_results(self, mock_context, empty_state):
|
|
"""Test results menu with no search results."""
|
|
# State with no search results
|
|
state_no_results = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(search_results=None)
|
|
)
|
|
|
|
result = results(mock_context, state_no_results)
|
|
|
|
# Should go back when no results
|
|
assert result == ControlFlow.BACK
|
|
|
|
def test_results_menu_empty_media_list(self, mock_context, empty_state):
|
|
"""Test results menu with empty media list."""
|
|
# State with empty search results
|
|
empty_search_results = MediaSearchResult(
|
|
media=[],
|
|
page_info=PageInfo(
|
|
total=0,
|
|
per_page=15,
|
|
current_page=1,
|
|
has_next_page=False
|
|
)
|
|
)
|
|
state_empty_results = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(search_results=empty_search_results)
|
|
)
|
|
|
|
result = results(mock_context, state_empty_results)
|
|
|
|
# Should go back when no media found
|
|
assert result == ControlFlow.BACK
|
|
|
|
def test_results_menu_display_anime_list(self, mock_context, state_with_media_api):
|
|
"""Test results menu displays anime list correctly."""
|
|
mock_context.selector.choose.return_value = "Back"
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Should go back when "Back" is selected
|
|
assert result == ControlFlow.BACK
|
|
|
|
# Verify selector was called with anime choices
|
|
mock_context.selector.choose.assert_called_once()
|
|
call_args = mock_context.selector.choose.call_args
|
|
choices = call_args[1]['choices']
|
|
|
|
# Should contain Back option
|
|
assert "Back" in choices
|
|
# Should contain formatted anime titles
|
|
assert len(choices) >= 2 # At least anime + Back
|
|
|
|
def test_results_menu_select_anime(self, mock_context, state_with_media_api, sample_media_item):
|
|
"""Test selecting an anime from results."""
|
|
# Mock the format function to return a predictable title
|
|
with patch('fastanime.cli.interactive.menus.results._format_anime_choice') as mock_format:
|
|
mock_format.return_value = "Test Anime"
|
|
mock_context.selector.choose.return_value = "Test Anime"
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Should transition to MEDIA_ACTIONS state
|
|
assert isinstance(result, State)
|
|
assert result.menu_name == "MEDIA_ACTIONS"
|
|
assert result.media_api.anime == sample_media_item
|
|
|
|
def test_results_menu_pagination_next_page(self, mock_context, empty_state):
|
|
"""Test pagination - next page navigation."""
|
|
# Create search results with next page available
|
|
search_results = MediaSearchResult(
|
|
media=[
|
|
MediaItem(
|
|
id=1,
|
|
title={"english": "Test Anime", "romaji": "Test Anime"},
|
|
status="FINISHED",
|
|
episodes=12
|
|
)
|
|
],
|
|
page_info=PageInfo(
|
|
total=30,
|
|
per_page=15,
|
|
current_page=1,
|
|
has_next_page=True
|
|
)
|
|
)
|
|
|
|
state_with_pagination = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(search_results=search_results)
|
|
)
|
|
|
|
mock_context.selector.choose.return_value = "Next Page (Page 2)"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results._handle_pagination') as mock_pagination:
|
|
mock_pagination.return_value = State(menu_name="RESULTS")
|
|
|
|
result = results(mock_context, state_with_pagination)
|
|
|
|
# Should call pagination handler
|
|
mock_pagination.assert_called_once_with(mock_context, state_with_pagination, 1)
|
|
|
|
def test_results_menu_pagination_previous_page(self, mock_context, empty_state):
|
|
"""Test pagination - previous page navigation."""
|
|
# Create search results on page 2
|
|
search_results = MediaSearchResult(
|
|
media=[
|
|
MediaItem(
|
|
id=1,
|
|
title={"english": "Test Anime", "romaji": "Test Anime"},
|
|
status="FINISHED",
|
|
episodes=12
|
|
)
|
|
],
|
|
page_info=PageInfo(
|
|
total=30,
|
|
per_page=15,
|
|
current_page=2,
|
|
has_next_page=False
|
|
)
|
|
)
|
|
|
|
state_with_pagination = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(search_results=search_results)
|
|
)
|
|
|
|
mock_context.selector.choose.return_value = "Previous Page (Page 1)"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results._handle_pagination') as mock_pagination:
|
|
mock_pagination.return_value = State(menu_name="RESULTS")
|
|
|
|
result = results(mock_context, state_with_pagination)
|
|
|
|
# Should call pagination handler
|
|
mock_pagination.assert_called_once_with(mock_context, state_with_pagination, -1)
|
|
|
|
def test_results_menu_no_choice_made(self, mock_context, state_with_media_api):
|
|
"""Test results menu when no choice is made (exit)."""
|
|
mock_context.selector.choose.return_value = None
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
assert result == ControlFlow.EXIT
|
|
|
|
def test_results_menu_with_preview(self, mock_context, state_with_media_api):
|
|
"""Test results menu with preview enabled."""
|
|
mock_context.config.general.preview = "text"
|
|
mock_context.selector.choose.return_value = "Back"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.get_anime_preview') as mock_preview:
|
|
mock_preview.return_value = "preview_command"
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Should call preview function when preview is enabled
|
|
mock_preview.assert_called_once()
|
|
|
|
# Verify preview was passed to selector
|
|
call_args = mock_context.selector.choose.call_args
|
|
assert call_args[1]['preview'] == "preview_command"
|
|
|
|
def test_results_menu_no_preview(self, mock_context, state_with_media_api):
|
|
"""Test results menu with preview disabled."""
|
|
mock_context.config.general.preview = "none"
|
|
mock_context.selector.choose.return_value = "Back"
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Verify no preview was passed to selector
|
|
call_args = mock_context.selector.choose.call_args
|
|
assert call_args[1]['preview'] is None
|
|
|
|
def test_results_menu_auth_status_display(self, mock_context, state_with_media_api):
|
|
"""Test that authentication status is displayed in header."""
|
|
mock_context.selector.choose.return_value = "Back"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.get_auth_status_indicator') as mock_auth:
|
|
mock_auth.return_value = ("🟢 Authenticated", Mock())
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Should call auth status function
|
|
mock_auth.assert_called_once_with(mock_context.media_api, mock_context.config.general.icons)
|
|
|
|
# Verify header contains auth status
|
|
call_args = mock_context.selector.choose.call_args
|
|
header = call_args[1]['header']
|
|
assert "🟢 Authenticated" in header
|
|
|
|
def test_results_menu_pagination_info_in_header(self, mock_context, empty_state):
|
|
"""Test that pagination info is displayed in header."""
|
|
search_results = MediaSearchResult(
|
|
media=[
|
|
MediaItem(
|
|
id=1,
|
|
title={"english": "Test Anime", "romaji": "Test Anime"},
|
|
status="FINISHED",
|
|
episodes=12
|
|
)
|
|
],
|
|
page_info=PageInfo(
|
|
total=30,
|
|
per_page=15,
|
|
current_page=2,
|
|
has_next_page=True
|
|
)
|
|
)
|
|
|
|
state_with_pagination = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(search_results=search_results)
|
|
)
|
|
|
|
mock_context.selector.choose.return_value = "Back"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.get_auth_status_indicator') as mock_auth:
|
|
mock_auth.return_value = ("Auth Status", Mock())
|
|
|
|
result = results(mock_context, state_with_pagination)
|
|
|
|
# Verify header contains pagination info
|
|
call_args = mock_context.selector.choose.call_args
|
|
header = call_args[1]['header']
|
|
assert "Page 2" in header
|
|
assert "~2" in header # Total pages
|
|
|
|
def test_results_menu_unknown_choice_fallback(self, mock_context, state_with_media_api):
|
|
"""Test results menu with unknown choice returns CONTINUE."""
|
|
mock_context.selector.choose.return_value = "Unknown Choice"
|
|
|
|
with patch('fastanime.cli.interactive.menus.results._format_anime_choice') as mock_format:
|
|
mock_format.return_value = "Test Anime"
|
|
|
|
result = results(mock_context, state_with_media_api)
|
|
|
|
# Should return CONTINUE for unknown choices
|
|
assert result == ControlFlow.CONTINUE
|
|
|
|
|
|
class TestResultsMenuHelperFunctions:
|
|
"""Test the helper functions in results menu."""
|
|
|
|
def test_format_anime_choice(self, mock_config, sample_media_item):
|
|
"""Test formatting anime choice for display."""
|
|
from fastanime.cli.interactive.menus.results import _format_anime_choice
|
|
|
|
# Test with English title preferred
|
|
mock_config.anilist.preferred_language = "english"
|
|
result = _format_anime_choice(sample_media_item, mock_config)
|
|
|
|
assert "Test Anime" in result
|
|
assert "12" in result # Episode count
|
|
|
|
def test_format_anime_choice_romaji(self, mock_config, sample_media_item):
|
|
"""Test formatting anime choice with romaji preference."""
|
|
from fastanime.cli.interactive.menus.results import _format_anime_choice
|
|
|
|
# Test with Romaji title preferred
|
|
mock_config.anilist.preferred_language = "romaji"
|
|
result = _format_anime_choice(sample_media_item, mock_config)
|
|
|
|
assert "Test Anime" in result
|
|
|
|
def test_format_anime_choice_no_episodes(self, mock_config):
|
|
"""Test formatting anime choice with no episode count."""
|
|
from fastanime.cli.interactive.menus.results import _format_anime_choice
|
|
|
|
anime_no_episodes = MediaItem(
|
|
id=1,
|
|
title={"english": "Test Anime", "romaji": "Test Anime"},
|
|
status="FINISHED",
|
|
episodes=None
|
|
)
|
|
|
|
result = _format_anime_choice(anime_no_episodes, mock_config)
|
|
|
|
assert "Test Anime" in result
|
|
assert "?" in result # Unknown episode count
|
|
|
|
def test_handle_pagination_next_page(self, mock_context, state_with_media_api):
|
|
"""Test pagination handler for next page."""
|
|
from fastanime.cli.interactive.menus.results import _handle_pagination
|
|
|
|
# Mock API search parameters from state
|
|
mock_context.media_api.search_media.return_value = MediaSearchResult(
|
|
media=[], page_info=PageInfo(total=0, per_page=15, current_page=2, has_next_page=False)
|
|
)
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.execute_with_feedback') as mock_execute:
|
|
mock_execute.return_value = (True, mock_context.media_api.search_media.return_value)
|
|
|
|
result = _handle_pagination(mock_context, state_with_media_api, 1)
|
|
|
|
# Should return new state with updated results
|
|
assert isinstance(result, State)
|
|
assert result.menu_name == "RESULTS"
|
|
|
|
def test_handle_pagination_api_failure(self, mock_context, state_with_media_api):
|
|
"""Test pagination handler when API fails."""
|
|
from fastanime.cli.interactive.menus.results import _handle_pagination
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.execute_with_feedback') as mock_execute:
|
|
mock_execute.return_value = (False, None)
|
|
|
|
result = _handle_pagination(mock_context, state_with_media_api, 1)
|
|
|
|
# Should return CONTINUE on API failure
|
|
assert result == ControlFlow.CONTINUE
|
|
|
|
def test_handle_pagination_user_list_params(self, mock_context, empty_state):
|
|
"""Test pagination with user list parameters."""
|
|
from fastanime.cli.interactive.menus.results import _handle_pagination
|
|
from fastanime.libs.api.params import UserListParams
|
|
|
|
# State with user list params
|
|
state_with_user_list = State(
|
|
menu_name="RESULTS",
|
|
media_api=MediaApiState(
|
|
search_results=MediaSearchResult(
|
|
media=[],
|
|
page_info=PageInfo(total=0, per_page=15, current_page=1, has_next_page=False)
|
|
),
|
|
original_user_list_params=UserListParams(status="CURRENT", per_page=15)
|
|
)
|
|
)
|
|
|
|
mock_context.media_api.fetch_user_list.return_value = MediaSearchResult(
|
|
media=[], page_info=PageInfo(total=0, per_page=15, current_page=2, has_next_page=False)
|
|
)
|
|
|
|
with patch('fastanime.cli.interactive.menus.results.execute_with_feedback') as mock_execute:
|
|
mock_execute.return_value = (True, mock_context.media_api.fetch_user_list.return_value)
|
|
|
|
result = _handle_pagination(mock_context, state_with_user_list, 1)
|
|
|
|
# Should call fetch_user_list instead of search_media
|
|
assert isinstance(result, State)
|
|
assert result.menu_name == "RESULTS"
|