Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions idom/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,17 @@
WEB_MODULES = CLIENT_DIR / "web_modules"


def core_module(name: str) -> str:
path = f"../{CORE_MODULES.name}/{name}.js"
if not core_module_exists(name):
raise ValueError(f"Module '{path}' does not exist.")
return path


def core_module_exists(name: str) -> bool:
return _find_module_os_path(CORE_MODULES, name) is not None


def web_module(name: str) -> str:
path = f"../{WEB_MODULES.name}/{name}.js"
if not web_module_exists(name):
raise ValueError(f"Module '{path}' does not exist.")
return path


def web_module_path(name: str) -> Optional[Path]:
return _find_module_os_path(WEB_MODULES, name)


def web_module_exists(name: str) -> bool:
return _find_module_os_path(WEB_MODULES, name) is not None

Expand Down
17 changes: 10 additions & 7 deletions idom/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def __iter__(self) -> Iterator[str]:
def __getitem__(self, key: str) -> "EventHandler":
return self._handlers[key]

def __repr__(self) -> str:
def __repr__(self) -> str: # pragma: no cover
return repr(self._handlers)


Expand Down Expand Up @@ -193,11 +193,6 @@ def remove(self, function: EventHandlerFunction) -> None:
"""
self._handlers.remove(function)

async def __call__(self, data: List[Any]) -> Any:
"""Trigger all callbacks in the event handler."""
for handler in self._handlers:
await handler(*data)

def serialize(self) -> Dict[str, Any]:
"""Serialize the event handler."""
return {
Expand All @@ -206,5 +201,13 @@ def serialize(self) -> Dict[str, Any]:
"stopPropagation": self._stop_propogation,
}

def __repr__(self) -> str:
async def __call__(self, data: List[Any]) -> Any:
"""Trigger all callbacks in the event handler."""
for handler in self._handlers:
await handler(*data)

def __contains__(self, function: Any) -> bool:
return function in self._handlers

def __repr__(self) -> str: # pragma: no cover
return f"{type(self).__name__}({self.serialize()})"
32 changes: 17 additions & 15 deletions idom/widgets/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ class Input(Generic[_InputType], AbstractElement):
"""

__slots__ = (
"_type",
"_value",
"_cast",
"_display_value",
"_label",
"_ignore_empty",
"_events",
"_attributes",
Expand All @@ -48,26 +48,23 @@ def __init__(
value: _InputType = "", # type: ignore
attributes: Optional[Dict[str, Any]] = None,
cast: Callable[[str], _InputType] = _pass_through,
label: Optional[str] = None,
ignore_empty: bool = True,
) -> None:
super().__init__()
self._type = type
self._value = value
self._display_value = str(value)
self._cast = cast
self._label = label
self._ignore_empty = ignore_empty
self._events = Events()
self._attributes = attributes or {}
self._attributes["type"] = type
self_ref = ref(self)

@self._events.on("change")
async def on_change(event: Dict[str, Any]) -> None:
self_deref = self_ref()
if self_deref is not None:
value = self_deref._cast(event["value"])
self_deref.update(value)
self_deref._set_str_value(event["value"])

@property
def value(self) -> _InputType:
Expand All @@ -86,22 +83,27 @@ def attributes(self) -> Dict[str, Any]:
def update(self, value: _InputType) -> None:
"""Update the current value of the input."""
self._set_value(value)
super().update()

async def render(self) -> VdomDict:
input_element = html.input(
self.attributes, {"value": self._display_value}, event_handlers=self.events,
self.attributes,
{"type": self._type, "value": self._display_value},
event_handlers=self.events,
)
if self._label is not None:
return html.label([self._label, input_element])
else:
return input_element
return input_element

def _set_str_value(self, value: str) -> None:
self._display_value = value
super().update()
print(value)
if not value and self._ignore_empty:
return
self._value = self._cast(value)

def _set_value(self, value: _InputType) -> None:
self._display_value = str(value)
if self._ignore_empty and not value:
return
self._value = value
super().update()

def __repr__(self) -> str:
def __repr__(self) -> str: # pragma: no cover
return f"{type(self).__name__}({self.value!r})"
28 changes: 26 additions & 2 deletions idom/widgets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Module:
An :class:`Import` element for the newly defined module.
"""

__slots__ = "_module"
__slots__ = ("_module", "_name", "_installed")

def __init__(
self,
Expand All @@ -34,28 +34,52 @@ def __init__(
source: Optional[IO] = None,
replace: bool = False,
) -> None:
self._installed = False
if install and source:
raise ValueError("Both 'install' and 'source' were given.")
elif (install or source) and not replace and client.web_module_exists(name):
self._module = client.web_module(name)
self._installed = True
self._name = name
elif source is not None:
client.define_web_module(name, source.read())
self._module = client.web_module(name)
self._installed = True
self._name = name
elif isinstance(install, str):
client.install({install: name})
self._module = client.web_module(name)
self._installed = True
self._name = name
elif install is True:
client.install({name: name})
self._module = client.web_module(name)
self._installed = True
self._name = name
else:
self._module = name

@property
def name(self) -> str:
if not self._installed:
raise ValueError("Module is not installed locally")
return self._name

@property
def url(self) -> str:
return self._module

def Import(self, name: str, *args, **kwargs) -> "Import":
return Import(self._module, name, *args, **kwargs)

def delete(self) -> None:
if not self._installed:
raise ValueError("Module is not installed locally")
client.delete_web_module(self._module)

def __repr__(self) -> str: # pragma: no cover
return f"{type(self).__name__}({self._module!r})"


class Import:
"""Import a react module
Expand Down Expand Up @@ -87,7 +111,7 @@ def __init__(
def __call__(self, *args: Any, **kwargs: Any,) -> VdomDict:
return self._constructor(import_source=self._import_source, *args, **kwargs)

def __repr__(self) -> str:
def __repr__(self) -> str: # pragma: no cover
items = ", ".join(f"{k}={v!r}" for k, v in self._import_source.items())
return f"{type(self).__name__}({items})"

Expand Down
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ exclude = idom/client/node_modules/*
testpaths = tests
xfail_strict = True
addopts = --cov=idom
markers =
slow: marks tests as slow (deselect with '-m "not slow"')

[coverage:report]
fail_under = 93
fail_under = 95
show_missing = True
skip_covered = True
sort = Miss
Expand Down
7 changes: 3 additions & 4 deletions tests/driver_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from selenium.webdriver.remote.webelement import WebElement


def send_keys(element: WebElement, *values: Any) -> None:
for keys in values:
for char in keys:
element.send_keys(char)
def send_keys(element: WebElement, keys: Any) -> None:
for char in keys:
element.send_keys(char)
14 changes: 14 additions & 0 deletions tests/test_core/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ async def key_press_handler():

assert isinstance(events["onClick"], EventHandler)
assert isinstance(events["onKeyPress"], EventHandler)
assert "onClick" in events
assert "onKeyPress" in events
assert len(events) == 2


def test_event_handler_serialization():
Expand Down Expand Up @@ -51,3 +54,14 @@ async def callback_2(event):
await event_handler([{}])

assert calls == [1, 2]


def test_remove_event_handlers():
def my_callback(event):
...

events = EventHandler()
events.add(my_callback)
assert my_callback in events
events.remove(my_callback)
assert my_callback not in events
Loading