diff --git a/pyscript.core/src/stdlib/pyscript/__init__.py b/pyscript.core/src/stdlib/pyscript/__init__.py index 45d8e84f..dcd42f62 100644 --- a/pyscript.core/src/stdlib/pyscript/__init__.py +++ b/pyscript.core/src/stdlib/pyscript/__init__.py @@ -43,6 +43,8 @@ from pyscript.magic_js import ( try: from pyscript.event_handling import when except: + # TODO: should we remove this? Or at the very least, we should capture + # the traceback otherwise it's very hard to debug from pyscript.util import NotSupported when = NotSupported( diff --git a/pyscript.core/src/stdlib/pyscript/event_handling.py b/pyscript.core/src/stdlib/pyscript/event_handling.py index 3fac89ea..23f930ce 100644 --- a/pyscript.core/src/stdlib/pyscript/event_handling.py +++ b/pyscript.core/src/stdlib/pyscript/event_handling.py @@ -1,6 +1,14 @@ import inspect -from pyodide.ffi.wrappers import add_event_listener +try: + from pyodide.ffi.wrappers import add_event_listener + +except ImportError: + + def add_event_listener(el, event_type, func): + el.addEventListener(event_type, func) + + from pyscript.magic_js import document @@ -27,19 +35,32 @@ def when(event_type=None, selector=None): f"Invalid selector: {selector}. Selector must" " be a string, a pydom.Element or a pydom.ElementCollection." ) + try: + sig = inspect.signature(func) + # Function doesn't receive events + if not sig.parameters: - sig = inspect.signature(func) - # Function doesn't receive events - if not sig.parameters: + def wrapper(*args, **kwargs): + func() + else: + wrapper = func + + except AttributeError: + # TODO: this is currently an quick hack to get micropython working but we need + # to actually properly replace inspect.signature with something else def wrapper(*args, **kwargs): - func() + try: + return func(*args, **kwargs) + except TypeError as e: + if "takes 0 positional arguments" in str(e): + return func() + + raise + + for el in elements: + add_event_listener(el, event_type, wrapper) - for el in elements: - add_event_listener(el, event_type, wrapper) - else: - for el in elements: - add_event_listener(el, event_type, func) return func return decorator diff --git a/pyscript.core/src/stdlib/pyweb/__init__.py b/pyscript.core/src/stdlib/pyweb/__init__.py new file mode 100644 index 00000000..0a5b12ff --- /dev/null +++ b/pyscript.core/src/stdlib/pyweb/__init__.py @@ -0,0 +1 @@ +from .pydom import dom as pydom diff --git a/pyscript.core/src/stdlib/pyweb/pydom.py b/pyscript.core/src/stdlib/pyweb/pydom.py index 2eadcc81..a9110faa 100644 --- a/pyscript.core/src/stdlib/pyweb/pydom.py +++ b/pyscript.core/src/stdlib/pyweb/pydom.py @@ -1,9 +1,34 @@ -import sys -import warnings -from functools import cached_property -from typing import Any +try: + from typing import Any +except ImportError: + Any = "Any" + +try: + import warnings +except ImportError: + # TODO: For now it probably means we are in MicroPython. We should figure + # out the "right" way to handle this. For now we just ignore the warning + # and logging to console + class warnings: + @staticmethod + def warn(*args, **kwargs): + print("WARNING: ", *args, **kwargs) + + +try: + from functools import cached_property +except ImportError: + # TODO: same comment about micropython as above + cached_property = property + +try: + from pyodide.ffi import JsProxy +except ImportError: + # TODO: same comment about micropython as above + def JsProxy(obj): + return obj + -from pyodide.ffi import JsProxy from pyscript import display, document, window alert = window.alert @@ -361,7 +386,7 @@ class OptionsProxy: return self.options[key] -class StyleProxy(dict): +class StyleProxy: # (dict): def __init__(self, element: Element) -> None: self._element = element @@ -480,7 +505,7 @@ class ElementCollection: class DomScope: - def __getattr__(self, __name: str) -> Any: + def __getattr__(self, __name: str): element = document[f"#{__name}"] if element: return element[0] @@ -494,7 +519,12 @@ class PyDom(BaseElement): ElementCollection = ElementCollection def __init__(self): - super().__init__(document) + # PyDom is a special case of BaseElement where we don't want to create a new JS element + # and it really doesn't have a need for styleproxy or parent to to call to __init__ + # (which actually fails in MP for some reason) + self._js = document + self._parent = None + self._proxies = {} self.ids = DomScope() self.body = Element(document.body) self.head = Element(document.head) @@ -503,10 +533,6 @@ class PyDom(BaseElement): return super().create(type_, is_child=False, classes=classes, html=html) def __getitem__(self, key): - if isinstance(key, int): - indices = range(*key.indices(len(self.list))) - return [self.list[i] for i in indices] - elements = self._js.querySelectorAll(key) if not elements: return None @@ -514,5 +540,3 @@ class PyDom(BaseElement): dom = PyDom() - -sys.modules[__name__] = dom diff --git a/pyscript.core/test/pydom.html b/pyscript.core/test/pydom.html index ff647381..cc959de4 100644 --- a/pyscript.core/test/pydom.html +++ b/pyscript.core/test/pydom.html @@ -3,7 +3,7 @@
-You can pass test parameters to this test suite by passing them as query params on the url. diff --git a/pyscript.core/test/pyscript_dom/tests/test_dom.py b/pyscript.core/test/pyscript_dom/tests/test_dom.py index 3172e1d9..49c96a5a 100644 --- a/pyscript.core/test/pyscript_dom/tests/test_dom.py +++ b/pyscript.core/test/pyscript_dom/tests/test_dom.py @@ -336,7 +336,7 @@ class TestSelect: assert select.options[0].html == "Option 1" # WHEN we add another option (blank this time) - select.options.add() + select.options.add("") # EXPECT the select element to have 2 options assert len(select.options) == 2 diff --git a/pyscript.core/tests/integration/support.py b/pyscript.core/tests/integration/support.py index 4cc40db7..b9aadf83 100644 --- a/pyscript.core/tests/integration/support.py +++ b/pyscript.core/tests/integration/support.py @@ -17,6 +17,7 @@ from playwright.sync_api import Error as PlaywrightError ROOT = py.path.local(__file__).dirpath("..", "..", "..") BUILD = ROOT.join("pyscript.core").join("dist") +TEST = ROOT.join("pyscript.core").join("test") def params_with_marks(params): @@ -206,6 +207,14 @@ class PyScriptTest: self.tmpdir = tmpdir # create a symlink to BUILD inside tmpdir tmpdir.join("build").mksymlinkto(BUILD) + # create a symlink ALSO to dist folder so we can run the tests in + # the test folder + tmpdir.join("dist").mksymlinkto(BUILD) + # create a symlink to TEST inside tmpdir so we can run tests in that + # manual test folder + tmpdir.join("test").mksymlinkto(TEST) + + # create a symlink to the favicon, so that we can use it in the HTML self.tmpdir.chdir() self.tmpdir.join("favicon.ico").write("") self.logger = logger diff --git a/pyscript.core/tests/integration/test_integration.py b/pyscript.core/tests/integration/test_integration.py new file mode 100644 index 00000000..4b9b0282 --- /dev/null +++ b/pyscript.core/tests/integration/test_integration.py @@ -0,0 +1,30 @@ +from .support import PyScriptTest, with_execution_thread + + +@with_execution_thread(None) +class TestSmokeTests(PyScriptTest): + """ + Each example requires the same three tests: + + - Test that the initial markup loads properly (currently done by + testing the