From f687e5659e26140bc43e3753c4d74bb8fc43b61b Mon Sep 17 00:00:00 2001 From: x0Lazarus <113273587+x0Lazarus@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:18:34 -0700 Subject: [PATCH] Recognize form feeds between HTML classes --- property.go | 2 +- property_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/property.go b/property.go index 0a8e46d..03750c6 100644 --- a/property.go +++ b/property.go @@ -6,7 +6,7 @@ import ( "golang.org/x/net/html" ) -var classTrimReplacer = strings.NewReplacer("\t", " ", "\r", " ", "\n", " ") +var classTrimReplacer = strings.NewReplacer("\t", " ", "\r", " ", "\n", " ", "\f", " ") // Attr gets the specified attribute's value for the first element in the // Selection. To get the value for each element individually, use a looping diff --git a/property_test.go b/property_test.go index e74040b..45b33ed 100644 --- a/property_test.go +++ b/property_test.go @@ -198,6 +198,70 @@ func TestHasClassNotFirst(t *testing.T) { } } +func TestClassOperationsASCIIWhitespace(t *testing.T) { + for _, tc := range []struct { + name, separator string + }{ + {"space", " "}, + {"tab", "\t"}, + {"line feed", "\n"}, + {"carriage return", "\r"}, + {"form feed", "\f"}, + } { + t.Run(tc.name, func(t *testing.T) { + markup := "
" + for _, op := range []struct { + name string + run func(*Selection) + want string + }{ + {"add", func(s *Selection) { s.AddClass("a") }, "a b"}, + {"remove", func(s *Selection) { s.RemoveClass("a") }, "b"}, + {"toggle", func(s *Selection) { s.ToggleClass("a") }, "b"}, + } { + t.Run(op.name, func(t *testing.T) { + doc, err := NewDocumentFromReader(strings.NewReader(markup)) + if err != nil { + t.Fatal(err) + } + sel := doc.Find("div") + for _, class := range []string{"a", "b"} { + if !sel.HasClass(class) { + t.Errorf("HasClass(%q) = false for %q", class, markup) + } + } + op.run(sel) + if got := sel.AttrOr("class", ""); got != op.want { + t.Errorf("class = %q, want %q", got, op.want) + } + }) + } + }) + } +} + +func TestClassOperationsRepeatedFormFeed(t *testing.T) { + for _, class := range []string{"\fa\fb\f", "a\f\fb", "a\fa\fb"} { + t.Run(class, func(t *testing.T) { + for _, toggle := range []bool{false, true} { + doc, err := NewDocumentFromReader(strings.NewReader("")) + if err != nil { + t.Fatal(err) + } + sel := doc.Find("div") + if toggle { + sel.ToggleClass("a") + } else { + sel.RemoveClass("a") + } + if got := sel.AttrOr("class", ""); got != "b" { + t.Errorf("toggle=%t: class = %q, want %q", toggle, got, "b") + } + } + }) + } +} + func TestRemoveClass(t *testing.T) { sel := Doc2Clone().Find("#nf1") sel.RemoveClass("one row")