mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-04 12:07:09 +00:00
fix(spec): a hint selector names the ladder both producers derive
hintText and placeholderValue are the accessible-name ladder, derived from the live element, and compiling them to [placeholder="..."] made them name the wrong field or none at all. A field labelled by an aria-label or a bound <label> carries no placeholder, so it resolved against the dump on the goja host and reached nothing here; one carrying both answered to its placeholder here where the dump answers to its aria-label, which lands a find on an element nobody named. Both keys read the same fieldHint elementHandle and the hierarchy dump (internal/driver/chrome/driver.go) derive the fact with, so a selector cannot name a field this host calls something else. An empty hint names nothing rather than everything that is no field: both producers write the fact only where the ladder answered. placeholder stays the attribute the markup writes, which is what the dump carries under that name too, so a field whose hint is something else still answers to it on both hosts.
This commit is contained in:
1 parent
01aa365d0e
commit
6e551e9559
2 files changed
+78
-2
No files matched your search
@@ -129,9 +129,10 @@ const KNOWN_KEY_TO_CSS: Record<string, (value: string) => string> = {
|
|||||||
// The name the ios sidecar writes the label under, and the canonical key
|
// The name the ios sidecar writes the label under, and the canonical key
|
||||||
// internal/hierarchy resolves the whole family through.
|
// internal/hierarchy resolves the whole family through.
|
||||||
accessibilityText: (v) => `[aria-label="${cssEscape(v)}"]`,
|
accessibilityText: (v) => `[aria-label="${cssEscape(v)}"]`,
|
||||||
|
// The attribute the markup writes, which is what the hierarchy dump carries
|
||||||
|
// under this name too. It says nothing about the ladder hintText climbs: a
|
||||||
|
// field whose placeholder an aria-label outranks still answers here.
|
||||||
placeholder: (v) => `[placeholder="${cssEscape(v)}"]`,
|
placeholder: (v) => `[placeholder="${cssEscape(v)}"]`,
|
||||||
placeholderValue: (v) => `[placeholder="${cssEscape(v)}"]`,
|
|
||||||
hintText: (v) => `[placeholder="${cssEscape(v)}"]`,
|
|
||||||
secure: secureSelector,
|
secure: secureSelector,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,8 +180,26 @@ const KNOWN_KEY_TO_STATE: Record<
|
|||||||
selected: stateMatcher(isSelected),
|
selected: stateMatcher(isSelected),
|
||||||
editable: stateMatcher(isEditable),
|
editable: stateMatcher(isEditable),
|
||||||
scrollable: scrollableMatcher,
|
scrollable: scrollableMatcher,
|
||||||
|
hintText: hintMatcher,
|
||||||
|
placeholderValue: hintMatcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// hintText is the accessible-name ladder, derived from the live element, and
|
||||||
|
// compiling it to [placeholder="..."] made it name the wrong field or none: a
|
||||||
|
// field labelled by an aria-label or a bound <label> carries no placeholder at
|
||||||
|
// all, so it resolved against the dump on the goja host and reached nothing
|
||||||
|
// here, and one carrying both answered to the placeholder here where the dump
|
||||||
|
// answers to the aria-label. It reads the same fieldHint elementHandle and the
|
||||||
|
// hierarchy dump (internal/driver/chrome/driver.go) derive the fact with, so a
|
||||||
|
// selector cannot name a field this host calls something else.
|
||||||
|
//
|
||||||
|
// An empty hint names nothing rather than everything that is no field: both
|
||||||
|
// producers write the attribute only where the ladder answered.
|
||||||
|
function hintMatcher(value: string): (element: Element) => boolean {
|
||||||
|
if (value === "") return () => false;
|
||||||
|
return (element) => fieldHint(element) === value;
|
||||||
|
}
|
||||||
|
|
||||||
// A value that is neither true nor false can match nothing, the way a CSS part
|
// A value that is neither true nor false can match nothing, the way a CSS part
|
||||||
// built from one resolves to `:not(*)`.
|
// built from one resolves to `:not(*)`.
|
||||||
function stateMatcher(
|
function stateMatcher(
|
||||||
|
|||||||
@@ -1063,6 +1063,63 @@ test("scrollable selects the containers this host offers a scroll on", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// hintText is the accessible-name ladder, not one attribute, and compiling it
|
||||||
|
// to [placeholder="..."] reached the wrong field or none: the fields labelled
|
||||||
|
// from a rung above the placeholder resolved against the dump on the goja host
|
||||||
|
// and named nothing here, and the one carrying both answered to its placeholder
|
||||||
|
// here where the dump answers to its aria-label. Matching MORE than the spec
|
||||||
|
// said is the half that lands a find on an element nobody wrote.
|
||||||
|
const hintFields = (): FakeElementSpec[] => [
|
||||||
|
{ tag: "input", x: 0, y: 0, width: 120, height: 20, id: "email", label: "Email" },
|
||||||
|
{
|
||||||
|
tag: "input", x: 0, y: 20, width: 120, height: 20, id: "search",
|
||||||
|
attrs: { placeholder: "Search customers" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "input", x: 0, y: 40, width: 120, height: 20, id: "amount",
|
||||||
|
label: "Amount in rupees", attrs: { placeholder: "0.00" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "input", x: 0, y: 60, width: 120, height: 20, id: "code",
|
||||||
|
attrs: { name: "verification_code" },
|
||||||
|
},
|
||||||
|
{ tag: "h1", x: 0, y: 80, width: 120, height: 20, id: "title", label: "Email" },
|
||||||
|
];
|
||||||
|
|
||||||
|
test("hintText selects the field by the name this host derives for it", () => {
|
||||||
|
withFakeDocument(hintFields().map(fakeElement), () => {
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "Email" }), ["email"]);
|
||||||
|
assert.deepEqual(matchedIDs("hintText:Email"), ["email"]);
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "Search customers" }), ["search"]);
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "Amount in rupees" }), ["amount"]);
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "verification_code" }), ["code"]);
|
||||||
|
// The rung the ladder passed over is not the field's hint, and a heading
|
||||||
|
// is no field at all, so neither answers.
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "0.00" }), []);
|
||||||
|
// Both producers write the fact only where the ladder answered, so an empty
|
||||||
|
// hint names nothing rather than everything that is no field.
|
||||||
|
assert.deepEqual(matchedIDs({ hintText: "" }), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("placeholderValue names the ladder hintText names", () => {
|
||||||
|
withFakeDocument(hintFields().map(fakeElement), () => {
|
||||||
|
assert.deepEqual(matchedIDs({ placeholderValue: "Email" }), ["email"]);
|
||||||
|
assert.deepEqual(matchedIDs({ placeholderValue: "Amount in rupees" }), ["amount"]);
|
||||||
|
assert.deepEqual(matchedIDs({ placeholderValue: "0.00" }), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// placeholder stays the attribute the markup writes, which is what the dump
|
||||||
|
// carries under that name too, so the two hosts read the same string for it.
|
||||||
|
test("placeholder names the attribute the markup writes", () => {
|
||||||
|
withFakeDocument(hintFields().map(fakeElement), () => {
|
||||||
|
assert.deepEqual(matchedIDs({ placeholder: "0.00" }), ["amount"]);
|
||||||
|
assert.deepEqual(matchedIDs({ placeholder: "Search customers" }), ["search"]);
|
||||||
|
assert.deepEqual(matchedIDs({ placeholder: "Email" }), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("attrs carries every other attribute alongside tag and aria-label", () => {
|
test("attrs carries every other attribute alongside tag and aria-label", () => {
|
||||||
const attrs = attrsOf(
|
const attrs = attrsOf(
|
||||||
domElement({ tag: "input", attributes: { id: "txn-note", placeholder: "What's this for?" } }),
|
domElement({ tag: "input", attributes: { id: "txn-note", placeholder: "What's this for?" } }),
|
||||||
|
|||||||
Reference in new issue
Block a user