from nicegui import ui

from ..windows import bash_window, python_window
from . import doc

doc.text('Project Structure', '''
    The NiceGUI package provides a [pytest plugin](https://docs.pytest.org/en/stable/how-to/writing_plugins.html)
    which can be activated via `pytest_plugins = ['nicegui.testing.plugin']`.
    This makes specialized [fixtures](https://docs.pytest.org/en/stable/explanation/fixtures.html) available for testing your NiceGUI user interface.
    With the [`screen` fixture](/documentation/screen) you can run the tests through a headless browser (slow)
    and with the [`user` fixture](/documentation/user) fully simulated in Python (fast).
    If you only want one kind of test fixtures,
    you can also use the plugin `nicegui.testing.user_plugin` or `nicegui.testing.screen_plugin`.

    There are a multitude of ways to structure your project and tests.
    Here we only present two approaches which we found useful,
    one for [small apps and experiments](/documentation/project_structure#simple)
    and a [modular one for larger projects](/documentation/project_structure#modular).
    You can find more information in the [pytest documentation](https://docs.pytest.org/en/stable/contents.html).
''')

doc.text('Simple', '''
    For small apps and experiments you can put the tests in a separate file,
    as we do in the examples
    [Chat App](https://github.com/zauberzeug/nicegui/tree/main/examples/chat_app)
    [Todo List](https://github.com/zauberzeug/nicegui/tree/main/examples/todo_list/) and
    [Authentication](https://github.com/zauberzeug/nicegui/tree/main/examples/authentication).
    To properly re-initialize your `main.py` in the tests,
    you place an empty `__init__.py` file next to your code to make it a package
    and use the `module_under_test` marker to automatically reload your main file for each test.
    Also don't forget the `pytest.ini` file
    to enable the [`asyncio_mode = auto`](/documentation/user#async_execution) option for the user fixture
    and make sure you properly guard the `ui.run()` call in your `main.py`
    to prevent the server from starting during the tests:
''')


@doc.ui
def simple_project_code():
    with ui.row(wrap=False).classes('gap-4 items-stretch'):
        with python_window(classes='w-[400px]'):
            ui.markdown('''
                ```python
                from nicegui import ui

                def hello() -> None:
                    ui.notify('Hello World!')

                ui.button('Click me', on_click=hello)

                if __name__ in {'__main__', '__mp_main__'}:
                    ui.run()
                ```
            ''')

        with python_window(classes='w-[400px]', title='test_app.py'):
            ui.markdown('''
                ```python
                import pytest
                from nicegui import ui
                from nicegui.testing import User
                from . import main

                pytest_plugins = ['nicegui.testing.user_plugin']

                @pytest.mark.module_under_test(main)
                async def test_click(user: User) -> None:
                    await user.open('/')
                    await user.should_see('Click me')
                    user.find(ui.button).click()
                    await user.should_see('Hello World!')
                ```
            ''')


@doc.ui
def simple_project_bash():
    with bash_window(classes='max-w-[820px] w-full h-42'):
        ui.markdown('''
            ```bash
            $ ls
            __init__.py         main.py        test_app.py       pytest.ini

            $ pytest
            ==================== test session starts =====================
            test_app.py .                                     [100%]
            ===================== 1 passed in 0.51 s ======================
            ```
        ''')


doc.text('Modular', '''
    A more modular approach is to create a package for your code with an empty `__init__.py`
    and a separate `tests` folder for your tests.
    In your package a `startup.py` file can be used to register pages and do all necessary app initialization.
    The `main.py` at root level then only imports the startup routine and calls `ui.run()`.
    An empty `conftest.py` file in the root directory makes the package with its `startup` routine available to the tests.
    Also don't forget the `pytest.ini` file
    to enable the [`asyncio_mode = auto`](/documentation/user#async_execution) option for the user fixture.
''')


@doc.ui
def modular_project():
    with ui.row(wrap=False).classes('gap-4 items-stretch'):
        with python_window(classes='w-[400px]'):
            ui.markdown('''
                ```python
                from nicegui import ui, app
                from app.startup import startup

                app.on_startup(startup)

                ui.run()
                ```
            ''')

        with python_window(classes='w-[400px]', title='app/startup.py'):
            ui.markdown('''
                ```python
                from nicegui import ui

                def hello() -> None:
                    ui.notify('Hello World!')

                def startup() -> None:
                    @ui.page('/')
                    def index():
                        ui.button('Click me', on_click=hello)
                ```
            ''')

    with ui.row(wrap=False).classes('gap-4 items-stretch'):
        with python_window(classes='w-[400px]', title='tests/test_app.py'):
            ui.markdown('''
                ```python
                from nicegui import ui
                from nicegui.testing import User
                from app.startup import startup

                pytest_plugins = ['nicegui.testing.user_plugin']

                async def test_click(user: User) -> None:
                    startup()
                    await user.open('/')
                    await user.should_see('Click me')
                    user.find(ui.button).click()
                    await user.should_see('Hello World!')
                ```
                ''')

        with bash_window(classes='w-[400px]'):
            ui.markdown('''
                ```bash
                $ tree
                .
                ├── main.py
                ├── pytest.ini
                ├── app
                │   ├── __init__.py
                │   └── startup.py
                └── tests
                    ├── conftest.py
                    └── test_app.py
                ```
            ''')


doc.text('', '''
    You can also define your own fixtures in the `conftest.py` which call the `startup` routine.
    Pytest has some magic to automatically find and use this specialized fixture in your tests.
    This way you can keep your tests clean and simple.
    See the [pytests example](https://github.com/zauberzeug/nicegui/tree/main/examples/pytests)
    for a full demonstration of this setup.
''')


@doc.ui
def custom_user_fixture():
    with ui.row(wrap=False).classes('gap-4 items-stretch'):
        with python_window(classes='w-[400px]', title='tests/test_app.py'):
            ui.markdown('''
                ```python
                from nicegui import ui
                from nicegui.testing import User

                async def test_click(user: User) -> None:
                    await user.open('/')
                    await user.should_see('Click me')
                    user.find(ui.button).click()
                    await user.should_see('Hello World!')
                ```
            ''')

        with python_window(classes='w-[400px]', title='conftest.py'):
            ui.markdown('''
                ```python
                import pytest
                from nicegui.testing import User
                from app.startup import startup

                pytest_plugins = ['nicegui.testing.user_plugin']

                @pytest.fixture
                def user(user: User) -> User:
                    startup()
                    return user
                ```
            ''')