mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-18 04:01:10 -05:00
* Remove duplicate LICENSE. * Remove un-userd pyscript.sw directory and its content. * Remove ReadTheDocs settings (unused). * Remove un-used pyproject.toml * Remove now unused CHANGELOG. Changes now tracked via release notes on GitHub. * Updated / cleaned release page template and associated GH actions. * Update prettierignore to remove un-needed refs. * Move troubleshooting into correct README. * Add reason for the index.html * Rename the "pyscript.core" directory to "core". * Update PR template because CHANGELOG is no longer used. * Codespell configuration in pyproject.toml. * Update pyscript.core -> core in .githubignore * Remove test-results/.last-run.json. This should be ignored by git. * Pin nodejs version. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
abb1eb28fe
commit
9dad29ec17
7
core/tests/python/example_js_module.js
Normal file
7
core/tests/python/example_js_module.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
A simple JavaScript module to test the integration with Python.
|
||||
*/
|
||||
|
||||
export function hello() {
|
||||
return "Hello from JavaScript!";
|
||||
}
|
||||
7
core/tests/python/example_js_worker_module.js
Normal file
7
core/tests/python/example_js_worker_module.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
A simple JavaScript module to test the integration with Python on a worker.
|
||||
*/
|
||||
|
||||
export function hello() {
|
||||
return "Hello from JavaScript in a web worker!";
|
||||
}
|
||||
23
core/tests/python/helper.js
Normal file
23
core/tests/python/helper.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const qs = new URLSearchParams(location.search);
|
||||
|
||||
const src = './main.py';
|
||||
let config = './settings_mpy.json';
|
||||
|
||||
// terminal=0 to NOT have a terminal
|
||||
const terminal = qs.has('terminal') ? qs.get('terminal') : 1;
|
||||
// worker=1 to have a worker
|
||||
const worker = qs.has('worker');
|
||||
|
||||
const interpreter = qs.get('type') || 'mpy';
|
||||
if (interpreter === 'py') {
|
||||
config = "./settings_py.json";
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.type = interpreter;
|
||||
if (src) script.src = src;
|
||||
if (config) script.setAttribute('config', config);
|
||||
script.toggleAttribute('terminal', terminal);
|
||||
script.toggleAttribute('worker', worker);
|
||||
|
||||
document.write(script.outerHTML);
|
||||
75
core/tests/python/index.html
Normal file
75
core/tests/python/index.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Pure Python PyScript tests</title>
|
||||
<link rel="stylesheet" href="../../dist/core.css">
|
||||
<script type="module" src="../../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./helper.js"></script>
|
||||
<!-- script type="mpy" src="./main.py" config="./settings.json" terminal></script-->
|
||||
<template id="test_card_with_element_template">
|
||||
<p>This is a test. {foo}</p>
|
||||
</template>
|
||||
|
||||
<div id="test_id_selector" style="visibility: hidden;">You found test_id_selector</div>
|
||||
<div id="test_class_selector" class="a-test-class" style="visibility: hidden;">You found test_class_selector</div>
|
||||
<div id="test_selector_w_children" class="a-test-class" style="visibility: hidden;">
|
||||
<div id="test_selector_w_children_child_1" class="a-test-class" style="visibility: hidden;">Child 1</div>
|
||||
<div id="test_selector_w_children_child_2" style="visibility: hidden;">Child 2</div>
|
||||
</div>
|
||||
|
||||
<div id="div-no-classes"></div>
|
||||
|
||||
<div style="visibility: hidden;">
|
||||
<h2>Test Read and Write</h2>
|
||||
<div id="test_rr_div">Content test_rr_div</div>
|
||||
<h3 id="test_rr_h3">Content test_rr_h3</h3>
|
||||
|
||||
<div id="multi-elem-div" class="multi-elems">Content multi-elem-div</div>
|
||||
<p id="multi-elem-p" class="multi-elems">Content multi-elem-p</p>
|
||||
<h2 id="multi-elem-h2" class="multi-elems">Content multi-elem-h2</h2>
|
||||
|
||||
<form>
|
||||
<input id="test_rr_input_text" type="text" value="Content test_rr_input_text">
|
||||
<input id="test_rr_input_button" type="button" value="Content test_rr_input_button">
|
||||
<input id="test_rr_input_email" type="email" value="Content test_rr_input_email">
|
||||
<input id="test_rr_input_password" type="password" value="Content test_rr_input_password">
|
||||
</form>
|
||||
|
||||
<select id="test_select_element"></select>
|
||||
<select id="test_select_element_w_options">
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2" selected="selected">Option 2</option>
|
||||
</select>
|
||||
<select id="test_select_element_to_clear">
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="4">Option 4</option>
|
||||
</select>
|
||||
|
||||
<select id="test_select_element_to_remove">
|
||||
<option value="1">Option 1</option>
|
||||
<option value="2">Option 2</option>
|
||||
<option value="3">Option 3</option>
|
||||
<option value="4">Option 4</option>
|
||||
</select>
|
||||
|
||||
<div id="element-creation-test"></div>
|
||||
|
||||
<button id="a-test-button">I'm a button to be clicked</button>
|
||||
<button>I'm another button you can click</button>
|
||||
<button id="a-third-button">2 is better than 3 :)</button>
|
||||
|
||||
<div id="element-append-tests"></div>
|
||||
<p class="collection"></p>
|
||||
<div class="collection"></div>
|
||||
<h3 class="collection"></h3>
|
||||
|
||||
<div id="element_attribute_tests"></div>
|
||||
</div>
|
||||
<div id="test-element-container"></div>
|
||||
</body>
|
||||
</html>
|
||||
8
core/tests/python/main.py
Normal file
8
core/tests/python/main.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import json
|
||||
|
||||
import upytest
|
||||
from pyscript import web
|
||||
|
||||
result = await upytest.run("./tests", random=True)
|
||||
output = web.div(json.dumps(result), id="result")
|
||||
web.page.append(output)
|
||||
26
core/tests/python/settings_mpy.json
Normal file
26
core/tests/python/settings_mpy.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"files": {
|
||||
"https://raw.githubusercontent.com/ntoll/upytest/1.0.8/upytest.py": "",
|
||||
"./tests/test_config.py": "tests/test_config.py",
|
||||
"./tests/test_current_target.py": "tests/test_current_target.py",
|
||||
"./tests/test_display.py": "tests/test_display.py",
|
||||
"./tests/test_document.py": "tests/test_document.py",
|
||||
"./tests/test_fetch.py": "tests/test_fetch.py",
|
||||
"./tests/test_ffi.py": "tests/test_ffi.py",
|
||||
"./tests/test_js_modules.py": "tests/test_js_modules.py",
|
||||
"./tests/test_storage.py": "tests/test_storage.py",
|
||||
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
|
||||
"./tests/test_web.py": "tests/test_web.py",
|
||||
"./tests/test_websocket.py": "tests/test_websocket.py",
|
||||
"./tests/test_when.py": "tests/test_when.py",
|
||||
"./tests/test_window.py": "tests/test_window.py"
|
||||
},
|
||||
"js_modules": {
|
||||
"main": {
|
||||
"./example_js_module.js": "greeting"
|
||||
},
|
||||
"worker": {
|
||||
"./example_js_worker_module.js": "greeting_worker"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
core/tests/python/settings_py.json
Normal file
27
core/tests/python/settings_py.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"files": {
|
||||
"https://raw.githubusercontent.com/ntoll/upytest/1.0.8/upytest.py": "",
|
||||
"./tests/test_config.py": "tests/test_config.py",
|
||||
"./tests/test_current_target.py": "tests/test_current_target.py",
|
||||
"./tests/test_display.py": "tests/test_display.py",
|
||||
"./tests/test_document.py": "tests/test_document.py",
|
||||
"./tests/test_fetch.py": "tests/test_fetch.py",
|
||||
"./tests/test_ffi.py": "tests/test_ffi.py",
|
||||
"./tests/test_js_modules.py": "tests/test_js_modules.py",
|
||||
"./tests/test_storage.py": "tests/test_storage.py",
|
||||
"./tests/test_running_in_worker.py": "tests/test_running_in_worker.py",
|
||||
"./tests/test_web.py": "tests/test_web.py",
|
||||
"./tests/test_websocket.py": "tests/test_websocket.py",
|
||||
"./tests/test_when.py": "tests/test_when.py",
|
||||
"./tests/test_window.py": "tests/test_window.py"
|
||||
},
|
||||
"js_modules": {
|
||||
"main": {
|
||||
"./example_js_module.js": "greeting"
|
||||
},
|
||||
"worker": {
|
||||
"./example_js_worker_module.js": "greeting_worker"
|
||||
}
|
||||
},
|
||||
"packages": ["Pillow" ]
|
||||
}
|
||||
20
core/tests/python/tests/test_config.py
Normal file
20
core/tests/python/tests/test_config.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Tests for the pyscript.config dictionary.
|
||||
"""
|
||||
|
||||
from pyscript import config, document, fetch
|
||||
from upytest import is_micropython
|
||||
|
||||
|
||||
async def test_config_reads_expected_settings_correctly():
|
||||
"""
|
||||
The config dictionary should read expected settings for this test suite.
|
||||
|
||||
Just grab the raw JSON for the settings and compare it to the config
|
||||
dictionary.
|
||||
"""
|
||||
settings = "/settings_mpy.json" if is_micropython else "/settings_py.json"
|
||||
url = document.location.href.rsplit("/", 1)[0] + settings
|
||||
raw_config = await fetch(url).json()
|
||||
for key, value in raw_config.items():
|
||||
assert config[key] == value, f"Expected {key} to be {value}, got {config[key]}"
|
||||
22
core/tests/python/tests/test_current_target.py
Normal file
22
core/tests/python/tests/test_current_target.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Ensure the pyscript.current_target function returns the expected target
|
||||
element's id.
|
||||
"""
|
||||
|
||||
from pyscript import RUNNING_IN_WORKER, current_target
|
||||
from upytest import is_micropython
|
||||
|
||||
|
||||
def test_current_target():
|
||||
"""
|
||||
The current_target function should return the expected target element's id.
|
||||
"""
|
||||
expected = "py-0"
|
||||
if is_micropython:
|
||||
if RUNNING_IN_WORKER:
|
||||
expected = "mpy-w0-target"
|
||||
else:
|
||||
expected = "mpy-0"
|
||||
elif RUNNING_IN_WORKER:
|
||||
expected = "py-w0-target"
|
||||
assert current_target() == expected, f"Expected {expected} got {current_target()}"
|
||||
289
core/tests/python/tests/test_display.py
Normal file
289
core/tests/python/tests/test_display.py
Normal file
@@ -0,0 +1,289 @@
|
||||
"""
|
||||
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 == "<p>hello world</p>"
|
||||
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")
|
||||
17
core/tests/python/tests/test_document.py
Normal file
17
core/tests/python/tests/test_document.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Sanity check for the pyscript.document object.
|
||||
"""
|
||||
|
||||
from pyscript import document
|
||||
|
||||
|
||||
def test_document():
|
||||
"""
|
||||
The document object should be available and we can change its attributes
|
||||
(in this case, the title).
|
||||
"""
|
||||
title = document.title
|
||||
assert title
|
||||
document.title = "A new title"
|
||||
assert document.title == "A new title"
|
||||
document.title = title
|
||||
83
core/tests/python/tests/test_fetch.py
Normal file
83
core/tests/python/tests/test_fetch.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Ensure the pyscript.test function behaves as expected.
|
||||
"""
|
||||
|
||||
from pyscript import fetch
|
||||
|
||||
|
||||
async def test_fetch_json():
|
||||
"""
|
||||
The fetch function should return the expected JSON response.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
|
||||
assert response.ok
|
||||
data = await response.json()
|
||||
assert data["userId"] == 1
|
||||
assert data["id"] == 1
|
||||
assert data["title"] == "delectus aut autem"
|
||||
assert data["completed"] is False
|
||||
|
||||
|
||||
async def test_fetch_text():
|
||||
"""
|
||||
The fetch function should return the expected text response.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
|
||||
assert response.ok
|
||||
text = await response.text()
|
||||
assert "delectus aut autem" in text
|
||||
assert "completed" in text
|
||||
assert "false" in text
|
||||
assert "1" in text
|
||||
|
||||
|
||||
async def test_fetch_bytearray():
|
||||
"""
|
||||
The fetch function should return the expected bytearray response.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
|
||||
assert response.ok
|
||||
data = await response.bytearray()
|
||||
assert b"delectus aut autem" in data
|
||||
assert b"completed" in data
|
||||
assert b"false" in data
|
||||
assert b"1" in data
|
||||
|
||||
|
||||
async def test_fetch_array_buffer():
|
||||
"""
|
||||
The fetch function should return the expected array buffer response.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
|
||||
assert response.ok
|
||||
data = await response.arrayBuffer()
|
||||
bytes_ = bytes(data)
|
||||
assert b"delectus aut autem" in bytes_
|
||||
assert b"completed" in bytes_
|
||||
assert b"false" in bytes_
|
||||
assert b"1" in bytes_
|
||||
|
||||
|
||||
async def test_fetch_ok():
|
||||
"""
|
||||
The fetch function should return a response with ok set to True for an
|
||||
existing URL.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
|
||||
assert response.ok
|
||||
assert response.status == 200
|
||||
data = await response.json()
|
||||
assert data["userId"] == 1
|
||||
assert data["id"] == 1
|
||||
assert data["title"] == "delectus aut autem"
|
||||
assert data["completed"] is False
|
||||
|
||||
|
||||
async def test_fetch_not_ok():
|
||||
"""
|
||||
The fetch function should return a response with ok set to False for a
|
||||
non-existent URL.
|
||||
"""
|
||||
response = await fetch("https://jsonplaceholder.typicode.com/todos/1000")
|
||||
assert not response.ok
|
||||
assert response.status == 404
|
||||
40
core/tests/python/tests/test_ffi.py
Normal file
40
core/tests/python/tests/test_ffi.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Exercise (as much as is possible) the pyscript.ffi namespace.
|
||||
"""
|
||||
|
||||
import upytest
|
||||
from pyscript import ffi
|
||||
|
||||
|
||||
def test_create_proxy():
|
||||
"""
|
||||
The create_proxy function should return a proxy object that is callable.
|
||||
"""
|
||||
|
||||
def func():
|
||||
return 42
|
||||
|
||||
proxy = ffi.create_proxy(func)
|
||||
assert proxy() == 42
|
||||
if upytest.is_micropython:
|
||||
from jsffi import JsProxy
|
||||
else:
|
||||
from pyodide.ffi import JsProxy
|
||||
assert isinstance(proxy, JsProxy)
|
||||
|
||||
|
||||
def test_to_js():
|
||||
"""
|
||||
The to_js function should convert a Python object to a JavaScript object.
|
||||
In this instance, a Python dict should be converted to a JavaScript object
|
||||
represented by a JsProxy object.
|
||||
"""
|
||||
obj = {"a": 1, "b": 2}
|
||||
js_obj = ffi.to_js(obj)
|
||||
assert js_obj.a == 1
|
||||
assert js_obj.b == 2
|
||||
if upytest.is_micropython:
|
||||
from jsffi import JsProxy
|
||||
else:
|
||||
from pyodide.ffi import JsProxy
|
||||
assert isinstance(js_obj, JsProxy)
|
||||
52
core/tests/python/tests/test_js_modules.py
Normal file
52
core/tests/python/tests/test_js_modules.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Ensure referenced JavaScript modules are available via the pyscript.js_modules
|
||||
object.
|
||||
"""
|
||||
|
||||
import upytest
|
||||
from pyscript import RUNNING_IN_WORKER
|
||||
|
||||
|
||||
@upytest.skip("Main thread only.", skip_when=RUNNING_IN_WORKER)
|
||||
def test_js_module_is_available_on_main():
|
||||
"""
|
||||
The "hello" function in the example_js_module.js file is available via the
|
||||
js_modules object while running in the main thread. See the settings.json
|
||||
file for the configuration that makes this possible.
|
||||
"""
|
||||
from pyscript.js_modules import greeting
|
||||
|
||||
assert greeting.hello() == "Hello from JavaScript!"
|
||||
|
||||
|
||||
@upytest.skip("Worker only.", skip_when=not RUNNING_IN_WORKER)
|
||||
def test_js_module_is_available_on_worker():
|
||||
"""
|
||||
The "hello" function in the example_js_module.js file is available via the
|
||||
js_modules object while running in a worker. See the settings.json file for
|
||||
the configuration that makes this possible.
|
||||
"""
|
||||
from pyscript.js_modules import greeting
|
||||
|
||||
assert greeting.hello() == "Hello from JavaScript!"
|
||||
|
||||
|
||||
@upytest.skip("Worker only.", skip_when=not RUNNING_IN_WORKER)
|
||||
def test_js_module_is_available_on_worker():
|
||||
"""
|
||||
The "hello" function in the example_js_worker_module.js file is available
|
||||
via the js_modules object while running in a worker.
|
||||
"""
|
||||
from pyscript.js_modules import greeting_worker
|
||||
|
||||
assert greeting_worker.hello() == "Hello from JavaScript in a web worker!"
|
||||
|
||||
|
||||
@upytest.skip("Main thread only.", skip_when=RUNNING_IN_WORKER)
|
||||
def test_js_worker_module_is_not_available_on_main():
|
||||
"""
|
||||
The "hello" function in the example_js_worker_module.js file is not
|
||||
available via the js_modules object while running in the main thread.
|
||||
"""
|
||||
with upytest.raises(ImportError):
|
||||
from pyscript.js_modules import greeting_worker
|
||||
27
core/tests/python/tests/test_running_in_worker.py
Normal file
27
core/tests/python/tests/test_running_in_worker.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Ensure the pyscript.RUNNING_IN_WORKER flag is set correctly (a sanity check).
|
||||
"""
|
||||
|
||||
import upytest
|
||||
from pyscript import RUNNING_IN_WORKER, document
|
||||
|
||||
# In the test suite, running in a worker is flagged by the presence of the
|
||||
# "worker" query string. We do this to avoid using RUNNING_IN_WORKER to skip
|
||||
# tests that check RUNNING_IN_WORKER.
|
||||
in_worker = "worker" in document.location.search.lower()
|
||||
|
||||
|
||||
@upytest.skip("Main thread only.", skip_when=in_worker)
|
||||
def test_running_in_main():
|
||||
"""
|
||||
The flag should be False.
|
||||
"""
|
||||
assert RUNNING_IN_WORKER is False
|
||||
|
||||
|
||||
@upytest.skip("Worker only.", skip_when=not in_worker)
|
||||
def test_running_in_worker():
|
||||
"""
|
||||
The flag should be True.
|
||||
"""
|
||||
assert RUNNING_IN_WORKER is True
|
||||
89
core/tests/python/tests/test_storage.py
Normal file
89
core/tests/python/tests/test_storage.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Ensure the pyscript.storage object behaves as a Python dict.
|
||||
"""
|
||||
|
||||
from pyscript import Storage, storage
|
||||
|
||||
test_store = None
|
||||
|
||||
|
||||
async def setup():
|
||||
global test_store
|
||||
if test_store is None:
|
||||
test_store = await storage("test_store")
|
||||
test_store.clear()
|
||||
await test_store.sync()
|
||||
|
||||
|
||||
async def teardown():
|
||||
if test_store:
|
||||
test_store.clear()
|
||||
await test_store.sync()
|
||||
|
||||
|
||||
async def test_storage_as_dict():
|
||||
"""
|
||||
The storage object should behave as a Python dict.
|
||||
"""
|
||||
# Assign
|
||||
test_store["a"] = 1
|
||||
# Retrieve
|
||||
assert test_store["a"] == 1
|
||||
assert "a" in test_store
|
||||
assert len(test_store) == 1
|
||||
# Iterate
|
||||
for k, v in test_store.items():
|
||||
assert k == "a"
|
||||
assert v == 1
|
||||
# Remove
|
||||
del test_store["a"]
|
||||
assert "a" not in test_store
|
||||
assert len(test_store) == 0
|
||||
|
||||
|
||||
async def test_storage_types():
|
||||
"""
|
||||
The storage object should support different types of values.
|
||||
"""
|
||||
test_store["boolean"] = False
|
||||
test_store["integer"] = 42
|
||||
test_store["float"] = 3.14
|
||||
test_store["string"] = "hello"
|
||||
test_store["none"] = None
|
||||
test_store["list"] = [1, 2, 3]
|
||||
test_store["dict"] = {"a": 1, "b": 2}
|
||||
test_store["tuple"] = (1, 2, 3)
|
||||
test_store["bytearray"] = bytearray(b"hello")
|
||||
test_store["memoryview"] = memoryview(b"hello")
|
||||
await test_store.sync()
|
||||
assert test_store["boolean"] is False
|
||||
assert isinstance(test_store["boolean"], bool)
|
||||
assert test_store["integer"] == 42
|
||||
assert isinstance(test_store["integer"], int)
|
||||
assert test_store["float"] == 3.14
|
||||
assert isinstance(test_store["float"], float)
|
||||
assert test_store["string"] == "hello"
|
||||
assert isinstance(test_store["string"], str)
|
||||
assert test_store["none"] is None
|
||||
assert isinstance(test_store["none"], type(None))
|
||||
assert test_store["list"] == [1, 2, 3]
|
||||
assert isinstance(test_store["list"], list)
|
||||
assert test_store["dict"] == {"a": 1, "b": 2}
|
||||
assert isinstance(test_store["dict"], dict)
|
||||
assert test_store["tuple"] == (1, 2, 3)
|
||||
assert isinstance(test_store["tuple"], tuple)
|
||||
assert test_store["bytearray"] == bytearray(b"hello")
|
||||
assert isinstance(test_store["bytearray"], bytearray)
|
||||
assert test_store["memoryview"] == memoryview(b"hello")
|
||||
assert isinstance(test_store["memoryview"], memoryview)
|
||||
|
||||
|
||||
async def test_storage_clear():
|
||||
"""
|
||||
The clear method should remove all items from the storage object.
|
||||
"""
|
||||
test_store["a"] = 1
|
||||
test_store["b"] = 2
|
||||
assert len(test_store) == 2
|
||||
test_store.clear()
|
||||
assert len(test_store) == 0
|
||||
1147
core/tests/python/tests/test_web.py
Normal file
1147
core/tests/python/tests/test_web.py
Normal file
File diff suppressed because it is too large
Load Diff
99
core/tests/python/tests/test_websocket.py
Normal file
99
core/tests/python/tests/test_websocket.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Exercise the pyscript.Websocket class.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from pyscript import WebSocket
|
||||
|
||||
|
||||
async def test_websocket_with_attributes():
|
||||
"""
|
||||
Event handlers assigned via object attributes.
|
||||
|
||||
The Websocket class should be able to connect to a websocket server and
|
||||
send and receive messages.
|
||||
|
||||
Use of echo.websocket.org means:
|
||||
|
||||
1) When connecting it responds with a "Request served by" message.
|
||||
2) When sending a message it echos it back.
|
||||
"""
|
||||
connected_flag = False
|
||||
closed_flag = False
|
||||
messages = []
|
||||
ready_to_test = asyncio.Event()
|
||||
|
||||
def on_open(event):
|
||||
nonlocal connected_flag
|
||||
connected_flag = True
|
||||
ws.send("Hello, world!") # A message to echo.
|
||||
|
||||
def on_message(event):
|
||||
messages.append(event.data)
|
||||
if len(messages) == 2: # We're done.
|
||||
ws.close()
|
||||
|
||||
def on_close(event):
|
||||
nonlocal closed_flag
|
||||
closed_flag = True
|
||||
ready_to_test.set() # Finished!
|
||||
|
||||
ws = WebSocket(url="wss://echo.websocket.org")
|
||||
ws.onopen = on_open
|
||||
ws.onmessage = on_message
|
||||
ws.onclose = on_close
|
||||
# Wait for everything to be finished.
|
||||
await ready_to_test.wait()
|
||||
assert connected_flag is True
|
||||
assert len(messages) == 2
|
||||
assert "request served by" in messages[0].lower()
|
||||
assert messages[1] == "Hello, world!"
|
||||
assert closed_flag is True
|
||||
|
||||
|
||||
async def test_websocket_with_init():
|
||||
"""
|
||||
Event handlers assigned via __init__ arguments.
|
||||
|
||||
The Websocket class should be able to connect to a websocket server and
|
||||
send and receive messages.
|
||||
|
||||
Use of echo.websocket.org means:
|
||||
|
||||
1) When connecting it responds with a "Request served by" message.
|
||||
2) When sending a message it echos it back.
|
||||
"""
|
||||
connected_flag = False
|
||||
closed_flag = False
|
||||
messages = []
|
||||
ready_to_test = asyncio.Event()
|
||||
|
||||
def on_open(event):
|
||||
nonlocal connected_flag
|
||||
connected_flag = True
|
||||
ws.send("Hello, world!") # A message to echo.
|
||||
|
||||
def on_message(event):
|
||||
messages.append(event.data)
|
||||
if len(messages) == 2: # We're done.
|
||||
ws.close()
|
||||
|
||||
def on_close(event):
|
||||
nonlocal closed_flag
|
||||
closed_flag = True
|
||||
ready_to_test.set() # Finished!
|
||||
|
||||
ws = WebSocket(
|
||||
url="wss://echo.websocket.org",
|
||||
onopen=on_open,
|
||||
onmessage=on_message,
|
||||
onclose=on_close,
|
||||
)
|
||||
# Wait for everything to be finished.
|
||||
await ready_to_test.wait()
|
||||
assert connected_flag is True
|
||||
assert len(messages) == 2
|
||||
assert "request served by" in messages[0].lower()
|
||||
assert messages[1] == "Hello, world!"
|
||||
assert closed_flag is True
|
||||
216
core/tests/python/tests/test_when.py
Normal file
216
core/tests/python/tests/test_when.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Tests for the pyscript.when decorator.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import upytest
|
||||
from pyscript import RUNNING_IN_WORKER, web
|
||||
|
||||
|
||||
def get_container():
|
||||
return web.page.find("#test-element-container")[0]
|
||||
|
||||
|
||||
def setup():
|
||||
container = get_container()
|
||||
container.innerHTML = ""
|
||||
|
||||
|
||||
def teardown():
|
||||
container = get_container()
|
||||
container.innerHTML = ""
|
||||
|
||||
|
||||
async def test_when_decorator_with_event():
|
||||
"""
|
||||
When the decorated function takes a single parameter,
|
||||
it should be passed the event object
|
||||
"""
|
||||
btn = web.button("foo_button", id="foo_id")
|
||||
container = get_container()
|
||||
container.append(btn)
|
||||
|
||||
called = False
|
||||
call_flag = asyncio.Event()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
def foo(evt):
|
||||
nonlocal called
|
||||
called = evt
|
||||
call_flag.set()
|
||||
|
||||
btn.click()
|
||||
await call_flag.wait()
|
||||
assert called.target.id == "foo_id"
|
||||
|
||||
|
||||
async def test_when_decorator_without_event():
|
||||
"""
|
||||
When the decorated function takes no parameters (not including 'self'),
|
||||
it should be called without the event object
|
||||
"""
|
||||
btn = web.button("foo_button", id="foo_id")
|
||||
container = get_container()
|
||||
container.append(btn)
|
||||
|
||||
called = False
|
||||
call_flag = asyncio.Event()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
def foo():
|
||||
nonlocal called
|
||||
called = True
|
||||
call_flag.set()
|
||||
|
||||
btn.click()
|
||||
await call_flag.wait()
|
||||
assert called
|
||||
|
||||
|
||||
async def test_two_when_decorators():
|
||||
"""
|
||||
When decorating a function twice, both should function
|
||||
"""
|
||||
btn = web.button("foo_button", id="foo_id")
|
||||
container = get_container()
|
||||
container.append(btn)
|
||||
|
||||
called1 = False
|
||||
called2 = False
|
||||
call_flag1 = asyncio.Event()
|
||||
call_flag2 = asyncio.Event()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
def foo1(evt):
|
||||
nonlocal called1
|
||||
called1 = True
|
||||
call_flag1.set()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
def foo2(evt):
|
||||
nonlocal called2
|
||||
called2 = True
|
||||
call_flag2.set()
|
||||
|
||||
btn.click()
|
||||
await call_flag1.wait()
|
||||
await call_flag2.wait()
|
||||
assert called1
|
||||
assert called2
|
||||
|
||||
|
||||
async def test_two_when_decorators_same_element():
|
||||
"""
|
||||
When decorating a function twice *on the same DOM element*, both should
|
||||
function
|
||||
"""
|
||||
btn = web.button("foo_button", id="foo_id")
|
||||
container = get_container()
|
||||
container.append(btn)
|
||||
|
||||
counter = 0
|
||||
call_flag = asyncio.Event()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
@web.when("click", selector="#foo_id")
|
||||
def foo(evt):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
call_flag.set()
|
||||
|
||||
assert counter == 0, counter
|
||||
btn.click()
|
||||
await call_flag.wait()
|
||||
assert counter == 2, counter
|
||||
|
||||
|
||||
async def test_when_decorator_multiple_elements():
|
||||
"""
|
||||
The @when decorator's selector should successfully select multiple
|
||||
DOM elements
|
||||
"""
|
||||
btn1 = web.button(
|
||||
"foo_button1",
|
||||
id="foo_id1",
|
||||
classes=[
|
||||
"foo_class",
|
||||
],
|
||||
)
|
||||
btn2 = web.button(
|
||||
"foo_button2",
|
||||
id="foo_id2",
|
||||
classes=[
|
||||
"foo_class",
|
||||
],
|
||||
)
|
||||
container = get_container()
|
||||
container.append(btn1)
|
||||
container.append(btn2)
|
||||
|
||||
counter = 0
|
||||
call_flag1 = asyncio.Event()
|
||||
call_flag2 = asyncio.Event()
|
||||
|
||||
@web.when("click", selector=".foo_class")
|
||||
def foo(evt):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
if evt.target.id == "foo_id1":
|
||||
call_flag1.set()
|
||||
else:
|
||||
call_flag2.set()
|
||||
|
||||
assert counter == 0, counter
|
||||
btn1.click()
|
||||
await call_flag1.wait()
|
||||
assert counter == 1, counter
|
||||
btn2.click()
|
||||
await call_flag2.wait()
|
||||
assert counter == 2, counter
|
||||
|
||||
|
||||
async def test_when_decorator_duplicate_selectors():
|
||||
"""
|
||||
When is not idempotent, so it should be possible to add multiple
|
||||
@when decorators with the same selector.
|
||||
"""
|
||||
btn = web.button("foo_button", id="foo_id")
|
||||
container = get_container()
|
||||
container.append(btn)
|
||||
|
||||
counter = 0
|
||||
call_flag = asyncio.Event()
|
||||
|
||||
@web.when("click", selector="#foo_id")
|
||||
@web.when("click", selector="#foo_id") # duplicate
|
||||
def foo1(evt):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
call_flag.set()
|
||||
|
||||
assert counter == 0, counter
|
||||
btn.click()
|
||||
await call_flag.wait()
|
||||
assert counter == 2, counter
|
||||
|
||||
|
||||
@upytest.skip(
|
||||
"Only works in Pyodide on main thread",
|
||||
skip_when=upytest.is_micropython or RUNNING_IN_WORKER,
|
||||
)
|
||||
def test_when_decorator_invalid_selector():
|
||||
"""
|
||||
When the selector parameter of @when is invalid, it should raise an error.
|
||||
"""
|
||||
if upytest.is_micropython:
|
||||
from jsffi import JsException
|
||||
else:
|
||||
from pyodide.ffi import JsException
|
||||
|
||||
with upytest.raises(JsException) as e:
|
||||
|
||||
@web.when("click", selector="#.bad")
|
||||
def foo(evt): ...
|
||||
|
||||
assert "'#.bad' is not a valid selector" in str(e.exception), str(e.exception)
|
||||
25
core/tests/python/tests/test_window.py
Normal file
25
core/tests/python/tests/test_window.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Ensure the pyscript.window object refers to the main thread's window object.
|
||||
"""
|
||||
|
||||
import upytest
|
||||
from pyscript import RUNNING_IN_WORKER, window
|
||||
|
||||
|
||||
@upytest.skip("Main thread only.", skip_when=RUNNING_IN_WORKER)
|
||||
def test_window_in_main_thread():
|
||||
"""
|
||||
The window object should refer to the main thread's window object.
|
||||
"""
|
||||
# The window will have a document.
|
||||
assert window.document
|
||||
|
||||
|
||||
@upytest.skip("Worker only.", skip_when=not RUNNING_IN_WORKER)
|
||||
def test_window_in_worker():
|
||||
"""
|
||||
The window object should refer to the worker's self object, even though
|
||||
this code is running in a web worker.
|
||||
"""
|
||||
# The window will have a document.
|
||||
assert window.document
|
||||
Reference in New Issue
Block a user