mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
test(chrome): resolve the five state selectors on both matchers
the fixture differs one state at a time: a disabled button and an aria-disabled role control, a box ticked by script with no checked attribute beside one cleared by script that has it, and a select whose first option is selected without the markup saying so anywhere. half the states are asked inside one container, because a state the whole page has an opinion about answers with most of the document and a want list nobody can check by reading.
This commit is contained in:
1 parent
c8ca350115
commit
654c0afd90
2 files changed
+234
No files matched your search
@@ -225,6 +225,211 @@ func TestSelectors_SecureCombinesWithAnotherKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The other five boolean states are derived from the live element the same way
|
||||
// secure is, and were reached the same wrong way: as a markup attribute, which
|
||||
// builds [clickable="true"] and matches nothing on any page. The key is
|
||||
// accepted, so no unknown-key error fires, and the worked example in
|
||||
// docs/manual/spec-language.md, find({testTag: ..., clickable: true}), resolved
|
||||
// to no element at all on web while resolving against the dump on the goja host.
|
||||
//
|
||||
// Half the states are asked inside one container, because a state the whole page
|
||||
// has an opinion about (everything is enabled, almost nothing is checked)
|
||||
// answers with most of the document and a want list nobody can check by reading.
|
||||
func TestSelectors_BooleanStatesNameWhatBothProducersReport(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)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
selector string
|
||||
scope string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "clickable",
|
||||
selector: "clickable:true",
|
||||
want: []string{
|
||||
"login_email", "login_password", "login_note", "login_remember",
|
||||
"state_save", "state_cancel", "state_submit", "state_remember",
|
||||
"state_agree", "state_month", "todo_link",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not clickable",
|
||||
selector: "clickable:false",
|
||||
scope: "state_row",
|
||||
want: []string{"state_january", "state_february"},
|
||||
},
|
||||
{
|
||||
// A control that carries no disabled property is marked by
|
||||
// aria-disabled alone, and both producers read both.
|
||||
name: "not enabled",
|
||||
selector: "enabled:false",
|
||||
want: []string{"state_cancel", "state_submit"},
|
||||
},
|
||||
{
|
||||
name: "enabled",
|
||||
selector: "enabled:true",
|
||||
scope: "state_row",
|
||||
want: []string{
|
||||
"state_save", "state_remember", "state_agree", "state_month",
|
||||
"state_january", "state_february",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "focused",
|
||||
selector: "focused:true",
|
||||
want: []string{"state_save"},
|
||||
},
|
||||
{
|
||||
name: "not focused",
|
||||
selector: "focused:false",
|
||||
scope: "state_row",
|
||||
want: []string{
|
||||
"state_cancel", "state_submit", "state_remember", "state_agree",
|
||||
"state_month", "state_january", "state_february",
|
||||
},
|
||||
},
|
||||
{
|
||||
// state_remember was ticked by script and carries no checked
|
||||
// attribute; state_agree carries the attribute and was cleared. The
|
||||
// markup attribute is the page's starting state, so a selector
|
||||
// reading it names the pair the wrong way round.
|
||||
name: "checked",
|
||||
selector: "checked:true",
|
||||
want: []string{"state_remember"},
|
||||
},
|
||||
{
|
||||
name: "not checked",
|
||||
selector: "checked:false",
|
||||
scope: "state_row",
|
||||
want: []string{
|
||||
"state_save", "state_cancel", "state_submit", "state_agree",
|
||||
"state_month", "state_january", "state_february",
|
||||
},
|
||||
},
|
||||
{
|
||||
// The first option of a select is selected without the markup
|
||||
// saying so anywhere.
|
||||
name: "selected",
|
||||
selector: "selected:true",
|
||||
want: []string{"state_january"},
|
||||
},
|
||||
{
|
||||
name: "not selected",
|
||||
selector: "selected:false",
|
||||
scope: "state_row",
|
||||
want: []string{
|
||||
"state_save", "state_cancel", "state_submit", "state_remember",
|
||||
"state_agree", "state_month", "state_february",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
key, value, _ := strings.Cut(testCase.selector, ":")
|
||||
object := objectSelector(key, value)
|
||||
encoded := objectSelectorJSON(object)
|
||||
if testCase.scope != "" {
|
||||
scope := objectSelector("id", testCase.scope)
|
||||
path := []hierarchy.Selector{scope, object}
|
||||
encoded = "[" + objectSelectorJSON(scope) + "," + encoded + "]"
|
||||
if native := selectorIDsFromDumpPath(tree, path); !slices.Equal(
|
||||
native,
|
||||
testCase.want,
|
||||
) {
|
||||
t.Errorf("the dump matched %v under %s, want %v",
|
||||
native, testCase.scope, testCase.want)
|
||||
}
|
||||
} else {
|
||||
if native := selectorIDsFromDump(tree, testCase.selector); !slices.Equal(
|
||||
native,
|
||||
testCase.want,
|
||||
) {
|
||||
t.Errorf("the dump matched %v for %s, want %v",
|
||||
native, testCase.selector, testCase.want)
|
||||
}
|
||||
if web := selectorIDsFromWebRuntime(
|
||||
ctx, t, d, testCase.selector,
|
||||
); !slices.Equal(web, testCase.want) {
|
||||
t.Errorf("the web runtime matched %v for %s, want %v",
|
||||
web, testCase.selector, testCase.want)
|
||||
}
|
||||
if native := selectorIDsFromDumpObject(tree, object); !slices.Equal(
|
||||
native,
|
||||
testCase.want,
|
||||
) {
|
||||
t.Errorf("the dump object form matched %v, want %v",
|
||||
native, testCase.want)
|
||||
}
|
||||
}
|
||||
if web := selectorIDsFromWebRuntime(ctx, t, d, encoded); !slices.Equal(
|
||||
web,
|
||||
testCase.want,
|
||||
) {
|
||||
t.Errorf("the web runtime matched %v for %s, want %v",
|
||||
web, encoded, testCase.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A type selector is valid only at the head of a compound, and a multi-key
|
||||
// object selector concatenates its parts into one, so {id, tag} built
|
||||
// '[id="state_month"]select' and querySelectorAll threw a SyntaxError. Which of
|
||||
// the two a spec got depended on the order its author wrote the keys in.
|
||||
func TestSelectors_TagCombinesWithAnotherKey(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: "state_month"},
|
||||
{Attr: "tag", Value: "select"},
|
||||
}}
|
||||
want := []string{"state_month"}
|
||||
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
|
||||
// (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
|
||||
@@ -474,6 +679,14 @@ func objectSelectorJSON(sel hierarchy.Selector) string {
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
func selectorIDsFromDumpPath(tree *hierarchy.Tree, path []hierarchy.Selector) []string {
|
||||
var ids []string
|
||||
for _, node := range tree.FindAllBySelectorPath(path) {
|
||||
ids = append(ids, node.Element.ResourceID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func selectorIDsFromDumpObject(tree *hierarchy.Tree, sel hierarchy.Selector) []string {
|
||||
var ids []string
|
||||
for _, node := range tree.FindAllBySelector(sel) {
|
||||
|
||||
@@ -27,6 +27,27 @@
|
||||
<div id="login_terms" contenteditable="true">terms</div>
|
||||
<input id="login_remember" type="checkbox" aria-label="login_remember" />
|
||||
</form>
|
||||
<!-- The boolean states are derived from the live element, never written by
|
||||
the markup, so these controls differ one state at a time: a disabled
|
||||
button and an aria-disabled role control, a box ticked by script with
|
||||
no checked attribute beside one cleared by script that has the
|
||||
attribute, and a select whose first option is selected without one. -->
|
||||
<div id="state_row">
|
||||
<button id="state_save">save</button>
|
||||
<button id="state_cancel" disabled>cancel</button>
|
||||
<div id="state_submit" role="button" aria-disabled="true">submit</div>
|
||||
<input id="state_remember" type="checkbox" aria-label="state_remember" />
|
||||
<input id="state_agree" type="checkbox" checked aria-label="state_agree" />
|
||||
<select id="state_month" aria-label="state_month">
|
||||
<option id="state_january">January</option>
|
||||
<option id="state_february">February</option>
|
||||
</select>
|
||||
</div>
|
||||
<script id="state_script">
|
||||
document.getElementById("state_remember").checked = true;
|
||||
document.getElementById("state_agree").checked = false;
|
||||
document.getElementById("state_save").focus();
|
||||
</script>
|
||||
<todo-app id="todo_app">
|
||||
<todo-list id="todo_list">
|
||||
<li id="todo_1">Buy milk</li>
|
||||
|
||||
Reference in new issue
Block a user