Compare commits

...

10 Commits

Author SHA1 Message Date
Andrea Giammarchi
0d0ea96435 Defaulting to async for top-level await (#2134) 2024-08-05 15:55:53 +02:00
Andrea Giammarchi
fafdf74007 Fixed async methods attached to window (#2136) 2024-08-05 11:58:22 +02:00
Martin
999897df12 The all-new, pyscript.web (ignore the branch name :) ) (#2129)
* Minor cleanups: move all Element classes to bottom of module.

* Commenting.

* Commenting.

* Commenting.

* Group dunder methods.

* Don't cache the element's parent.

* Remove style type check until we decide whether or not to add for classes too.

* Add ability to register/unregister element classes.

* Implement __iter__ for container elements.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Minor renaming to make it clear when we have an Element instance vs an actual DOM element.

* remove duplication: added Element.get_tag_name

* Commenting.

* Allow Element.append to 1) use *args, 2) accept iterables

* Remove iterable check - inteferes with js proxies.

* Don't use *args, so it quacks more like a list ;)

* Element.append take 2 :)

* Remove unused code.

* Move to web.py with a page object!

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added 'page.title' too :)

* Add __getitem__ as a shortcut for page.find

* Add Element.__getitem__ to be consistent

* Make __getitem__ consistent for Page, Element and ElementCollection.

* Docstringing.

* Docstringing.

* Docstringing/commenting.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix select.add (revert InnerHTML->html)

* Commenting.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Hand-edit some of the AI :)

* Rename ElementCollection.children -> ElementCollection.elements

* Remove unnecessary guard.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-01 10:36:57 +01:00
Andrea Giammarchi
d47fb58ede Update Pyodide to its 0.26.2 version (#2133) 2024-07-31 20:34:21 +02:00
Andrea Giammarchi
f316341e73 Updated Polyscript and added Panel worker test (#2130)
* Updated Polyscript and added Panel worker test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-31 14:31:35 +02:00
Andrea Giammarchi
8c46fcabf7 Updating polyscript to its latest (#2128)
* Updating polyscript to its latest
2024-07-29 16:59:31 +02:00
Martin
e4ff4d8fab Controversial version where Element just delegates to the underlying DOM element. (#2127)
* Update elements.py

* remove grid which allows simpler class tag mapping.
2024-07-24 13:27:12 -05:00
Martin
f20a0003ed fix: broken methods video.snap and canvas.download (#2126)
* fix: broken methods video.snap and canvas.download

* Allow canvas.draw to use actual image width/height.
2024-07-23 15:35:07 -05:00
Martin
6c938dfe3b Override __getattr__ and __setattr__ on ElementCollection. (#2116)
* Override __getattr__ and __setattr__ on ElementCollection.

* fix: bug when using a string to query an ElementCollection.

* Use Element.find when indexing ElementCollection with a string.

* For consistency also have a find method on ElementCollection.

* ElementCollection.find now returns a collection of collections :)

* fix tests: for textContent

* Revert to extend for ElementCollection.find :)

* Make element_from_dom a classmethod Element.from_dom

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* rename: Element.from_dom -> Element.from_dom_element

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* PyCharm warning sweep.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Workaround for mp not allowing setting via __dict__

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-22 10:32:37 -05:00
Andrea Giammarchi
d884586a82 Updated Polyscript to its latest (#2124) 2024-07-19 12:21:04 +02:00
28 changed files with 1580 additions and 2538 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.4.55", "version": "0.5.3",
"type": "module", "type": "module",
"description": "PyScript", "description": "PyScript",
"module": "./index.js", "module": "./index.js",
@@ -44,7 +44,7 @@
"dependencies": { "dependencies": {
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.13.8", "polyscript": "^0.15.0",
"sticky-module": "^0.1.1", "sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1", "to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7" "type-checked-collections": "^0.1.7"
@@ -54,24 +54,24 @@
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.1.6",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.10.2",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.28.3", "@codemirror/view": "^6.30.0",
"@playwright/test": "^1.45.1", "@playwright/test": "^1.45.3",
"@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@webreflection/toml-j0.4": "^1.1.3", "@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.17", "bun": "^1.1.21",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"eslint": "^9.6.0", "eslint": "^9.8.0",
"flatted": "^3.3.1", "flatted": "^3.3.1",
"rollup": "^4.18.0", "rollup": "^4.20.0",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3", "static-handler": "^0.4.3",
"typescript": "^5.5.3", "typescript": "^5.5.4",
"xterm": "^5.3.0", "xterm": "^5.3.0",
"xterm-readline": "^1.1.1" "xterm-readline": "^1.1.1"
}, },

Binary file not shown.

View File

@@ -12,6 +12,7 @@ import {
define, define,
defineProperty, defineProperty,
dispatch, dispatch,
isSync,
queryTarget, queryTarget,
unescape, unescape,
whenDefined, whenDefined,
@@ -202,15 +203,13 @@ for (const [TYPE, interpreter] of TYPES) {
} }
if (isScript(element)) { if (isScript(element)) {
const { const isAsync = !isSync(element);
attributes: { async: isAsync, target }, const target = element.getAttribute("target");
} = element; const show = target
const hasTarget = !!target?.value; ? queryTarget(element, target)
const show = hasTarget
? queryTarget(element, target.value)
: document.createElement("script-py"); : document.createElement("script-py");
if (!hasTarget) { if (!target) {
const { head, body } = document; const { head, body } = document;
if (head.contains(element)) body.append(show); if (head.contains(element)) body.append(show);
else element.after(show); else element.after(show);
@@ -331,7 +330,7 @@ for (const [TYPE, interpreter] of TYPES) {
async connectedCallback() { async connectedCallback() {
if (!this.executed) { if (!this.executed) {
this.executed = true; this.executed = true;
const isAsync = this.hasAttribute("async"); const isAsync = !isSync(this);
const { io, run, runAsync } = await this._wrap const { io, run, runAsync } = await this._wrap
.promise; .promise;
this.srcCode = await fetchSource( this.srcCode = await fetchSource(

View File

@@ -34,7 +34,10 @@ async function execute({ currentTarget }) {
if (!envs.has(env)) { if (!envs.has(env)) {
const srcLink = URL.createObjectURL(new Blob([""])); const srcLink = URL.createObjectURL(new Blob([""]));
const details = { type: this.interpreter }; const details = {
type: this.interpreter,
serviceWorker: this.serviceWorker,
};
const { config } = this; const { config } = this;
if (config) { if (config) {
details.configURL = relative_url(config); details.configURL = relative_url(config);
@@ -163,8 +166,17 @@ const init = async (script, type, interpreter) => {
let isSetup = script.hasAttribute("setup"); let isSetup = script.hasAttribute("setup");
const hasConfig = script.hasAttribute("config"); const hasConfig = script.hasAttribute("config");
const serviceWorker = script.getAttribute("service-worker");
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`; const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
// helps preventing too lazy ServiceWorker initialization on button run
if (serviceWorker) {
new XWorker("data:application/javascript,postMessage(0)", {
type: "dummy",
serviceWorker,
}).onmessage = ({ target }) => target.terminate();
}
if (hasConfig && configs.has(env)) { if (hasConfig && configs.has(env)) {
throw new SyntaxError( throw new SyntaxError(
configs.get(env) configs.get(env)
@@ -181,6 +193,7 @@ const init = async (script, type, interpreter) => {
const context = { const context = {
// allow the listener to be overridden at distance // allow the listener to be overridden at distance
handleEvent: execute, handleEvent: execute,
serviceWorker,
interpreter, interpreter,
env, env,
config: hasConfig && script.getAttribute("config"), config: hasConfig && script.getAttribute("config"),

View File

@@ -20,7 +20,7 @@ def when(event_type=None, selector=None):
def decorator(func): def decorator(func):
from pyscript.web.elements import Element, ElementCollection from pyscript.web import Element, ElementCollection
if isinstance(selector, str): if isinstance(selector, str):
elements = document.querySelectorAll(selector) elements = document.querySelectorAll(selector)

View File

@@ -36,7 +36,6 @@ if RUNNING_IN_WORKER:
) )
try: try:
globalThis.SharedArrayBuffer.new(4)
import js import js
window = polyscript.xworker.window window = polyscript.xworker.window
@@ -47,17 +46,11 @@ if RUNNING_IN_WORKER:
"return (...urls) => Promise.all(urls.map((url) => import(url)))" "return (...urls) => Promise.all(urls.map((url) => import(url)))"
)() )()
except: except:
globalThis.console.debug("SharedArrayBuffer is not available") message = "Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
# in this scenario none of the utilities would work globalThis.console.warn(message)
# as expected so we better export these as NotSupported window = NotSupported("pyscript.window", message)
window = NotSupported( document = NotSupported("pyscript.document", message)
"pyscript.window", js_import = None
"pyscript.window in workers works only via SharedArrayBuffer",
)
document = NotSupported(
"pyscript.document",
"pyscript.document in workers works only via SharedArrayBuffer",
)
sync = polyscript.xworker.sync sync = polyscript.xworker.sync

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
from pyscript import document
from pyscript.web.elements import ElementCollection, element_from_dom
class DOM:
def __init__(self):
self.body = element_from_dom(document.body)
self.head = element_from_dom(document.head)
def __getitem__(self, selector):
return self.find(selector)
def find(self, selector):
return ElementCollection(
[element_from_dom(el) for el in document.querySelectorAll(selector)]
)
dom = DOM()

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,17 @@
<script type="module" src="../dist/core.js"></script> <script type="module" src="../dist/core.js"></script>
</head> </head>
<body> <body>
<py-script async> <py-script>
import asyncio import asyncio
print('foo') print('py-script sleep')
await asyncio.sleep(1) await asyncio.sleep(1)
print('bar') print('py-script done')
</py-script> </py-script>
<script type="py">
import asyncio
print('script-py sleep')
await asyncio.sleep(1)
print('script-py done')
</script>
</body> </body>
</html> </html>

View File

@@ -48,12 +48,12 @@
</script> </script>
</head> </head>
<body> <body>
<script type="mpy" worker> <script type="mpy" async="false" worker>
from pyscript import document from pyscript import document
print("actual code in worker") print("actual code in worker")
document.documentElement.classList.add('worker') document.documentElement.classList.add('worker')
</script> </script>
<script type="mpy"> <script type="mpy" async="false">
print("actual code in main") print("actual code in main")
</script> </script>
</body> </body>

View File

@@ -0,0 +1,4 @@
packages = [
"https://cdn.holoviz.org/panel/wheels/bokeh-3.5.0-py3-none-any.whl",
"https://cdn.holoviz.org/panel/1.5.0-b.2/dist/wheels/panel-1.5.0b2-py3-none-any.whl"
]

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../../dist/core.css">
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.5.0.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.5.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.5.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@holoviz/panel@1.5.0-b.2/dist/panel.min.js"></script>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py" src="main.py" config="config.toml" worker></script>
<div id="simple_app"></div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import panel as pn
pn.extension(sizing_mode="stretch_width")
slider = pn.widgets.FloatSlider(start=0, end=10, name="amplitude")
def callback(new):
return f"Amplitude is: {new}"
pn.Row(slider, pn.bind(callback, slider)).servable(target="simple_app")

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy-editor" service-worker="./sw.js">
from pyscript import document
document.body.append("OK")
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);

View File

@@ -32,7 +32,7 @@
</style> </style>
</head> </head>
<body> <body>
<script type="py" src="./run_tests.py" config="./tests.toml"></script> <script type="py" src="/test/pyscript_dom/run_tests.py" config="/test/pyscript_dom/tests.toml"></script>
<h1>pyscript.dom Tests</h1> <h1>pyscript.dom Tests</h1>
<p>You can pass test parameters to this test suite by passing them as query params on the url. <p>You can pass test parameters to this test suite by passing them as query params on the url.

View File

@@ -1,13 +1,11 @@
from pyscript import document, when from pyscript import document, when
from pyscript.web import dom from pyscript.web import Element, ElementCollection, div, p, page
from pyscript.web import elements as el
from pyscript.web.elements import ElementCollection
class TestDocument: class TestDocument:
def test__element(self): def test__element(self):
assert dom.body._dom_element == document.body assert page.body._dom_element == document.body
assert dom.head._dom_element == document.head assert page.head._dom_element == document.head
def test_getitem_by_id(): def test_getitem_by_id():
@@ -16,13 +14,13 @@ def test_getitem_by_id():
txt = "You found test_id_selector" txt = "You found test_id_selector"
selector = f"#{id_}" selector = f"#{id_}"
# EXPECT the element to be found by id # EXPECT the element to be found by id
result = dom.find(selector) result = page.find(selector)
div = result[0] div = result[0]
# EXPECT the element text value to match what we expect and what # EXPECT the element text value to match what we expect and what
# the JS document.querySelector API would return # the JS document.querySelector API would return
assert document.querySelector(selector).innerHTML == div.innerHTML == txt assert document.querySelector(selector).innerHTML == div.innerHTML == txt
# EXPECT the results to be of the right types # EXPECT the results to be of the right types
assert isinstance(div, el.Element) assert isinstance(div, Element)
assert isinstance(result, ElementCollection) assert isinstance(result, ElementCollection)
@@ -33,8 +31,7 @@ def test_getitem_by_class():
"test_selector_w_children_child_1", "test_selector_w_children_child_1",
] ]
expected_class = "a-test-class" expected_class = "a-test-class"
result = dom.find(f".{expected_class}") result = page.find(f".{expected_class}")
div = result[0]
# EXPECT to find exact number of elements with the class in the page (== 3) # EXPECT to find exact number of elements with the class in the page (== 3)
assert len(result) == 3 assert len(result) == 3
@@ -44,7 +41,7 @@ def test_getitem_by_class():
def test_read_n_write_collection_elements(): def test_read_n_write_collection_elements():
elements = dom.find(".multi-elems") elements = page.find(".multi-elems")
for element in elements: for element in elements:
assert element.innerHTML == f"Content {element.id.replace('#', '')}" assert element.innerHTML == f"Content {element.id.replace('#', '')}"
@@ -59,15 +56,15 @@ class TestElement:
def test_query(self): def test_query(self):
# GIVEN an existing element on the page, with at least 1 child element # GIVEN an existing element on the page, with at least 1 child element
id_ = "test_selector_w_children" id_ = "test_selector_w_children"
parent_div = dom.find(f"#{id_}")[0] parent_div = page.find(f"#{id_}")[0]
# EXPECT it to be able to query for the first child element # EXPECT it to be able to query for the first child element
div = parent_div.find("div")[0] div = parent_div.find("div")[0]
# EXPECT the new element to be associated with the parent # EXPECT the new element to be associated with the parent
assert div.parent == parent_div assert div.parent == parent_div
# EXPECT the new element to be a el.Element # EXPECT the new element to be an Element
assert isinstance(div, el.Element) assert isinstance(div, Element)
# EXPECT the div attributes to be == to how they are configured in the page # EXPECT the div attributes to be == to how they are configured in the page
assert div.innerHTML == "Child 1" assert div.innerHTML == "Child 1"
assert div.id == "test_selector_w_children_child_1" assert div.id == "test_selector_w_children_child_1"
@@ -76,8 +73,8 @@ class TestElement:
# GIVEN 2 different Elements pointing to the same underlying element # GIVEN 2 different Elements pointing to the same underlying element
id_ = "test_id_selector" id_ = "test_id_selector"
selector = f"#{id_}" selector = f"#{id_}"
div = dom.find(selector)[0] div = page.find(selector)[0]
div2 = dom.find(selector)[0] div2 = page.find(selector)[0]
# EXPECT them to be equal # EXPECT them to be equal
assert div == div2 assert div == div2
@@ -92,27 +89,27 @@ class TestElement:
def test_append_element(self): def test_append_element(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = dom.find(f"#{id_}")[0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
new_el = el.p("new element") new_el = p("new element")
div.append(new_el) div.append(new_el)
assert len(div.children) == len_children_before + 1 assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el assert div.children[-1] == new_el
def test_append_dom_element_element(self): def test_append_dom_element_element(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = dom.find(f"#{id_}")[0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
new_el = el.p("new element") new_el = p("new element")
div.append(new_el._dom_element) div.append(new_el._dom_element)
assert len(div.children) == len_children_before + 1 assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el assert div.children[-1] == new_el
def test_append_collection(self): def test_append_collection(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = dom.find(f"#{id_}")[0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
collection = dom.find(".collection") collection = page.find(".collection")
div.append(collection) div.append(collection)
assert len(div.children) == len_children_before + len(collection) assert len(div.children) == len_children_before + len(collection)
@@ -122,16 +119,16 @@ class TestElement:
def test_read_classes(self): def test_read_classes(self):
id_ = "test_class_selector" id_ = "test_class_selector"
expected_class = "a-test-class" expected_class = "a-test-class"
div = dom.find(f"#{id_}")[0] div = page.find(f"#{id_}")[0]
assert div.classes == [expected_class] assert div.classes == [expected_class]
def test_add_remove_class(self): def test_add_remove_class(self):
id_ = "div-no-classes" id_ = "div-no-classes"
classname = "tester-class" classname = "tester-class"
div = dom.find(f"#{id_}")[0] div = page.find(f"#{id_}")[0]
assert not div.classes assert not div.classes
div.classes.add(classname) div.classes.add(classname)
same_div = dom.find(f"#{id_}")[0] same_div = page.find(f"#{id_}")[0]
assert div.classes == [classname] == same_div.classes assert div.classes == [classname] == same_div.classes
div.classes.remove(classname) div.classes.remove(classname)
assert div.classes == [] == same_div.classes assert div.classes == [] == same_div.classes
@@ -139,7 +136,7 @@ class TestElement:
def test_when_decorator(self): def test_when_decorator(self):
called = False called = False
just_a_button = dom.find("#a-test-button")[0] just_a_button = page.find("#a-test-button")[0]
@when("click", just_a_button) @when("click", just_a_button)
def on_click(event): def on_click(event):
@@ -153,9 +150,9 @@ class TestElement:
assert called assert called
def test_html_attribute(self): def test_inner_html_attribute(self):
# GIVEN an existing element on the page with a known empty text content # GIVEN an existing element on the page with a known empty text content
div = dom.find("#element_attribute_tests")[0] div = page.find("#element_attribute_tests")[0]
# WHEN we set the html attribute # WHEN we set the html attribute
div.innerHTML = "<b>New Content</b>" div.innerHTML = "<b>New Content</b>"
@@ -163,14 +160,14 @@ class TestElement:
# EXPECT the element html and underlying JS Element innerHTML property # EXPECT the element html and underlying JS Element innerHTML property
# to match what we expect and what # to match what we expect and what
assert div.innerHTML == div._dom_element.innerHTML == "<b>New Content</b>" assert div.innerHTML == div._dom_element.innerHTML == "<b>New Content</b>"
assert div.text == div._dom_element.textContent == "New Content" assert div.textContent == div._dom_element.textContent == "New Content"
def test_text_attribute(self): def test_text_attribute(self):
# GIVEN an existing element on the page with a known empty text content # GIVEN an existing element on the page with a known empty text content
div = dom.find("#element_attribute_tests")[0] div = page.find("#element_attribute_tests")[0]
# WHEN we set the html attribute # WHEN we set the html attribute
div.text = "<b>New Content</b>" div.textContent = "<b>New Content</b>"
# EXPECT the element html and underlying JS Element innerHTML property # EXPECT the element html and underlying JS Element innerHTML property
# to match what we expect and what # to match what we expect and what
@@ -179,17 +176,17 @@ class TestElement:
== div._dom_element.innerHTML == div._dom_element.innerHTML
== "&lt;b&gt;New Content&lt;/b&gt;" == "&lt;b&gt;New Content&lt;/b&gt;"
) )
assert div.text == div._dom_element.textContent == "<b>New Content</b>" assert div.textContent == div._dom_element.textContent == "<b>New Content</b>"
class TestCollection: class TestCollection:
def test_iter_eq_children(self): def test_iter_eq_children(self):
elements = dom.find(".multi-elems") elements = page.find(".multi-elems")
assert [el for el in elements] == [el for el in elements.children] assert [el for el in elements] == [el for el in elements.elements]
assert len(elements) == 3 assert len(elements) == 3
def test_slices(self): def test_slices(self):
elements = dom.find(".multi-elems") elements = page.find(".multi-elems")
assert elements[0] assert elements[0]
_slice = elements[:2] _slice = elements[:2]
assert len(_slice) == 2 assert len(_slice) == 2
@@ -199,26 +196,26 @@ class TestCollection:
def test_style_rule(self): def test_style_rule(self):
selector = ".multi-elems" selector = ".multi-elems"
elements = dom.find(selector) elements = page.find(selector)
for el in elements: for el in elements:
assert el.style["background-color"] != "red" assert el.style["background-color"] != "red"
elements.style["background-color"] = "red" elements.style["background-color"] = "red"
for i, el in enumerate(dom.find(selector)): for i, el in enumerate(page.find(selector)):
assert elements[i].style["background-color"] == "red" assert elements[i].style["background-color"] == "red"
assert el.style["background-color"] == "red" assert el.style["background-color"] == "red"
elements.style.remove("background-color") elements.style.remove("background-color")
for i, el in enumerate(dom.find(selector)): for i, el in enumerate(page.find(selector)):
assert el.style["background-color"] != "red" assert el.style["background-color"] != "red"
assert elements[i].style["background-color"] != "red" assert elements[i].style["background-color"] != "red"
def test_when_decorator(self): def test_when_decorator(self):
called = False called = False
buttons_collection = dom.find("button") buttons_collection = page.find("button")
@when("click", buttons_collection) @when("click", buttons_collection)
def on_click(event): def on_click(event):
@@ -236,34 +233,33 @@ class TestCollection:
class TestCreation: class TestCreation:
def test_create_document_element(self): def test_create_document_element(self):
# TODO: This test should probably be removed since it's testing the elements module # TODO: This test should probably be removed since it's testing the elements
new_el = el.div("new element") # module.
new_el = div("new element")
new_el.id = "new_el_id" new_el.id = "new_el_id"
assert isinstance(new_el, el.Element) assert isinstance(new_el, Element)
assert new_el._dom_element.tagName == "DIV" assert new_el._dom_element.tagName == "DIV"
# EXPECT the new element to be associated with the document # EXPECT the new element to be associated with the document
assert new_el.parent == None assert new_el.parent is None
dom.body.append(new_el) page.body.append(new_el)
assert dom.find("#new_el_id")[0].parent == dom.body assert page.find("#new_el_id")[0].parent == page.body
def test_create_element_child(self): def test_create_element_child(self):
selector = "#element-creation-test" selector = "#element-creation-test"
parent_div = dom.find(selector)[0] parent_div = page.find(selector)[0]
# Creating an element from another element automatically creates that element # Creating an element from another element automatically creates that element
# as a child of the original element # as a child of the original element
new_el = el.p( new_el = p("a div", classes=["code-description"], innerHTML="Ciao PyScripters!")
"a div", classes=["code-description"], innerHTML="Ciao PyScripters!"
)
parent_div.append(new_el) parent_div.append(new_el)
assert isinstance(new_el, el.Element) assert isinstance(new_el, Element)
assert new_el._dom_element.tagName == "P" assert new_el._dom_element.tagName == "P"
# EXPECT the new element to be associated with the document # EXPECT the new element to be associated with the document
assert new_el.parent == parent_div assert new_el.parent == parent_div
assert dom.find(selector)[0].children[0] == new_el assert page.find(selector)[0].children[0] == new_el
class TestInput: class TestInput:
@@ -277,7 +273,7 @@ class TestInput:
def test_value(self): def test_value(self):
for id_ in self.input_ids: for id_ in self.input_ids:
expected_type = id_.split("_")[-1] expected_type = id_.split("_")[-1]
result = dom.find(f"#{id_}") result = page.find(f"#{id_}")
input_el = result[0] input_el = result[0]
assert input_el._dom_element.type == expected_type assert input_el._dom_element.type == expected_type
assert input_el.value == f"Content {id_}" == input_el._dom_element.value assert input_el.value == f"Content {id_}" == input_el._dom_element.value
@@ -295,7 +291,7 @@ class TestInput:
def test_set_value_collection(self): def test_set_value_collection(self):
for id_ in self.input_ids: for id_ in self.input_ids:
input_el = dom.find(f"#{id_}") input_el = page.find(f"#{id_}")
assert input_el.value[0] == f"Content {id_}" == input_el[0].value assert input_el.value[0] == f"Content {id_}" == input_el[0].value
@@ -308,30 +304,30 @@ class TestInput:
# actually on the class. Maybe a job for __setattr__? # actually on the class. Maybe a job for __setattr__?
# #
# def test_element_without_value(self): # def test_element_without_value(self):
# result = dom.find(f"#tests-terminal"][0] # result = page.find(f"#tests-terminal"][0]
# with pytest.raises(AttributeError): # with pytest.raises(AttributeError):
# result.value = "some value" # result.value = "some value"
# #
# def test_element_without_value_via_collection(self): # def test_element_without_value_via_collection(self):
# result = dom.find(f"#tests-terminal"] # result = page.find(f"#tests-terminal"]
# with pytest.raises(AttributeError): # with pytest.raises(AttributeError):
# result.value = "some value" # result.value = "some value"
class TestSelect: class TestSelect:
def test_select_options_iter(self): def test_select_options_iter(self):
select = dom.find(f"#test_select_element_w_options")[0] select = page.find(f"#test_select_element_w_options")[0]
for i, option in enumerate(select.options, 1): for i, option in enumerate(select.options, 1):
assert option.value == f"{i}" assert option.value == f"{i}"
assert option.innerHTML == f"Option {i}" assert option.innerHTML == f"Option {i}"
def test_select_options_len(self): def test_select_options_len(self):
select = dom.find(f"#test_select_element_w_options")[0] select = page.find(f"#test_select_element_w_options")[0]
assert len(select.options) == 2 assert len(select.options) == 2
def test_select_options_clear(self): def test_select_options_clear(self):
select = dom.find(f"#test_select_element_to_clear")[0] select = page.find(f"#test_select_element_to_clear")[0]
assert len(select.options) == 3 assert len(select.options) == 3
select.options.clear() select.options.clear()
@@ -340,7 +336,7 @@ class TestSelect:
def test_select_element_add(self): def test_select_element_add(self):
# GIVEN the existing select element with no options # GIVEN the existing select element with no options
select = dom.find(f"#test_select_element")[0] select = page.find(f"#test_select_element")[0]
# EXPECT the select element to have no options # EXPECT the select element to have no options
assert len(select.options) == 0 assert len(select.options) == 0
@@ -431,7 +427,7 @@ class TestSelect:
def test_select_options_remove(self): def test_select_options_remove(self):
# GIVEN the existing select element with 3 options # GIVEN the existing select element with 3 options
select = dom.find(f"#test_select_element_to_remove")[0] select = page.find(f"#test_select_element_to_remove")[0]
# EXPECT the select element to have 3 options # EXPECT the select element to have 3 options
assert len(select.options) == 4 assert len(select.options) == 4
@@ -453,7 +449,7 @@ class TestSelect:
def test_select_get_selected_option(self): def test_select_get_selected_option(self):
# GIVEN the existing select element with one selected option # GIVEN the existing select element with one selected option
select = dom.find(f"#test_select_element_w_options")[0] select = page.find(f"#test_select_element_w_options")[0]
# WHEN we get the selected option # WHEN we get the selected option
selected_option = select.options.selected selected_option = select.options.selected

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Service Worker</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" service-worker="./sabayon.js" worker>
from pyscript import document
document.body.append('OK')
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
(({ document: d, navigator: { serviceWorker: s } }) => {
if (d) {
const { currentScript: c } = d;
s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
r.addEventListener('updatefound', () => location.reload());
if (r.active && !s.controller) location.reload();
});
}
else {
addEventListener('install', () => skipWaiting());
addEventListener('activate', e => e.waitUntil(clients.claim()));
addEventListener('fetch', e => {
const { request: r } = e;
if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
e.respondWith(fetch(r).then(r => {
const { body, status, statusText } = r;
if (!status || status > 399) return r;
const h = new Headers(r.headers);
h.set('Cross-Origin-Opener-Policy', 'same-origin');
h.set('Cross-Origin-Embedder-Policy', 'require-corp');
h.set('Cross-Origin-Resource-Policy', 'cross-origin');
return new Response(body, { status, statusText, headers: h });
}));
});
}
})(self);

View File

@@ -0,0 +1 @@
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>named workers</title>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from pyscript import workers
await (await workers["mpy"]).greetings()
await (await workers["py"]).greetings()
</script>
<script type="mpy" worker name="mpy">
def greetings():
print("micropython")
__export__ = ['greetings']
</script>
<script type="py" worker name="py">
def greetings():
print("pyodide")
__export__ = ['greetings']
</script>
</body>
</html>

View File

@@ -97,7 +97,7 @@ class TestBasic(PyScriptTest):
def test_input_exception(self): def test_input_exception(self):
self.pyscript_run( self.pyscript_run(
""" """
<script type="py"> <script type="py" async="false">
input("what's your name?") input("what's your name?")
</script> </script>
""" """

View File

@@ -43,12 +43,12 @@ class TestDisplay(PyScriptTest):
def test_consecutive_display(self): def test_consecutive_display(self):
self.pyscript_run( self.pyscript_run(
""" """
<script type="py"> <script type="py" async="false">
from pyscript import display from pyscript import display
display('hello 1') display('hello 1')
</script> </script>
<p>hello 2</p> <p>hello 2</p>
<script type="py"> <script type="py" async="false">
from pyscript import display from pyscript import display
display('hello 3') display('hello 3')
</script> </script>
@@ -177,16 +177,16 @@ class TestDisplay(PyScriptTest):
def test_consecutive_display_target(self): def test_consecutive_display_target(self):
self.pyscript_run( self.pyscript_run(
""" """
<script type="py" id="first"> <script type="py" id="first" async="false">
from pyscript import display from pyscript import display
display('hello 1') display('hello 1')
</script> </script>
<p>hello in between 1 and 2</p> <p>hello in between 1 and 2</p>
<script type="py" id="second"> <script type="py" id="second" async="false">
from pyscript import display from pyscript import display
display('hello 2', target="second") display('hello 2', target="second")
</script> </script>
<script type="py" id="third"> <script type="py" id="third" async="false">
from pyscript import display from pyscript import display
display('hello 3') display('hello 3')
</script> </script>

View File

@@ -101,11 +101,9 @@ class TestElements(PyScriptTest):
code_ = f""" code_ = f"""
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyscript.web import dom from pyscript.web import page, {el_type}
from pyscript.web.elements import {el_type}
el = {el_type}({attributes}) el = {el_type}({attributes})
dom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)
@@ -620,13 +618,12 @@ class TestElements(PyScriptTest):
code_ = f""" code_ = f"""
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyscript.web import dom from pyscript.web import page, div, p
from pyscript.web.elements import div, p
el = div("{div_text_content}") el = div("{div_text_content}")
child = p('{p_text_content}') child = p('{p_text_content}')
el.append(child) el.append(child)
dom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)
@@ -664,14 +661,13 @@ class TestElements(PyScriptTest):
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyscript import document from pyscript import document
from pyscript.web import dom from pyscript.web import page, div, p
from pyscript.web.elements import div, p
el = div("{div_text_content}") el = div("{div_text_content}")
child = document.createElement('P') child = document.createElement('P')
child.textContent = '{p_text_content}' child.textContent = '{p_text_content}'
el.append(child) el.append(child)
dom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)
@@ -709,15 +705,14 @@ class TestElements(PyScriptTest):
code_ = f""" code_ = f"""
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyscript.web import dom from pyscript.web import page, div, p, ElementCollection
from pyscript.web.elements import div, p, ElementCollection
el = div("{div_text_content}") el = div("{div_text_content}")
child1 = p('{p_text_content}') child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2') child2 = p('{p2_text_content}', id='child2')
collection = ElementCollection([child1, child2]) collection = ElementCollection([child1, child2])
el.append(collection) el.append(collection)
dom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)
@@ -765,20 +760,19 @@ class TestElements(PyScriptTest):
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyscript import document from pyscript import document
from pyscript.web import dom from pyscript.web import page, div, p, ElementCollection
from pyscript.web.elements import div, p, ElementCollection
el = div("{div_text_content}") el = div("{div_text_content}")
child1 = p('{p_text_content}') child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2') child2 = p('{p2_text_content}', id='child2')
dom.body.append(child1) page.body.append(child1)
dom.body.append(child2) page.body.append(child2)
nodes = document.querySelectorAll('p') nodes = document.querySelectorAll('p')
el.append(nodes) el.append(nodes)
dom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)

View File

@@ -54,5 +54,5 @@ declare const exportedHooks: {
}; };
}; };
declare const exportedConfig: {}; declare const exportedConfig: {};
declare const exportedWhenDefined: (type: string) => Promise<any>; declare const exportedWhenDefined: (type: string) => Promise<object>;
export { stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined }; export { stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };

View File

@@ -9,10 +9,7 @@ declare namespace _default {
"magic_js.py": string; "magic_js.py": string;
"storage.py": string; "storage.py": string;
"util.py": string; "util.py": string;
web: { "web.py": string;
"__init__.py": string;
"elements.py": string;
};
"websocket.py": string; "websocket.py": string;
"workers.py": string; "workers.py": string;
}; };