kepub_test.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package kepub
  2. import (
  3. "bytes"
  4. "context"
  5. "image"
  6. "image/jpeg"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "strings"
  12. "testing"
  13. "time"
  14. "golang.org/x/net/html"
  15. "git.monoexp.com/bajdev/rekopub/internal/build"
  16. "git.monoexp.com/bajdev/rekopub/internal/clean"
  17. "git.monoexp.com/bajdev/rekopub/internal/epubtest"
  18. "git.monoexp.com/bajdev/rekopub/internal/extract"
  19. )
  20. // testEPUB builds a small plain EPUB with a cover and two sections.
  21. func testEPUB(t *testing.T) []byte {
  22. t.Helper()
  23. doc, err := html.Parse(strings.NewReader(`<body><p>First sentence here. Second sentence follows it!</p>
  24. <h2>A section</h2><p>Text in the section, with <em>emphasis</em> and a <a href="https://example.com/x">link</a>.</p>
  25. <h2>Another</h2><ul><li>One item.</li><li>Two items.</li></ul></body>`))
  26. if err != nil {
  27. t.Fatal(err)
  28. }
  29. a := &extract.Article{
  30. URL: "https://example.com/post", Title: "KEPUB Test", Author: "Ada Lovelace",
  31. SiteName: "Example", Language: "en", Published: time.Date(2025, 1, 2, 0, 0, 0, 0, time.UTC),
  32. Content: doc.FirstChild.LastChild,
  33. }
  34. u, _ := url.Parse(a.URL)
  35. body, err := clean.Content(a.Content, u)
  36. if err != nil {
  37. t.Fatal(err)
  38. }
  39. var cover bytes.Buffer
  40. if err := jpeg.Encode(&cover, image.NewGray(image.Rect(0, 0, 30, 40)), nil); err != nil {
  41. t.Fatal(err)
  42. }
  43. data, err := build.Build(a, body, nil, cover.Bytes())
  44. if err != nil {
  45. t.Fatal(err)
  46. }
  47. return data
  48. }
  49. var tags = regexp.MustCompile(`<[^>]+>`)
  50. // bodyText returns the visible text of a chapter's <body>, with whitespace
  51. // collapsed.
  52. func bodyText(chapter string) string {
  53. if i := strings.Index(chapter, "<body"); i >= 0 {
  54. chapter = chapter[i:]
  55. }
  56. return strings.Join(strings.Fields(tags.ReplaceAllString(chapter, " ")), " ")
  57. }
  58. func TestConvert(t *testing.T) {
  59. plain := testEPUB(t)
  60. data, err := Convert(context.Background(), plain)
  61. if err != nil {
  62. t.Fatal(err)
  63. }
  64. book := epubtest.Open(t, data) // also checks mimetype is first and stored
  65. book.AssertWellFormed(t)
  66. before := epubtest.Open(t, plain)
  67. chapter := book.File(t, "chapter.xhtml")
  68. if !strings.Contains(chapter, `<span class="koboSpan" id="kobo.1.1">`) {
  69. t.Errorf("chapter has no koboSpan elements:\n%s", chapter)
  70. }
  71. // Sentences are spanned separately, so Kobo can count and highlight them.
  72. if !strings.Contains(chapter, `>First sentence here. </span><span class="koboSpan"`) {
  73. t.Errorf("sentences aren't split into spans:\n%s", chapter)
  74. }
  75. if !strings.Contains(chapter, "hyphens: auto") {
  76. t.Errorf("hyphenation isn't enabled")
  77. }
  78. // The words must be exactly the same, just wrapped.
  79. if got, want := bodyText(chapter), bodyText(before.File(t, "chapter.xhtml")); got != want {
  80. t.Errorf("text changed by conversion:\n got %q\nwant %q", got, want)
  81. }
  82. opf := book.File(t, ".opf")
  83. for _, want := range []string{`properties="cover-image"`, "<dc:publisher>Example</dc:publisher>", "<dc:source>https://example.com/post</dc:source>"} {
  84. if !strings.Contains(opf, want) {
  85. t.Errorf("OPF lost %q:\n%s", want, opf)
  86. }
  87. }
  88. if !strings.Contains(book.File(t, "nav.xhtml"), "#sec-2") || !strings.Contains(book.File(t, "toc.ncx"), "dtb:uid") {
  89. t.Errorf("table of contents changed by conversion")
  90. }
  91. path := filepath.Join(t.TempDir(), "book.kepub.epub")
  92. if err := os.WriteFile(path, data, 0o644); err != nil {
  93. t.Fatal(err)
  94. }
  95. epubtest.EPUBCheck(t, path)
  96. }
  97. func TestConvertRejectsNonEPUB(t *testing.T) {
  98. if _, err := Convert(context.Background(), []byte("not a zip file")); err == nil {
  99. t.Error("Convert accepted data that isn't an EPUB")
  100. }
  101. }
  102. func TestConvertCancelled(t *testing.T) {
  103. ctx, cancel := context.WithCancel(context.Background())
  104. cancel()
  105. if _, err := Convert(ctx, testEPUB(t)); err == nil {
  106. t.Error("Convert succeeded after cancellation")
  107. }
  108. }