Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change html-macro's checked attribute to specify checkedness #206

Merged
merged 9 commits into from
Sep 29, 2024
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ members = [
"crates/percy-router-macro-test",
"crates/virtual-node",
"examples/component-preview",
"examples/input-element-default-checkedness",
"examples/isomorphic/app",
"examples/isomorphic/client",
"examples/isomorphic/server",
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [On Create Element](./html-macro/real-elements-and-nodes/on-create-elem/README.md)
- [On Remove Element](./html-macro/real-elements-and-nodes/on-remove-elem/README.md)
- [Boolean Attributes](./html-macro/boolean-attributes/README.md)
- [Special Attributes](./html-macro/special-attributes/README.md)
- [Lists](./lists/README.md)
- [Virtual DOM](./virtual-dom/README.md)
- [Unit Testing your Views](./virtual-dom/unit-testing-views.md)
Expand Down
33 changes: 33 additions & 0 deletions book/src/html-macro/special-attributes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Special Attributes
SFBdragon marked this conversation as resolved.
Show resolved Hide resolved

Some attributes do not merely set or remove the corresponding HTML attribute of the same name.

## `checked` Attribute

According to the [HTML spec](https://html.spec.whatwg.org/multipage/input.html#attr-input-checked), the `checked` HTML attribute only controls the default checkedness.
Changing the `checked` HTML attribute may not cause the checkbox's checkedness to change.

By contrast: specifying `html! { <input checked={bool} /> }` causes `percy` to always render the checkbox with the specified checkedness.
- If the VDOM is updated from `html! { <input checked=true /> }` to `html { <input checked=false /> }`, the input element's checkedness will definitely change.
- If the VDOM is updated from `html! { <input checked=true /> }` to `html { <input checked=true /> }`, the input element's checkedness will be reverted to `true` even if the user interacted with the checkbox in between.

`percy` updates both
- the `checked` attribute (default checkedness, reflected in HTML) and,
- the `checked` property (current checkedness, not reflected in HTML).

This behavior is more desirable because `percy` developers are accustomed to declaratively controlling the DOM and rendered HTML.

## `value` Attribute

According to the [HTML spec](https://html.spec.whatwg.org/multipage/input.html#attr-input-value), the `value` HTML attribute only controls the default value.
Changing the `value` HTML attribute may not cause the input element's value to change.

By contrast: specifying `html! { <input value="..." /> }` causes `percy` to always render the input element with the specified value.
- If the VDOM is updated from `html! { <input value="hello" /> }` to `html { <input value="goodbye" /> }`, the input element's value will definitely change.
- If the VDOM is updated from `html! { <input value="hello" /> }` to `html { <input value="hello" /> }`, the input element's value will be reverted to `"hello"` even if the user interacted with the input element in between.

`percy` updates both
- the `value` attribute (default value, reflected in HTML) and,
- the `value` property (current value, not reflected in HTML).

This behavior is more desirable because `percy` developers are accustomed to declaratively controlling the DOM and rendered HTML.
33 changes: 33 additions & 0 deletions crates/percy-dom/src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ fn find_attributes_to_add<'a>(
attributes_to_add.insert(new_attr_name, new_attr_val);
} else if new_attr_name == "value" {
ctx.push_patch(Patch::ValueAttributeUnchanged(cur_node_idx, new_attr_val));
} else if new_attr_name == "checked" {
ctx.push_patch(Patch::CheckedAttributeUnchanged(cur_node_idx, new_attr_val));
SFBdragon marked this conversation as resolved.
Show resolved Hide resolved
}
}
None => {
Expand Down Expand Up @@ -1246,6 +1248,37 @@ mod tests {
.test();
}

/// Verify that if an input (e.g. checkbox) has `checked` specified, we always push a
/// patch for setting the checked attribute and property so that the checkbox is
/// rendered in the specified state, regardless if any intervening input has occurred.
#[test]
fn always_pushes_patch_for_checked() {
for checkedness in [false, true] {
DiffTestCase {
old: html! { <input checked={checkedness} /> },
new: html! { <input checked={checkedness} /> },
expected: vec![Patch::CheckedAttributeUnchanged(0, &checkedness.into())],
}
.test();
}

for old_checkedness in [false, true] {
let new_checkedness = !old_checkedness;

DiffTestCase {
old: html! { <input checked=old_checkedness /> },
new: html! { <input checked=new_checkedness /> },
expected: vec![Patch::AddAttributes(
0,
vec![("checked", &new_checkedness.into())]
.into_iter()
.collect(),
)],
}
.test();
}
}

/// Verify that we push an on create elem patch if the new node has the special attribute
/// and the old node does not.
#[test]
Expand Down
8 changes: 8 additions & 0 deletions crates/percy-dom/src/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ pub enum Patch<'a> {
/// The value attribute of a textarea or input element has not changed, but we will still patch
/// it anyway in case something was typed into the field.
ValueAttributeUnchanged(BreadthFirstNodeIdx, &'a AttributeValue),
/// The checked attribute of a input element has not changed, but we will still patch
/// it anyway in case the checkbox was toggled by the user. Otherwise the checkbox
/// could have a different state to what is rendered.
CheckedAttributeUnchanged(BreadthFirstNodeIdx, &'a AttributeValue),
/// Add attributes that the new node has that the old node does not
AddAttributes(BreadthFirstNodeIdx, HashMap<&'a str, &'a AttributeValue>),
/// Remove attributes that the old node had that the new node doesn't
Expand Down Expand Up @@ -153,6 +157,7 @@ impl<'a> Patch<'a> {
Patch::RemoveAttributes(node_idx, _) => *node_idx,
Patch::ChangeText(node_idx, _) => *node_idx,
Patch::ValueAttributeUnchanged(node_idx, _) => *node_idx,
Patch::CheckedAttributeUnchanged(node_idx, _) => *node_idx,
Patch::SpecialAttribute(special) => match special {
PatchSpecialAttribute::CallOnCreateElemOnExistingNode(node_idx, _) => *node_idx,
PatchSpecialAttribute::SetDangerousInnerHtml(node_idx, _) => *node_idx,
Expand Down Expand Up @@ -210,6 +215,9 @@ impl<'a> Patch<'a> {
Patch::ValueAttributeUnchanged(node_idx, _) => {
to_find.insert(*node_idx);
}
Patch::CheckedAttributeUnchanged(node_idx, _) => {
to_find.insert(*node_idx);
}
Patch::SpecialAttribute(special) => match special {
PatchSpecialAttribute::CallOnCreateElemOnExistingNode(node_idx, _) => {
to_find.insert(*node_idx);
Expand Down
27 changes: 27 additions & 0 deletions crates/percy-dom/src/patch/apply_patches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ fn apply_element_patch(
} else {
node.remove_attribute(attrib_name)?;
}

// Use `set_checked` in addition to `{set,remove}_attribute` for the `checked` attribute.
// The "checked" attribute determines default checkedness,
// but `percy` interprets `checked` as determining current checkedness too.
// See crates/percy-dom/tests/checked_attribute.rs for more info.
if *attrib_name == "checked" {
maybe_set_checked_property(node, *val_bool);
}
}
}
}
Expand Down Expand Up @@ -390,6 +398,18 @@ fn apply_element_patch(

Ok(())
}
Patch::CheckedAttributeUnchanged(_node_idx, value) => {
let value = value.as_bool().unwrap();
maybe_set_checked_property(node, value);

if value {
node.set_attribute("checked", "")?;
} else {
node.remove_attribute("checked")?;
}

Ok(())
}
Patch::SpecialAttribute(special) => match special {
PatchSpecialAttribute::CallOnCreateElemOnExistingNode(_node_idx, new_node) => {
new_node
Expand Down Expand Up @@ -512,6 +532,13 @@ fn maybe_set_value_property(node: &Element, value: &str) {
}
}

// See crates/percy-dom/tests/checked_attribute.rs
fn maybe_set_checked_property(node: &Element, checked: bool) {
if let Some(input_node) = node.dyn_ref::<HtmlInputElement>() {
input_node.set_checked(checked);
}
}

// Looks for a property on the element. If it's there then this is a Percy element.
//
// TODO: We need to know not just if the node was created by Percy... but if it was created by
Expand Down
133 changes: 133 additions & 0 deletions crates/percy-dom/tests/checked_attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! `percy-dom` treats the `checked` virtual node attribute specially.
//! `percy-dom` sets the `checked` element property (actual checkedness), as well as
//! the `checked` HTML attribute (default checkedness).
//!
//! Developers, are likely to assume that `checked` specifies the state of the checkbox
//! directly. `percy-dom` ensures that this is true.
//!
//! See the tests for more details. Start with [`patch_uses_set_checked_function`].
//!
//! To run all tests in this file:
//!
//! wasm-pack test --chrome --headless crates/percy-dom --test checked_attribute

use wasm_bindgen_test::*;

use percy_dom::event::VirtualEvents;
use wasm_bindgen::JsCast;
use web_sys::*;

use percy_dom::prelude::*;

wasm_bindgen_test_configure!(run_in_browser);

/// Verify that `percy_dom::patch` uses `set_checked` to set the checkedness
SFBdragon marked this conversation as resolved.
Show resolved Hide resolved
/// of an input element when specified.
///
/// ## Why?
///
/// The `checked` HTML attribute, only determines the default checkedness.
/// The browser uses the default checkedness as the checkbox's
/// checkedness until the user clicks on the checkbox, setting the "dirty checked" browser
/// flag https://html.spec.whatwg.org/multipage/input.html#concept-input-checked-dirty
/// which results in the browser maintaining the checkbox's state INDEPENDENTLY from the
/// default checked state. i.e. Changing the default checkedness no longer affects the actual
/// checkedness after the user has pressed the input.
///
/// We want `html!{ ... checked=val ... }` to specify the current checkedness of the checkbox
/// directly - avoiding the checkbox rendering in a different state to what the developer
/// specified in the virtual DOM.
///
/// Using web-sys's `set_checked` sets the actual checkbox's checkedness. Futhermore, it enables the
/// dirty-checked flag (NB: BUT ONLY WHEN THE CHECKBOX STATE IS CHANGED), which we can test for.
///
/// ## Test approach
///
/// - Create a virtual node with the checkbox having checkedness !C, and patch it to have checkedness C.
/// (This should cause the dirty flag to be set IF `set_checked` is used.)
/// - Assert that the corresponding DOM element has checkedness of C.
///
/// - Now, remove the attribute if the checkbox is checked, or set the attribute if not.
/// (The checkbox should hold its state as the dirty flag is checked, therefore
/// changing the default checkedness through the `checked` attribute no longer
/// should affect the checkedness of the checkbox.)
/// - Assert that the checkedness of the checkbox element is still B.
#[wasm_bindgen_test]
fn patch_sets_checked_property() {
for checkedness in [false, true] {
let start_input = html! {<input checked={!checkedness}>};
let end_input = html! {<input checked=checkedness>};

let mut events = VirtualEvents::new();
let (input_node, enode) = start_input.create_dom_node(&mut events);
events.set_root(enode);

let input_elem = input_node.dyn_ref::<HtmlInputElement>().unwrap();

let patches = percy_dom::diff(&start_input, &end_input);
percy_dom::patch(input_node.clone(), &end_input, &mut events, &patches).unwrap();
assert_eq!(input_elem.checked(), checkedness);

if checkedness {
input_elem.remove_attribute("checked").unwrap();
} else {
input_elem.set_attribute("checked", "").unwrap();
}
assert_eq!(input_elem.checked(), checkedness);
}
}

/// Verify that `percy_dom::patch` uses `set_checked` to set the `checked` attribute
/// of an input element even if the the specified `checked` value does not change
/// between the `old` and `new` virtual nodes.
///
/// ## Why?
///
/// Note: the rationale given in [`patch_sets_checked_property`] is prerequisite reading.
///
/// The user might interact with the checkbox in between the previous render and the next
/// one, changing the checkedness state in the browser, but `diff` would not realize this
/// assuming the rendered `checked` value does not change.
///
/// For example:
/// - Developer renders `html! { ... checked=true ... }`
/// - User clicks on the checkbox, changing the browser's checkbox checkedness to false.
/// - Developer renders `html! { ... checked=true ... }`
/// - `diff` doesn't realize anything needs to change, so it doesn't issue any changes.
/// - Developer is still trying to render the checkbox as checked but the browser checkbox
/// stays unchecked.
///
/// If `percy_dom::diff` always specifies that `percy_dom::patch` should set the `checked`
/// attribute if its specified, then the above cannot happen. The element's checked state
/// will be fixed when `percy_dom::patch` is called, keeping the developer-specified `checked`
/// value and the checkbox element's visual state in sync.
///
/// ## Test approach
///
/// - Create a a DOM node with the checkbox having checkedness C.
/// - Set it's checkedness to be !C.
/// - Diff and patch with the virtual node still specifying it's checkedness as C.
/// - Assert that the checkedness has been reset to C, even though the virtual node did not change.
#[wasm_bindgen_test]
fn patch_always_sets_checked_property_and_attribute() {
for checkedness in [false, true] {
let start_input = html! {<input checked=checkedness>};
let end_input = html! {<input checked=checkedness>};

let mut events = VirtualEvents::new();
let (input_node, enode) = start_input.create_dom_node(&mut events);
events.set_root(enode);

let input_elem = input_node.dyn_ref::<HtmlInputElement>().unwrap();
assert_eq!(input_elem.checked(), checkedness);

input_elem.set_checked(!checkedness); // modify checked property
input_elem.set_default_checked(!checkedness); // modify checked attribute

let patches = percy_dom::diff(&start_input, &end_input);
percy_dom::patch(input_node.clone(), &end_input, &mut events, &patches).unwrap();

assert_eq!(input_elem.checked(), checkedness);
assert_eq!(input_elem.default_checked(), checkedness);
}
}
6 changes: 3 additions & 3 deletions crates/percy-router/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ mod tests {
("/users/foo", false),
],
}
.test();
.test();
}

/// Verify that a `/` route definition doesn't capture `/some-route`.
Expand All @@ -230,7 +230,7 @@ mod tests {
("/foo", false),
],
}
.test();
.test();
}

/// Verify that we correctly match when a static segment comes after a dynamic segment.
Expand All @@ -247,7 +247,7 @@ mod tests {
("/5/bar", false),
],
}
.test();
.test();
}

struct MatchRouteTestCase {
Expand Down
19 changes: 19 additions & 0 deletions examples/input-element-default-checkedness/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "input-element-default-checkedness"
version = "0.1.0"
edition = "2021"
publish = []

[lib]
crate-type = ["cdylib"]

[dependencies]
percy-dom = { path = "../../crates/percy-dom" }
wasm-bindgen-test = "0.3"

[dependencies.web-sys]
version = "0.3"
features = [
"Document",
"HtmlElement",
]
38 changes: 38 additions & 0 deletions examples/input-element-default-checkedness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# input-element-default-checkedness
SFBdragon marked this conversation as resolved.
Show resolved Hide resolved

This example demonstrates an input element that maintains a **explicitly configured default checkedness** (i.e. does not change the `checked` HTML attribute) despite changes to `HTMLInputElement.checked`.

`percy` does not support explicitly setting the default checkedness ([why?](#why-does-percy-override-default-checkedness)), so this also serves as an example of **appending independently-managed DOM elements to the DOM elements controlled by `percy-dom`'s virtual DOM**. Elements that are outside `percy-dom`'s virtual DOM model and will not be affected by it (unless, for example, the parent element is removed).

---

### When Should I Configure Default Checkedness Independently of Checkedness?

This is irrelevant to server-side rendering, where there is no server-side DOM and only the HTML attributes are sent.

This is not generally useful for client side rendering, as the default only matters if you want to configure what happens when checkedness isn't specified and `HTMLFormElement.reset()` is called or similarly, a reset element is pressed (which changes the element's checkedness to the default checkedness).
- Most `percy` applications are expected to have a client-side application state, with GUI reflecting that app state. This is in contrast to UI-driven apps (which often scale poorly as complexity increases). In this case, the checkedness of an input is always specified according to app state.
- Calling external functions that independently affect DOM elements such as `HTMLFormElement.reset()` may cause the DOM state may desync from the virtual DOM and app state, and hence is typically best avoided.

---

### Why Does `percy` Override Default Checkedness?
Copy link
Contributor Author

@SFBdragon SFBdragon Sep 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if you're using percy-dom strictly for server-side rendering, with no client-side logic, this would be a regression.

I'm having doubts that this was the right direction. Why would anyone ever use percy-dom for server-side rendering? The server shouldn't be manipulating a DOM server-side. That should be actively discouraged, right?

Therefore percy-dom has no reason to be overriding the default checkedness except to match virtual-node's behavior, but why should it? They're doing two different things. One is encoding a virtual node tree as HTML, the other is creating, diffing, and patching DOM nodes based on two different virtual node trees.

At this point, this example seems like a whole lot of faffing because we're doing the wrong thing.

Proposal:

  • Let's be explicit that percy-dom is not for server-side rendering.
  • Let's have percy-dom explicitly do what makes sense for the context of the browser: checked manipulates the HTMLInputElement.checked property - it doesn't mess around with default checkedness as there's no reason to do so. People writing typical/idiomatic percy apps should never need to worry about it. And if for some reason they do, they shouldn't have to work around this strange behavior.

What we gain from keeping this behavior:

  • percy-dom maintains closer HTML in the browser as it does to if you were to call virtual_node.to_string() (I don't see why this is useful, and can't think of a way this would cause issues. Can you?)

What is gained from tossing this behavior:

  • Nobody needs to worry about what percy-dom does to default checkedness: nothing.
  • percy-dom can avoid dealing with default checkedness entirely, as it's not useful for application-state-driven GUIs (which should always specify checkedness according to app state).
  • percy-dom doesn't have an expectation of being used for something it shouldn't be used for.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember where I wrote that quote, so I don't understand what you're referring to.


The server shouldn't be manipulating a DOM server-side.

A DOM does require on the browser in any way. We should not couple these unrelated concepts.

Whether percy can manipulate a DOM should never depend on whether it is running in a browser.

I'll name some reasons:

  • testing. It is an eventual goal that to be able to test the vast majority of your percy application outside of a browser. So, instead of using web-sys, we would use generic-dom-traits that used web-sys in a browser and
    something else outside of the browser.
    This would enable fast testing of things like "what happens when we click on this element but the onclick handler calls event.prevent_default()".
    For instance, we might use in-process-memory DOM implementation that was reliable enough that you trusted the results, and fast enough that one wouldn't hesitate to write as many test cases as they wanted

    • then a majority of an application's tests could be fast, and a minority that really needed to be in a browser could bear that overhead
  • simulation / benchmarking / etc

    • if you want to run your application in an environment that has a DOM, you should be able to. We should not arbitrarily limit your application to only be able to run inside of probably-slow, and probably-cumbersome-to-use web browser

it doesn't mess around with default checkedness as there's no reason to do so

It comes down to the framing. You're framing it as being about "default checkedness".
In that context, yes we currently have no reason to believe that we need percy-dom to do anything to the "default checkedness".

However, if this is framed around "setting the checked attribute" and "the user thinks that they're setting the checked attribute", then that changes things.

So, the question becomes, would a user ever want to set the "checked" attribute?

If <input foo="bar" /> sets the foo attribute, a user might expect <input checked=true /> to set the "checked" attribute.

If we pointed the user at an application and asked "what do you think happens when we render <input checked=true /> they should probably say "The checked attribute gets set".

In practice this would only impact the user if they were doing input.getAttribute('checked') and got back null instead of "true".

It's entirely possible that roughly 0 people will ever do this.

And, if they do, they can open an issue and we can do further design work.

So, I'm okay with, for now, having percy-dom only control the checked property, and not the checked attribute.

Proposal

Let's be explicit that percy-dom is not for server-side rendering.

Don't do this. Instead, we can say that if a user truly wants to set the "checked" attribute they can use .setAttribute on the node themselves https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute .

checked manipulates the HTMLInputElement.checked property - it doesn't mess around with default checkedness checked attribute (words swapped by me)

Sounds good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Neat.

I'll delete the example as it's no longer illustrative of doing something reasonable. If users want to set the default checkedness of an element, they should just get the DOM element and add/remove the attribute. It can be grabbed from the commit history if its useful later?

It's also worth mentioning that the situation with value is exactly the same as with checked, and having different behavior for them would be strange, at least long-term. If nothing explodes by releasing this change out into the wild, I think changing how percy-dom treats value should be changed to match checked. For now, the book entries reflect the difference.

Let's be explicit that percy-dom is not for server-side rendering.

I will leave it out. It was only in service of motivating the checked VDOM attribute behavior.

However, if this is framed around "setting the checked attribute" and "the user thinks that they're setting the checked attribute", then that changes things.

It's unfortunate.

  1. The core issue is the HTML standard. The checked HTML attribute should be called defaultChecked or something to distinguish it.
  2. Secondly, using a macro called html! with a HTML-like format to manipulate a DOM. It's misleading because an HTML document and a DOM have aliased but distinct concepts, such as the checked attribute and the checked property. ...It's a small enough issue that the choice might be worth the benefit of the intuitive markup.

Unless we remove the checked attribute from percy (and use something like is_checked and default_checked just to make it harder to assume what they mean) I think we're going to need to bite the bullet that devs will sometimes assume that percy-dom does the wrong thing - with respect to percy's intended utility.

But at least percy-dom will be doing the sensible thing (as far as we can tell, at least), so it's "just" a learning speed bump. At least percy isn't hard to use once the dev (hopefully) reads the book section to figure out what's going on.


Some leftover thoughts:

A DOM does require on the browser in any way. We should not couple these unrelated concepts.

Whether percy can manipulate a DOM should never depend on whether it is running in a browser.

I agree. We're talking past each other though. I don't think this is relevant to what I'm proposing.

Testing, simulation, benchmarking, etc. in environments without a browser seem neat. I just don't see what this has to do with server-side rendering though.

Server-side rendering is: generating a static HTML document and sending it off to the client. Manipulating a DOM is not useful in this process. Manipulating a virtual DOM is useful. But percy already has a distinction between virtual-node+html-macro and percy-dom such that manipulating a virtual DOM server-side to generate HTML has no dependency on the idea of actually manipulating a real or simulated or mocked DOM.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll delete the example

Unless it would be hard, let's just change it to be a embed-non-percy-node example where we show you how to create your own node that percy doesn't touch.

Can just delete all of the checkedness stuff and instead just make the example create a <div hello="world" /> or something using web-sys and insert that into
a percy contain .on_create_element.

If nothing explodes by releasing this change out into the wild, I think changing how percy-dom treats value should be changed to match checked

Sounds good.

Secondly, using a macro called html! with a HTML-like format to manipulate a DOM

html! macro is describing the DOM that you want.

Then percy-dom manipulates it.

The macro isn't manipulating the DOM, just declaring one.

So, it's designed to stay close to how a user would expect to be able to declare a DOM tree. Since HTML is likely the most common way to way to define a DOM tree, it's designed to feel like HTML.

This lets users avoid learning percy and instead just learn common specs/concepts. Transferable knowledge.

Unless we remove the checked attribute from percy (and use something like is_checked and default_checked just to make it harder to assume what they mean)
I think we're going to need to bite the bullet that devs will sometimes assume that percy-dom does the wrong thing - with respect to percy's intended utility.

rough idea

I could see a potential future (would require design work) where the html! macro gave a compile time error if you used checked.

It would tell you to use is_checked or how to set the attribute if you really wanted to do that.

That wouldn't help users that use VirtualNode directly, however.

But at least percy-dom will be doing the sensible thing (as far as we can tell, at least), so it's "just" a learning speed bump

Yeah, especially if the macro compile time error points you to the docs.

I agree. We're talking past each other though

Yeah I only realized that after I continued reading your comment

Server-side rendering is: generating a static HTML document and sending it off to the client. Manipulating a DOM is not useful in this process.

Here's what I had in mind:

  • user starts some sort of simulation application where they are running their percy application in an emulated browser

  • users triggers a bunch of clicks / input events / etc

  • user now wants to take the DOM tree from that emulated browser and turn it into a string

If you're really on default checkedness that DOM tree will be different from your virtual tree. Your virtual tree doesn't know whether the element is checked. Only the emulated DOM has info about whether the input is checked.

I haven't thought enough about this to know if it relates to our convo, so I'm more so communicating that I'm not certain that we should operate as if server-side rendering of a "real DOM" will never be useful.


- Server-side rendering sends HTML and HTML attributes only, no DOM properties. Therefore `percy`'s `virtual-node` needs to set the `checked` attribute (default checkedness) for server side rendering, using `VirtualNode`'s `Display` implementation i.e. `virtual_node.to_string()`
- `percy-dom` is intended for client-side rendering, but it overrides the default checkedness to match `VirtualNode`'s `Display` implementation.

---

## Running this example

```sh
git clone [email protected]:chinedufn/percy.git
cd percy

# Use one of the following to run the example in a headless web browser
CHROMEDRIVER=chromedriver ./examples/input-element-default-checkedness/start.sh
GECKODRIVER=geckodriver ./examples/input-element-default-checkedness/start.sh
SAFARIDRIVER=safaridriver ./examples/input-element-default-checkedness/start.sh
```

You may need to install the appropriate driver and (optionally) add it to your `PATH`.
Loading