Files
pyscript/pyscript.core/tests/python/tests/test_display.py
Nicholas Tollervey 06138bbb48 Update test suite (#2181)
* pyscript.web tests pass with upytest.
* Refactor of old integration tests to new Python tests.
* Added comprehensive test suite for Python based `pyscript` module.
* Add integration tests to Makefile (and CI)
* Remove un-needed upload action.
* Ensure fails are properly logged as an array. Remove the explicit test step, since this is already built into the build step.
* Bump polyscript.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Andrea Giammarchi <andrea.giammarchi@gmail.com>
2024-09-25 16:23:59 +01:00

290 lines
8.6 KiB
Python

"""
Tests for the display function in PyScript.
"""
import asyncio
import upytest
from pyscript import HTML, RUNNING_IN_WORKER, display, py_import, web
async def get_display_container():
"""
Get the element that contains the output of the display function.
"""
if RUNNING_IN_WORKER:
# Needed to ensure the DOM has time to catch up with the display calls
# made in the worker thread.
await asyncio.sleep(0.01)
py_display = web.page.find("script-py")
if len(py_display) == 1:
return py_display[0]
mpy_display = web.page.find("script-mpy")
if len(mpy_display) == 1:
return mpy_display[0]
return None
async def setup():
"""
Setup function for the test_display.py module. Remove all references to the
display output in the DOM so we always start from a clean state.
"""
container = await get_display_container()
if container:
container.replaceChildren()
target_container = web.page.find("#test-element-container")[0]
target_container.innerHTML = ""
async def teardown():
"""
Like setup.
"""
container = await get_display_container()
if container:
container.replaceChildren()
target_container = web.page.find("#test-element-container")[0]
target_container.innerHTML = ""
async def test_simple_display():
"""
Test the display function with a simple string.
"""
display("Hello, world")
container = await get_display_container()
assert len(container.children) == 1, "Expected one child in the display container."
assert (
container.children[0].tagName == "DIV"
), "Expected a div element in the display container."
assert container.children[0].innerHTML == "Hello, world"
async def test_consecutive_display():
"""
Display order should be preserved.
"""
display("hello 1")
display("hello 2")
container = await get_display_container()
assert (
len(container.children) == 2
), "Expected two children in the display container."
assert container.children[0].innerHTML == "hello 1"
assert container.children[1].innerHTML == "hello 2"
def test_target_parameter():
"""
The output from display is placed in the target element.
"""
display("hello world", target="test-element-container")
target = web.page.find("#test-element-container")[0]
assert target.innerText == "hello world"
def test_target_parameter_with_hash():
"""
The target parameter can have a hash in front of it.
"""
display("hello world", target="#test-element-container")
target = web.page.find("#test-element-container")[0]
assert target.innerText == "hello world"
def test_non_existing_id_target_raises_value_error():
"""
If the target parameter is set to a non-existing element, a ValueError should be raised.
"""
with upytest.raises(ValueError):
display("hello world", target="non-existing")
def test_empty_string_target_raises_value_error():
"""
If the target parameter is an empty string, a ValueError should be raised.
"""
with upytest.raises(ValueError) as exc:
display("hello world", target="")
assert str(exc.exception) == "Cannot have an empty target"
def test_non_string_target_values_raise_typerror():
"""
The target parameter must be a string.
"""
with upytest.raises(TypeError) as exc:
display("hello world", target=True)
assert str(exc.exception) == "target must be str or None, not bool"
with upytest.raises(TypeError) as exc:
display("hello world", target=123)
assert str(exc.exception) == "target must be str or None, not int"
async def test_tag_target_attribute():
"""
The order and arrangement of the display calls (including targets) should be preserved.
"""
display("item 1")
display("item 2", target="test-element-container")
display("item 3")
container = await get_display_container()
assert (
len(container.children) == 2
), "Expected two children in the display container."
assert container.children[0].innerHTML == "item 1"
assert container.children[1].innerHTML == "item 3"
target = web.page.find("#test-element-container")[0]
assert target.innerText == "item 2"
async def test_multiple_display_calls_same_tag():
"""
Multiple display calls in the same script tag should be displayed in order.
"""
display("item 1")
display("item 2")
container = await get_display_container()
assert (
len(container.children) == 2
), "Expected two children in the display container."
assert container.children[0].innerHTML == "item 1"
assert container.children[1].innerHTML == "item 2"
async def test_append_true():
"""
Explicit append flag as true should append to the expected container element.
"""
display("item 1", append=True)
display("item 2", append=True)
container = await get_display_container()
assert (
len(container.children) == 2
), "Expected two children in the display container."
assert container.children[0].innerHTML == "item 1"
assert container.children[1].innerHTML == "item 2"
async def test_append_false():
"""
Explicit append flag as false should replace the expected container element.
"""
display("item 1", append=False)
display("item 2", append=False)
container = await get_display_container()
assert container.innerText == "item 2"
async def test_display_multiple_values():
"""
Display multiple values in the same call.
"""
display("hello", "world")
container = await get_display_container()
assert container.innerText == "hello\nworld", container.innerText
async def test_display_multiple_append_false():
display("hello", "world", append=False)
container = await get_display_container()
assert container.innerText == "world"
def test_display_multiple_append_false_with_target():
"""
TODO: this is a display.py issue to fix when append=False is used
do not use the first element, just clean up and then append
remove the # display comment once that's done
"""
class Circle:
r = 0
def _repr_svg_(self):
return (
f'<svg height="{self.r*2}" width="{self.r*2}">'
f'<circle cx="{self.r}" cy="{self.r}" r="{self.r}" fill="red"></circle></svg>'
)
circle = Circle()
circle.r += 5
display(circle, circle, target="test-element-container", append=False)
target = web.page.find("#test-element-container")[0]
assert target.innerHTML == circle._repr_svg_()
async def test_display_list_dict_tuple():
"""
Display a list, dictionary, and tuple with the expected __repr__.
NOTE: MicroPython doesn't (yet) have ordered dicts. Hence the rather odd
check that the dictionary is displayed as a string.
"""
l = ["A", 1, "!"]
d = {"B": 2, "List": l}
t = ("C", 3, "!")
display(l, d, t)
container = await get_display_container()
l2, d2, t2 = container.innerText.split("\n")
assert l == eval(l2)
assert d == eval(d2)
assert t == eval(t2)
async def test_display_should_escape():
display("<p>hello world</p>")
container = await get_display_container()
assert container[0].innerHTML == "&lt;p&gt;hello world&lt;/p&gt;"
assert container.innerText == "<p>hello world</p>"
async def test_display_HTML():
display(HTML("<p>hello world</p>"))
container = await get_display_container()
assert container[0].innerHTML == "<p>hello world</p>"
assert container.innerText == "hello world"
@upytest.skip(
"Pyodide main thread only",
skip_when=upytest.is_micropython or RUNNING_IN_WORKER,
)
async def test_image_display():
"""
Check an image is displayed correctly.
"""
mpl = await py_import("matplotlib")
import matplotlib.pyplot as plt
xpoints = [3, 6, 9]
ypoints = [1, 2, 3]
plt.plot(xpoints, ypoints)
display(plt)
container = await get_display_container()
img = container.find("img")[0]
img_src = img.getAttribute("src").replace(
"data:image/png;charset=utf-8;base64,", ""
)
assert len(img_src) > 0
@upytest.skip(
"Pyodide main thread only",
skip_when=upytest.is_micropython or RUNNING_IN_WORKER,
)
async def test_image_renders_correctly():
"""
This is just a sanity check to make sure that images are rendered
in a reasonable way.
"""
from PIL import Image
img = Image.new("RGB", (4, 4), color=(0, 0, 0))
display(img, target="test-element-container", append=False)
target = web.page.find("#test-element-container")[0]
img = target.find("img")[0]
assert img.src.startswith("data:image/png;charset=utf-8;base64")