mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(spec): keep a secure selector valid beside another key
a multi-key object selector concatenates its parts into one compound,
and a type selector is valid only at the head of one, so {id, secure}
built '[id="pwd"]input[type="password"]' and querySelectorAll threw.
This commit is contained in:
1 parent
c8d52cb7d5
commit
e2f9864201
2 files changed
+46
-1
No files matched your search
@@ -185,6 +185,46 @@ func TestSelectors_ResolveTheSameElementsAsTheWebRuntime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A multi-key object selector concatenates its parts into ONE compound CSS
|
||||||
|
// selector, and a type selector is valid only at the head of a compound. secure
|
||||||
|
// resolves to a type selector, so pairing it with any key that sorts before it
|
||||||
|
// turned the whole selector into a parse error: querySelectorAll throws, and
|
||||||
|
// what a spec sees is an exception out of the extractor rather than an element.
|
||||||
|
func TestSelectors_SecureCombinesWithAnotherKey(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.FileServer(http.Dir("testdata")))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
d := New()
|
||||||
|
defer d.Terminate(context.Background())
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := d.Launch(ctx, server.URL+"/selector-parity.html", false, nil); err != nil {
|
||||||
|
t.Fatalf("Launch: %v", err)
|
||||||
|
}
|
||||||
|
dump, err := d.Hierarchy(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Hierarchy: %v", err)
|
||||||
|
}
|
||||||
|
tree, err := hierarchy.Parse(dump)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse hierarchy: %v", err)
|
||||||
|
}
|
||||||
|
installSelectorProbe(ctx, t, d)
|
||||||
|
|
||||||
|
selector := hierarchy.Selector{Filters: []hierarchy.AttrFilter{
|
||||||
|
{Attr: "id", Value: "login_password"},
|
||||||
|
{Attr: "secure", Value: "true"},
|
||||||
|
}}
|
||||||
|
want := []string{"login_password"}
|
||||||
|
if native := selectorIDsFromDumpObject(tree, selector); !slices.Equal(native, want) {
|
||||||
|
t.Errorf("hierarchy matched %v, want %v", native, want)
|
||||||
|
}
|
||||||
|
encoded := objectSelectorJSON(selector)
|
||||||
|
if web := selectorIDsFromWebRuntime(ctx, t, d, encoded); !slices.Equal(web, want) {
|
||||||
|
t.Errorf("the web runtime matched %v for %s, want %v", web, encoded, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `text:` is a substring match on text content wherever the spec runs
|
// `text:` is a substring match on text content wherever the spec runs
|
||||||
// (docs/manual/spec-language.md), so a badge reading "Sent ✓" answers to
|
// (docs/manual/spec-language.md), so a badge reading "Sent ✓" answers to
|
||||||
// text:Sent on web the way it already does on Android and iOS, and one reading
|
// text:Sent on web the way it already does on Android and iOS, and one reading
|
||||||
|
|||||||
@@ -129,8 +129,13 @@ const KNOWN_KEY_TO_CSS: Record<string, (value: string) => string> = {
|
|||||||
// call editable, so false is every editable field that is NOT a password entry
|
// call editable, so false is every editable field that is NOT a password entry
|
||||||
// rather than everything that is not one: an element that is no field reports
|
// rather than everything that is not one: an element that is no field reports
|
||||||
// null, as android reports null for everything, and answers to neither value.
|
// null, as android reports null for everything, and answers to neither value.
|
||||||
|
//
|
||||||
|
// Both arms are wrapped in `:is()` because a multi-key selector concatenates the
|
||||||
|
// parts into one compound, where a type selector is valid only at the head:
|
||||||
|
// `{id, secure}` built `[id="pwd"]input[type="password"]`, which is a parse
|
||||||
|
// error, and querySelectorAll throws rather than answering with nothing.
|
||||||
function secureSelector(value: string): string {
|
function secureSelector(value: string): string {
|
||||||
if (value === "true") return `input[type="password"]`;
|
if (value === "true") return `:is(input[type="password"])`;
|
||||||
if (value !== "false") return ":not(*)";
|
if (value !== "false") return ":not(*)";
|
||||||
const textInput = ["password", ...NON_TEXT_INPUT_TYPES]
|
const textInput = ["password", ...NON_TEXT_INPUT_TYPES]
|
||||||
.map((type) => `:not([type="${type}"])`)
|
.map((type) => `:not([type="${type}"])`)
|
||||||
|
|||||||
Reference in new issue
Block a user