diff --git a/README.md b/README.md index 3f6e86f..04a74b7 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Coming soon... ## TODOs * Implement NextUntil() and PrevUntil(). -* Check each method, make sure it uses only ElementNodes except when explicitly specified (i.e.: `Contents()`). Cascadia's selectors act only on elements. -* Tests to validate that all methods returning a new `*Selection` "rollback" correctly to the previous Selection when calling `.End()`. +* Fix Prev...() functions to return nodes in the same order as jQuery (starting with the immediately preceding node, up until first child of the parent). +* Tests to validate that all methods returning a new `*Selection` "rollback" correctly to the previous Selection when calling `.End()`. Done for array, expand and filter. * Benchmarks so that future changes have a baseline to compare to. * Add jQuery's `Closest()`? Other missing functions? * Support negative indices in `Slice()`, like jQuery. diff --git a/array.go b/array.go index 2888c08..412f027 100644 --- a/array.go +++ b/array.go @@ -47,20 +47,8 @@ func (this *Selection) Get(index int) *html.Node { // Index() returns the position of the first element within the Selection object // relative to its sibling elements. func (this *Selection) Index() int { - // TODO : Eventually refactor with prevAll(), like jQuery's code if len(this.Nodes) > 0 { - n := this.Nodes[0] - if p := n.Parent; p != nil { - var i = 0 - for _, c := range p.Child { - if c == n { - // This is the index of the element - return i - } else if c.Type == html.ElementNode { - i++ - } - } - } + return newSingleSelection(this.Nodes[0], this.document).PrevAll().Length() } return -1 } diff --git a/array_test.go b/array_test.go index 0390cf3..8f3c077 100644 --- a/array_test.go +++ b/array_test.go @@ -14,6 +14,12 @@ func TestFirstEmpty(t *testing.T) { Doc().Root.Find(".pvk-zzcontentzz").First() } +func TestFirstRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.First().End() + AssertEqual(t, sel, sel2) +} + func TestLast(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Last() AssertLength(t, sel.Nodes, 1) @@ -25,6 +31,12 @@ func TestLast(t *testing.T) { } } +func TestLastRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Last().End() + AssertEqual(t, sel, sel2) +} + func TestEq(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Eq(1) AssertLength(t, sel.Nodes, 1) @@ -41,6 +53,12 @@ func TestEqNegative(t *testing.T) { } } +func TestEqRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Eq(1).End() + AssertEqual(t, sel, sel2) +} + func TestSlice(t *testing.T) { sel := Doc().Root.Find(".pvk-content").Slice(0, 2) @@ -52,6 +70,12 @@ func TestSliceOutOfBounds(t *testing.T) { Doc().Root.Find(".pvk-content").Slice(2, 12) } +func TestSliceRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Slice(0, 2).End() + AssertEqual(t, sel, sel2) +} + func TestGet(t *testing.T) { sel := Doc().Root.Find(".pvk-content") node := sel.Get(1) diff --git a/doc.go b/doc.go index 81a73e5..482953a 100644 --- a/doc.go +++ b/doc.go @@ -51,6 +51,11 @@ The various methods are split into files based on the category of behavior: - Last() - Slice() +* expand.go : methods that expand or augment the selection's set. + - Add...() + - AndSelf() + - Union(), which is an alias for AddSelection() + * filter.go : filtering methods, that reduce the selection's set. - End() - Filter...() @@ -58,15 +63,9 @@ The various methods are split into files based on the category of behavior: - Intersection(), which is an alias of FilterSelection() - Not...() -* expand.go : methods that expand or augment the selection's set. - - Add...() - - AndSelf() - - Union(), which is an alias for AddSelection() - -* query.go : methods that query, or reflect, a node's identity. - - Contains() - - HasClass() - - Is...() +* iteration.go : methods to loop over the selection's nodes. + - Each() + - Map() * property.go : methods that inspect and get the node's properties values. - Attr() @@ -75,6 +74,11 @@ The various methods are split into files based on the category of behavior: - Size(), which is an alias for Length() - Text() +* query.go : methods that query, or reflect, a node's identity. + - Contains() + - HasClass() + - Is...() + * traversal.go : methods to traverse the HTML document tree. - Children...() - Contents() @@ -84,21 +88,8 @@ The various methods are split into files based on the category of behavior: - Prev...() - Siblings...() -* iteration.go : methods to loop over the selection's nodes. - - Each() - - Map() - * type.go : definition of the types exposed by GoQuery. - Document - Selection */ package goquery - -// DONE array.go : Positional Manipulation: First(), Last(), Eq(), Get(), Index(), Slice() -// DONE filter.go : Filtering: Filter(), Not(), Has(), End() -// DONE expand.go : "Expanding": Add(), AndSelf() -// DONE query.go : Reflect (query) node: Is(), Contains(), HasClass() -// DONE property.go : Inspect node: Html(), Text(), Attr(), Length(), Size() -// traversal.go : Traversal: Contents(), Find(), Children(), Parents...(), Next...(), Prev...(), Siblings() -// DONE iteration.go : Iteration: Each(), Map() -// DONE type.go : Selection and Document diff --git a/expand_test.go b/expand_test.go index 37ea247..4e8f5ec 100644 --- a/expand_test.go +++ b/expand_test.go @@ -9,6 +9,12 @@ func TestAdd(t *testing.T) { AssertLength(t, sel.Nodes, 19) } +func TestAddRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Add("a").End() + AssertEqual(t, sel, sel2) +} + func TestAddSelection(t *testing.T) { sel := Doc().Root.Find("div.row-fluid") sel2 := Doc().Root.Find("a") @@ -24,6 +30,13 @@ func TestAddSelectionNil(t *testing.T) { AssertLength(t, sel.Nodes, 9) } +func TestAddSelectionRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Find("a") + sel2 = sel.AddSelection(sel2).End() + AssertEqual(t, sel, sel2) +} + func TestAddNodes(t *testing.T) { sel := Doc().Root.Find("div.pvk-gutter") sel2 := Doc().Root.Find(".pvk-content") @@ -36,7 +49,20 @@ func TestAddNodesNone(t *testing.T) { AssertLength(t, sel.Nodes, 6) } +func TestAddNodesRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Find("a") + sel2 = sel.AddNodes(sel2.Nodes...).End() + AssertEqual(t, sel, sel2) +} + func TestAndSelf(t *testing.T) { sel := Doc().Root.Find(".span12").Last().AndSelf() AssertLength(t, sel.Nodes, 2) } + +func TestAndSelfRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Find("a").AndSelf().End().End() + AssertEqual(t, sel, sel2) +} diff --git a/filter_test.go b/filter_test.go index eb497fa..9df4661 100644 --- a/filter_test.go +++ b/filter_test.go @@ -14,6 +14,12 @@ func TestFilterNone(t *testing.T) { AssertLength(t, sel.Nodes, 0) } +func TestFilterRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.Filter(".alert").End() + AssertEqual(t, sel, sel2) +} + func TestFilterFunction(t *testing.T) { sel := Doc().Root.Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool { return i > 0 @@ -21,12 +27,26 @@ func TestFilterFunction(t *testing.T) { AssertLength(t, sel.Nodes, 2) } +func TestFilterFunctionRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.FilterFunction(func(i int, s *Selection) bool { + return i > 0 + }).End() + AssertEqual(t, sel, sel2) +} + func TestFilterNode(t *testing.T) { sel := Doc().Root.Find(".pvk-content") sel2 := sel.FilterNodes(sel.Nodes[2]) AssertLength(t, sel2.Nodes, 1) } +func TestFilterNodeRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.FilterNodes(sel.Nodes[2]).End() + AssertEqual(t, sel, sel2) +} + func TestFilterSelection(t *testing.T) { sel := Doc().Root.Find(".link") sel2 := Doc().Root.Find("a[ng-click]") @@ -34,6 +54,13 @@ func TestFilterSelection(t *testing.T) { AssertLength(t, sel3.Nodes, 1) } +func TestFilterSelectionRollback(t *testing.T) { + sel := Doc().Root.Find(".link") + sel2 := Doc().Root.Find("a[ng-click]") + sel2 = sel.FilterSelection(sel2).End() + AssertEqual(t, sel, sel2) +} + func TestFilterSelectionNil(t *testing.T) { var sel2 *Selection @@ -47,6 +74,12 @@ func TestNot(t *testing.T) { AssertLength(t, sel.Nodes, 1) } +func TestNotRollback(t *testing.T) { + sel := Doc().Root.Find(".span12") + sel2 := sel.Not(".alert").End() + AssertEqual(t, sel, sel2) +} + func TestNotNone(t *testing.T) { sel := Doc().Root.Find(".span12").Not(".zzalert") AssertLength(t, sel.Nodes, 2) @@ -59,12 +92,26 @@ func TestNotFunction(t *testing.T) { AssertLength(t, sel.Nodes, 1) } +func TestNotFunctionRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.NotFunction(func(i int, s *Selection) bool { + return i > 0 + }).End() + AssertEqual(t, sel, sel2) +} + func TestNotNode(t *testing.T) { sel := Doc().Root.Find(".pvk-content") sel2 := sel.NotNodes(sel.Nodes[2]) AssertLength(t, sel2.Nodes, 2) } +func TestNotNodeRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-content") + sel2 := sel.NotNodes(sel.Nodes[2]).End() + AssertEqual(t, sel, sel2) +} + func TestNotSelection(t *testing.T) { sel := Doc().Root.Find(".link") sel2 := Doc().Root.Find("a[ng-click]") @@ -72,18 +119,38 @@ func TestNotSelection(t *testing.T) { AssertLength(t, sel3.Nodes, 6) } +func TestNotSelectionRollback(t *testing.T) { + sel := Doc().Root.Find(".link") + sel2 := Doc().Root.Find("a[ng-click]") + sel2 = sel.NotSelection(sel2).End() + AssertEqual(t, sel, sel2) +} + func TestIntersection(t *testing.T) { sel := Doc().Root.Find(".pvk-gutter") sel2 := Doc().Root.Find("div").Intersection(sel) AssertLength(t, sel2.Nodes, 6) } +func TestIntersectionRollback(t *testing.T) { + sel := Doc().Root.Find(".pvk-gutter") + sel2 := Doc().Root.Find("div") + sel2 = sel.Intersection(sel2).End() + AssertEqual(t, sel, sel2) +} + func TestHas(t *testing.T) { sel := Doc().Root.Find(".container-fluid").Has(".center-content") AssertLength(t, sel.Nodes, 2) // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content } +func TestHasRollback(t *testing.T) { + sel := Doc().Root.Find(".container-fluid") + sel2 := sel.Has(".center-content").End() + AssertEqual(t, sel, sel2) +} + func TestHasNodes(t *testing.T) { sel := Doc().Root.Find(".container-fluid") sel2 := Doc().Root.Find(".center-content") @@ -92,6 +159,13 @@ func TestHasNodes(t *testing.T) { // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content } +func TestHasNodesRollback(t *testing.T) { + sel := Doc().Root.Find(".container-fluid") + sel2 := Doc().Root.Find(".center-content") + sel2 = sel.HasNodes(sel2.Nodes...).End() + AssertEqual(t, sel, sel2) +} + func TestHasSelection(t *testing.T) { sel := Doc().Root.Find("p") sel2 := Doc().Root.Find("small") @@ -99,6 +173,13 @@ func TestHasSelection(t *testing.T) { AssertLength(t, sel.Nodes, 1) } +func TestHasSelectionRollback(t *testing.T) { + sel := Doc().Root.Find("p") + sel2 := Doc().Root.Find("small") + sel2 = sel.HasSelection(sel2).End() + AssertEqual(t, sel, sel2) +} + func TestEnd(t *testing.T) { sel := Doc().Root.Find("p").Has("small").End() AssertLength(t, sel.Nodes, 4) diff --git a/iteration.go b/iteration.go index 17a5ee7..ff07f29 100644 --- a/iteration.go +++ b/iteration.go @@ -1,7 +1,7 @@ package goquery // Each() iterates over a Selection object, executing a function for each -// matched element. +// matched element. It returns the current Selection object. func (this *Selection) Each(f func(int, *Selection)) *Selection { for i, n := range this.Nodes { f(i, newSingleSelection(n, this.document)) diff --git a/property.go b/property.go index c793cba..a29c208 100644 --- a/property.go +++ b/property.go @@ -20,6 +20,7 @@ func (this *Selection) Attr(attrName string) (val string, exists bool) { func (this *Selection) Text() string { var buf bytes.Buffer + // Slightly optimized vs calling Each(): no single selection object created for _, n := range this.Nodes { buf.WriteString(getNodeText(n)) } @@ -37,7 +38,7 @@ func (this *Selection) Length() int { } // Html() gets the HTML contents of the first element in the set of matched -// elements. +// elements. It includes text and comment nodes. func (this *Selection) Html() (ret string, e error) { // Since there is no .innerHtml, the HTML content must be re-created from // the nodes usint html.Render(). @@ -59,7 +60,6 @@ func (this *Selection) Html() (ret string, e error) { // Get the specified node's text content. func getNodeText(node *html.Node) string { if node.Type == html.TextNode { - //ret = strings.Trim(node.Data, " \t\r\n") // Keep newlines and spaces, like jQuery return node.Data } else if len(node.Child) > 0 { diff --git a/query.go b/query.go index 3a7bc62..daf0cef 100644 --- a/query.go +++ b/query.go @@ -39,19 +39,19 @@ func (this *Selection) Is(selector string) bool { return false } -// Is() checks the current matched set of elements against a predicate and +// IsFunction() checks the current matched set of elements against a predicate and // returns true if at least one of these elements matches. func (this *Selection) IsFunction(f func(int, *Selection) bool) bool { return this.FilterFunction(f).Length() > 0 } -// Is() checks the current matched set of elements against a Selection object +// IsSelection() checks the current matched set of elements against a Selection object // and returns true if at least one of these elements matches. func (this *Selection) IsSelection(s *Selection) bool { return this.FilterSelection(s).Length() > 0 } -// Is() checks the current matched set of elements against the specified nodes +// IsNodes() checks the current matched set of elements against the specified nodes // and returns true if at least one of these elements matches. func (this *Selection) IsNodes(nodes ...*html.Node) bool { return this.FilterNodes(nodes...).Length() > 0 diff --git a/traversal.go b/traversal.go index 3e48cb1..4ff5eff 100644 --- a/traversal.go +++ b/traversal.go @@ -10,11 +10,13 @@ type siblingType int // Sibling type, used internally when iterating over children at the same // level (siblings) to specify which nodes are requested. const ( - siblintPrevAll siblingType = iota - 2 + siblingPrevUntil siblingType = iota - 3 + siblingPrevAll siblingPrev siblingAll siblingNext siblingNextAll + siblingNextUntil siblingAllIncludingNonElements ) @@ -206,14 +208,14 @@ func (this *Selection) PrevFiltered(selector string) *Selection { // PrevAll() gets all the preceding siblings of each element in the // Selection. It returns a new Selection object containing the matched elements. func (this *Selection) PrevAll() *Selection { - return pushStack(this, getSiblingNodes(this.Nodes, siblintPrevAll)) + return pushStack(this, getSiblingNodes(this.Nodes, siblingPrevAll)) } // PrevAllFiltered() gets all the preceding siblings of each element in the // Selection filtered by a selector. It returns a new Selection object // containing the matched elements. func (this *Selection) PrevAllFiltered(selector string) *Selection { - return filterAndPush(this, getSiblingNodes(this.Nodes, siblintPrevAll), selector) + return filterAndPush(this, getSiblingNodes(this.Nodes, siblingPrevAll), selector) } // Filter and push filters the nodes based on a selector, and pushes the results @@ -314,7 +316,11 @@ func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *htm // If child is not the current node, check if sibling type requires // to add it to the result. - if c != skipNode && (st == siblingAll || st == siblingAllIncludingNonElements || (st == siblintPrevAll && !nFound) || (st == siblingNextAll && nFound)) { + if c != skipNode && + (st == siblingAll || + st == siblingAllIncludingNonElements || + (st == siblingPrevAll && !nFound) || + (st == siblingNextAll && nFound)) { result = append(result, c) } } diff --git a/type_test.go b/type_test.go index 00a7510..da55224 100644 --- a/type_test.go +++ b/type_test.go @@ -37,6 +37,12 @@ func AssertPanic(t *testing.T) { } } +func AssertEqual(t *testing.T, s1 *Selection, s2 *Selection) { + if s1 != s2 { + t.Error("Expected selection objects to be the same.") + } +} + func EnsureDocLoaded() { if f, e := os.Open("./testdata/page.html"); e != nil { panic(e.Error())