Test bullet parser; fix marker-less passthrough
The previous parser treated any non-empty non-header line as a bullet, so 'plain paragraph' answers were silently rewrapped as <ul><li>. Now we only count a line if it had a recognized bullet marker (- * • ·); if none did, fall back to raw + <pre> as originally intended. Tests cover dash/asterisk/unicode bullets, blank-line tolerance, Summary:/TL;DR header stripping, HTML escaping, single-bullet output, the no-marker passthrough path, and the IsQuestionReaction set.
This commit is contained in:
@@ -139,29 +139,36 @@ func (e *Explainer) summarize(headline, body string) (string, error) {
|
||||
func formatSummary(raw string) (plain, htmlBody string) {
|
||||
lines := strings.Split(raw, "\n")
|
||||
var bullets []string
|
||||
sawMarker := false
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// Normalize common bullet markers
|
||||
for _, prefix := range []string{"- ", "* ", "• ", "·"} {
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
line = strings.TrimSpace(line[len(prefix):])
|
||||
break
|
||||
}
|
||||
}
|
||||
// Skip header-ish lines the model sometimes adds
|
||||
low := strings.ToLower(line)
|
||||
if strings.HasPrefix(low, "summary:") || strings.HasPrefix(low, "tl;dr") {
|
||||
continue
|
||||
}
|
||||
// Normalize common bullet markers; only count this line if it had one.
|
||||
hadMarker := false
|
||||
for _, prefix := range []string{"- ", "* ", "• ", "·"} {
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
line = strings.TrimSpace(line[len(prefix):])
|
||||
hadMarker = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hadMarker {
|
||||
continue
|
||||
}
|
||||
sawMarker = true
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
bullets = append(bullets, line)
|
||||
}
|
||||
if len(bullets) == 0 {
|
||||
if !sawMarker || len(bullets) == 0 {
|
||||
// Model produced something but we couldn't parse bullets — pass through.
|
||||
return raw, "<pre>" + html.EscapeString(raw) + "</pre>"
|
||||
}
|
||||
|
||||
110
internal/explainer/explainer_test.go
Normal file
110
internal/explainer/explainer_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package explainer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatSummary(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantPlain string
|
||||
wantHTML string
|
||||
}{
|
||||
{
|
||||
name: "dash bullets",
|
||||
in: "- First point.\n- Second point.\n- Third point.",
|
||||
wantPlain: "• First point.\n• Second point.\n• Third point.",
|
||||
wantHTML: "<ul><li>First point.</li><li>Second point.</li><li>Third point.</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "asterisk bullets",
|
||||
in: "* alpha\n* beta",
|
||||
wantPlain: "• alpha\n• beta",
|
||||
wantHTML: "<ul><li>alpha</li><li>beta</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "unicode bullets",
|
||||
in: "• one\n• two",
|
||||
wantPlain: "• one\n• two",
|
||||
wantHTML: "<ul><li>one</li><li>two</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "blank lines ignored",
|
||||
in: "- a\n\n- b\n\n\n- c",
|
||||
wantPlain: "• a\n• b\n• c",
|
||||
wantHTML: "<ul><li>a</li><li>b</li><li>c</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "summary header stripped",
|
||||
in: "Summary:\n- first\n- second",
|
||||
wantPlain: "• first\n• second",
|
||||
wantHTML: "<ul><li>first</li><li>second</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "tldr header stripped",
|
||||
in: "TL;DR of the article\n- key point\n- other point",
|
||||
wantPlain: "• key point\n• other point",
|
||||
wantHTML: "<ul><li>key point</li><li>other point</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "single bullet",
|
||||
in: "- just one",
|
||||
wantPlain: "• just one",
|
||||
wantHTML: "<ul><li>just one</li></ul>",
|
||||
},
|
||||
{
|
||||
name: "html escaped in bullets",
|
||||
in: `- A "quote" & <tag>`,
|
||||
wantPlain: `• A "quote" & <tag>`,
|
||||
wantHTML: "<ul><li>A "quote" & <tag></li></ul>",
|
||||
},
|
||||
{
|
||||
name: "leading whitespace trimmed",
|
||||
in: " - first\n - second ",
|
||||
wantPlain: "• first\n• second",
|
||||
wantHTML: "<ul><li>first</li><li>second</li></ul>",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
gotPlain, gotHTML := formatSummary(c.in)
|
||||
if gotPlain != c.wantPlain {
|
||||
t.Errorf("plain:\n got %q\n want %q", gotPlain, c.wantPlain)
|
||||
}
|
||||
if gotHTML != c.wantHTML {
|
||||
t.Errorf("html:\n got %q\n want %q", gotHTML, c.wantHTML)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatSummary_NoBulletsPassthrough(t *testing.T) {
|
||||
in := "Just a single paragraph with no bullets at all."
|
||||
plain, htmlBody := formatSummary(in)
|
||||
if plain != in {
|
||||
t.Errorf("plain should pass through raw, got %q", plain)
|
||||
}
|
||||
if !strings.HasPrefix(htmlBody, "<pre>") || !strings.HasSuffix(htmlBody, "</pre>") {
|
||||
t.Errorf("html should wrap raw in <pre>, got %q", htmlBody)
|
||||
}
|
||||
if !strings.Contains(htmlBody, "single paragraph") {
|
||||
t.Errorf("html should contain the body text, got %q", htmlBody)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsQuestionReaction(t *testing.T) {
|
||||
want := []string{"❓", "❔", "⁉", "⁉️", "🤔", "?", "?"}
|
||||
for _, k := range want {
|
||||
if !IsQuestionReaction(k) {
|
||||
t.Errorf("expected %q to trigger explain", k)
|
||||
}
|
||||
}
|
||||
notQuestion := []string{"👍", "👎", "🔥", "", "??", "❤️"}
|
||||
for _, k := range notQuestion {
|
||||
if IsQuestionReaction(k) {
|
||||
t.Errorf("expected %q to NOT trigger explain", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user