Bring back pyweb as it was (#2105)

This commit is contained in:
Andrea Giammarchi
2024-06-21 14:49:20 +02:00
committed by GitHub
parent 15c19aa708
commit 4b90ebdef5
12 changed files with 1704 additions and 71 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@pyscript/core",
"version": "0.4.48",
"version": "0.4.50",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pyscript/core",
"version": "0.4.48",
"version": "0.4.50",
"license": "APACHE-2.0",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@pyscript/core",
"version": "0.4.48",
"version": "0.4.50",
"type": "module",
"description": "PyScript",
"module": "./index.js",

View File

@@ -19,17 +19,16 @@ def when(event_type=None, selector=None):
"""
def decorator(func):
from pyscript.web.elements import Element, ElementCollection
if isinstance(selector, str):
elements = document.querySelectorAll(selector)
else:
# TODO: This is a hack that will be removed when pyscript becomes a package
# and we can better manage the imports without circular dependencies
if isinstance(selector, Element):
from pyweb import pydom
if isinstance(selector, pydom.Element):
elements = [selector._js]
elif isinstance(selector, ElementCollection):
elif isinstance(selector, pydom.ElementCollection):
elements = [el._js for el in selector]
else:
raise ValueError(

View File

@@ -0,0 +1,2 @@
from .pydom import JSProperty
from .pydom import dom as pydom

View File

@@ -0,0 +1,95 @@
from pyodide.ffi import to_js
from pyscript import window
class Device:
"""Device represents a media input or output device, such as a microphone,
camera, or headset.
"""
def __init__(self, device):
self._js = device
@property
def id(self):
return self._js.deviceId
@property
def group(self):
return self._js.groupId
@property
def kind(self):
return self._js.kind
@property
def label(self):
return self._js.label
def __getitem__(self, key):
return getattr(self, key)
@classmethod
async def load(cls, audio=False, video=True):
"""Load the device stream."""
options = window.Object.new()
options.audio = audio
if isinstance(video, bool):
options.video = video
else:
# TODO: Think this can be simplified but need to check it on the pyodide side
# TODO: this is pyodide specific. shouldn't be!
options.video = window.Object.new()
for k in video:
setattr(
options.video,
k,
to_js(video[k], dict_converter=window.Object.fromEntries),
)
stream = await window.navigator.mediaDevices.getUserMedia(options)
return stream
async def get_stream(self):
key = self.kind.replace("input", "").replace("output", "")
options = {key: {"deviceId": {"exact": self.id}}}
return await self.load(**options)
async def list_devices() -> list[dict]:
"""
Return the list of the currently available media input and output devices,
such as microphones, cameras, headsets, and so forth.
Output:
list(dict) - list of dictionaries representing the available media devices.
Each dictionary has the following keys:
* deviceId: a string that is an identifier for the represented device
that is persisted across sessions. It is un-guessable by other
applications and unique to the origin of the calling application.
It is reset when the user clears cookies (for Private Browsing, a
different identifier is used that is not persisted across sessions).
* groupId: a string that is a group identifier. Two devices have the same
group identifier if they belong to the same physical device — for
example a monitor with both a built-in camera and a microphone.
* kind: an enumerated value that is either "videoinput", "audioinput"
or "audiooutput".
* label: a string describing this device (for example "External USB
Webcam").
Note: the returned list will omit any devices that are blocked by the document
Permission Policy: microphone, camera, speaker-selection (for output devices),
and so on. Access to particular non-default devices is also gated by the
Permissions API, and the list will omit devices for which the user has not
granted explicit permission.
"""
# https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
return [
Device(obj) for obj in await window.navigator.mediaDevices.enumerateDevices()
]

View File

@@ -0,0 +1,569 @@
import inspect
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 pyscript import display, document, window
alert = window.alert
class JSProperty:
"""JS property descriptor that directly maps to the property with the same
name in the underlying JS component."""
def __init__(self, name: str, allow_nones: bool = False):
self.name = name
self.allow_nones = allow_nones
def __get__(self, obj, objtype=None):
return getattr(obj._js, self.name)
def __set__(self, obj, value):
if not self.allow_nones and value is None:
return
setattr(obj._js, self.name, value)
class BaseElement:
def __init__(self, js_element):
self._js = js_element
self._parent = None
self.style = StyleProxy(self)
self._proxies = {}
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, BaseElement) and obj._js == self._js
@property
def parent(self):
if self._parent:
return self._parent
if self._js.parentElement:
self._parent = self.__class__(self._js.parentElement)
return self._parent
@property
def __class(self):
return self.__class__ if self.__class__ != PyDom else Element
def create(self, type_, is_child=True, classes=None, html=None, label=None):
js_el = document.createElement(type_)
element = self.__class(js_el)
if classes:
for class_ in classes:
element.add_class(class_)
if html is not None:
element.html = html
if label is not None:
element.label = label
if is_child:
self.append(element)
return element
def find(self, selector):
"""Return an ElementCollection representing all the child elements that
match the specified selector.
Args:
selector (str): A string containing a selector expression
Returns:
ElementCollection: A collection of elements matching the selector
"""
elements = self._js.querySelectorAll(selector)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
class Element(BaseElement):
@property
def children(self):
return [self.__class__(el) for el in self._js.children]
def append(self, child):
# TODO: this is Pyodide specific for now!!!!!!
# if we get passed a JSProxy Element directly we just map it to the
# higher level Python element
if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
return self.append(Element(child))
elif isinstance(child, Element):
self._js.appendChild(child._js)
return child
elif isinstance(child, ElementCollection):
for el in child:
self.append(el)
# -------- Pythonic Interface to Element -------- #
@property
def html(self):
return self._js.innerHTML
@html.setter
def html(self, value):
self._js.innerHTML = value
@property
def text(self):
return self._js.textContent
@text.setter
def text(self, value):
self._js.textContent = value
@property
def content(self):
# TODO: This breaks with with standard template elements. Define how to best
# handle this specifica use case. Just not support for now?
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return None
return self._js.innerHTML
@content.setter
def content(self, value):
# TODO: (same comment as above)
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return
display(value, target=self.id)
@property
def id(self):
return self._js.id
@id.setter
def id(self, value):
self._js.id = value
@property
def options(self):
if "options" in self._proxies:
return self._proxies["options"]
if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
raise AttributeError(
f"Element {self._js.tagName} has no options attribute."
)
self._proxies["options"] = OptionsProxy(self)
return self._proxies["options"]
@property
def value(self):
return self._js.value
@value.setter
def value(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "value"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.value = value
@property
def selected(self):
return self._js.selected
@selected.setter
def selected(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "selected"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.selected = value
def clone(self, new_id=None):
clone = Element(self._js.cloneNode(True))
clone.id = new_id
return clone
def remove_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.remove(*classname)
else:
classList.remove(classname)
return self
def add_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.add(*classname)
else:
self._js.classList.add(classname)
return self
@property
def classes(self):
classes = self._js.classList.values()
return [x for x in classes]
def show_me(self):
self._js.scrollIntoView()
def snap(
self,
to: BaseElement | str = None,
width: int | None = None,
height: int | None = None,
):
"""
Captures a snapshot of a video element. (Only available for video elements)
Inputs:
* to: element where to save the snapshot of the video frame to
* width: width of the image
* height: height of the image
Output:
(Element) canvas element where the video frame snapshot was drawn into
"""
if self._js.tagName != "VIDEO":
raise AttributeError("Snap method is only available for video Elements")
if to is None:
canvas = self.create("canvas")
if width is None:
width = self._js.width
if height is None:
height = self._js.height
canvas._js.width = width
canvas._js.height = height
elif isinstance(to, Element):
if to._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a canvas.")
canvas = to
elif getattr(to, "tagName", "") == "CANVAS":
canvas = Element(to)
elif isinstance(to, str):
canvas = pydom[to][0]
if canvas._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a be canvas.")
canvas.draw(self, width, height)
return canvas
def download(self, filename: str = "snapped.png") -> None:
"""Download the current element (only available for canvas elements) with the filename
provided in input.
Inputs:
* filename (str): name of the file being downloaded
Output:
None
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The download method is only available for canvas Elements"
)
link = self.create("a")
link._js.download = filename
link._js.href = self._js.toDataURL()
link._js.click()
def draw(self, what, width, height):
"""Draw `what` on the current element (only available for canvas elements).
Inputs:
* what (canvas image source): An element to draw into the context. The specification permits any canvas
image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The draw method is only available for canvas Elements"
)
if isinstance(what, Element):
what = what._js
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
self._js.getContext("2d").drawImage(what, 0, 0, width, height)
class OptionsProxy:
"""This class represents the options of a select element. It
allows to access to add and remove options by using the `add` and `remove` methods.
"""
def __init__(self, element: Element) -> None:
self._element = element
if self._element._js.tagName.lower() != "select":
raise AttributeError(
f"Element {self._element._js.tagName} has no options attribute."
)
def add(
self,
value: Any = None,
html: str = None,
text: str = None,
before: Element | int = None,
**kws,
) -> None:
"""Add a new option to the select element"""
# create the option element and set the attributes
option = document.createElement("option")
if value is not None:
kws["value"] = value
if html is not None:
option.innerHTML = html
if text is not None:
kws["text"] = text
for key, value in kws.items():
option.setAttribute(key, value)
if before:
if isinstance(before, Element):
before = before._js
self._element._js.add(option, before)
def remove(self, item: int) -> None:
"""Remove the option at the specified index"""
self._element._js.remove(item)
def clear(self) -> None:
"""Remove all the options"""
for i in range(len(self)):
self.remove(0)
@property
def options(self):
"""Return the list of options"""
return [Element(opt) for opt in self._element._js.options]
@property
def selected(self):
"""Return the selected option"""
return self.options[self._element._js.selectedIndex]
def __iter__(self):
yield from self.options
def __len__(self):
return len(self.options)
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
def __getitem__(self, key):
return self.options[key]
class StyleProxy: # (dict):
def __init__(self, element: Element) -> None:
self._element = element
@cached_property
def _style(self):
return self._element._js.style
def __getitem__(self, key):
return self._style.getPropertyValue(key)
def __setitem__(self, key, value):
self._style.setProperty(key, value)
def remove(self, key):
self._style.removeProperty(key)
def set(self, **kws):
for k, v in kws.items():
self._element._js.style.setProperty(k, v)
# CSS Properties
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
# Following prperties automatically generated from the above reference using
# tools/codegen_css_proxy.py
@property
def visible(self):
return self._element._js.style.visibility
@visible.setter
def visible(self, value):
self._element._js.style.visibility = value
class StyleCollection:
def __init__(self, collection: "ElementCollection") -> None:
self._collection = collection
def __get__(self, obj, objtype=None):
return obj._get_attribute("style")
def __getitem__(self, key):
return self._collection._get_attribute("style")[key]
def __setitem__(self, key, value):
for element in self._collection._elements:
element.style[key] = value
def remove(self, key):
for element in self._collection._elements:
element.style.remove(key)
class ElementCollection:
def __init__(self, elements: [Element]) -> None:
self._elements = elements
self.style = StyleCollection(self)
def __getitem__(self, key):
# If it's an integer we use it to access the elements in the collection
if isinstance(key, int):
return self._elements[key]
# If it's a slice we use it to support slice operations over the elements
# in the collection
elif isinstance(key, slice):
return ElementCollection(self._elements[key])
# If it's anything else (basically a string) we use it as a selector
# TODO: Write tests!
elements = self._element.querySelectorAll(key)
return ElementCollection([Element(el) for el in elements])
def __len__(self):
return len(self._elements)
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, ElementCollection) and obj._elements == self._elements
def _get_attribute(self, attr, index=None):
if index is None:
return [getattr(el, attr) for el in self._elements]
# As JQuery, when getting an attr, only return it for the first element
return getattr(self._elements[index], attr)
def _set_attribute(self, attr, value):
for el in self._elements:
setattr(el, attr, value)
@property
def html(self):
return self._get_attribute("html")
@html.setter
def html(self, value):
self._set_attribute("html", value)
@property
def value(self):
return self._get_attribute("value")
@value.setter
def value(self, value):
self._set_attribute("value", value)
@property
def children(self):
return self._elements
def __iter__(self):
yield from self._elements
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
class DomScope:
def __getattr__(self, __name: str):
element = document[f"#{__name}"]
if element:
return element[0]
class PyDom(BaseElement):
# Add objects we want to expose to the DOM namespace since this class instance is being
# remapped as "the module" itself
BaseElement = BaseElement
Element = Element
ElementCollection = ElementCollection
def __init__(self):
# 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)
def create(self, type_, classes=None, html=None):
return super().create(type_, is_child=False, classes=classes, html=html)
def __getitem__(self, key):
elements = self._js.querySelectorAll(key)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
dom = PyDom()

View File

@@ -0,0 +1 @@
from . import elements

View File

@@ -0,0 +1,947 @@
import inspect
import sys
from pyscript import document, when, window
from pyweb import JSProperty, pydom
#: A flag to show if MicroPython is the current Python interpreter.
is_micropython = "MicroPython" in sys.version
def getmembers_static(cls):
"""Cross-interpreter implementation of inspect.getmembers_static."""
if is_micropython: # pragma: no cover
return [(name, getattr(cls, name)) for name, _ in inspect.getmembers(cls)]
return inspect.getmembers_static(cls)
class ElementBase(pydom.Element):
tag = "div"
# GLOBAL ATTRIBUTES
# These are attribute that all elements have (this list is a subset of the official one)
# We are trying to capture the most used ones
accesskey = JSProperty("accesskey")
autofocus = JSProperty("autofocus")
autocapitalize = JSProperty("autocapitalize")
className = JSProperty("className")
contenteditable = JSProperty("contenteditable")
draggable = JSProperty("draggable")
enterkeyhint = JSProperty("enterkeyhint")
hidden = JSProperty("hidden")
id = JSProperty("id")
lang = JSProperty("lang")
nonce = JSProperty("nonce")
part = JSProperty("part")
popover = JSProperty("popover")
slot = JSProperty("slot")
spellcheck = JSProperty("spellcheck")
tabindex = JSProperty("tabindex")
title = JSProperty("title")
translate = JSProperty("translate")
virtualkeyboardpolicy = JSProperty("virtualkeyboardpolicy")
def __init__(self, style=None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if isinstance(style, dict):
for key, value in style.items():
self.style[key] = value
elif style is None:
pass
else:
raise ValueError(
f"Style should be a dictionary, received {style} (type {type(style)}) instead."
)
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
self._init_properties(**kwargs)
def _init_properties(self, **kwargs):
"""Set all the properties (of type JSProperties) provided in input as properties
of the class instance.
Args:
**kwargs: The properties to set
"""
# Look at all the properties of the class and see if they were provided in kwargs
for attr_name, attr in getmembers_static(self.__class__):
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
try:
setattr(self, attr_name, kwargs[attr_name])
except Exception as e:
print(f"Error setting {attr_name} to {kwargs[attr_name]}: {e}")
raise
class TextElementBase(ElementBase):
def __init__(self, content=None, style=None, **kwargs):
super().__init__(style=style, **kwargs)
# If it's an element, append the element
if isinstance(content, pydom.Element):
self.append(content)
# If it's a list of elements
elif isinstance(content, list):
for item in content:
self.append(item)
# If the content wasn't set just ignore
elif content is None:
pass
else:
# Otherwise, set content as the html of the element
self.html = content
# IMPORTANT: For all HTML components defined below, we are not mapping all
# available attributes, just the global and the most common ones.
# If you need to access a specific attribute, you can always use the `_js.<attribute>`
class a(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""
tag = "a"
download = JSProperty("download")
href = JSProperty("href")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
target = JSProperty("target")
type = JSProperty("type")
class abbr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
tag = "abbr"
class address(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
tag = "address"
class area(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
tag = "area"
alt = JSProperty("alt")
coords = JSProperty("coords")
download = JSProperty("download")
href = JSProperty("href")
ping = JSProperty("ping")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
shape = JSProperty("shape")
target = JSProperty("target")
class article(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
tag = "article"
class aside(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
tag = "aside"
class audio(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
tag = "audio"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
controlslist = JSProperty("controlslist")
crossorigin = JSProperty("crossorigin")
disableremoteplayback = JSProperty("disableremoteplayback")
loop = JSProperty("loop")
muted = JSProperty("muted")
preload = JSProperty("preload")
src = JSProperty("src")
class b(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
tag = "b"
class blockquote(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
tag = "blockquote"
cite = JSProperty("cite")
class br(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
tag = "br"
class button(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
tag = "button"
autofocus = JSProperty("autofocus")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
name = JSProperty("name")
type = JSProperty("type")
value = JSProperty("value")
class canvas(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
tag = "canvas"
height = JSProperty("height")
width = JSProperty("width")
class caption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""
tag = "caption"
class cite(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""
tag = "cite"
class code(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
tag = "code"
class data(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""
tag = "data"
value = JSProperty("value")
class datalist(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""
tag = "datalist"
class dd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
tag = "dd"
class del_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
tag = "del"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class details(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
tag = "details"
open = JSProperty("open")
class dialog(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
tag = "dialog"
open = JSProperty("open")
class div(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
tag = "div"
class dl(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
tag = "dl"
value = JSProperty("value")
class dt(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
tag = "dt"
class em(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
tag = "em"
class embed(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
tag = "embed"
height = JSProperty("height")
src = JSProperty("src")
type = JSProperty("type")
width = JSProperty("width")
class fieldset(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
tag = "fieldset"
disabled = JSProperty("disabled")
form = JSProperty("form")
name = JSProperty("name")
class figcaption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
tag = "figcaption"
class figure(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
tag = "figure"
class footer(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
tag = "footer"
class form(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
tag = "form"
accept_charset = JSProperty("accept-charset")
action = JSProperty("action")
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
enctype = JSProperty("enctype")
name = JSProperty("name")
method = JSProperty("method")
nonvalidate = JSProperty("nonvalidate")
rel = JSProperty("rel")
target = JSProperty("target")
class h1(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
tag = "h1"
class h2(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
tag = "h2"
class h3(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
tag = "h3"
class h4(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
tag = "h4"
class h5(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
tag = "h5"
class h6(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
tag = "h6"
class header(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
tag = "header"
class hgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
tag = "hgroup"
class hr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
tag = "hr"
class i(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
tag = "i"
class iframe(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
tag = "iframe"
allow = JSProperty("allow")
allowfullscreen = JSProperty("allowfullscreen")
height = JSProperty("height")
loading = JSProperty("loading")
name = JSProperty("name")
referrerpolicy = JSProperty("referrerpolicy")
sandbox = JSProperty("sandbox")
src = JSProperty("src")
srcdoc = JSProperty("srcdoc")
width = JSProperty("width")
class img(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
tag = "img"
alt = JSProperty("alt")
crossorigin = JSProperty("crossorigin")
decoding = JSProperty("decoding")
fetchpriority = JSProperty("fetchpriority")
height = JSProperty("height")
ismap = JSProperty("ismap")
loading = JSProperty("loading")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
src = JSProperty("src")
width = JSProperty("width")
# NOTE: Input is a reserved keyword in Python, so we use input_ instead
class input_(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
tag = "input"
accept = JSProperty("accept")
alt = JSProperty("alt")
autofocus = JSProperty("autofocus")
capture = JSProperty("capture")
checked = JSProperty("checked")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
height = JSProperty("height")
list = JSProperty("list")
max = JSProperty("max")
maxlength = JSProperty("maxlength")
min = JSProperty("min")
minlength = JSProperty("minlength")
multiple = JSProperty("multiple")
name = JSProperty("name")
pattern = JSProperty("pattern")
placeholder = JSProperty("placeholder")
popovertarget = JSProperty("popovertarget")
popovertargetaction = JSProperty("popovertargetaction")
readonly = JSProperty("readonly")
required = JSProperty("required")
size = JSProperty("size")
src = JSProperty("src")
step = JSProperty("step")
type = JSProperty("type")
value = JSProperty("value")
width = JSProperty("width")
class ins(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
tag = "ins"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class kbd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
tag = "kbd"
class label(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
tag = "label"
for_ = JSProperty("for")
class legend(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
tag = "legend"
class li(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
tag = "li"
value = JSProperty("value")
class link(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
tag = "link"
as_ = JSProperty("as")
crossorigin = JSProperty("crossorigin")
disabled = JSProperty("disabled")
fetchpriority = JSProperty("fetchpriority")
href = JSProperty("href")
imagesizes = JSProperty("imagesizes")
imagesrcset = JSProperty("imagesrcset")
integrity = JSProperty("integrity")
media = JSProperty("media")
rel = JSProperty("rel")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
title = JSProperty("title")
type = JSProperty("type")
class main(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
tag = "main"
class map_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
tag = "map"
name = JSProperty("name")
class mark(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
tag = "mark"
class menu(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
tag = "menu"
class meter(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
tag = "meter"
form = JSProperty("form")
high = JSProperty("high")
low = JSProperty("low")
max = JSProperty("max")
min = JSProperty("min")
optimum = JSProperty("optimum")
value = JSProperty("value")
class nav(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
tag = "nav"
class object_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
tag = "object"
data = JSProperty("data")
form = JSProperty("form")
height = JSProperty("height")
name = JSProperty("name")
type = JSProperty("type")
usemap = JSProperty("usemap")
width = JSProperty("width")
class ol(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""
tag = "ol"
reversed = JSProperty("reversed")
start = JSProperty("start")
type = JSProperty("type")
class optgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""
tag = "optgroup"
disabled = JSProperty("disabled")
label = JSProperty("label")
class option(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
tag = "option"
disabled = JSProperty("value")
label = JSProperty("label")
selected = JSProperty("selected")
value = JSProperty("value")
class output(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
tag = "output"
for_ = JSProperty("for")
form = JSProperty("form")
name = JSProperty("name")
class p(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
tag = "p"
class picture(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
tag = "picture"
class pre(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
tag = "pre"
class progress(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
tag = "progress"
max = JSProperty("max")
value = JSProperty("value")
class q(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
tag = "q"
cite = JSProperty("cite")
class s(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
tag = "s"
class script(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
tag = "script"
# Let's add async manually since it's a reserved keyword in Python
async_ = JSProperty("async")
blocking = JSProperty("blocking")
crossorigin = JSProperty("crossorigin")
defer = JSProperty("defer")
fetchpriority = JSProperty("fetchpriority")
integrity = JSProperty("integrity")
nomodule = JSProperty("nomodule")
nonce = JSProperty("nonce")
referrerpolicy = JSProperty("referrerpolicy")
src = JSProperty("src")
type = JSProperty("type")
class section(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""
tag = "section"
class select(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""
tag = "select"
class small(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
tag = "small"
class source(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
tag = "source"
media = JSProperty("media")
sizes = JSProperty("sizes")
src = JSProperty("src")
srcset = JSProperty("srcset")
type = JSProperty("type")
class span(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
tag = "span"
class strong(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
tag = "strong"
class style(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
tag = "style"
blocking = JSProperty("blocking")
media = JSProperty("media")
nonce = JSProperty("nonce")
title = JSProperty("title")
class sub(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
tag = "sub"
class summary(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
tag = "summary"
class sup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
tag = "sup"
class table(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
tag = "table"
class tbody(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
tag = "tbody"
class td(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
tag = "td"
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
class template(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
tag = "template"
shadowrootmode = JSProperty("shadowrootmode")
class textarea(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
tag = "textarea"
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
autofocus = JSProperty("autofocus")
cols = JSProperty("cols")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
maxlength = JSProperty("maxlength")
minlength = JSProperty("minlength")
name = JSProperty("name")
placeholder = JSProperty("placeholder")
readonly = JSProperty("readonly")
required = JSProperty("required")
rows = JSProperty("rows")
spellcheck = JSProperty("spellcheck")
wrap = JSProperty("wrap")
class tfoot(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
tag = "tfoot"
class th(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
tag = "th"
class thead(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
tag = "thead"
class time(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
tag = "time"
datetime = JSProperty("datetime")
class title(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
tag = "title"
class tr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
tag = "tr"
abbr = JSProperty("abbr")
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
scope = JSProperty("scope")
class track(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
tag = "track"
default = JSProperty("default")
kind = JSProperty("kind")
label = JSProperty("label")
src = JSProperty("src")
srclang = JSProperty("srclang")
class u(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
tag = "u"
class ul(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
tag = "ul"
class var(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
tag = "var"
class video(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
tag = "video"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
crossorigin = JSProperty("crossorigin")
disablepictureinpicture = JSProperty("disablepictureinpicture")
disableremoteplayback = JSProperty("disableremoteplayback")
height = JSProperty("height")
loop = JSProperty("loop")
muted = JSProperty("muted")
playsinline = JSProperty("playsinline")
poster = JSProperty("poster")
preload = JSProperty("preload")
src = JSProperty("src")
width = JSProperty("width")
# Custom Elements
class grid(TextElementBase):
tag = "div"
def __init__(self, layout, content=None, gap=None, **kwargs):
super().__init__(content, **kwargs)
self.style["display"] = "grid"
self.style["grid-template-columns"] = layout
# TODO: This should be a property
if not gap is None:
self.style["gap"] = gap

View File

@@ -4,7 +4,7 @@ import time
from datetime import datetime as dt
from pyscript import display, when
from pyscript.web import dom
from pyweb import pydom
display(sys.version, target="system-info")
@@ -19,15 +19,18 @@ def on_click():
tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}"
timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:])
display(f"Hello from PyScript, time is: {timenow}", append=False, target="#result")
display(f"Hello from PyScript, time is: {timenow}", append=False, target="result")
@when("click", "#color-button")
def on_color_click(event):
btn = dom["#result"]
btn = pydom["#result"]
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
@when("click", "#color-reset-button")
def reset_color(*args, **kwargs):
dom["#result"].style["background-color"] = "white"
pydom["#result"].style["background-color"] = "white"
# btn_reset = pydom["#color-reset-button"][0].when('click', reset_color)

View File

@@ -2,13 +2,22 @@ from unittest import mock
import pytest
from pyscript import document, when
from pyscript.web import dom
from pyscript.web import elements as el
from pyweb import pydom
class TestDocument:
def test__element(self):
assert dom._js == document
assert pydom._js == document
def test_no_parent(self):
assert pydom.parent is None
def test_create_element(self):
new_el = pydom.create("div")
assert isinstance(new_el, pydom.BaseElement)
assert new_el._js.tagName == "DIV"
# EXPECT the new element to be associated with the document
assert new_el.parent == None
def test_getitem_by_id():
@@ -17,14 +26,14 @@ def test_getitem_by_id():
txt = "You found test_id_selector"
selector = f"#{id_}"
# EXPECT the element to be found by id
result = dom[selector]
result = pydom[selector]
div = result[0]
# EXPECT the element text value to match what we expect and what
# the JS document.querySelector API would return
assert document.querySelector(selector).innerHTML == div.html == txt
# EXPECT the results to be of the right types
assert isinstance(div, el.BaseElement)
assert isinstance(result, dom.ElementCollection)
assert isinstance(div, pydom.BaseElement)
assert isinstance(result, pydom.ElementCollection)
def test_getitem_by_class():
@@ -34,7 +43,7 @@ def test_getitem_by_class():
"test_selector_w_children_child_1",
]
expected_class = "a-test-class"
result = dom[f".{expected_class}"]
result = pydom[f".{expected_class}"]
div = result[0]
# EXPECT to find exact number of elements with the class in the page (== 3)
@@ -45,7 +54,7 @@ def test_getitem_by_class():
def test_read_n_write_collection_elements():
elements = dom[".multi-elems"]
elements = pydom[".multi-elems"]
for element in elements:
assert element.html == f"Content {element.id.replace('#', '')}"
@@ -60,15 +69,15 @@ class TestElement:
def test_query(self):
# GIVEN an existing element on the page, with at least 1 child element
id_ = "test_selector_w_children"
parent_div = dom[f"#{id_}"][0]
parent_div = pydom[f"#{id_}"][0]
# EXPECT it to be able to query for the first child element
div = parent_div.find("div")[0]
# EXPECT the new element to be associated with the parent
assert div.parent == parent_div
# EXPECT the new element to be a el.BaseElement
assert isinstance(div, el.BaseElement)
# EXPECT the new element to be a BaseElement
assert isinstance(div, pydom.BaseElement)
# EXPECT the div attributes to be == to how they are configured in the page
assert div.html == "Child 1"
assert div.id == "test_selector_w_children_child_1"
@@ -77,8 +86,8 @@ class TestElement:
# GIVEN 2 different Elements pointing to the same underlying element
id_ = "test_id_selector"
selector = f"#{id_}"
div = dom[selector][0]
div2 = dom[selector][0]
div = pydom[selector][0]
div2 = pydom[selector][0]
# EXPECT them to be equal
assert div == div2
@@ -93,27 +102,27 @@ class TestElement:
def test_append_element(self):
id_ = "element-append-tests"
div = dom[f"#{id_}"][0]
div = pydom[f"#{id_}"][0]
len_children_before = len(div.children)
new_el = el.p("new element")
new_el = div.create("p")
div.append(new_el)
assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el
def test_append_js_element(self):
id_ = "element-append-tests"
div = dom[f"#{id_}"][0]
div = pydom[f"#{id_}"][0]
len_children_before = len(div.children)
new_el = el.p("new element")
new_el = div.create("p")
div.append(new_el._js)
assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el
def test_append_collection(self):
id_ = "element-append-tests"
div = dom[f"#{id_}"][0]
div = pydom[f"#{id_}"][0]
len_children_before = len(div.children)
collection = dom[".collection"]
collection = pydom[".collection"]
div.append(collection)
assert len(div.children) == len_children_before + len(collection)
@@ -123,16 +132,16 @@ class TestElement:
def test_read_classes(self):
id_ = "test_class_selector"
expected_class = "a-test-class"
div = dom[f"#{id_}"][0]
div = pydom[f"#{id_}"][0]
assert div.classes == [expected_class]
def test_add_remove_class(self):
id_ = "div-no-classes"
classname = "tester-class"
div = dom[f"#{id_}"][0]
div = pydom[f"#{id_}"][0]
assert not div.classes
div.add_class(classname)
same_div = dom[f"#{id_}"][0]
same_div = pydom[f"#{id_}"][0]
assert div.classes == [classname] == same_div.classes
div.remove_class(classname)
assert div.classes == [] == same_div.classes
@@ -140,7 +149,7 @@ class TestElement:
def test_when_decorator(self):
called = False
just_a_button = dom["#a-test-button"][0]
just_a_button = pydom["#a-test-button"][0]
@when("click", just_a_button)
def on_click(event):
@@ -148,7 +157,7 @@ class TestElement:
called = True
# Now let's simulate a click on the button (using the low level JS API)
# so we don't risk dom getting in the way
# so we don't risk pydom getting in the way
assert not called
just_a_button._js.click()
@@ -156,7 +165,7 @@ class TestElement:
def test_html_attribute(self):
# GIVEN an existing element on the page with a known empty text content
div = dom["#element_attribute_tests"][0]
div = pydom["#element_attribute_tests"][0]
# WHEN we set the html attribute
div.html = "<b>New Content</b>"
@@ -168,7 +177,7 @@ class TestElement:
def test_text_attribute(self):
# GIVEN an existing element on the page with a known empty text content
div = dom["#element_attribute_tests"][0]
div = pydom["#element_attribute_tests"][0]
# WHEN we set the html attribute
div.text = "<b>New Content</b>"
@@ -181,12 +190,12 @@ class TestElement:
class TestCollection:
def test_iter_eq_children(self):
elements = dom[".multi-elems"]
elements = pydom[".multi-elems"]
assert [el for el in elements] == [el for el in elements.children]
assert len(elements) == 3
def test_slices(self):
elements = dom[".multi-elems"]
elements = pydom[".multi-elems"]
assert elements[0]
_slice = elements[:2]
assert len(_slice) == 2
@@ -196,26 +205,26 @@ class TestCollection:
def test_style_rule(self):
selector = ".multi-elems"
elements = dom[selector]
elements = pydom[selector]
for el in elements:
assert el.style["background-color"] != "red"
elements.style["background-color"] = "red"
for i, el in enumerate(dom[selector]):
for i, el in enumerate(pydom[selector]):
assert elements[i].style["background-color"] == "red"
assert el.style["background-color"] == "red"
elements.style.remove("background-color")
for i, el in enumerate(dom[selector]):
for i, el in enumerate(pydom[selector]):
assert el.style["background-color"] != "red"
assert elements[i].style["background-color"] != "red"
def test_when_decorator(self):
called = False
buttons_collection = dom["button"]
buttons_collection = pydom["button"]
@when("click", buttons_collection)
def on_click(event):
@@ -223,7 +232,7 @@ class TestCollection:
called = True
# Now let's simulate a click on the button (using the low level JS API)
# so we don't risk dom getting in the way
# so we don't risk pydom getting in the way
assert not called
for button in buttons_collection:
button._js.click()
@@ -233,32 +242,32 @@ class TestCollection:
class TestCreation:
def test_create_document_element(self):
# TODO: This test should probably be removed since it's testing the elements module
new_el = el.div("new element")
new_el = pydom.create("div")
new_el.id = "new_el_id"
assert isinstance(new_el, el.BaseElement)
assert isinstance(new_el, pydom.BaseElement)
assert new_el._js.tagName == "DIV"
# EXPECT the new element to be associated with the document
assert new_el.parent == None
dom.body.append(new_el)
pydom.body.append(new_el)
assert dom["#new_el_id"][0].parent == dom.body
assert pydom["#new_el_id"][0].parent == pydom.body
def test_create_element_child(self):
selector = "#element-creation-test"
parent_div = dom[selector][0]
parent_div = pydom[selector][0]
# Creating an element from another element automatically creates that element
# as a child of the original element
new_el = el.p("a div", classes=["code-description"], html="Ciao PyScripters!")
parent_div.append(new_el)
new_el = parent_div.create(
"p", classes=["code-description"], html="Ciao PyScripters!"
)
assert isinstance(new_el, el.BaseElement)
assert isinstance(new_el, pydom.BaseElement)
assert new_el._js.tagName == "P"
# EXPECT the new element to be associated with the document
assert new_el.parent == parent_div
assert dom[selector][0].children[0] == new_el
assert pydom[selector][0].children[0] == new_el
class TestInput:
@@ -272,7 +281,7 @@ class TestInput:
def test_value(self):
for id_ in self.input_ids:
expected_type = id_.split("_")[-1]
result = dom[f"#{id_}"]
result = pydom[f"#{id_}"]
input_el = result[0]
assert input_el._js.type == expected_type
assert input_el.value == f"Content {id_}" == input_el._js.value
@@ -290,7 +299,7 @@ class TestInput:
def test_set_value_collection(self):
for id_ in self.input_ids:
input_el = dom[f"#{id_}"]
input_el = pydom[f"#{id_}"]
assert input_el.value[0] == f"Content {id_}" == input_el[0].value
@@ -299,35 +308,35 @@ class TestInput:
assert input_el.value[0] == new_value == input_el[0].value
def test_element_without_value(self):
result = dom[f"#tests-terminal"][0]
result = pydom[f"#tests-terminal"][0]
with pytest.raises(AttributeError):
result.value = "some value"
def test_element_without_collection(self):
result = dom[f"#tests-terminal"]
result = pydom[f"#tests-terminal"]
with pytest.raises(AttributeError):
result.value = "some value"
def test_element_without_collection(self):
result = dom[f"#tests-terminal"]
result = pydom[f"#tests-terminal"]
with pytest.raises(AttributeError):
result.value = "some value"
class TestSelect:
def test_select_options_iter(self):
select = dom[f"#test_select_element_w_options"][0]
select = pydom[f"#test_select_element_w_options"][0]
for i, option in enumerate(select.options, 1):
assert option.value == f"{i}"
assert option.html == f"Option {i}"
def test_select_options_len(self):
select = dom[f"#test_select_element_w_options"][0]
select = pydom[f"#test_select_element_w_options"][0]
assert len(select.options) == 2
def test_select_options_clear(self):
select = dom[f"#test_select_element_to_clear"][0]
select = pydom[f"#test_select_element_to_clear"][0]
assert len(select.options) == 3
select.options.clear()
@@ -336,7 +345,7 @@ class TestSelect:
def test_select_element_add(self):
# GIVEN the existing select element with no options
select = dom[f"#test_select_element"][0]
select = pydom[f"#test_select_element"][0]
# EXPECT the select element to have no options
assert len(select.options) == 0
@@ -417,7 +426,7 @@ class TestSelect:
def test_select_options_remove(self):
# GIVEN the existing select element with 3 options
select = dom[f"#test_select_element_to_remove"][0]
select = pydom[f"#test_select_element_to_remove"][0]
# EXPECT the select element to have 3 options
assert len(select.options) == 4
@@ -439,7 +448,7 @@ class TestSelect:
def test_select_get_selected_option(self):
# GIVEN the existing select element with one selected option
select = dom[f"#test_select_element_w_options"][0]
select = pydom[f"#test_select_element_w_options"][0]
# WHEN we get the selected option
selected_option = select.options.selected

View File

@@ -101,11 +101,10 @@ class TestElements(PyScriptTest):
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript.web import dom
from pyscript.web.elements import {el_type}
from pyweb import pydom
from pyweb.ui.elements import {el_type}
el = {el_type}({attributes})
dom.body.append(el)
pydom.body.append(el)
</script>
"""
self.pyscript_run(code_)

View File

@@ -17,5 +17,14 @@ declare namespace _default {
};
"websocket.py": string;
};
let pyweb: {
"__init__.py": string;
"media.py": string;
"pydom.py": string;
ui: {
"__init__.py": string;
"elements.py": string;
};
};
}
export default _default;