| 119 | } |
| 120 | |
| 121 | func TestParseHTML(t *testing.T) { |
| 122 | t.Run("text/html part surfaces decoded HTML, text flattened", func(t *testing.T) { |
| 123 | raw := "From: a@x.com\r\nContent-Type: text/html\r\n\r\n<div>Hello<br><b>World</b></div>" |
| 124 | v := Parse([]byte(raw), 0) |
| 125 | if v.HTML != "<div>Hello<br><b>World</b></div>" { |
| 126 | t.Fatalf("HTML not preserved verbatim, got %q", v.HTML) |
| 127 | } |
| 128 | // Text view stays the flattened, tag-free representation. |
| 129 | if strings.Contains(v.Text, "<") || !strings.Contains(v.Text, "World") { |
| 130 | t.Fatalf("text should be flattened, got %q", v.Text) |
| 131 | } |
| 132 | }) |
| 133 | |
| 134 | t.Run("multipart/alternative: text from plain, html from html part", func(t *testing.T) { |
| 135 | raw := "From: a@x.com\r\nContent-Type: multipart/alternative; boundary=B\r\n\r\n" + |
| 136 | "--B\r\nContent-Type: text/plain\r\n\r\nPlain version.\r\n" + |
| 137 | "--B\r\nContent-Type: text/html\r\n\r\n<p>HTML <i>version</i></p>\r\n--B--\r\n" |
| 138 | v := Parse([]byte(raw), 0) |
| 139 | if v.Text != "Plain version." { |
| 140 | t.Fatalf("text: got %q, want plain part", v.Text) |
| 141 | } |
| 142 | if v.HTML != "<p>HTML <i>version</i></p>" { |
| 143 | t.Fatalf("html: got %q", v.HTML) |
| 144 | } |
| 145 | }) |
| 146 | |
| 147 | t.Run("quoted-printable HTML decoded", func(t *testing.T) { |
| 148 | // Mirrors the screenshot bug: soft-break '=' and entity must decode. |
| 149 | raw := "From: a@x.com\r\nContent-Type: text/html\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n<div>Caf=C3=A9 =\r\ntime</div>" |
| 150 | v := Parse([]byte(raw), 0) |
| 151 | if v.HTML != "<div>Café time</div>" { |
| 152 | t.Fatalf("QP HTML decode: got %q", v.HTML) |
| 153 | } |
| 154 | }) |
| 155 | |
| 156 | t.Run("plain-text-only message has no HTML", func(t *testing.T) { |
| 157 | raw := "From: a@x.com\r\nContent-Type: text/plain\r\n\r\nJust text.\r\n" |
| 158 | v := Parse([]byte(raw), 0) |
| 159 | if v.HTML != "" { |
| 160 | t.Fatalf("expected empty HTML for text-only message, got %q", v.HTML) |
| 161 | } |
| 162 | }) |
| 163 | |
| 164 | t.Run("attached .html file is not surfaced as the display body", func(t *testing.T) { |
| 165 | // multipart/mixed: a text/plain body + a text/html ATTACHMENT. The |
| 166 | // attachment must not become parsed.html (it's a file, not the body). |
| 167 | raw := "From: a@x.com\r\nContent-Type: multipart/mixed; boundary=M\r\n\r\n" + |
| 168 | "--M\r\nContent-Type: text/plain\r\n\r\nThe real body.\r\n" + |
| 169 | "--M\r\nContent-Type: text/html\r\nContent-Disposition: attachment; filename=report.html\r\n\r\n" + |
| 170 | "<h1>Attached report</h1>\r\n--M--\r\n" |
| 171 | v := Parse([]byte(raw), 0) |
| 172 | if v.Text != "The real body." { |
| 173 | t.Fatalf("text: got %q, want the plain body", v.Text) |
| 174 | } |
| 175 | if v.HTML != "" { |
| 176 | t.Fatalf("html attachment must not surface as display body, got %q", v.HTML) |
| 177 | } |
| 178 | }) |