// SPDX-License-Identifier: GPL-3.0-or-later package model import ( "context" "os" "os/exec" "path/filepath" "strings" "testing" ) // TestPreviewKinds: a text file comes back as text, a picture as a picture, // and something krino cannot show says so instead of guessing. func TestPreviewKinds(t *testing.T) { dir := t.TempDir() write := func(name string, body []byte) string { p := filepath.Join(dir, name) if err := os.WriteFile(p, body, 0o644); err != nil { t.Fatal(err) } return p } text := write("notes.txt", []byte("first line\nsecond line\n")) png := write("a.png", []byte("\x89PNG\r\n\x1a\nnot really, but the name is what counts here")) binary := write("a.bin", []byte{0, 1, 2, 3, 0}) tmp := t.TempDir() if p := MakePreview(context.Background(), text, tmp, 0); p.Kind != PreviewText || !strings.Contains(p.Text, "second line") { t.Errorf("text preview = %+v", p) } if p := MakePreview(context.Background(), png, tmp, 0); p.Kind != PreviewImage || p.Image != png { t.Errorf("image preview = %+v", p) } if p := MakePreview(context.Background(), binary, tmp, 0); p.Kind != PreviewNone || !strings.Contains(p.Note, "no preview") { t.Errorf("binary preview = %+v", p) } if p := MakePreview(context.Background(), filepath.Join(dir, "gone.txt"), tmp, 0); p.Kind != PreviewNone { t.Errorf("a missing file = %+v", p) } if p := MakePreview(context.Background(), dir, tmp, 0); p.Kind != PreviewNone { t.Errorf("a directory = %+v", p) } } // TestPreviewReadsOnly: looking at a file changes nothing about it, and // nothing is left beside it. func TestPreviewReadsOnly(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "notes.txt") body := []byte("one\ntwo\n") if err := os.WriteFile(p, body, 0o644); err != nil { t.Fatal(err) } before, err := os.Stat(p) if err != nil { t.Fatal(err) } MakePreview(context.Background(), p, t.TempDir(), 0) after, err := os.Stat(p) if err != nil { t.Fatal(err) } if !before.ModTime().Equal(after.ModTime()) || before.Size() != after.Size() { t.Error("the file changed") } if entries, _ := os.ReadDir(dir); len(entries) != 1 { t.Errorf("the directory holds %d files, want the one", len(entries)) } if on, _ := os.ReadFile(p); string(on) != string(body) { t.Error("the contents changed") } } // TestPreviewShowsOnlyTheHead: a long file is cut, and says it was. func TestPreviewShowsOnlyTheHead(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "long.txt") var b strings.Builder for i := 0; i < previewLines*2; i++ { b.WriteString("line\n") } if err := os.WriteFile(p, []byte(b.String()), 0o644); err != nil { t.Fatal(err) } pv := MakePreview(context.Background(), p, t.TempDir(), 0) if pv.Kind != PreviewText { t.Fatalf("preview = %+v", pv) } if n := strings.Count(pv.Text, "\n"); n > previewLines+1 { t.Errorf("%d lines shown, want at most %d", n, previewLines) } if !strings.HasSuffix(pv.Text, "...") { t.Error("a cut file does not say it was cut") } } // TestPdfPreviewsDoNotMixUp: two PDFs looked at one after the other each // get their own rendered page. pdftoppm names its file after the page // number, so previews sharing a directory would collide - one of his ended // up showing another document's cover. func TestPdfPreviewsDoNotMixUp(t *testing.T) { if _, err := exec.LookPath("pdftoppm"); err != nil { t.Skip("pdftoppm is not installed") } if _, err := exec.LookPath("groff"); err != nil { t.Skip("groff is not installed, so there is nothing to make a PDF with") } dir := t.TempDir() pdf := func(name, text string) string { p := filepath.Join(dir, name) f, err := os.Create(p) if err != nil { t.Fatal(err) } defer f.Close() cmd := exec.Command("groff", "-Tpdf", "-ms") cmd.Stdin = strings.NewReader(".TL\n" + text + "\n") cmd.Stdout = f if err := cmd.Run(); err != nil { t.Skipf("groff cannot write a PDF here: %v", err) } return p } first := pdf("first.pdf", "The first document") second := pdf("second.pdf", "The second document") tmp := t.TempDir() a := MakePreview(context.Background(), first, tmp, 0) b := MakePreview(context.Background(), second, tmp, 0) if a.Kind != PreviewImage || b.Kind != PreviewImage { t.Fatalf("previews = %+v, %+v", a, b) } if a.Image == b.Image { t.Fatalf("both previews point at %s", a.Image) } sameBytes := func(x, y string) bool { bx, err1 := os.ReadFile(x) by, err2 := os.ReadFile(y) return err1 == nil && err2 == nil && string(bx) == string(by) } if sameBytes(a.Image, b.Image) { t.Error("the second preview is a copy of the first page rendered") } // The caller is told where to clean up. for _, p := range []Preview{a, b} { if p.Dir == "" { t.Error("a rendered page comes with no directory to remove") } } } // pngSize reads a PNG's dimensions from its header, so a test can say how // large a page was rendered without decoding it. func pngSize(t *testing.T, path string) (w, h int) { t.Helper() data, err := os.ReadFile(path) if err != nil || len(data) < 24 || string(data[1:4]) != "PNG" { t.Fatalf("%s is not a PNG: %v", path, err) } be := func(b []byte) int { return int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) } return be(data[16:20]), be(data[20:24]) } // TestPreviewRendersToTheSizeAsked: a taller preview gets a larger page, so // dragging the pane open does not just magnify a small render (his request, // 2026-09-17). func TestPreviewRendersToTheSizeAsked(t *testing.T) { if _, err := exec.LookPath("pdftoppm"); err != nil { t.Skip("pdftoppm is not installed") } if _, err := exec.LookPath("groff"); err != nil { t.Skip("groff is not installed, so there is nothing to make a PDF with") } dir := t.TempDir() path := filepath.Join(dir, "doc.pdf") f, err := os.Create(path) if err != nil { t.Fatal(err) } cmd := exec.Command("groff", "-Tpdf", "-ms") cmd.Stdin = strings.NewReader(".TL\nA document\n") cmd.Stdout = f if err := cmd.Run(); err != nil { f.Close() t.Skipf("groff cannot write a PDF here: %v", err) } f.Close() small := MakePreview(context.Background(), path, t.TempDir(), 200) large := MakePreview(context.Background(), path, t.TempDir(), 900) if small.Kind != PreviewImage || large.Kind != PreviewImage { t.Fatalf("previews = %+v, %+v", small, large) } _, sh := pngSize(t, small.Image) _, lh := pngSize(t, large.Image) if lh <= sh { t.Errorf("asking for a taller preview rendered %d px, no more than %d", lh, sh) } if sh < 700 { t.Errorf("the smallest render is %d px, below the floor", sh) } }