1package message
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "log/slog"
8 "mime/multipart"
9 "net/textproto"
10 "strings"
11 "testing"
12
13 "github.com/mjl-/mox/mlog"
14)
15
16func TestPreviewText(t *testing.T) {
17 check := func(body, expLine string) {
18 t.Helper()
19
20 line, err := previewText(strings.NewReader(body))
21 tcompare(t, err, nil)
22 if line != expLine {
23 t.Fatalf("got %q, expected %q, for body %q", line, expLine, body)
24 }
25 }
26
27 check("", "")
28 check("single line", "single line\n")
29 check("single line\n", "single line\n")
30 check("> quoted\n", "[...]\n")
31 check("> quoted\nresponse\n", "[...]\nresponse\n")
32 check("> quoted\n[...]\nresponse after author snip\n", "[...]\nresponse after author snip\n")
33 check("[...]\nresponse after author snip\n", "[...]\nresponse after author snip\n")
34 check("[…]\nresponse after author snip\n", "[…]\nresponse after author snip\n")
35 check(">> quoted0\n> quoted1\n>quoted2\n[...]\nresponse after author snip\n", "[...]\nresponse after author snip\n")
36 check(">quoted\n\n>quoted\ncoalesce line-separated quotes\n", "[...]\ncoalesce line-separated quotes\n")
37 check("On <date> <user> wrote:\n> hi\nresponse", "[...]\nresponse\n")
38 check("On <longdate>\n<user> wrote:\n> hi\nresponse", "[...]\nresponse\n")
39 check("> quote\nresponse\n--\nsignature\n", "[...]\nresponse\n")
40 check("> quote\nline1\nline2\nline3\n", "[...]\nline1\nline2\nline3\n")
41}
42
43func tcompose(t *testing.T, typeContents ...string) *bytes.Reader {
44 var b bytes.Buffer
45
46 xc := NewComposer(&b, 100*1024, true)
47 xc.Header("MIME-Version", "1.0")
48
49 var cur, alt *multipart.Writer
50
51 xcreateMultipart := func(subtype string) *multipart.Writer {
52 mp := multipart.NewWriter(xc)
53 if cur == nil {
54 xc.Header("Content-Type", fmt.Sprintf(`multipart/%s; boundary="%s"`, subtype, mp.Boundary()))
55 xc.Line()
56 } else {
57 _, err := cur.CreatePart(textproto.MIMEHeader{"Content-Type": []string{fmt.Sprintf(`multipart/%s; boundary="%s"`, subtype, mp.Boundary())}})
58 tcheck(t, err, "adding multipart")
59 }
60 cur = mp
61 return mp
62 }
63 xcreatePart := func(header textproto.MIMEHeader) io.Writer {
64 if cur == nil {
65 for k, vl := range header {
66 for _, v := range vl {
67 xc.Header(k, v)
68 }
69 }
70 xc.Line()
71 return xc
72 }
73 p, err := cur.CreatePart(header)
74 tcheck(t, err, "adding part")
75 return p
76 }
77
78 if len(typeContents)/2 > 1 {
79 alt = xcreateMultipart("alternative")
80 }
81 for i := 0; i < len(typeContents); i += 2 {
82 body, ct, cte := xc.TextPart(typeContents[i], typeContents[i+1])
83 tp := xcreatePart(textproto.MIMEHeader{"Content-Type": []string{ct}, "Content-Transfer-Encoding": []string{cte}})
84 _, err := tp.Write([]byte(body))
85 tcheck(t, err, "write part")
86 }
87 if alt != nil {
88 err := alt.Close()
89 tcheck(t, err, "close multipart")
90 }
91 xc.Flush()
92
93 buf := b.Bytes()
94 return bytes.NewReader(buf)
95}
96
97func TestPreviewHTML(t *testing.T) {
98 check := func(r *bytes.Reader, exp string) {
99 t.Helper()
100
101 p, err := Parse(slog.Default(), false, r)
102 tcheck(t, err, "parse")
103 err = p.Walk(slog.Default(), nil)
104 tcheck(t, err, "walk")
105 log := mlog.New("message", nil)
106 s, err := p.Preview(log)
107 tcheck(t, err, "preview")
108 tcompare(t, s, exp)
109 }
110
111 // We use the first part for the preview.
112 m := tcompose(t, "plain", "the text", "html", "<html><body>the html</body></html>")
113 check(m, "the text\n")
114
115 // HTML before text.
116 m = tcompose(t, "html", "<body>the html</body>", "plain", "the text")
117 check(m, "the html\n")
118
119 // Only text.
120 m = tcompose(t, "plain", "the text")
121 check(m, "the text\n")
122
123 // Only html.
124 m = tcompose(t, "html", "<body>the html</body>")
125 check(m, "the html\n")
126
127 // No preview
128 m = tcompose(t, "other", "other text")
129 check(m, "")
130
131 // HTML with quoted text.
132 m = tcompose(t, "html", "<html><div>On ... someone wrote:</div><blockquote>something worth replying</blockquote><div>agreed</div></body>")
133 check(m, "[...]\nagreed\n")
134
135 // HTML with ignored elements, inline elements and tables.
136 const moreHTML = `<!doctype html>
137<html>
138 <head>
139 <title>title</title>
140 <style>head style</style>
141 <script>head script</script>
142 </head>
143<body>
144<script>body script</script>
145<style>body style</style>
146<div>line1</div>
147<div>line2</div>
148<div><a href="about:blank">link1 </a> text <span>word</span><span>word2</span>.</div>
149<table><tr><td>col1</td><th>col2</th></tr><tr><td>row2</td></tr></table>
150</body></html>
151`
152 m = tcompose(t, "html", moreHTML)
153 check(m, `line1
154line2
155link1 text wordword2.
156col1 col2
157row2
158`)
159}
160