Skip to content

Commit ac27f36

Browse files
committed
add public test tooling
1 parent baa52f8 commit ac27f36

File tree

5 files changed

+205
-113
lines changed

5 files changed

+205
-113
lines changed

Diff for: idom/client/app/package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
"author": "Ryan Morshead",
66
"license": "MIT",
77
"repository": {
8+
"type": "git",
89
"url": "https://github.com/rmorshea/idom"
910
},
1011
"main": "src/layout.js",
1112
"files": [
12-
"src/*.js"
13+
"src/**/*.js"
1314
],
1415
"scripts": {
15-
"build": "npx snowpack && npx snowpack build",
16-
"format": "npx prettier --write ./src"
16+
"build": "snowpack && snowpack build",
17+
"format": "prettier --write ./src",
18+
"test": "echo \"Error: no test specified\" && exit 1"
1719
},
1820
"devDependencies": {
1921
"prettier": "2.1.1",

Diff for: idom/testing.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
from contextlib import contextmanager, AbstractContextManager
2+
from typing import Callable, Tuple, Iterator, Type, Optional, Union, Any
3+
4+
from sanic import Sanic
5+
from selenium.webdriver.remote.webdriver import WebDriver
6+
from selenium.webdriver import Chrome
7+
from selenium.webdriver.chrome.options import Options
8+
9+
import idom
10+
from idom.core.element import AbstractElement, ElementConstructor
11+
from idom.server.sanic import SanicRenderServer, PerClientStateServer
12+
from idom.server.prefab import hotswap_server
13+
from idom.server.utils import find_available_port
14+
from idom.utils import Ref
15+
16+
17+
DisplayFunction = Callable[[Union[ElementConstructor, AbstractElement], str], None]
18+
19+
20+
def create_selenium_display_context(
21+
headless: bool, driver_timeout: float = 3.0
22+
) -> Iterator[DisplayFunction]:
23+
host = "127.0.0.1"
24+
port = find_available_port(host)
25+
server_url = f"http://{host}:{port}"
26+
27+
with open_selenium_chrome_driver(
28+
headless=headless,
29+
page_load_timeout=driver_timeout,
30+
implicit_wait_timeout=driver_timeout,
31+
) as driver:
32+
with open_sanic_mount_and_server(
33+
server_type=PerClientStateServer, host=host, port=port
34+
) as (mount, server):
35+
return create_selenium_page_get_and_display_context(
36+
driver, server, server_url, mount
37+
)[1]
38+
39+
40+
def create_selenium_page_get_and_display_context(
41+
driver: WebDriver,
42+
server: "_RenderServerSavesLastError",
43+
server_url: str,
44+
element_mount_function: Callable[..., None],
45+
) -> Tuple[Callable[[str], None], "AbstractContextManager[DisplayFunction]"]:
46+
display_id = Ref(0)
47+
48+
def get_page(query: str = "") -> None:
49+
"""Navigate the driver to an IDOM server at the given host and port.
50+
51+
The ``query`` parameter is typically used to specify a ``view_id`` when a
52+
``multiview()`` elemement has been mounted to the layout.
53+
"""
54+
driver.get(f"{server_url}/client/index.html?{query}")
55+
56+
def display_function(
57+
element: Union[Callable[[], Any], AbstractElement],
58+
query: str = "",
59+
check_mount: bool = True,
60+
) -> None:
61+
d_id = display_id.current
62+
display_id.current += 1
63+
display_attrs = {"id": f"display-{d_id}"}
64+
element_constructor = element if callable(element) else lambda: element
65+
element_mount_function(
66+
lambda: idom.html.div(display_attrs, element_constructor())
67+
)
68+
69+
get_page(query)
70+
if check_mount:
71+
# Ensure element was actually mounted.
72+
driver.find_element_by_id(f"display-{d_id}")
73+
74+
return None
75+
76+
@contextmanager
77+
def display_context():
78+
server.last_server_error_for_idom_testing.current = None
79+
try:
80+
yield display_function
81+
finally:
82+
if server.last_server_error_for_idom_testing.current is not None:
83+
raise server.last_server_error_for_idom_testing.current
84+
85+
return get_page, display_context
86+
87+
88+
@contextmanager
89+
def open_sanic_mount_and_server(
90+
server_type: Type[SanicRenderServer],
91+
host: str,
92+
port: int,
93+
app: Optional[Sanic] = None,
94+
) -> Iterator[Tuple[Callable[..., None], SanicRenderServer]]:
95+
try:
96+
yield hotswap_server(
97+
(
98+
server_type
99+
if issubclass(server_type, _RenderServerSavesLastError)
100+
else create_sanic_server_type_for_testing(server_type)
101+
),
102+
host,
103+
port,
104+
server_options={"cors": True},
105+
run_options={},
106+
sync_views=False,
107+
app=app,
108+
)
109+
finally:
110+
if server_type.last_server_error_for_idom_testing.current is not None:
111+
raise server_type.last_server_error_for_idom_testing.current
112+
113+
114+
def create_sanic_server_type_for_testing(
115+
server_type: Type[SanicRenderServer],
116+
) -> Type["_RenderServerSavesLastError"]:
117+
return type(
118+
server_type.__name__,
119+
(_RenderServerSavesLastError, server_type),
120+
{"last_server_error_for_idom_testing": Ref(None)},
121+
)
122+
123+
124+
@contextmanager
125+
def open_selenium_chrome_driver(
126+
headless: bool,
127+
implicit_wait_timeout: float = 3.0,
128+
page_load_timeout: float = 3.0,
129+
window_size: Tuple[int, int] = (1080, 800),
130+
) -> Iterator[Chrome]:
131+
options = Options()
132+
options.headless = headless
133+
134+
driver = Chrome(options=options)
135+
136+
driver.set_window_size(*window_size)
137+
driver.set_page_load_timeout(page_load_timeout)
138+
driver.implicitly_wait(implicit_wait_timeout)
139+
140+
try:
141+
yield driver
142+
finally:
143+
driver.quit()
144+
145+
146+
class _RenderServerSavesLastError(SanicRenderServer):
147+
"""A server that updates the ``last_server_error`` fixture"""
148+
149+
last_server_error_for_idom_testing: Ref[Optional[Exception]]
150+
151+
async def _run_dispatcher(self, *args: Any, **kwargs: Any) -> None:
152+
self.last_server_error_for_idom_testing.current = None
153+
try:
154+
await super()._run_dispatcher(*args, **kwargs)
155+
except Exception as e:
156+
self.last_server_error_for_idom_testing.current = e

Diff for: pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[build-system]
2-
requires = ["setuptools >= 40.9.0", "wheel"]
2+
requires = ["setuptools >= 40.9.0", "wheel", "setuptools_scm"]
33
build-backend = "setuptools.build_meta"

Diff for: setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
"keywords": ["interactive", "widgets", "DOM", "React"],
3434
"include_package_data": True,
3535
"zip_safe": False,
36-
"setup_requires": ["setuptools_scm"],
3736
"use_scm_version": True,
3837
"classifiers": [
38+
"Framework :: IDOM",
3939
"Intended Audience :: Developers",
4040
"Intended Audience :: Science/Research",
4141
"Topic :: Multimedia :: Graphics",

0 commit comments

Comments
 (0)