Skip to content

Commit

Permalink
begin with documentation of fixtures and pytest setup
Browse files Browse the repository at this point in the history
  • Loading branch information
rodja committed Jul 29, 2024
1 parent 71cdb1e commit c37170d
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 26 deletions.
16 changes: 13 additions & 3 deletions nicegui/testing/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ async def open(self, path: str, *, clear_forward_history: bool = True) -> None:
self.activate()

def activate(self) -> Self:
"""Activate the user for interaction."""
"""Activate the user for interaction.
This can be used if you have multiple users and want to switch between them."""
if self.current_user:
self.current_user.deactivate()
self.current_user = self
Expand All @@ -62,7 +64,9 @@ def activate(self) -> Self:
return self

def deactivate(self, *_) -> None:
"""Deactivate the user."""
"""Deactivate the user.
This can be used if you have multiple users and want to switch between them."""
assert self.client
self.client.__exit__()
self.current_user = None
Expand Down Expand Up @@ -93,7 +97,13 @@ async def should_see(self,
content: Union[str, list[str], None] = None,
retries: int = 3,
) -> None:
"""Assert that the page contains an input with the given value."""
"""Assert that the page contains an input with the given value.
Note that there is no scrolling in the user simulation -- the entire page is always *visible*.
Due to asynchronous execution, sometimes the expected elements only appear after a short delay.
By default `should_see` makes three attempts to find the element before failing. This can be adjusted with the `retries` parameter.
"""
assert self.client
for _ in range(retries):
with self.client:
Expand Down
3 changes: 2 additions & 1 deletion website/documentation/content/doc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .api import demo, extra_column, get_page, intro, redirects, reference, registry, text, title, ui
from .api import demo, extra_column, get_page, intro, redirects, reference, registry, text, title, ui, pytest

__all__ = [
'demo',
Expand All @@ -11,4 +11,5 @@
'ui',
'get_page',
'extra_column',
'pytest',
]
28 changes: 28 additions & 0 deletions website/documentation/content/doc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,34 @@ def decorator(function: Callable) -> Callable:
return decorator


def pytest(*args, **kwargs) -> Callable[[Callable], Callable]:
"""Add a pytest demo section to the current documentation page."""
if len(args) == 2:
title_, description = args
is_markdown = True
else:
obj = args[0]
doc = obj.__doc__
if isinstance(obj, type) and not doc:
doc = obj.__init__.__doc__ # type: ignore
title_, description = doc.split('\n', 1)
title_ = title_.rstrip('.')
is_markdown = False

description = remove_indentation(description)
page = _get_current_page()

def decorator(function: Callable) -> Callable:
page.parts.append(DocumentationPart(
title=title_,
description=description,
description_format='md' if is_markdown else 'rst',
demo=Demo(function=function, lazy=kwargs.get('lazy', True), tab=kwargs.get('tab'), raw=True),
))
return function
return decorator


def ui(function: Callable) -> Callable:
"""Add arbitrary UI to the current documentation page."""
_get_current_page().parts.append(DocumentationPart(ui=function))
Expand Down
1 change: 1 addition & 0 deletions website/documentation/content/doc/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Demo:
function: Callable
lazy: bool = True
tab: Optional[Union[str, Callable]] = None
raw: bool = False


@dataclass(**KWONLY_SLOTS)
Expand Down
23 changes: 22 additions & 1 deletion website/documentation/content/element_filter_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ def main_demo() -> None:
with ui.card():
ui.button('button A')
ui.label('label A')

with ui.card().mark('important'):
ui.button('button B')
ui.label('label B')
Expand Down Expand Up @@ -38,4 +37,26 @@ def text_element() -> None:
ui.label(', '.join(b.text for b in ElementFilter(kind=TextElement, local_scope=True)))


@doc.demo('Markers', '''
Markers are a simple way to tag elements with a string which can be queried by an ElementFilter.
''')
def marker_demo() -> None:
from nicegui import ElementFilter

with ui.card().mark('red'):
ui.label('label A')
with ui.card().mark('strong'):
ui.label('label B')
with ui.card().mark('red strong'):
ui.label('label C')

# ElementFilter(marker='red').classes('bg-red-200')
# ElementFilter(marker='strong').classes('text-bold')
# ElementFilter(marker='red strong').classes('bg-red-600 text-white')
# END OF DEMO
ElementFilter(marker='red', local_scope=True).classes('bg-red-200')
ElementFilter(marker='strong', local_scope=True).classes('text-bold')
ElementFilter(marker='red strong', local_scope=True).classes('bg-red-600 text-white')


doc.reference(ElementFilter)
17 changes: 16 additions & 1 deletion website/documentation/content/overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
section_action_events,
section_audiovisual_elements,
section_binding_properties,
section_configuration_deployment,
section_controls,
section_data_elements,
section_page_layout,
section_pages_routing,
section_styling_appearance,
section_configuration_deployment,
section_text_elements,
section_testing,
)

doc.title('*NiceGUI* Documentation', 'Reference, Demos and more')
Expand Down Expand Up @@ -84,6 +85,17 @@
Out of the box, NiceGUI provides everything you need to make modern, stylish, responsive user interfaces.
''')

doc.text('Testing', '''
NiceGUI provides a comprehensive testing framework based on [pytest](https://docs.pytest.org/) which allows you to automate the testing of your user interface.
You can utilize the `screen` fixture which starts a real (headless) browser to interact with your application.
This is great if you have browser specific behavior to test.
But most of the time, NiceGUIs newly introduced `user` fixture is more suited:
It only simulates the user interaction on a Python level and hence is blazing fast.
That way the classical [test pyramid](https://martinfowler.com/bliki/TestPyramid.html) where UI tests are considered slow and expensive does not apply anymore.
This can have a huge impact on your development speed, quality and confidence.
''')

tiles = [
(section_text_elements, '''
Elements like `ui.label`, `ui.markdown`, `ui.restructured_text` and `ui.html` can be used to display text and other content.
Expand Down Expand Up @@ -115,6 +127,9 @@
(section_configuration_deployment, '''
Whether you want to run your app locally or on a server, native or in a browser, we got you covered.
'''),
(section_testing, '''
Write automated UI tests which run in a browser (slow) or fully simulated in Python (fast).
'''),
]


Expand Down
11 changes: 11 additions & 0 deletions website/documentation/content/screen_documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from nicegui.testing import Screen
from . import doc

doc.text('Screen Fixture', '''
The `screen` fixture starts a real(headless) browser to interact with your application.
This is only necessary if you have browser specific behavior to test.
NiceGUI itself is thoroughly tested with this fixture to ensure each component works as expected.
So only use it if you have to.
''')

doc.reference(Screen)
12 changes: 12 additions & 0 deletions website/documentation/content/section_testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from . import (
doc,
test_setup_documentation,
user_documentation,
screen_documentation,
)

doc.title('*Testing*')

doc.intro(test_setup_documentation)
doc.intro(user_documentation)
doc.intro(screen_documentation)
Loading

0 comments on commit c37170d

Please sign in to comment.