@@ -1083,6 +1083,202 @@ func TestNewIMMarkdownContextExtractsBaseURL(t *testing.T) {
10831083 }
10841084}
10851085
1086+ func TestIMMarkdownBaseURLFromInputEdges (t * testing.T ) {
1087+ t .Parallel ()
1088+
1089+ tests := []struct {
1090+ name string
1091+ input string
1092+ want string
1093+ ok bool
1094+ }{
1095+ {
1096+ name : "empty candidate before marker is skipped" ,
1097+ input : "///docx/doc_token" ,
1098+ },
1099+ {
1100+ name : "scheme candidate before marker is returned" ,
1101+ input : "//https://tenant.example.com/docx/doc_token" ,
1102+ want : "https://tenant.example.com" ,
1103+ ok : true ,
1104+ },
1105+ {
1106+ name : "host without dot before marker is rejected" ,
1107+ input : "tenant/docx/doc_token" ,
1108+ },
1109+ {
1110+ name : "no document marker is rejected" ,
1111+ input : "tenant.example.com/path/doc_token" ,
1112+ },
1113+ }
1114+
1115+ for _ , tt := range tests {
1116+ t .Run (tt .name , func (t * testing.T ) {
1117+ t .Parallel ()
1118+
1119+ got , ok := imMarkdownBaseURLFromInput (tt .input )
1120+ if ok != tt .ok {
1121+ t .Fatalf ("ok = %v, want %v" , ok , tt .ok )
1122+ }
1123+ if got != tt .want {
1124+ t .Fatalf ("baseURL = %q, want %q" , got , tt .want )
1125+ }
1126+ })
1127+ }
1128+ }
1129+
1130+ func TestIMMarkdownHandlerDirectFallbackBranches (t * testing.T ) {
1131+ t .Parallel ()
1132+
1133+ ctx := imMarkdownContext {baseURL : "https://tenant.example.com" }
1134+ cases := []struct {
1135+ name string
1136+ got string
1137+ want string
1138+ }{
1139+ {
1140+ name : "empty heading" ,
1141+ got : handleIMMarkdownHeading (2 )("" , " " , nil , ctx ),
1142+ want : "" ,
1143+ },
1144+ {
1145+ name : "empty paragraph" ,
1146+ got : handleIMMarkdownParagraph ("" , " " , nil , ctx ),
1147+ want : "" ,
1148+ },
1149+ {
1150+ name : "list item seq trims trailing dot" ,
1151+ got : handleIMMarkdownListItem ("" , "first\n second" , map [string ]string {"seq" : "7." }, ctx ),
1152+ want : "7. first\n second\n " ,
1153+ },
1154+ {
1155+ name : "list item seq auto uses bullet" ,
1156+ got : handleIMMarkdownListItem ("" , "first" , map [string ]string {"seq" : "auto" }, ctx ),
1157+ want : "- first\n " ,
1158+ },
1159+ {
1160+ name : "empty list item" ,
1161+ got : handleIMMarkdownListItem ("" , " " , map [string ]string {"seq" : "3" }, ctx ),
1162+ want : "" ,
1163+ },
1164+ {
1165+ name : "empty callout" ,
1166+ got : handleIMMarkdownCallout ("" , "" , nil , ctx ),
1167+ want : "---\n ---" ,
1168+ },
1169+ {
1170+ name : "empty blockquote" ,
1171+ got : handleIMMarkdownBlockquote ("" , " " , nil , ctx ),
1172+ want : "" ,
1173+ },
1174+ {
1175+ name : "blockquote preserves blank quote lines" ,
1176+ got : handleIMMarkdownBlockquote ("" , "first\n \n second" , nil , ctx ),
1177+ want : "> first\n >\n > second" ,
1178+ },
1179+ {
1180+ name : "empty latex" ,
1181+ got : handleIMMarkdownLatex ("" , " " , nil , ctx ),
1182+ want : "" ,
1183+ },
1184+ {
1185+ name : "image without URL" ,
1186+ got : handleIMMarkdownImage ("" , "" , map [string ]string {"alt" : "A" }, ctx ),
1187+ want : "" ,
1188+ },
1189+ {
1190+ name : "empty strong" ,
1191+ got : handleIMMarkdownStrong ("" , " " , nil , ctx ),
1192+ want : "" ,
1193+ },
1194+ {
1195+ name : "empty emphasis" ,
1196+ got : handleIMMarkdownEmphasis ("" , " " , nil , ctx ),
1197+ want : "" ,
1198+ },
1199+ {
1200+ name : "empty delete" ,
1201+ got : handleIMMarkdownDelete ("" , " " , nil , ctx ),
1202+ want : "" ,
1203+ },
1204+ {
1205+ name : "anchor without href" ,
1206+ got : handleIMMarkdownAnchor ("" , "<b>plain</b>" , nil , ctx ),
1207+ want : "**plain**" ,
1208+ },
1209+ {
1210+ name : "table skips rows without cells" ,
1211+ got : handleIMMarkdownTable ("<table><tr></tr></table>" , "<tr></tr>" , nil , ctx ),
1212+ want : "`<table><tr></tr></table>`" ,
1213+ },
1214+ {
1215+ name : "empty normalized table cell" ,
1216+ got : normalizeIMMarkdownTableCell ("<span> </span>" ),
1217+ want : "" ,
1218+ },
1219+ {
1220+ name : "plain fenced code uses minimum fence" ,
1221+ got : imMarkdownFencedCode ("plain" , "" ),
1222+ want : "```\n plain\n ```" ,
1223+ },
1224+ }
1225+
1226+ for _ , tt := range cases {
1227+ t .Run (tt .name , func (t * testing.T ) {
1228+ t .Parallel ()
1229+
1230+ if tt .got != tt .want {
1231+ t .Fatalf ("got %q, want %q" , tt .got , tt .want )
1232+ }
1233+ })
1234+ }
1235+ }
1236+
1237+ func TestIMMarkdownExtractionAndListBreakBranches (t * testing.T ) {
1238+ t .Parallel ()
1239+
1240+ rowBodies := extractIMMarkdownElementBodies (`</tr><tr/><tr>open` , imMarkdownRowTagRE )
1241+ if want := []string {"" }; ! reflect .DeepEqual (rowBodies , want ) {
1242+ t .Fatalf ("extractIMMarkdownElementBodies() = %#v, want %#v" , rowBodies , want )
1243+ }
1244+
1245+ if _ , _ , ok := findIMMarkdownElementClosingTag (`<tr><td>open</td>` , len ("<tr>" ), imMarkdownRowTagRE ); ok {
1246+ t .Fatal ("findIMMarkdownElementClosingTag() found closing tag, want false" )
1247+ }
1248+
1249+ if got := convertIMMarkdownListItems ("" , false , imMarkdownContext {}); got != "" {
1250+ t .Fatalf ("empty list conversion = %q, want empty" , got )
1251+ }
1252+ if got := convertIMMarkdownListItems ("<li>open" , false , imMarkdownContext {}); got != "" {
1253+ t .Fatalf ("unclosed list conversion = %q, want empty" , got )
1254+ }
1255+ if _ , _ , ok := findIMMarkdownListItemClosingTag (`<li>outer<li>inner</li>` , len ("<li>" )); ok {
1256+ t .Fatal ("findIMMarkdownListItemClosingTag() found closing tag for unbalanced nested item" )
1257+ }
1258+ }
1259+
1260+ func TestIMMarkdownLinkAndEncodingFallbackBranches (t * testing.T ) {
1261+ t .Parallel ()
1262+
1263+ text , href , ok := extractIMMarkdownInnerLink (`<a href='https://example.com/ref'></a>` )
1264+ if ! ok {
1265+ t .Fatal ("extractIMMarkdownInnerLink() ok = false, want true" )
1266+ }
1267+ if text != "https://example.com/ref" || href != "https://example.com/ref" {
1268+ t .Fatalf ("inner link = (%q, %q), want href fallback" , text , href )
1269+ }
1270+
1271+ if got := escapeMarkdownLinkDestination ("a%zz%" ); got != "a%25zz%25" {
1272+ t .Fatalf ("escaped invalid percent = %q, want %q" , got , "a%25zz%25" )
1273+ }
1274+ if got := escapeMarkdownLinkDestination ("研发" ); got != "%E7%A0%94%E5%8F%91" {
1275+ t .Fatalf ("escaped unicode = %q, want encoded UTF-8 bytes" , got )
1276+ }
1277+ if got := escapeMarkdownLinkDestination (string ([]byte {'a' , 0xff , 'b' })); got != "a%FFb" {
1278+ t .Fatalf ("escaped invalid UTF-8 = %q, want %q" , got , "a%FFb" )
1279+ }
1280+ }
1281+
10861282type imMarkdownCase struct {
10871283 name string
10881284 input string
0 commit comments