test(sync): add cross-element matching tests, enable <em> KEPUB conversion test
cfi_converter_test.go: - TestFindTextInNode_SingleTextNode: baseline single-node match - TestFindTextInNode_CrossEmElement: 'Vokalia and Consonantia' across two <em> elements — verifies match returns the 'Vokalia' text node - TestFindTextInNode_CrossStrongElement: text crossing <strong> boundary - TestFindTextInNode_DoesNotCrossParagraphs: verifies block boundary enforcement — text split across <p> elements is NOT matched - TestFindTextInNode_NestedFormatting: <em><strong> nesting - TestFindBlockParent: verifies findBlockParent walks up through inline elements to find block-level <p> - TestCollectInlineText: verifies text segments are collected in order with correct content kepub_cfi_converter_test.go: - TestKEPUBConvertWithEmElements: previously skipped, now expects exact precision and verifies round-trip conversion works for text spanning <em> element boundaries
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func TestParseCREXPointer(t *testing.T) {
|
||||
@@ -351,3 +354,141 @@ func TestReversePercentageFallback(t *testing.T) {
|
||||
t.Error("expected empty XPointer for percentage fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTextInNode_SingleTextNode(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>Hello world this is a test</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
node, offset := findTextInNode(body, "Hello world")
|
||||
if node == nil {
|
||||
t.Fatal("expected to find text")
|
||||
}
|
||||
if offset != 0 {
|
||||
t.Errorf("offset = %d, want 0", offset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTextInNode_CrossEmElement(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>the countries <em>Vokalia</em> and <em>Consonantia</em> live here</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
node, offset := findTextInNode(body, "Vokalia and Consonantia")
|
||||
if node == nil {
|
||||
t.Fatal("expected to find text across <em> elements")
|
||||
}
|
||||
if node.Data != "Vokalia" {
|
||||
t.Errorf("expected match in 'Vokalia' text node, got %q", node.Data)
|
||||
}
|
||||
if offset != 0 {
|
||||
t.Errorf("offset = %d, want 0", offset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTextInNode_CrossStrongElement(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>Some <strong>bold and italic</strong> text here</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
node, _ := findTextInNode(body, "bold and italic text")
|
||||
if node == nil {
|
||||
t.Fatal("expected to find text across <strong> boundary")
|
||||
}
|
||||
if node.Data != "bold and italic" {
|
||||
t.Errorf("expected match in 'bold and italic' text node, got %q", node.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTextInNode_DoesNotCrossParagraphs(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>first paragraph</p><p>second paragraph</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
node, _ := findTextInNode(body, "paragraph second")
|
||||
if node != nil {
|
||||
t.Error("should not match text across <p> boundaries")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTextInNode_NestedFormatting(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>before <em><strong>bold italic</strong></em> after</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
node, offset := findTextInNode(body, "bold italic after")
|
||||
if node == nil {
|
||||
t.Fatal("expected to find text across nested formatting")
|
||||
}
|
||||
if node.Data != "bold italic" {
|
||||
t.Errorf("expected match in 'bold italic' text node, got %q", node.Data)
|
||||
}
|
||||
_ = offset
|
||||
}
|
||||
|
||||
func TestFindBlockParent(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>text <em>inside <strong>deep</strong></em></p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
var deepNode *html.Node
|
||||
var walk func(*html.Node)
|
||||
walk = func(n *html.Node) {
|
||||
if n.Type == html.TextNode && n.Data == "deep" {
|
||||
deepNode = n
|
||||
return
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
walk(c)
|
||||
}
|
||||
}
|
||||
walk(body)
|
||||
|
||||
if deepNode == nil {
|
||||
t.Fatal("could not find 'deep' text node")
|
||||
}
|
||||
|
||||
block := findBlockParent(deepNode)
|
||||
if block == nil {
|
||||
t.Fatal("expected block parent")
|
||||
}
|
||||
if block.Data != "p" {
|
||||
t.Errorf("block parent = %q, want 'p'", block.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectInlineText(t *testing.T) {
|
||||
doc := parseTestHTML(`<html><body><p>the countries <em>Vokalia</em> and <em>Consonantia</em> live</p></body></html>`)
|
||||
body := findBody(doc)
|
||||
|
||||
var p *html.Node
|
||||
var walk func(*html.Node)
|
||||
walk = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "p" {
|
||||
p = n
|
||||
return
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
walk(c)
|
||||
}
|
||||
}
|
||||
walk(body)
|
||||
|
||||
if p == nil {
|
||||
t.Fatal("could not find <p> element")
|
||||
}
|
||||
|
||||
segments := collectInlineText(p)
|
||||
|
||||
var collected []rune
|
||||
for _, seg := range segments {
|
||||
collected = append(collected, seg.runes...)
|
||||
}
|
||||
|
||||
flattened := strings.TrimSpace(string(collected))
|
||||
if flattened != "the countries Vokalia and Consonantia live" {
|
||||
t.Errorf("collected text = %q", flattened)
|
||||
}
|
||||
}
|
||||
|
||||
func parseTestHTML(s string) *html.Node {
|
||||
doc, err := html.Parse(strings.NewReader(s))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user