Skip to content

Commit b0bf11b

Browse files
authored
Merge branch 'main' into main
2 parents 0484d0c + 64b567d commit b0bf11b

File tree

16 files changed

+166
-48
lines changed

16 files changed

+166
-48
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/ambv/black
3-
rev: 21.12b0
3+
rev: 22.1.0
44
hooks:
55
- id: black
66
- repo: https://github.com/PyCQA/flake8

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2019 Ryan S. Morshead
3+
Copyright (c) 2019-2022 Ryan S. Morshead
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.35.4
1+
0.36.0

docs/source/developing-idom/changelog.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ team are working on, or have feedback on how issues should be prioritized, feel
77
:discussion-type:`open up a discussion <question>`.
88

99

10+
0.36.0
11+
------
12+
13+
This release includes an important fix for errors produced after :pull:`623` was merged.
14+
In addition there is not a new ``http.script`` element which can behave similarly to a
15+
standard HTML ``<script>`` or, if no attributes are given, operate similarly to an
16+
effect. If no attributes are given, and when the script evaluates to a function, that
17+
function will be called the first time it is mounted and any time the content of the
18+
script is subsequently changed. If the function then returns another function, that
19+
returned function will be called when the script is removed from the view, or just
20+
before the content of the script changes.
21+
22+
**Closed Issues**
23+
24+
- State mismatch during component update - :issue:`629`
25+
- Implement a script tag - :issue:`544`
26+
27+
**Pull Requests**
28+
29+
- make scripts behave more like normal html script element - :pull:`632`
30+
- Fix state mismatch during component update - :pull:`631`
31+
- implement script element - :pull:`617`
32+
33+
1034
0.35.4
1135
------
1236

docs/source/reference-material/_examples/snake_game.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def on_direction_change(event):
8383
if snake[-1] in snake[:-1]:
8484
assign_grid_block_color(grid, snake[-1], "red")
8585
new_game_state = GameState.lost
86-
elif len(snake) == grid_size ** 2:
86+
elif len(snake) == grid_size**2:
8787
assign_grid_block_color(grid, snake[-1], "yellow")
8888
new_game_state = GameState.won
8989

src/client/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"publish": "npm --workspaces publish",
1515
"test": "npm --workspaces test"
1616
},
17-
"version": "0.35.4",
17+
"version": "0.36.0",
1818
"workspaces": [
1919
"./packages/*"
2020
]

src/client/packages/idom-app-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
"format": "prettier --write ./src",
2222
"test": "echo 'no tests'"
2323
},
24-
"version": "0.35.4"
24+
"version": "0.36.0"
2525
}

src/client/packages/idom-client-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
"test": "uvu tests"
3232
},
3333
"type": "module",
34-
"version": "0.35.4"
34+
"version": "0.36.0"
3535
}

src/client/packages/idom-client-react/src/components.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function Element({ model }) {
3535
return null;
3636
}
3737
} else if (model.tagName == "script") {
38-
return html`<${ScriptElement} script=${model.children[0]} />`;
38+
return html`<${ScriptElement} model=${model} />`;
3939
} else if (model.importSource) {
4040
return html`<${ImportedElement} model=${model} />`;
4141
} else {
@@ -58,10 +58,31 @@ function StandardElement({ model }) {
5858
);
5959
}
6060

61-
function ScriptElement({ script }) {
62-
const el = React.useRef();
63-
React.useEffect(eval(script), [script]);
64-
return null;
61+
function ScriptElement({ model }) {
62+
const ref = React.useRef();
63+
React.useEffect(() => {
64+
if (model?.children?.length > 1) {
65+
console.error("Too many children for 'script' element.");
66+
}
67+
68+
let scriptContent = model?.children?.[0];
69+
70+
let scriptElement;
71+
if (model.attributes) {
72+
scriptElement = document.createElement("script");
73+
for (const [k, v] of Object.entries(model.attributes)) {
74+
scriptElement.setAttribute(k, v);
75+
}
76+
scriptElement.appendChild(document.createTextNode(scriptContent));
77+
ref.current.appendChild(scriptElement);
78+
} else {
79+
let scriptResult = eval(scriptContent);
80+
if (typeof scriptResult == "function") {
81+
return scriptResult();
82+
}
83+
}
84+
}, [model.key]);
85+
return html`<div ref=${ref} />`;
6586
}
6687

6788
function ImportedElement({ model }) {

src/idom/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222

2323
__author__ = "idom-team"
24-
__version__ = "0.35.4" # DO NOT MODIFY
24+
__version__ = "0.36.0" # DO NOT MODIFY
2525

2626
__all__ = [
2727
"component",

src/idom/core/layout.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,6 @@ def _render_component(
216216
pass
217217
else:
218218
key, index = new_state.key, new_state.index
219-
if old_state is not None:
220-
assert key == old_state.key, (
221-
"state mismatch during component update - "
222-
f"key {key!r}!={old_state.key!r} "
223-
)
224219
parent.children_by_key[key] = new_state
225220
# need to do insertion in case where old_state is None and we're appending
226221
parent.model.current["children"][index : index + 1] = [

src/idom/html.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,12 @@
150150
- :func:`template`
151151
"""
152152

153+
from __future__ import annotations
154+
155+
from typing import Any, Mapping
156+
153157
from .core.proto import VdomDict
154-
from .core.vdom import make_vdom_constructor
158+
from .core.vdom import coalesce_attributes_and_children, make_vdom_constructor
155159

156160

157161
# Dcument metadata
@@ -250,18 +254,46 @@
250254
noscript = make_vdom_constructor("noscript")
251255

252256

253-
def script(content: str) -> VdomDict:
257+
def script(
258+
*attributes_and_children: Mapping[str, Any] | str,
259+
key: str | int | None = None,
260+
) -> VdomDict:
254261
"""Create a new `<{script}> <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script>`__ element.
255262
256-
Parameters:
257-
content:
258-
The text of the script should evaluate to a function. This function will be
259-
called when the script is initially created or when the content of the
260-
script changes. The function may optionally return a teardown function that
261-
is called when the script element is removed from the tree, or when the
262-
script content changes.
263+
This behaves slightly differently than a normal script element in that it may be run
264+
multiple times if its key changes (depending on specific browser behaviors). If no
265+
key is given, the key is inferred to be the content of the script or, lastly its
266+
'src' attribute if that is given.
267+
268+
If no attributes are given, the content of the script may evaluate to a function.
269+
This function will be called when the script is initially created or when the
270+
content of the script changes. The function may itself optionally return a teardown
271+
function that is called when the script element is removed from the tree, or when
272+
the script content changes.
263273
"""
264-
return {"tagName": "script", "children": [content]}
274+
model: VdomDict = {"tagName": "script"}
275+
276+
attributes, children = coalesce_attributes_and_children(attributes_and_children)
277+
278+
if children:
279+
if len(children) > 1:
280+
raise ValueError("'script' nodes may have, at most, one child.")
281+
elif not isinstance(children[0], str):
282+
raise ValueError("The child of a 'script' must be a string.")
283+
else:
284+
model["children"] = children
285+
if key is None:
286+
key = children[0]
287+
288+
if attributes:
289+
model["attributes"] = attributes
290+
if key is None and not children and "src" in attributes:
291+
key = attributes["src"]
292+
293+
if key is not None:
294+
model["key"] = key
295+
296+
return model
265297

266298

267299
# Demarcating edits

tests/test_core/test_vdom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def test_is_vdom(result, value):
7272
{"tagName": "div", "children": [0, 1, 2]},
7373
),
7474
(
75-
idom.vdom("div", map(lambda x: x ** 2, [1, 2, 3])),
75+
idom.vdom("div", map(lambda x: x**2, [1, 2, 3])),
7676
{"tagName": "div", "children": [1, 4, 9]},
7777
),
7878
(

tests/test_html.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from idom import component, html, use_state
1+
import pytest
2+
3+
from idom import component, config, html, use_state
24
from idom.utils import Ref
35

46

@@ -7,8 +9,8 @@ def use_toggle():
79
return state, lambda: set_state(not state)
810

911

10-
def use_counter():
11-
state, set_state = use_state(1)
12+
def use_counter(initial_value):
13+
state, set_state = use_state(initial_value)
1214
return state, lambda: set_state(state + 1)
1315

1416

@@ -61,7 +63,7 @@ def test_script_re_run_on_content_change(driver, driver_wait, display):
6163

6264
@component
6365
def HasScript():
64-
count, incr_count.current = use_counter()
66+
count, incr_count.current = use_counter(1)
6567
return html.div(
6668
html.div({"id": "mount-count", "data-value": 0}),
6769
html.div({"id": "unmount-count", "data-value": 0}),
@@ -92,3 +94,51 @@ def HasScript():
9294

9395
driver_wait.until(lambda d: mount_count.get_attribute("data-value") == "3")
9496
driver_wait.until(lambda d: unmount_count.get_attribute("data-value") == "2")
97+
98+
99+
def test_script_from_src(driver, driver_wait, display):
100+
incr_src_id = Ref()
101+
file_name_template = "__some_js_script_{src_id}__.js"
102+
103+
@component
104+
def HasScript():
105+
src_id, incr_src_id.current = use_counter(0)
106+
if src_id == 0:
107+
# on initial display we haven't added the file yet.
108+
return html.div()
109+
else:
110+
return html.div(
111+
html.div({"id": "run-count", "data-value": 0}),
112+
html.script(
113+
{"src": f"/modules/{file_name_template.format(src_id=src_id)}"}
114+
),
115+
)
116+
117+
display(HasScript)
118+
119+
for i in range(1, 4):
120+
script_file = config.IDOM_WED_MODULES_DIR.current / file_name_template.format(
121+
src_id=i
122+
)
123+
script_file.write_text(
124+
f"""
125+
let runCountEl = document.getElementById("run-count");
126+
runCountEl.setAttribute("data-value", {i});
127+
"""
128+
)
129+
130+
incr_src_id.current()
131+
132+
run_count = driver.find_element("id", "run-count")
133+
134+
driver_wait.until(lambda d: (run_count.get_attribute("data-value") == "1"))
135+
136+
137+
def test_script_may_only_have_one_child():
138+
with pytest.raises(ValueError, match="'script' nodes may have, at most, one child"):
139+
html.script("one child", "two child")
140+
141+
142+
def test_child_of_script_must_be_string():
143+
with pytest.raises(ValueError, match="The child of a 'script' must be a string"):
144+
html.script(1)

tests/test_web/test_utils.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,13 @@ def test_resolve_module_exports_from_source():
136136
names, references = resolve_module_exports_from_source(
137137
fixture_file.read_text(), exclude_default=False
138138
)
139-
assert (
140-
names
141-
== (
142-
{f"name{i}" for i in range(1, 21)}
143-
| {
144-
"functionName",
145-
"ClassName",
146-
}
147-
)
148-
and references == {"https://source1.com", "https://source2.com"}
149-
)
139+
assert names == (
140+
{f"name{i}" for i in range(1, 21)}
141+
| {
142+
"functionName",
143+
"ClassName",
144+
}
145+
) and references == {"https://source1.com", "https://source2.com"}
150146

151147

152148
def test_log_on_unknown_export_type(caplog):

0 commit comments

Comments
 (0)