package sync import ( "archive/zip" "os" "strings" "testing" "golang.org/x/net/html" ) func TestParseCREXPointer(t *testing.T) { tests := []struct { input string wantFrag int wantPath int wantChar int }{ {"/body/DocFragment[0]/body/div[4]/p[38]/text().541", 0, 2, 541}, {"/body/DocFragment[2]/body/div/p[5]/text()[2].16", 2, 2, 16}, {"/body/DocFragment[1]/body", 1, 0, 0}, {"/body/DocFragment[5]/body/div[3]/p[28]", 5, 2, 0}, } for _, tt := range tests { xp, err := ParseCREXPointer(tt.input) if err != nil { t.Errorf("ParseCREXPointer(%q) error: %v", tt.input, err) continue } if xp.FragmentIndex != tt.wantFrag { t.Errorf("FragmentIndex = %d, want %d", xp.FragmentIndex, tt.wantFrag) } if len(xp.ElementPath) != tt.wantPath { t.Errorf("len(ElementPath) = %d, want %d (got %+v)", len(xp.ElementPath), tt.wantPath, xp.ElementPath) } if xp.CharOffset != tt.wantChar { t.Errorf("CharOffset = %d, want %d", xp.CharOffset, tt.wantChar) } } } func TestParseCREXPointerInvalid(t *testing.T) { _, err := ParseCREXPointer("epubcfi(/6/4!/4/2/1:0)") if err == nil { t.Error("expected error for standard epubcfi") } _, err = ParseCREXPointer("") if err == nil { t.Error("expected error for empty string") } } func TestIsCREXPointer(t *testing.T) { if !IsCREXPointer("/body/DocFragment[0]/body/div/p") { t.Error("should recognize CRE XPointer") } if !IsCREXPointer("#_doc_fragment_5_ link2HCH0002") { t.Error("should recognize CRE fragment ID") } if IsCREXPointer("epubcfi(/6/4!/4/2/1:0)") { t.Error("should not recognize standard epubcfi as CRE") } } func TestIsCREFragmentID(t *testing.T) { if !IsCREFragmentID("#_doc_fragment_5_ link2HCH0002") { t.Error("should recognize fragment ID") } if IsCREFragmentID("/body/DocFragment[2]/body") { t.Error("should not recognize XPointer as fragment ID") } } func TestParseCREFragmentID(t *testing.T) { tests := []struct { input string wantSpine int wantAnchor string }{ {"#_doc_fragment_5_ link2HCH0002", 5, "link2HCH0002"}, {"#_doc_fragment_0_", 0, ""}, {"#_doc_fragment_12_someid123", 12, "someid123"}, } for _, tt := range tests { frag, err := ParseCREFragmentID(tt.input) if err != nil { t.Errorf("ParseCREFragmentID(%q) error: %v", tt.input, err) continue } if frag.SpineIndex != tt.wantSpine { t.Errorf("SpineIndex = %d, want %d", frag.SpineIndex, tt.wantSpine) } if frag.Anchor != tt.wantAnchor { t.Errorf("Anchor = %q, want %q", frag.Anchor, tt.wantAnchor) } } } func TestParseCREFragmentIDInvalid(t *testing.T) { _, err := ParseCREFragmentID("/body/DocFragment[2]/body") if err == nil { t.Error("expected error for XPointer input") } _, err = ParseCREFragmentID("#_doc_fragment_") if err == nil { t.Error("expected error for missing index") } } func TestIsStandardEPUBCFI(t *testing.T) { if !IsStandardEPUBCFI("epubcfi(/6/4!/4/2/1:0)") { t.Error("should recognize standard epubcfi") } if IsStandardEPUBCFI("/body/DocFragment[0]/body") { t.Error("should not recognize CRE as standard") } } // writeTestEPUB builds a minimal, deterministic EPUB in a temp dir so the // conversion tests exercise the real zip→OPF→spine→document pipeline // without depending on books in a particular machine's uploads/ tree. // // Spine: doc1..doc6. doc2 carries the Dashwood sentence used for exact and // text-search anchoring; doc6 has an id anchor for fragment-ID conversion. func writeTestEPUB(t *testing.T) string { t.Helper() type spineDoc struct { name string body string } docs := []spineDoc{ {"doc1.xhtml", "
Chapter one opening page.
The family of Dashwood had long been settled in Sussex.
Their estate was large, and their residence was at Norland Park.
Chapter three contents.
Chapter four contents.
Chapter five contents.
He was neither fit to be a husband nor a father.
Hello world this is a test
`) 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(`the countries Vokalia and Consonantia live here
`) body := findBody(doc) node, offset := findTextInNode(body, "Vokalia and Consonantia") if node == nil { t.Fatal("expected to find text across 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(`Some bold and italic text here
`) body := findBody(doc) node, _ := findTextInNode(body, "bold and italic text") if node == nil { t.Fatal("expected to find text across 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(`first paragraph
second paragraph
`) body := findBody(doc) node, _ := findTextInNode(body, "paragraph second") if node != nil { t.Error("should not match text acrossboundaries") } } func TestFindTextInNode_NestedFormatting(t *testing.T) { doc := parseTestHTML(`
before bold italic after
`) 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(`text inside deep
`) 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(`the countries Vokalia and Consonantia live
`) 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 findelement") } 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 }