# Released under the MIT License. See LICENSE for details.
#
"""Routes for the doc-ui test domain."""
from __future__ import annotations # Docs-generation hack.
from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated, override
from efro.dataclassio import ioprepped, IOAttrs
import bacommon.docui.v2 as dui2
from bacommon.docui.routes import (
DocUIRoute,
DocUILocalActionBase,
DocUIState,
family_members,
)
if TYPE_CHECKING:
import bacommon.docui.v2
[docs]
class TestRoute(DocUIRoute):
"""Family class for doc-ui test routes."""
[docs]
@override
@classmethod
def get_route_types(cls) -> tuple[type[DocUIRoute], ...]:
return family_members(AnyTestRoute)
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# Test pages are dense showcases built for the full-size
# window.
return dui2.WindowLayout.LARGE
[docs]
@ioprepped
@dataclass
class Root(TestRoute, path='/'):
"""The root test page."""
#: Draw bounds and other debug bits.
debug: bool = False
#: Have the response include some client-effects.
test_effects: bool = False
#: Have the response include a local-action.
test_action: bool = False
[docs]
@ioprepped
@dataclass
class Test2(TestRoute, path='/test2'):
"""A second simple page."""
[docs]
@ioprepped
@dataclass
class Slow(TestRoute, path='/slow'):
"""A page that takes a while to load."""
[docs]
@ioprepped
@dataclass
class TimedActions(TestRoute, path='/timedactions'):
"""A page that counts down via timed-actions and then closes."""
val: int = 5
[docs]
@ioprepped
@dataclass
class DisplayItems(TestRoute, path='/displayitems'):
"""Display-item tests."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class TextImages(TestRoute, path='/textimages'):
"""Text-with-images tests."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class LiveTimes(TestRoute, path='/livetimes'):
"""Time-varying text (countdowns, elapsed times) that ticks itself."""
[docs]
@ioprepped
@dataclass
class TextWrapping(TestRoute, path='/textwrapping'):
"""Titles, subtitles and footnotes word-wrapped to their columns."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class Animation(TestRoute, path='/animation'):
"""Decorations animated by keyframe client-effects."""
[docs]
@ioprepped
@dataclass
class Depictions(TestRoute, path='/depictions'):
"""Depiction tests."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class Viewers(TestRoute, path='/viewers'):
"""Live viewer tests: good cases and bad data, one shown at a time.
The page's own controls switch cases, and the pane's style and
side (page state), in place.
"""
#: Which case the viewer pane shows (a test-page case name; unknown
#: names show the first).
case: str = ''
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
return dui2.WindowLayout.VIEWER
[docs]
@ioprepped
@dataclass
class Names(TestRoute, path='/names'):
"""Name depiction tests (basic and capsule forms)."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class NinePatch(TestRoute, path='/ninepatch'):
"""9-patch image tests (stretched and tile-fit, tinted)."""
debug: bool = False
[docs]
@ioprepped
@dataclass
class EmptyPage(TestRoute, path='/emptypage'):
"""A page with nothing on it."""
[docs]
@ioprepped
@dataclass
class BoundsTests(TestRoute, path='/boundstests'):
"""Button-style bounds tests."""
[docs]
class ControlRowKind(Enum):
"""Kinds of control row (for :class:`NavTest`)."""
CHECKBOX = 'checkbox'
TEXT_INPUT = 'text'
CHOICE = 'choice'
SLIDER = 'slider'
NUMBER = 'number'
COLOR = 'color'
BUTTON = 'button'
[docs]
@ioprepped
@dataclass
class NavTest(TestRoute, path='/navtest'):
"""A button row, then one control row of some kind as the last row.
For checking directional navigation on control rows: down from the
last row should reach the toolbars, left should reach the back
button, and right from a row's rightmost control should do nothing.
"""
kind: ControlRowKind = ControlRowKind.CHECKBOX
[docs]
@ioprepped
@dataclass
class Sections(TestRoute, path='/sections'):
"""Sections: headings, notes, backings and spacing between them."""
debug: bool = False
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# A column, like the settings pages sections mostly live in.
return dui2.WindowLayout.SMALL_TALLER
[docs]
@ioprepped
@dataclass
class WindowLayouts(TestRoute, path='/windowlayouts'):
"""Assorted rows for judging window layouts (browse to it at each).
Control rows, titles and button rows at every alignment, and long
rows, which in a narrow column scroll within it.
"""
debug: bool = False
#: The layout we're being browsed to at, for the page to name (the
#: browse sets the actual layout; this is just for show).
layout: dui2.WindowLayout | None = None
#: Which side the page's viewer pane sits on at viewer layouts, or
#: None for a page with no viewer (its rows then span the window).
#: Ignored at other layouts.
viewer_side: dui2.ViewerSide | None = None
[docs]
@ioprepped
@dataclass
class WideFit(TestRoute, path='/widefit'):
"""A row exactly as big as wide pages get (browse at each layout).
It should fill the page's height and width with no scrolling, at
every ui-scale; with ``over`` / ``wide_over`` set it's a hair
taller / wider, which should scroll (at small ui-scale, only on the
smallest screens: windows there fill the screen, so the page sizes
are minimums). ``layout`` says which wide
layout it's being shown in (browse at that one): wide and wider
pages are the same height everywhere, wide-tall ones depend on the
ui-scale and wide-taller ones on the screen too; wider's width
follows the screen, so there's no width to overflow there.
"""
over: bool = False
wide_over: bool = False
layout: dui2.WindowLayout = dui2.WindowLayout.WIDE
[docs]
class Flavor(Enum):
"""Something to pick from on the widgets test page."""
VANILLA = 'v'
CHOCOLATE = 'c'
STRAWBERRY = 's'
MINT = 'm'
[docs]
class Size(Enum):
"""Something else to pick from on the widgets test page."""
S = 's'
M = 'm'
L = 'l'
XL = 'x'
[docs]
@ioprepped
@dataclass
class WebTestGet(TestRoute, path='/webtest/get'):
"""A page fetched from a web server via GET."""
[docs]
@ioprepped
@dataclass
class WebTestPost(
TestRoute, path='/webtest/post', method=dui2.RequestMethod.POST
):
"""A page fetched from a web server via POST."""
[docs]
@ioprepped
@dataclass
class CloudMsgTestGet(TestRoute, path='/cloudmsgtest/get'):
"""A page fetched through our cloud connection via GET."""
[docs]
@ioprepped
@dataclass
class CloudMsgTestPost(
TestRoute, path='/cloudmsgtest/post', method=dui2.RequestMethod.POST
):
"""A page fetched through our cloud connection via POST."""
# All routes in the family. Handlers can match against this with
# assert_never to be sure they cover everything.
AnyTestRoute = (
Root
| Test2
| Slow
| TimedActions
| DisplayItems
| TextImages
| LiveTimes
| TextWrapping
| Animation
| Depictions
| Viewers
| Names
| NinePatch
| EmptyPage
| BoundsTests
| Widgets
| NavTest
| Sections
| ButtonGrids
| WindowLayouts
| WideFit
| WebTestGet
| WebTestPost
| CloudMsgTestGet
| CloudMsgTestPost
)
[docs]
class TestLocalAction(DocUILocalActionBase):
"""Family class for doc-ui test local-actions."""
[docs]
@override
@classmethod
def get_action_types(cls) -> tuple[type[DocUILocalActionBase], ...]:
return family_members(AnyTestLocalAction)
[docs]
@ioprepped
@dataclass
class TestAction(TestLocalAction, name='testaction'):
"""Show a message proving we got here."""
testparam: int
# All local-actions in the family (just the one for now).
[docs]
@ioprepped
@dataclass
class ShowVolume(TestLocalAction, name='showvolume'):
"""Show the volume slider's live value (fired while dragging)."""
AnyTestLocalAction = TestAction | ShowVolume
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self, TypeIs)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum