From ee8a7e0a3a2c7e89bc02c7c00235c1a22b87ee7b Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Sun, 13 Jun 2021 15:02:43 -0400 Subject: [PATCH] Test and benchmark the Single[Matcher] funcs --- bench_traversal_test.go | 20 ++++++++++++++++++++ type.go | 18 ++++++++++++------ type_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/bench_traversal_test.go b/bench_traversal_test.go index de84bcd..8cb673b 100644 --- a/bench_traversal_test.go +++ b/bench_traversal_test.go @@ -2,6 +2,8 @@ package goquery import ( "testing" + + "github.com/andybalholm/cascadia" ) func BenchmarkFind(b *testing.B) { @@ -800,3 +802,21 @@ func BenchmarkClosestNodes(b *testing.B) { b.Fatalf("want 2, got %d", n) } } + +func BenchmarkSingleMatcher(b *testing.B) { + doc := Doc() + multi := cascadia.MustCompile(`div`) + single := SingleMatcher(multi) + b.ResetTimer() + + b.Run("multi", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = doc.FindMatcher(multi) + } + }) + b.Run("single", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = doc.FindMatcher(single) + } + }) +} diff --git a/type.go b/type.go index e3d0b68..943756b 100644 --- a/type.go +++ b/type.go @@ -124,9 +124,17 @@ type Matcher interface { // Single compiles a selector string to a Matcher that stops after the first // match is found. // -// By default, Selection.Find and other functions that accept a selector string -// will use all matches corresponding to that selector. By using the Matcher -// returned by Single, at most the first match will be used. +// By default, Selection.Find and other functions that accept a selector +// string to find nodes will use all matches corresponding to that selector. +// By using the Matcher returned by Single, at most the first match will be +// used. +// +// Note that the single-selection property of the Matcher only applies for +// methods where the Matcher is used to select nodes, not to filter or check +// if a node matches the Matcher - in those cases, the behaviour of the +// Matcher is unchanged (e.g. FilterMatcher(Single("div")) will still result +// in a Selection with multiple "div"s if there were many "div"s in the +// Selection to begin with). func Single(selector string) Matcher { return singleMatcher{compileMatcher(selector)} } @@ -134,9 +142,7 @@ func Single(selector string) Matcher { // SingleMatcher returns a Matcher matches the same nodes as m, but that stops // after the first match is found. // -// By default, Selection.FindMatcher and other functions that accept a Matcher -// will use all corresponding matches. By using the Matcher returned by -// SingleMatcher, at most the first match will be used. +// See the documentation of function Single for more details. func SingleMatcher(m Matcher) Matcher { if _, ok := m.(singleMatcher); ok { // m is already a singleMatcher diff --git a/type_test.go b/type_test.go index 798d4ea..0bb2a4f 100644 --- a/type_test.go +++ b/type_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/andybalholm/cascadia" "golang.org/x/net/html" ) @@ -208,3 +209,40 @@ func TestIssue103(t *testing.T) { } t.Log(text) } + +func TestSingle(t *testing.T) { + data := ` + + +
1
+
2
+
3
+

4

+ + +` + doc, err := NewDocumentFromReader(strings.NewReader(data)) + if err != nil { + t.Fatal(err) + } + + text := doc.FindMatcher(Single("div")).Text() + if text != "1" { + t.Fatalf("want %q, got %q", "1", text) + } + + // Here, the Single has no effect as the selector is used to filter + // from the existing selection, not to find nodes in the document. + divs := doc.Find("div") + text = divs.FilterMatcher(Single(".a")).Text() + if text != "23" { + t.Fatalf("want %q, got %q", "23", text) + } + + classA := cascadia.MustCompile(".a") + classB := cascadia.MustCompile(".b") + text = doc.FindMatcher(classB).AddMatcher(SingleMatcher(classA)).Text() + if text != "142" { + t.Fatalf("want %q, got %q", "142", text) + } +}