Skip to content

Commit

Permalink
Test and benchmark the Single[Matcher] funcs
Browse files Browse the repository at this point in the history
  • Loading branch information
mna committed Jun 13, 2021
1 parent 0126a1f commit ee8a7e0
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 6 deletions.
20 changes: 20 additions & 0 deletions bench_traversal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package goquery

import (
"testing"

"github.com/andybalholm/cascadia"
)

func BenchmarkFind(b *testing.B) {
Expand Down Expand Up @@ -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)
}
})
}
18 changes: 12 additions & 6 deletions type.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,25 @@ 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)}
}

// 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
Expand Down
38 changes: 38 additions & 0 deletions type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)

Expand Down Expand Up @@ -208,3 +209,40 @@ func TestIssue103(t *testing.T) {
}
t.Log(text)
}

func TestSingle(t *testing.T) {
data := `
<html>
<body>
<div class="b">1</div>
<div class="a">2</div>
<div class="a">3</div>
<p class="b">4</p>
</body>
</html>
`
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)
}
}

0 comments on commit ee8a7e0

Please sign in to comment.