tempestweb 0.5.2__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tempestweb-0.5.2 → tempestweb-0.5.3}/CHANGELOG.md +22 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/PKG-INFO +7 -1
- {tempestweb-0.5.2 → tempestweb-0.5.3}/README.md +6 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/dom.js +74 -6
- {tempestweb-0.5.2 → tempestweb-0.5.3}/pyproject.toml +1 -1
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/dom.test.js +25 -6
- {tempestweb-0.5.2 → tempestweb-0.5.3}/.gitignore +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/constants.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/events.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/livereload.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/audio.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/camera.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/clipboard.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/file.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/geolocation.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/http.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/index.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/install.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/notifications.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/onnx.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/share.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/native/storage.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/offline/store.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/offline/sync.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/push/web-push-client.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/pwa/install-prompt.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/pwa/manifest.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/router.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/style.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/sw/register.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/sw/sw.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/tempestweb.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/transport-sse.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/transport-wasm.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/transport-ws.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/transport.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/client/virtualize.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/_core/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/build.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/dev.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/new.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/run.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/commands/sync.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/config.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/loader.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/main.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/cli/scaffold.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/components/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/components/fields.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/components/forms.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/core/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/core/constants.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/devserver/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/devserver/http.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/devserver/reload.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/devserver/watcher.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/audio.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/bridges.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/camera.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/clipboard.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/dispatch.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/file.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/geolocation.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/http.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/install.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/notifications.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/onnx.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/share.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/native/storage.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/auth.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/error_boundary.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/feature_flags.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/logger.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/observability/telemetry.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/pwa/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/pwa/icons.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/pwa/manifest.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/pwa/pyodide_vendor.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/events.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/serialize.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/session.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/wasm.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/runtime/wasm_main.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/server/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/server/app.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/server/webpush.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/transports/__init__.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/transports/base.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/transports/sse.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/transports/wasm.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tempestweb/transports/websocket.py +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/events.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/mount.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/native-onnx-file.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/native.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/offline-store.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/offline-sync.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/pwa-install-prompt.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/pwa-manifest.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/router.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/setup.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/smoke.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/style.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/sw-register.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/sw-strategies.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/transport-sse.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/transport-wasm.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/transport-ws.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/virtualize.test.js +0 -0
- {tempestweb-0.5.2 → tempestweb-0.5.3}/tests/client/web-push-client.test.js +0 -0
|
@@ -4,6 +4,28 @@ All notable changes to **tempestweb** are documented here. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project adheres to semantic
|
|
5
5
|
versioning.
|
|
6
6
|
|
|
7
|
+
## [0.5.3] — 2026-06-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`Checkbox` now renders its label as visible text on the web.** The DOM
|
|
12
|
+
renderer mapped `Checkbox` to a bare `<input type=checkbox>` and put its
|
|
13
|
+
`label` on `aria-label` only, so labelled checkboxes (todo items, settings
|
|
14
|
+
toggles) showed as empty boxes. A `Checkbox` now renders as a `<label>`
|
|
15
|
+
wrapping the real `<input>` plus a caption text node: the box and its caption
|
|
16
|
+
lay out as one tidy row (the wrapper also gives the input its accessible name
|
|
17
|
+
natively). The `<label>` is the keyed, path-addressed element; the nested
|
|
18
|
+
input carries `checked` and fires `change`, which bubbles to the label for
|
|
19
|
+
event delegation. An explicit widget `Style` still wins.
|
|
20
|
+
|
|
21
|
+
### Examples
|
|
22
|
+
|
|
23
|
+
- **Fixed three examples that passed `children=` to `Container`.** `Container`
|
|
24
|
+
holds a single `child`, not a `children` list (that is `Column`/`Row`/`Stack`).
|
|
25
|
+
Pydantic silently dropped the unknown kwarg, so the container rendered empty:
|
|
26
|
+
`list_demo` lost its row text (1000-item list showed blank rows), `gesture_demo`
|
|
27
|
+
lost its "swipe or tap me" hint, and `anim_demo` carried a no-op `children=[]`.
|
|
28
|
+
|
|
7
29
|
## [0.5.2] — 2026-06-13
|
|
8
30
|
|
|
9
31
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempestweb
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Build web apps in typed Python — one tree, a DOM renderer, two execution modes (WASM + server).
|
|
5
5
|
Author-email: Mauricio Benjamin <mauricio.benjamin@reloverelations.com>
|
|
6
6
|
License: MIT
|
|
@@ -60,6 +60,12 @@ demos (stopwatch, forms, data table, kanban, chat, theming, i18n, canvas, native
|
|
|
60
60
|
capabilities, observability, PWA/WebPush, and a server-mode walkthrough), each
|
|
61
61
|
running unchanged in both modes.
|
|
62
62
|
|
|
63
|
+
Building something real? Read the **[App architecture & best practices](https://mauriciobenjamin700.github.io/tempestweb/best-practices/)**
|
|
64
|
+
guide ([EN](https://mauriciobenjamin700.github.io/tempestweb/en/best-practices/)) —
|
|
65
|
+
the ideal layered structure (routes · pages · components · styles · controllers ·
|
|
66
|
+
services · storages · schemas · utils · core), mirroring `tempest-fastapi-sdk`, so
|
|
67
|
+
your app doesn't rot into garbage code.
|
|
68
|
+
|
|
63
69
|
## How it works
|
|
64
70
|
|
|
65
71
|
```text
|
|
@@ -32,6 +32,12 @@ demos (stopwatch, forms, data table, kanban, chat, theming, i18n, canvas, native
|
|
|
32
32
|
capabilities, observability, PWA/WebPush, and a server-mode walkthrough), each
|
|
33
33
|
running unchanged in both modes.
|
|
34
34
|
|
|
35
|
+
Building something real? Read the **[App architecture & best practices](https://mauriciobenjamin700.github.io/tempestweb/best-practices/)**
|
|
36
|
+
guide ([EN](https://mauriciobenjamin700.github.io/tempestweb/en/best-practices/)) —
|
|
37
|
+
the ideal layered structure (routes · pages · components · styles · controllers ·
|
|
38
|
+
services · storages · schemas · utils · core), mirroring `tempest-fastapi-sdk`, so
|
|
39
|
+
your app doesn't rot into garbage code.
|
|
40
|
+
|
|
35
41
|
## How it works
|
|
36
42
|
|
|
37
43
|
```text
|
|
@@ -32,7 +32,12 @@ const TAG_BY_TYPE = Object.freeze({
|
|
|
32
32
|
Text: "span",
|
|
33
33
|
Button: "button",
|
|
34
34
|
Input: "input",
|
|
35
|
-
Checkbox
|
|
35
|
+
// A Checkbox renders as a <label> wrapping a real <input type=checkbox> plus
|
|
36
|
+
// its caption text, so the box and its label show side by side and the input
|
|
37
|
+
// gets its accessible name natively. The <label> is the keyed, path-addressed
|
|
38
|
+
// element; the input it wraps is renderer-internal (Checkbox is an IR leaf, so
|
|
39
|
+
// no patch path ever descends into it).
|
|
40
|
+
Checkbox: "label",
|
|
36
41
|
Image: "img",
|
|
37
42
|
});
|
|
38
43
|
|
|
@@ -89,14 +94,15 @@ function applyProps(el, props) {
|
|
|
89
94
|
el.removeAttribute("style");
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
|
-
// Text-bearing props. A Checkbox is
|
|
93
|
-
//
|
|
97
|
+
// Text-bearing props. A Checkbox is a <label> wrapping an <input>, so its
|
|
98
|
+
// caption is set as a trailing text node (see setCheckboxLabel) rather than
|
|
99
|
+
// overwriting textContent, which would drop the nested input.
|
|
94
100
|
if ("content" in props) {
|
|
95
101
|
el.textContent = props.content == null ? "" : String(props.content);
|
|
96
102
|
}
|
|
97
103
|
if ("label" in props) {
|
|
98
104
|
if (type === "Checkbox") {
|
|
99
|
-
el
|
|
105
|
+
setCheckboxLabel(el, props.label == null ? "" : String(props.label));
|
|
100
106
|
} else {
|
|
101
107
|
el.textContent = props.label == null ? "" : String(props.label);
|
|
102
108
|
}
|
|
@@ -188,6 +194,46 @@ function applyLazyProps(el, type, props) {
|
|
|
188
194
|
el.setAttribute("data-tw-window-start", String(start ?? 0));
|
|
189
195
|
}
|
|
190
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Get (or lazily create) the real ``<input type=checkbox>`` nested inside a
|
|
199
|
+
* Checkbox's ``<label>`` wrapper. The label is the keyed, path-addressed
|
|
200
|
+
* element; this nested input carries the actual ``checked`` state and fires the
|
|
201
|
+
* native ``change`` event (which bubbles up to the keyed label for delegation).
|
|
202
|
+
*
|
|
203
|
+
* @param {HTMLElement} el The Checkbox ``<label>`` element.
|
|
204
|
+
* @returns {HTMLInputElement} The nested checkbox input.
|
|
205
|
+
*/
|
|
206
|
+
function ensureCheckboxInput(el) {
|
|
207
|
+
let input = /** @type {HTMLInputElement|null} */ (el.querySelector("input"));
|
|
208
|
+
if (input == null) {
|
|
209
|
+
input = /** @type {HTMLInputElement} */ (document.createElement("input"));
|
|
210
|
+
input.setAttribute("type", "checkbox");
|
|
211
|
+
el.insertBefore(input, el.firstChild);
|
|
212
|
+
}
|
|
213
|
+
return input;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Set a Checkbox's visible caption, kept as a single text node after the nested
|
|
218
|
+
* input so the box and its label render side by side. Wrapping the input in the
|
|
219
|
+
* ``<label>`` also gives it its accessible name natively (no ``aria-label``).
|
|
220
|
+
*
|
|
221
|
+
* @param {HTMLElement} el The Checkbox ``<label>`` element.
|
|
222
|
+
* @param {string} text The caption text (``""`` clears it).
|
|
223
|
+
* @returns {void}
|
|
224
|
+
*/
|
|
225
|
+
function setCheckboxLabel(el, text) {
|
|
226
|
+
const input = ensureCheckboxInput(el);
|
|
227
|
+
for (const node of Array.from(el.childNodes)) {
|
|
228
|
+
if (node !== input) {
|
|
229
|
+
el.removeChild(node);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (text) {
|
|
233
|
+
el.appendChild(document.createTextNode(text));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
191
237
|
function applyControlProps(el, type, props) {
|
|
192
238
|
if (type === "Input") {
|
|
193
239
|
el.setAttribute("type", props.secure ? "password" : "text");
|
|
@@ -201,9 +247,29 @@ function applyControlProps(el, type, props) {
|
|
|
201
247
|
el.setAttribute("maxlength", String(props.max_length));
|
|
202
248
|
}
|
|
203
249
|
} else if (type === "Checkbox") {
|
|
204
|
-
el
|
|
250
|
+
const input = ensureCheckboxInput(el);
|
|
205
251
|
if ("checked" in props) {
|
|
206
|
-
|
|
252
|
+
input.checked = Boolean(props.checked);
|
|
253
|
+
}
|
|
254
|
+
// Lay the box and caption out on one line, as a block-level row so stacked
|
|
255
|
+
// checkboxes each get their own line (an inline default would let adjacent
|
|
256
|
+
// labels flow together in a non-flex parent). Defaults only — an explicit
|
|
257
|
+
// Style on the widget (applied just before this) wins. Re-applied here on
|
|
258
|
+
// every update because a `style` patch resets the element's inline cssText.
|
|
259
|
+
if (!el.style.display) {
|
|
260
|
+
el.style.display = "flex";
|
|
261
|
+
}
|
|
262
|
+
if (!el.style.alignItems) {
|
|
263
|
+
el.style.alignItems = "center";
|
|
264
|
+
}
|
|
265
|
+
if (!el.style.gap) {
|
|
266
|
+
el.style.gap = "0.4em";
|
|
267
|
+
}
|
|
268
|
+
// Size to the box + caption. Without this the <label>, as a flex item of a
|
|
269
|
+
// column, collapses to the input's width and the caption overflows onto the
|
|
270
|
+
// next row; fit-content keeps it a tidy one-line row instead of stretching.
|
|
271
|
+
if (!el.style.width) {
|
|
272
|
+
el.style.width = "fit-content";
|
|
207
273
|
}
|
|
208
274
|
} else if (type === "Image") {
|
|
209
275
|
if ("src" in props && props.src != null) {
|
|
@@ -263,6 +329,8 @@ function applyUpdate(root, patch) {
|
|
|
263
329
|
for (const key of patch.unset_props ?? []) {
|
|
264
330
|
if (key === "style") {
|
|
265
331
|
el.removeAttribute("style");
|
|
332
|
+
} else if (key === "label" && el.getAttribute(TYPE_ATTR) === "Checkbox") {
|
|
333
|
+
setCheckboxLabel(el, "");
|
|
266
334
|
} else if (key === "content" || key === "label") {
|
|
267
335
|
el.textContent = "";
|
|
268
336
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
4
|
import { fixture, freshDom } from "./setup.js";
|
|
5
|
-
import { applyPatches, buildElement, KEY_ATTR } from "../../client/dom.js";
|
|
5
|
+
import { applyPatches, buildElement, KEY_ATTR, TYPE_ATTR } from "../../client/dom.js";
|
|
6
6
|
|
|
7
7
|
/** Install jsdom's `document` globally so dom.js's `document.createElement` works. */
|
|
8
8
|
function withDocument() {
|
|
@@ -160,7 +160,7 @@ test("a secure Input renders type=password", () => {
|
|
|
160
160
|
assert.equal(el.getAttribute("type"), "password");
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
test("Checkbox builds
|
|
163
|
+
test("Checkbox builds a <label> wrapping a checkbox input plus visible caption", () => {
|
|
164
164
|
withDocument();
|
|
165
165
|
const el = buildElement({
|
|
166
166
|
type: "Checkbox",
|
|
@@ -168,10 +168,29 @@ test("Checkbox builds an <input type=checkbox> reflecting checked", () => {
|
|
|
168
168
|
props: { checked: true, label: "Agree" },
|
|
169
169
|
children: [],
|
|
170
170
|
});
|
|
171
|
-
|
|
172
|
-
assert.equal(el.
|
|
173
|
-
assert.equal(el.
|
|
174
|
-
|
|
171
|
+
// The keyed, path-addressed element is the <label>; the real input is nested.
|
|
172
|
+
assert.equal(el.tagName, "LABEL");
|
|
173
|
+
assert.equal(el.getAttribute(TYPE_ATTR), "Checkbox");
|
|
174
|
+
const input = el.querySelector("input");
|
|
175
|
+
assert.equal(input.getAttribute("type"), "checkbox");
|
|
176
|
+
assert.equal(input.checked, true);
|
|
177
|
+
// The caption is visible text (the <label> gives the input its name natively).
|
|
178
|
+
assert.equal(el.textContent.trim(), "Agree");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("Checkbox Update toggles checked and relabels without dropping the input", () => {
|
|
182
|
+
withDocument();
|
|
183
|
+
const el = buildElement({
|
|
184
|
+
type: "Checkbox",
|
|
185
|
+
key: "c",
|
|
186
|
+
props: { checked: false, label: "Old" },
|
|
187
|
+
children: [],
|
|
188
|
+
});
|
|
189
|
+
applyPatches(el, [{ path: [], set_props: { checked: true, label: "New" } }]);
|
|
190
|
+
const input = el.querySelector("input");
|
|
191
|
+
assert.equal(el.querySelectorAll("input").length, 1, "exactly one nested input");
|
|
192
|
+
assert.equal(input.checked, true);
|
|
193
|
+
assert.equal(el.textContent.trim(), "New");
|
|
175
194
|
});
|
|
176
195
|
|
|
177
196
|
test("Image builds an <img> with src/alt", () => {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|