@@ -63,6 +63,112 @@ func TestSnippet_FlattensNewlines(t *testing.T) {
6363 }
6464}
6565
66+ func TestStripMarkdown (t * testing.T ) {
67+ tests := []struct {
68+ name , in , want string
69+ }{
70+ {"bold stars" , "the **bold** word" , "the bold word" },
71+ {"bold underscores" , "the __bold__ word" , "the bold word" },
72+ {"italic stars" , "an *italic* word" , "an italic word" },
73+ {"inline code" , "use `fmt.Println` here" , "use fmt.Println here" },
74+ {"fenced code block" , "before ```go\n code\n ``` after" , "before code after" },
75+ {"heading h3" , "### My heading" , "My heading" },
76+ {"heading h1" , "# Title" , "Title" },
77+ {"horizontal rule dashes" , "above --- below" , "above below" },
78+ {"horizontal rule stars" , "above *** below" , "above below" },
79+ {"horizontal rule underscores" , "above ___ below" , "above below" },
80+ {"pipe table" , "| col1 | col2 |" , "col1 col2" },
81+ {"bullet dash" , "- item one" , "item one" },
82+ {"bullet star" , "* item two" , "item two" },
83+ {"bullet plus" , "+ item three" , "item three" },
84+ {"ordered list" , "1. first item" , "first item" },
85+ {"blockquote" , "> quoted text" , "quoted text" },
86+ {"plain text" , "no markdown here" , "no markdown here" },
87+ {"real world" , "**Goal:** describe the system" , "Goal: describe the system" },
88+ }
89+ for _ , tt := range tests {
90+ t .Run (tt .name , func (t * testing.T ) {
91+ got := StripMarkdown (tt .in )
92+ // Normalize whitespace for comparison
93+ got = strings .Join (strings .Fields (got ), " " )
94+ want := strings .Join (strings .Fields (tt .want ), " " )
95+ if got != want {
96+ t .Errorf ("StripMarkdown(%q) = %q, want %q" , tt .in , got , want )
97+ }
98+ })
99+ }
100+ }
101+
102+ func TestSnippet_DoesNotCutWords (t * testing.T ) {
103+ // Place query at rune 150 so start = 150-100 = 50.
104+ // Put a 30-char word at rune 40 so it spans the window edge.
105+ prefix := strings .Repeat ("a " , 20 ) // 40 runes
106+ longWord := "abcdefghijklmnopqrstuvwxyzabcd" // 30 runes, ends at 70
107+ // pad to push TARGET to ~rune 150
108+ mid := strings .Repeat ("z " , 40 ) // 80 runes (total so far: 40+30+1+80 = 151)
109+ text := prefix + longWord + " " + mid + "TARGET" + strings .Repeat (" mm" , 80 )
110+ got := Snippet (text , "TARGET" , MaxSnippetLen )
111+ trimmed := strings .TrimPrefix (got , "..." )
112+ if len (trimmed ) > 0 {
113+ firstSpace := strings .IndexByte (trimmed , ' ' )
114+ if firstSpace > 0 {
115+ firstWord := trimmed [:firstSpace ]
116+ // Should not be a fragment of the long word
117+ if len (firstWord ) > 2 && strings .Contains (longWord , firstWord ) && firstWord != longWord {
118+ t .Errorf ("snippet starts with word fragment %q: %q" , firstWord , got )
119+ }
120+ }
121+ }
122+ }
123+
124+ func TestSnippet_WordBoundaryPreservesWindowSize (t * testing.T ) {
125+ text := strings .Repeat ("abcdefgh " , 50 ) // ~450 chars
126+ got := Snippet (text , "abcdefgh" , MaxSnippetLen )
127+ runeLen := len ([]rune (strings .TrimPrefix (strings .TrimSuffix (got , "..." ), "..." )))
128+ // Should be within 40 runes of MaxSnippetLen
129+ if runeLen < MaxSnippetLen - 40 {
130+ t .Errorf ("snippet too short after word snapping: %d runes (min %d)" , runeLen , MaxSnippetLen - 40 )
131+ }
132+ }
133+
134+ func TestSnippet_WordBoundary_AllOneWord (t * testing.T ) {
135+ // No spaces to snap to - should still work without panic
136+ text := strings .Repeat ("x" , 400 )
137+ got := Snippet (text , "x" , MaxSnippetLen )
138+ if len ([]rune (got )) > MaxSnippetLen + 10 {
139+ t .Errorf ("snippet too long for all-one-word input: %d runes" , len ([]rune (got )))
140+ }
141+ }
142+
143+ func TestSnippet_WordBoundary_QueryAtStart (t * testing.T ) {
144+ text := "target " + strings .Repeat ("filler " , 60 )
145+ got := Snippet (text , "target" , MaxSnippetLen )
146+ if ! strings .HasPrefix (got , "target" ) {
147+ t .Errorf ("snippet should start with query when at start: %q" , got )
148+ }
149+ }
150+
151+ func TestSnippet_WordBoundary_QueryAtEnd (t * testing.T ) {
152+ text := strings .Repeat ("filler " , 60 ) + "target"
153+ got := Snippet (text , "target" , MaxSnippetLen )
154+ if ! strings .HasSuffix (got , "target" ) {
155+ t .Errorf ("snippet should end with query when at end: %q" , got )
156+ }
157+ }
158+
159+ func TestSnippet_StripsMarkdown (t * testing.T ) {
160+ text := "### **Goal:** describe the | system | using `code` and > quoted text"
161+ got := Snippet (text , "describe" , MaxSnippetLen )
162+ for _ , bad := range []string {"**" , "###" , "|" , "`" , ">" } {
163+ if strings .Contains (got , bad ) {
164+ t .Errorf ("snippet still contains %q: %q" , bad , got )
165+ }
166+ }
167+ if ! strings .Contains (got , "describe" ) {
168+ t .Errorf ("snippet should contain query 'describe': %q" , got )
169+ }
170+ }
171+
66172func TestMatchesAll_SingleTerm (t * testing.T ) {
67173 if ! MatchesAll ("hello world" , []string {"hello" }) {
68174 t .Error ("should match single term" )
0 commit comments