diff --git a/array_test.go b/array_test.go index 29d5d47..0390cf3 100644 --- a/array_test.go +++ b/array_test.go @@ -6,25 +6,18 @@ import ( func TestFirst(t *testing.T) { sel := Doc().Root.Find(".pvk-content").First() - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestFirstEmpty(t *testing.T) { - defer func() { - if e := recover(); e == nil { - t.Error("Expected a panic, First() called on empty Selection.") - } - }() + defer AssertPanic(t) Doc().Root.Find(".pvk-zzcontentzz").First() } func TestLast(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Last() - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) + // Should contain Footer foot := Doc().Root.Find(".footer") if !sel.Contains(foot.Nodes[0]) { @@ -34,16 +27,13 @@ func TestLast(t *testing.T) { func TestEq(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Eq(1) - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestEqNegative(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Eq(-1) - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) + // Should contain Footer foot := Doc().Root.Find(".footer") if !sel.Contains(foot.Nodes[0]) { @@ -53,17 +43,12 @@ func TestEqNegative(t *testing.T) { func TestSlice(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Slice(0, 2) - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + + AssertLength(t, sel.Nodes, 2) } func TestSliceOutOfBounds(t *testing.T) { - defer func() { - if e := recover(); e == nil { - t.Error("Expected a panic, Slice() called with out of bounds indices.") - } - }() + defer AssertPanic(t) Doc().Root.Find(".pvk-content").Slice(2, 12) } @@ -84,12 +69,7 @@ func TestGetNegative(t *testing.T) { } func TestGetInvalid(t *testing.T) { - defer func() { - if e := recover(); e == nil { - t.Error("Expected a panic, Get() called with out of bounds index.") - } - }() - + defer AssertPanic(t) sel := Doc().Root.Find(".pvk-content") sel.Get(129) } diff --git a/expand_test.go b/expand_test.go index bdf19f6..37ea247 100644 --- a/expand_test.go +++ b/expand_test.go @@ -6,52 +6,37 @@ import ( func TestAdd(t *testing.T) { sel := Doc().Root.Find("div.row-fluid").Add("a") - if len(sel.Nodes) != 19 { - t.Errorf("Expected 19 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 19) } func TestAddSelection(t *testing.T) { sel := Doc().Root.Find("div.row-fluid") sel2 := Doc().Root.Find("a") sel = sel.AddSelection(sel2) - if len(sel.Nodes) != 19 { - t.Errorf("Expected 19 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 19) } func TestAddSelectionNil(t *testing.T) { sel := Doc().Root.Find("div.row-fluid") - if len(sel.Nodes) != 9 { - t.Errorf("Expected div.row-fluid to have 9 nodes, found %v.", - len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 9) + sel = sel.AddSelection(nil) - if len(sel.Nodes) != 9 { - t.Errorf("Expected add nil to keep it to 9 nodes, found %v.", - len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 9) } func TestAddNodes(t *testing.T) { sel := Doc().Root.Find("div.pvk-gutter") sel2 := Doc().Root.Find(".pvk-content") sel = sel.AddNodes(sel2.Nodes...) - if len(sel.Nodes) != 9 { - t.Errorf("Expected 9 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 9) } func TestAddNodesNone(t *testing.T) { sel := Doc().Root.Find("div.pvk-gutter").AddNodes() - if len(sel.Nodes) != 6 { - t.Errorf("Expected 6 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 6) } func TestAndSelf(t *testing.T) { sel := Doc().Root.Find(".span12").Last().AndSelf() - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 2) } diff --git a/filter_test.go b/filter_test.go index a1fe282..eb497fa 100644 --- a/filter_test.go +++ b/filter_test.go @@ -6,42 +6,32 @@ import ( func TestFilter(t *testing.T) { sel := Doc().Root.Find(".span12").Filter(".alert") - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestFilterNone(t *testing.T) { sel := Doc().Root.Find(".span12").Filter(".zzalert") - if sel.Nodes != nil { - t.Error("Expected no node (nil), found some.") - } + AssertLength(t, sel.Nodes, 0) } func TestFilterFunction(t *testing.T) { sel := Doc().Root.Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool { return i > 0 }) - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 2) } func TestFilterNode(t *testing.T) { sel := Doc().Root.Find(".pvk-content") sel2 := sel.FilterNodes(sel.Nodes[2]) - if len(sel2.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel2.Nodes)) - } + AssertLength(t, sel2.Nodes, 1) } func TestFilterSelection(t *testing.T) { sel := Doc().Root.Find(".link") sel2 := Doc().Root.Find("a[ng-click]") sel3 := sel.FilterSelection(sel2) - if len(sel3.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel3.Nodes)) - } + AssertLength(t, sel3.Nodes, 1) } func TestFilterSelectionNil(t *testing.T) { @@ -49,64 +39,48 @@ func TestFilterSelectionNil(t *testing.T) { sel := Doc().Root.Find(".link") sel3 := sel.FilterSelection(sel2) - if len(sel3.Nodes) != 0 { - t.Errorf("Expected no node, found %v.", len(sel3.Nodes)) - } + AssertLength(t, sel3.Nodes, 0) } func TestNot(t *testing.T) { sel := Doc().Root.Find(".span12").Not(".alert") - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestNotNone(t *testing.T) { sel := Doc().Root.Find(".span12").Not(".zzalert") - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 2) } func TestNotFunction(t *testing.T) { sel := Doc().Root.Find(".pvk-content").NotFunction(func(i int, s *Selection) bool { return i > 0 }) - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestNotNode(t *testing.T) { sel := Doc().Root.Find(".pvk-content") sel2 := sel.NotNodes(sel.Nodes[2]) - if len(sel2.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel2.Nodes)) - } + AssertLength(t, sel2.Nodes, 2) } func TestNotSelection(t *testing.T) { sel := Doc().Root.Find(".link") sel2 := Doc().Root.Find("a[ng-click]") sel3 := sel.NotSelection(sel2) - if len(sel3.Nodes) != 6 { - t.Errorf("Expected 6 nodes, found %v.", len(sel3.Nodes)) - } + AssertLength(t, sel3.Nodes, 6) } func TestIntersection(t *testing.T) { sel := Doc().Root.Find(".pvk-gutter") sel2 := Doc().Root.Find("div").Intersection(sel) - if len(sel2.Nodes) != 6 { - t.Errorf("Expected 6 nodes, found %v.", len(sel2.Nodes)) - } + AssertLength(t, sel2.Nodes, 6) } func TestHas(t *testing.T) { sel := Doc().Root.Find(".container-fluid").Has(".center-content") - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 2) // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content } @@ -114,9 +88,7 @@ func TestHasNodes(t *testing.T) { sel := Doc().Root.Find(".container-fluid") sel2 := Doc().Root.Find(".center-content") sel = sel.HasNodes(sel2.Nodes...) - if len(sel.Nodes) != 2 { - t.Errorf("Expected 2 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 2) // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content } @@ -124,21 +96,15 @@ func TestHasSelection(t *testing.T) { sel := Doc().Root.Find("p") sel2 := Doc().Root.Find("small") sel = sel.HasSelection(sel2) - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 1) } func TestEnd(t *testing.T) { sel := Doc().Root.Find("p").Has("small").End() - if len(sel.Nodes) != 4 { - t.Errorf("Expected 4 nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 4) } func TestEndToTop(t *testing.T) { sel := Doc().Root.Find("p").Has("small").End().End().End() - if len(sel.Nodes) != 0 { - t.Errorf("Expected 0 node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 0) } diff --git a/iteration_test.go b/iteration_test.go index 7cfbb2c..c6187b5 100644 --- a/iteration_test.go +++ b/iteration_test.go @@ -16,9 +16,7 @@ func TestEach(t *testing.T) { if cnt != 4 { t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt) } - if len(sel.Nodes) != 6 { - t.Errorf("Expected 6 matching nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 6) } func TestEachEmptySelection(t *testing.T) { @@ -32,9 +30,7 @@ func TestEachEmptySelection(t *testing.T) { t.Error("Expected Each() to not be called on empty Selection.") } sel2 := sel.Find("div") - if sel2.Nodes != nil { - t.Error("Expected Find() on empty Selection to return an empty Selection.") - } + AssertLength(t, sel2.Nodes, 0) } func TestMap(t *testing.T) { diff --git a/traversal.go b/traversal.go index 5997c8b..ca4bdbe 100644 --- a/traversal.go +++ b/traversal.go @@ -27,7 +27,7 @@ func (this *Selection) FindSelection(sel *Selection) *Selection { }) } -// FindSelection() gets the descendants of each element in the current +// FindNodes() gets the descendants of each element in the current // Selection, filtered by some nodes. It returns a new Selection object // containing these matched elements. func (this *Selection) FindNodes(nodes ...*html.Node) *Selection { @@ -35,29 +35,12 @@ func (this *Selection) FindNodes(nodes ...*html.Node) *Selection { for _, n := range nodes { if sliceContains(this.Nodes, n) { - matches = appendWithoutDuplicates(matches, n) + matches = appendWithoutDuplicates(matches, []*html.Node{n}) } } return pushStack(this, matches) } -// Private internal implementation of the Find() methods -func findWithContext(selector string, nodes ...*html.Node) []*html.Node { - var matches []*html.Node - - sel := cascadia.MustCompile(selector) - // Match the selector on each node - for _, n := range nodes { - // Go down one level, becausejQuery's Find() selects only within descendants - for _, c := range n.Child { - if c.Type == html.ElementNode { - matches = appendWithoutDuplicates(matches, sel.MatchAll(c)) - } - } - } - return matches -} - // Contents() gets the children of each element in the Selection, // including text and comment nodes. It returns a new Selection object // containing these elements. @@ -97,6 +80,67 @@ func (this *Selection) ChildrenFiltered(selector string) *Selection { return pushStack(this, nodes) } +// Parent() gets the parent of each element in the Selection. It returns a +// new Selection object containing the matched elements. +func (this *Selection) Parent() *Selection { + return pushStack(this, getParentNodes(this.Nodes)) +} + +// Parent() gets the parent of each element in the Selection filtered by a +// selector. It returns a new Selection object containing the matched elements. +func (this *Selection) ParentFiltered(selector string) *Selection { + // Get the Parent() unfiltered + nodes := getParentNodes(this.Nodes) + // Create a temporary Selection to filter using winnow + sel := &Selection{nodes, this.document, nil} + // Filter based on selector + nodes = winnow(sel, selector, true) + return pushStack(this, nodes) +} + +// Internal implementation of parent nodes that return a raw slice of Nodes. +func getParentNodes(nodes []*html.Node) []*html.Node { + return mapNodes(nodes, func(i int, n *html.Node, _ *html.Node) []*html.Node { + if n.Parent != nil { + return []*html.Node{n.Parent} + } + return nil + }, nil) +} + +// Internal map function used by many traversing methods. Takes the source nodes +// to iterate on, the mapping function that returns an array of nodes, and a +// stop node used for the *Until methods. Returns an array of nodes mapped by +// calling the callback function once for each node in the source nodes. +func mapNodes(nodes []*html.Node, f func(int, *html.Node, *html.Node) []*html.Node, stopNode *html.Node) (result []*html.Node) { + + for i, n := range nodes { + if vals := f(i, n, stopNode); len(vals) > 0 { + result = appendWithoutDuplicates(result, vals) + } + } + + return +} + +// Private internal implementation of the Find() methods +func findWithContext(selector string, nodes ...*html.Node) []*html.Node { + var matches []*html.Node + + // TODO : Refactor to use mapNodes? + sel := cascadia.MustCompile(selector) + // Match the selector on each node + for _, n := range nodes { + // Go down one level, becausejQuery's Find() selects only within descendants + for _, c := range n.Child { + if c.Type == html.ElementNode { + matches = appendWithoutDuplicates(matches, sel.MatchAll(c)) + } + } + } + return matches +} + // Return the child nodes of each node in the Selection object, without // duplicates. func getSelectionChildren(s *Selection, elemOnly bool) (result []*html.Node) { diff --git a/traversal_test.go b/traversal_test.go index 760b143..88d771a 100644 --- a/traversal_test.go +++ b/traversal_test.go @@ -6,81 +6,61 @@ import ( func TestFind(t *testing.T) { sel := Doc().Root.Find("div.row-fluid") - if sel.Nodes == nil || len(sel.Nodes) != 9 { - t.Errorf("Expected 9 matching nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 9) } func TestFindNotSelf(t *testing.T) { sel := Doc().Root.Find("h1").Find("h1") - if len(sel.Nodes) > 0 { - t.Errorf("Expected no node, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 0) } func TestFindInvalidSelector(t *testing.T) { - defer func() { - if e := recover(); e == nil { - t.Error("Expected panic due to invalid selector.") - } - }() - + defer AssertPanic(t) Doc().Root.Find(":+ ^") } func TestChainedFind(t *testing.T) { sel := Doc().Root.Find("div.hero-unit").Find(".row-fluid") - if sel.Nodes == nil || len(sel.Nodes) != 4 { - t.Errorf("Expected 4 matching nodes, found %v.", len(sel.Nodes)) - } + AssertLength(t, sel.Nodes, 4) } func TestChildren(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Children() - if len(sel.Nodes) != 5 { - t.Errorf("Expected 5 child nodes, got %v.", len(sel.Nodes)) - for _, n := range sel.Nodes { - t.Logf("%+v", n) - } - } + AssertLength(t, sel.Nodes, 5) } func TestContents(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Contents() - if len(sel.Nodes) != 13 { - t.Errorf("Expected 13 child nodes, got %v.", len(sel.Nodes)) - for _, n := range sel.Nodes { - t.Logf("%+v", n) - } - } + AssertLength(t, sel.Nodes, 13) } func TestChildrenFiltered(t *testing.T) { sel := Doc().Root.Find(".pvk-content").ChildrenFiltered(".hero-unit") - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 child nodes, got %v.", len(sel.Nodes)) - for _, n := range sel.Nodes { - t.Logf("%+v", n) - } - } + AssertLength(t, sel.Nodes, 1) } func TestContentsFiltered(t *testing.T) { sel := Doc().Root.Find(".pvk-content").ContentsFiltered(".hero-unit") - if len(sel.Nodes) != 1 { - t.Errorf("Expected 1 child nodes, got %v.", len(sel.Nodes)) - for _, n := range sel.Nodes { - t.Logf("%+v", n) - } - } + AssertLength(t, sel.Nodes, 1) } func TestChildrenFilteredNone(t *testing.T) { sel := Doc().Root.Find(".pvk-content").ChildrenFiltered("a.btn") - if len(sel.Nodes) != 0 { - t.Errorf("Expected 0 child node, got %v.", len(sel.Nodes)) - for _, n := range sel.Nodes { - t.Logf("%+v", n) - } - } + AssertLength(t, sel.Nodes, 0) +} + +func TestParent(t *testing.T) { + sel := Doc().Root.Find(".container-fluid").Parent() + AssertLength(t, sel.Nodes, 3) +} + +func TestParentBody(t *testing.T) { + sel := Doc().Root.Find("body").Parent() + AssertLength(t, sel.Nodes, 1) +} + +func TestParentFiltered(t *testing.T) { + sel := Doc().Root.Find(".container-fluid").ParentFiltered(".hero-unit") + AssertLength(t, sel.Nodes, 1) + AssertClass(t, sel, "hero-unit") } diff --git a/type_test.go b/type_test.go index b7305b5..7ebf310 100644 --- a/type_test.go +++ b/type_test.go @@ -6,6 +6,7 @@ import ( "testing" ) +// Test helper functions and members var doc *Document func Doc() *Document { @@ -15,6 +16,27 @@ func Doc() *Document { return doc } +func AssertLength(t *testing.T, nodes []*html.Node, length int) { + if len(nodes) != length { + t.Errorf("Expected %i nodes, found %v.", length, len(nodes)) + for i, n := range nodes { + t.Logf("Node %i: %+v.", i, n) + } + } +} + +func AssertClass(t *testing.T, sel *Selection, class string) { + if !sel.HasClass(class) { + t.Errorf("Expected node to have class %s.", class) + } +} + +func AssertPanic(t *testing.T) { + if e := recover(); e == nil { + t.Error("Expected a panic.") + } +} + func EnsureDocLoaded() { if f, e := os.Open("./testdata/page.html"); e != nil { panic(e.Error()) @@ -28,17 +50,6 @@ func EnsureDocLoaded() { } } -func printNode(n *html.Node, t *testing.T) { - t.Logf("Type: %v, Data: %v\n", n.Type, n.Data) - for _, c := range n.Child { - printNode(c, t) - } -} - -func TestPrintAll(t *testing.T) { - //printNode(Doc().Root, t) -} - func TestNewDocument(t *testing.T) { if f, e := os.Open("./testdata/page.html"); e != nil { t.Error(e.Error())