All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
636 lines
20 KiB
Python
636 lines
20 KiB
Python
"""
|
|
Tests for async_loading mixin module
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, MagicMock, patch, PropertyMock
|
|
|
|
|
|
class TestAsyncLoadingMixinInit:
|
|
"""Tests for AsyncLoadingMixin initialization"""
|
|
|
|
def test_init_async_loading_creates_cache(self):
|
|
"""Test that _init_async_loading creates image cache"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
with (
|
|
patch("pyPhotoAlbum.mixins.async_loading.ImageCache") as mock_cache,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncImageLoader") as mock_loader,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncPDFGenerator") as mock_pdf,
|
|
):
|
|
|
|
mock_loader_instance = Mock()
|
|
mock_loader.return_value = mock_loader_instance
|
|
mock_pdf_instance = Mock()
|
|
mock_pdf.return_value = mock_pdf_instance
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj._init_async_loading()
|
|
|
|
mock_cache.assert_called_once_with(max_memory_mb=512)
|
|
assert hasattr(obj, "image_cache")
|
|
|
|
def test_init_async_loading_creates_image_loader(self):
|
|
"""Test that _init_async_loading creates async image loader"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
with (
|
|
patch("pyPhotoAlbum.mixins.async_loading.ImageCache") as mock_cache,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncImageLoader") as mock_loader,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncPDFGenerator") as mock_pdf,
|
|
):
|
|
|
|
mock_loader_instance = Mock()
|
|
mock_loader.return_value = mock_loader_instance
|
|
mock_pdf_instance = Mock()
|
|
mock_pdf.return_value = mock_pdf_instance
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj._init_async_loading()
|
|
|
|
mock_loader.assert_called_once()
|
|
assert hasattr(obj, "async_image_loader")
|
|
mock_loader_instance.start.assert_called_once()
|
|
|
|
def test_init_async_loading_creates_pdf_generator(self):
|
|
"""Test that _init_async_loading creates async PDF generator"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
with (
|
|
patch("pyPhotoAlbum.mixins.async_loading.ImageCache") as mock_cache,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncImageLoader") as mock_loader,
|
|
patch("pyPhotoAlbum.mixins.async_loading.AsyncPDFGenerator") as mock_pdf,
|
|
):
|
|
|
|
mock_loader_instance = Mock()
|
|
mock_loader.return_value = mock_loader_instance
|
|
mock_pdf_instance = Mock()
|
|
mock_pdf.return_value = mock_pdf_instance
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj._init_async_loading()
|
|
|
|
mock_pdf.assert_called_once()
|
|
assert hasattr(obj, "async_pdf_generator")
|
|
mock_pdf_instance.start.assert_called_once()
|
|
|
|
|
|
class TestAsyncLoadingMixinCleanup:
|
|
"""Tests for AsyncLoadingMixin cleanup"""
|
|
|
|
def test_cleanup_stops_image_loader(self):
|
|
"""Test that _cleanup_async_loading stops image loader"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
obj.async_pdf_generator = Mock()
|
|
obj.image_cache = Mock()
|
|
|
|
obj._cleanup_async_loading()
|
|
|
|
obj.async_image_loader.stop.assert_called_once()
|
|
|
|
def test_cleanup_stops_pdf_generator(self):
|
|
"""Test that _cleanup_async_loading stops PDF generator"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
obj.async_pdf_generator = Mock()
|
|
obj.image_cache = Mock()
|
|
|
|
obj._cleanup_async_loading()
|
|
|
|
obj.async_pdf_generator.stop.assert_called_once()
|
|
|
|
def test_cleanup_clears_cache(self):
|
|
"""Test that _cleanup_async_loading clears image cache"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
obj.async_pdf_generator = Mock()
|
|
obj.image_cache = Mock()
|
|
|
|
obj._cleanup_async_loading()
|
|
|
|
obj.image_cache.clear.assert_called_once()
|
|
|
|
def test_cleanup_handles_missing_components(self):
|
|
"""Test that _cleanup_async_loading handles missing components gracefully"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
# Don't set any async components
|
|
|
|
# Should not raise
|
|
obj._cleanup_async_loading()
|
|
|
|
|
|
class TestOnImageLoaded:
|
|
"""Tests for _on_image_loaded callback"""
|
|
|
|
def test_on_image_loaded_calls_element_callback(self):
|
|
"""Test that _on_image_loaded calls element's callback"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def update(self):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
|
|
mock_image = Mock()
|
|
mock_user_data = Mock()
|
|
mock_user_data._on_async_image_loaded = Mock()
|
|
|
|
obj._on_image_loaded(Path("/test/image.jpg"), mock_image, mock_user_data)
|
|
|
|
mock_user_data._on_async_image_loaded.assert_called_once_with(mock_image)
|
|
|
|
def test_on_image_loaded_triggers_update(self):
|
|
"""Test that _on_image_loaded triggers widget update"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def __init__(self):
|
|
self.update_called = False
|
|
|
|
def update(self):
|
|
self.update_called = True
|
|
|
|
obj = TestClass()
|
|
|
|
obj._on_image_loaded(Path("/test/image.jpg"), Mock(), None)
|
|
|
|
assert obj.update_called
|
|
|
|
def test_on_image_loaded_handles_none_user_data(self):
|
|
"""Test that _on_image_loaded handles None user_data"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def update(self):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
|
|
# Should not raise
|
|
obj._on_image_loaded(Path("/test/image.jpg"), Mock(), None)
|
|
|
|
|
|
class TestOnImageLoadFailed:
|
|
"""Tests for _on_image_load_failed callback"""
|
|
|
|
def test_on_image_load_failed_calls_element_callback(self):
|
|
"""Test that _on_image_load_failed calls element's callback"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
|
|
mock_user_data = Mock()
|
|
mock_user_data._on_async_image_load_failed = Mock()
|
|
|
|
obj._on_image_load_failed(Path("/test/image.jpg"), "Error message", mock_user_data)
|
|
|
|
mock_user_data._on_async_image_load_failed.assert_called_once_with("Error message")
|
|
|
|
def test_on_image_load_failed_handles_none_user_data(self):
|
|
"""Test that _on_image_load_failed handles None user_data"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
|
|
# Should not raise
|
|
obj._on_image_load_failed(Path("/test/image.jpg"), "Error", None)
|
|
|
|
|
|
class TestOnPdfProgress:
|
|
"""Tests for _on_pdf_progress callback"""
|
|
|
|
def test_on_pdf_progress_updates_dialog(self):
|
|
"""Test that _on_pdf_progress updates progress dialog"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj._pdf_progress_dialog = Mock()
|
|
|
|
obj._on_pdf_progress(5, 10, "Processing page 5")
|
|
|
|
obj._pdf_progress_dialog.setValue.assert_called_once_with(5)
|
|
obj._pdf_progress_dialog.setLabelText.assert_called_once_with("Processing page 5")
|
|
|
|
def test_on_pdf_progress_handles_no_dialog(self):
|
|
"""Test that _on_pdf_progress handles missing dialog"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
# No _pdf_progress_dialog attribute
|
|
|
|
# Should not raise
|
|
obj._on_pdf_progress(5, 10, "Processing")
|
|
|
|
|
|
class TestOnPdfComplete:
|
|
"""Tests for _on_pdf_complete callback"""
|
|
|
|
def test_on_pdf_complete_closes_dialog(self):
|
|
"""Test that _on_pdf_complete closes progress dialog"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return Mock(spec=[])
|
|
|
|
obj = TestClass()
|
|
mock_dialog = Mock()
|
|
obj._pdf_progress_dialog = mock_dialog
|
|
|
|
obj._on_pdf_complete(True, [])
|
|
|
|
mock_dialog.close.assert_called_once()
|
|
assert obj._pdf_progress_dialog is None
|
|
|
|
def test_on_pdf_complete_shows_success_status(self):
|
|
"""Test that _on_pdf_complete shows success status"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
mock_main_window = Mock()
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return mock_main_window
|
|
|
|
obj = TestClass()
|
|
|
|
obj._on_pdf_complete(True, [])
|
|
|
|
mock_main_window.show_status.assert_called_once()
|
|
call_args = mock_main_window.show_status.call_args[0]
|
|
assert "successfully" in call_args[0]
|
|
|
|
def test_on_pdf_complete_shows_warnings(self):
|
|
"""Test that _on_pdf_complete shows warning count"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
mock_main_window = Mock()
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return mock_main_window
|
|
|
|
obj = TestClass()
|
|
|
|
obj._on_pdf_complete(True, ["warning1", "warning2"])
|
|
|
|
mock_main_window.show_status.assert_called_once()
|
|
call_args = mock_main_window.show_status.call_args[0]
|
|
assert "2 warnings" in call_args[0]
|
|
|
|
def test_on_pdf_complete_shows_failure_status(self):
|
|
"""Test that _on_pdf_complete shows failure status"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
mock_main_window = Mock()
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return mock_main_window
|
|
|
|
obj = TestClass()
|
|
|
|
obj._on_pdf_complete(False, [])
|
|
|
|
mock_main_window.show_status.assert_called_once()
|
|
call_args = mock_main_window.show_status.call_args[0]
|
|
assert "failed" in call_args[0]
|
|
|
|
|
|
class TestOnPdfFailed:
|
|
"""Tests for _on_pdf_failed callback"""
|
|
|
|
def test_on_pdf_failed_closes_dialog(self):
|
|
"""Test that _on_pdf_failed closes progress dialog"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return Mock(spec=[])
|
|
|
|
obj = TestClass()
|
|
mock_dialog = Mock()
|
|
obj._pdf_progress_dialog = mock_dialog
|
|
|
|
obj._on_pdf_failed("Error occurred")
|
|
|
|
mock_dialog.close.assert_called_once()
|
|
assert obj._pdf_progress_dialog is None
|
|
|
|
def test_on_pdf_failed_shows_error_status(self):
|
|
"""Test that _on_pdf_failed shows error status"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
mock_main_window = Mock()
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
def window(self):
|
|
return mock_main_window
|
|
|
|
obj = TestClass()
|
|
|
|
obj._on_pdf_failed("Something went wrong")
|
|
|
|
mock_main_window.show_status.assert_called_once()
|
|
call_args = mock_main_window.show_status.call_args[0]
|
|
assert "failed" in call_args[0]
|
|
assert "Something went wrong" in call_args[0]
|
|
|
|
|
|
class TestRequestImageLoad:
|
|
"""Tests for request_image_load method"""
|
|
|
|
def test_request_image_load_no_loader(self):
|
|
"""Test request_image_load when loader not initialized"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
mock_image_data = Mock()
|
|
|
|
# Should not raise
|
|
obj.request_image_load(mock_image_data)
|
|
|
|
def test_request_image_load_empty_path(self):
|
|
"""Test request_image_load with empty image path"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
|
|
mock_image_data = Mock()
|
|
mock_image_data.image_path = ""
|
|
|
|
obj.request_image_load(mock_image_data)
|
|
|
|
obj.async_image_loader.request_load.assert_not_called()
|
|
|
|
def test_request_image_load_non_assets_path_skipped(self):
|
|
"""Test request_image_load skips paths outside assets folder"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
|
|
mock_image_data = Mock()
|
|
mock_image_data.image_path = "/absolute/path/image.jpg"
|
|
|
|
obj.request_image_load(mock_image_data)
|
|
|
|
obj.async_image_loader.request_load.assert_not_called()
|
|
|
|
def test_request_image_load_path_not_resolved(self):
|
|
"""Test request_image_load when path resolution fails"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
|
|
mock_image_data = Mock()
|
|
mock_image_data.image_path = "assets/missing.jpg"
|
|
mock_image_data.resolve_image_path.return_value = None
|
|
|
|
obj.request_image_load(mock_image_data)
|
|
|
|
obj.async_image_loader.request_load.assert_not_called()
|
|
|
|
def test_request_image_load_success(self, tmp_path):
|
|
"""Test successful request_image_load"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin, LoadPriority
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
|
|
# Create actual file
|
|
asset_path = tmp_path / "assets" / "photo.jpg"
|
|
asset_path.parent.mkdir(parents=True)
|
|
asset_path.write_text("test")
|
|
|
|
mock_image_data = Mock()
|
|
mock_image_data.image_path = "assets/photo.jpg"
|
|
mock_image_data.resolve_image_path.return_value = str(asset_path)
|
|
|
|
obj.request_image_load(mock_image_data, priority=LoadPriority.HIGH)
|
|
|
|
obj.async_image_loader.request_load.assert_called_once()
|
|
call_kwargs = obj.async_image_loader.request_load.call_args[1]
|
|
assert call_kwargs["priority"] == LoadPriority.HIGH
|
|
assert call_kwargs["user_data"] == mock_image_data
|
|
|
|
|
|
class TestExportPdfAsync:
|
|
"""Tests for export_pdf_async method"""
|
|
|
|
def test_export_pdf_async_no_generator(self):
|
|
"""Test export_pdf_async when generator not initialized"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
mock_project = Mock()
|
|
|
|
result = obj.export_pdf_async(mock_project, "/output.pdf")
|
|
|
|
assert result is False
|
|
|
|
def test_export_pdf_async_creates_progress_dialog(self, qtbot):
|
|
"""Test export_pdf_async creates progress dialog"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
from PyQt6.QtWidgets import QWidget
|
|
|
|
class TestWidget(QWidget, AsyncLoadingMixin):
|
|
pass
|
|
|
|
widget = TestWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.async_pdf_generator = Mock()
|
|
widget.async_pdf_generator.export_pdf.return_value = True
|
|
|
|
mock_project = Mock()
|
|
mock_project.pages = [Mock(is_cover=False, is_double_spread=False)]
|
|
|
|
widget.export_pdf_async(mock_project, "/output.pdf")
|
|
|
|
assert hasattr(widget, "_pdf_progress_dialog")
|
|
assert widget._pdf_progress_dialog is not None
|
|
|
|
def test_export_pdf_async_calls_generator(self, qtbot):
|
|
"""Test export_pdf_async calls the PDF generator"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
from PyQt6.QtWidgets import QWidget
|
|
|
|
class TestWidget(QWidget, AsyncLoadingMixin):
|
|
pass
|
|
|
|
widget = TestWidget()
|
|
qtbot.addWidget(widget)
|
|
|
|
widget.async_pdf_generator = Mock()
|
|
widget.async_pdf_generator.export_pdf.return_value = True
|
|
|
|
mock_project = Mock()
|
|
mock_project.pages = []
|
|
|
|
result = widget.export_pdf_async(mock_project, "/output.pdf", export_dpi=150)
|
|
|
|
widget.async_pdf_generator.export_pdf.assert_called_once_with(mock_project, "/output.pdf", 150)
|
|
assert result is True
|
|
|
|
|
|
class TestOnPdfCancel:
|
|
"""Tests for _on_pdf_cancel callback"""
|
|
|
|
def test_on_pdf_cancel_cancels_export(self):
|
|
"""Test that _on_pdf_cancel cancels the export"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_pdf_generator = Mock()
|
|
|
|
obj._on_pdf_cancel()
|
|
|
|
obj.async_pdf_generator.cancel_export.assert_called_once()
|
|
|
|
def test_on_pdf_cancel_handles_no_generator(self):
|
|
"""Test that _on_pdf_cancel handles missing generator"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
# No async_pdf_generator
|
|
|
|
# Should not raise
|
|
obj._on_pdf_cancel()
|
|
|
|
|
|
class TestGetAsyncStats:
|
|
"""Tests for get_async_stats method"""
|
|
|
|
def test_get_async_stats_empty(self):
|
|
"""Test get_async_stats with no components initialized"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
stats = obj.get_async_stats()
|
|
|
|
assert stats == {}
|
|
|
|
def test_get_async_stats_with_loader(self):
|
|
"""Test get_async_stats includes loader stats"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
obj.async_image_loader.get_stats.return_value = {"loaded": 10}
|
|
|
|
stats = obj.get_async_stats()
|
|
|
|
assert "image_loader" in stats
|
|
assert stats["image_loader"]["loaded"] == 10
|
|
|
|
def test_get_async_stats_with_pdf_generator(self):
|
|
"""Test get_async_stats includes PDF generator stats"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_pdf_generator = Mock()
|
|
obj.async_pdf_generator.get_stats.return_value = {"exports": 5}
|
|
|
|
stats = obj.get_async_stats()
|
|
|
|
assert "pdf_generator" in stats
|
|
assert stats["pdf_generator"]["exports"] == 5
|
|
|
|
def test_get_async_stats_with_all_components(self):
|
|
"""Test get_async_stats includes all component stats"""
|
|
from pyPhotoAlbum.mixins.async_loading import AsyncLoadingMixin
|
|
|
|
class TestClass(AsyncLoadingMixin):
|
|
pass
|
|
|
|
obj = TestClass()
|
|
obj.async_image_loader = Mock()
|
|
obj.async_image_loader.get_stats.return_value = {"loaded": 10}
|
|
obj.async_pdf_generator = Mock()
|
|
obj.async_pdf_generator.get_stats.return_value = {"exports": 5}
|
|
|
|
stats = obj.get_async_stats()
|
|
|
|
assert "image_loader" in stats
|
|
assert "pdf_generator" in stats
|