|
1 | 1 | import logging |
2 | 2 | import os |
| 3 | +from unittest.mock import AsyncMock, MagicMock, patch |
3 | 4 |
|
4 | 5 | import pytest |
5 | 6 |
|
6 | 7 | from reactpy import Ref, component, html, testing |
7 | 8 | from reactpy.logging import ROOT_LOGGER |
8 | | -from reactpy.testing.backend import _hotswap |
| 9 | +from reactpy.testing.backend import BackendFixture, _hotswap |
9 | 10 | from reactpy.testing.display import DisplayFixture |
10 | 11 | from tests.sample import SampleApp |
11 | 12 |
|
@@ -205,3 +206,80 @@ async def on_click(event): |
205 | 206 | await display.page.wait_for_selector("#hotswap-2") |
206 | 207 | await client_incr_button.click() |
207 | 208 | await display.page.wait_for_selector("#hotswap-3") |
| 209 | + |
| 210 | + |
| 211 | +@pytest.mark.asyncio |
| 212 | +async def test_backend_server_failure(): |
| 213 | + # We need to mock uvicorn.Server to fail starting |
| 214 | + with patch("uvicorn.Server") as mock_server_cls: |
| 215 | + mock_server = mock_server_cls.return_value |
| 216 | + mock_server.started = False |
| 217 | + mock_server.servers = [] |
| 218 | + mock_server.config.get_loop_factory = MagicMock() |
| 219 | + |
| 220 | + # Mock serve to just return (or sleep briefly then return) |
| 221 | + mock_server.serve = AsyncMock(return_value=None) |
| 222 | + |
| 223 | + backend = BackendFixture() |
| 224 | + |
| 225 | + # We need to speed up the loop |
| 226 | + with patch("asyncio.sleep", new_callable=AsyncMock): |
| 227 | + with pytest.raises(RuntimeError, match="Server failed to start"): |
| 228 | + await backend.__aenter__() |
| 229 | + |
| 230 | + |
| 231 | +@pytest.mark.asyncio |
| 232 | +async def test_display_fixture_headless_logic(): |
| 233 | + # Mock async_playwright to avoid launching real browser |
| 234 | + with patch("reactpy.testing.display.async_playwright") as mock_pw: |
| 235 | + mock_context_manager = mock_pw.return_value |
| 236 | + mock_playwright_instance = AsyncMock() |
| 237 | + mock_context_manager.__aenter__.return_value = mock_playwright_instance |
| 238 | + |
| 239 | + mock_browser = AsyncMock() |
| 240 | + mock_playwright_instance.chromium.launch.return_value = mock_browser |
| 241 | + |
| 242 | + mock_page = AsyncMock() |
| 243 | + # Configure synchronous methods on page |
| 244 | + mock_page.set_default_timeout = MagicMock() |
| 245 | + mock_page.on = MagicMock() |
| 246 | + |
| 247 | + mock_browser.new_page.return_value = mock_page |
| 248 | + |
| 249 | + # Case: headless=False, PLAYWRIGHT_HEADLESS='1' |
| 250 | + with patch.dict(os.environ, {"PLAYWRIGHT_HEADLESS": "1"}): |
| 251 | + async with DisplayFixture(headless=False): |
| 252 | + pass |
| 253 | + # Check that launch was called with headless=True |
| 254 | + mock_playwright_instance.chromium.launch.assert_called_with(headless=True) |
| 255 | + |
| 256 | + |
| 257 | +@pytest.mark.asyncio |
| 258 | +async def test_display_fixture_internal_backend(): |
| 259 | + # This covers line 87: await self.backend_exit_stack.aclose() |
| 260 | + # when backend is internal (default) |
| 261 | + |
| 262 | + with patch("reactpy.testing.display.async_playwright") as mock_pw: |
| 263 | + mock_context_manager = mock_pw.return_value |
| 264 | + mock_playwright_instance = AsyncMock() |
| 265 | + mock_context_manager.__aenter__.return_value = mock_playwright_instance |
| 266 | + |
| 267 | + mock_browser = AsyncMock() |
| 268 | + mock_playwright_instance.chromium.launch.return_value = mock_browser |
| 269 | + |
| 270 | + mock_page = AsyncMock() |
| 271 | + mock_page.set_default_timeout = MagicMock() |
| 272 | + mock_page.on = MagicMock() |
| 273 | + mock_browser.new_page.return_value = mock_page |
| 274 | + |
| 275 | + # We also need to mock BackendFixture to avoid starting real server |
| 276 | + with patch("reactpy.testing.display.BackendFixture") as mock_backend_cls: |
| 277 | + mock_backend = AsyncMock() |
| 278 | + mock_backend.mount = MagicMock() # mount is synchronous |
| 279 | + mock_backend_cls.return_value = mock_backend |
| 280 | + |
| 281 | + async with DisplayFixture() as display: |
| 282 | + assert not display.backend_is_external |
| 283 | + |
| 284 | + # Verify backend exit stack closed (implied if no error and backend.__aexit__ called) |
| 285 | + mock_backend.__aexit__.assert_called() |
0 commit comments