test(chrome): compare checked, selected and focused across both producers

the target enumeration carries none of the three, so they reach a spec
through the ax handle alone, and a selector naming one of them resolves
against that same reading. the shadow fixture holds the focused control
inside its shadow root, where document.activeElement names the mount
element and only a producer that descends finds the field.
This commit is contained in:
pj committed 2026-08-18 20:43:46 +05:30
1 parent 654c0afd90
commit 5404909e12
4 files changed
+50

No files matched your search

@@ -57,6 +57,13 @@ type elementFacts struct {
// typed value may be written into the shared record, so the two producers // typed value may be written into the shared record, so the two producers
// disagreeing about it writes a credential into a run's trace. // disagreeing about it writes a credential into a run's trace.
secure string secure string
// checked, selected and focused reach a spec through the ax handle alone:
// they decide nothing about which action is offered, so the target
// enumeration does not carry them, and a selector naming one of them
// resolves against this same reading.
checked bool
selected bool
focused bool
// handleClickable and handleEditable are the same facts on the ax element a // handleClickable and handleEditable are the same facts on the ax element a
// spec reaches through state.ax.find, a third place they are computed and the // spec reaches through state.ax.find, a third place they are computed and the
// one that has twice been the odd one out: clickable answered a hardcoded // one that has twice been the odd one out: clickable answered a hardcoded
@@ -135,6 +142,9 @@ func factsFromHierarchyDump(t *testing.T, dump string) []factRow {
scrollable: element.Attributes["scrollable"] == "true", scrollable: element.Attributes["scrollable"] == "true",
hintText: element.Attributes["hintText"], hintText: element.Attributes["hintText"],
secure: secureFromDump(element), secure: secureFromDump(element),
checked: element.Checked,
selected: element.Selected,
focused: element.Focused,
positiveBounds: hasPositiveBounds( positiveBounds: hasPositiveBounds(
element.Bounds.Width(), element.Bounds.Width(),
element.Bounds.Height(), element.Bounds.Height(),
@@ -184,6 +194,9 @@ func factsFromWebRuntime(
HandleClickable bool `json:"handleClickable"` HandleClickable bool `json:"handleClickable"`
HandleEditable bool `json:"handleEditable"` HandleEditable bool `json:"handleEditable"`
Secure *bool `json:"secure"` Secure *bool `json:"secure"`
Checked bool `json:"checked"`
Selected bool `json:"selected"`
Focused bool `json:"focused"`
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
} }
@@ -204,6 +217,9 @@ func factsFromWebRuntime(
handleClickable: item.HandleClickable, handleClickable: item.HandleClickable,
handleEditable: item.HandleEditable, handleEditable: item.HandleEditable,
secure: secureFromWebRuntime(item.Secure), secure: secureFromWebRuntime(item.Secure),
checked: item.Checked,
selected: item.Selected,
focused: item.Focused,
positiveBounds: hasPositiveBounds(item.Width, item.Height), positiveBounds: hasPositiveBounds(item.Width, item.Height),
}, },
}) })
@@ -268,6 +284,9 @@ func requireBothPolarities(t *testing.T, rows []factRow) {
{"editable", func(f elementFacts) bool { return f.editable }}, {"editable", func(f elementFacts) bool { return f.editable }},
{"scrollable", func(f elementFacts) bool { return f.scrollable }}, {"scrollable", func(f elementFacts) bool { return f.scrollable }},
{"hintText", func(f elementFacts) bool { return f.hintText != "" }}, {"hintText", func(f elementFacts) bool { return f.hintText != "" }},
{"checked", func(f elementFacts) bool { return f.checked }},
{"selected", func(f elementFacts) bool { return f.selected }},
{"focused", func(f elementFacts) bool { return f.focused }},
{"positiveBounds", func(f elementFacts) bool { return f.positiveBounds }}, {"positiveBounds", func(f elementFacts) bool { return f.positiveBounds }},
} { } {
var sawTrue, sawFalse bool var sawTrue, sawFalse bool
@@ -440,6 +459,9 @@ func compareDerivedFacts(t *testing.T, fromDump, fromWebRuntime []factRow) {
{"enabled", dump.enabled, web.enabled}, {"enabled", dump.enabled, web.enabled},
{"editable", dump.editable, web.editable}, {"editable", dump.editable, web.editable},
{"scrollable", dump.scrollable, web.scrollable}, {"scrollable", dump.scrollable, web.scrollable},
{"checked", dump.checked, web.checked},
{"selected", dump.selected, web.selected},
{"focused", dump.focused, web.focused},
{"positiveBounds", dump.positiveBounds, web.positiveBounds}, {"positiveBounds", dump.positiveBounds, web.positiveBounds},
} { } {
if fact.dump != fact.web { if fact.dump != fact.web {
+10
View File
@@ -27,9 +27,19 @@
<button id="shadow-cancel" disabled>cancel</button> <button id="shadow-cancel" disabled>cancel</button>
<input id="shadow-amount" type="text" value="10" placeholder="0.00" /> <input id="shadow-amount" type="text" value="10" placeholder="0.00" />
<input id="shadow-password" type="password" placeholder="Password" /> <input id="shadow-password" type="password" placeholder="Password" />
<input id="shadow-agree" type="checkbox" />
<select id="shadow-choice">
<option id="shadow-choice-first">first</option>
</select>
<div id="shadow-plain">plain</div> <div id="shadow-plain">plain</div>
</div> </div>
<div id="shadow-scroller"><div id="shadow-scroller-content"></div></div>`; <div id="shadow-scroller"><div id="shadow-scroller-content"></div></div>`;
// document.activeElement names the HOST while focus is inside its shadow
// root, so a producer that does not descend reports focus on the mount
// element and never on the control, which is what a Compose for Web page
// looks like from the outside.
root.getElementById("shadow-agree").checked = true;
root.getElementById("shadow-save").focus();
</script> </script>
</body> </body>
</html> </html>
+8
View File
@@ -97,6 +97,14 @@
<div id="filler"></div> <div id="filler"></div>
<script id="page-script"> <script id="page-script">
document.getElementById("delegating-root").onclick = function () {}; document.getElementById("delegating-root").onclick = function () {};
// checked and focused are live state: the markup attribute records only
// what the page loaded with, and nothing writes focus down at all. Both
// producers read them off the element, so the fixture has to hold one of
// each or the comparison is false against false over the whole page.
// #choice-first is the third: an option a select made current with no
// attribute anywhere saying so.
document.getElementById("agree").checked = true;
document.getElementById("save").focus();
</script> </script>
</body> </body>
</html> </html>
+10
View File
@@ -22,6 +22,9 @@ type AxHandle = {
clickable?: boolean; clickable?: boolean;
editable?: boolean; editable?: boolean;
secure?: boolean | null; secure?: boolean | null;
checked?: boolean;
selected?: boolean;
focused?: boolean;
}; };
type Ax = { find(selector: unknown): AxHandle | undefined }; type Ax = { find(selector: unknown): AxHandle | undefined };
@@ -55,6 +58,13 @@ function domFacts(): unknown[] {
// side compares it against a dump that leaves the fact out entirely for an // side compares it against a dump that leaves the fact out entirely for an
// element no producer states it for. // element no producer states it for.
secure: handle?.secure ?? null, secure: handle?.secure ?? null,
// The three states the target enumeration does not carry: they decide
// nothing about which action is offered, so they reach a spec through the
// handle alone, and a selector naming them resolves against the same
// reading.
checked: handle?.checked ?? false,
selected: handle?.selected ?? false,
focused: handle?.focused ?? false,
width: target.width ?? 0, width: target.width ?? 0,
height: target.height ?? 0, height: target.height ?? 0,
}; };