@@ -190,6 +190,16 @@ func TestHTMLType6AndClose(t *testing.T) {
190190 assert .False (t , containsType1Close ([]byte ("short" )), "shorter than any closer" )
191191}
192192
193+ func TestContainsFold (t * testing.T ) {
194+ needle := []byte ("</pre>" )
195+ assert .True (t , containsFold ([]byte ("</PRE>" ), needle ), "match at position 0" )
196+ assert .True (t , containsFold ([]byte ("x</Pre>y" ), needle ), "match in middle" )
197+ assert .True (t , containsFold ([]byte ("x</PRE>" ), needle ), "match at last window" )
198+ assert .False (t , containsFold ([]byte ("x</em>y" ), needle ), "no match" )
199+ assert .False (t , containsFold ([]byte ("</pr" ), needle ), "needle longer than line" )
200+ assert .False (t , containsFold ([]byte ("" ), needle ), "empty line" )
201+ }
202+
193203func TestContainerMarkerScanners (t * testing.T ) {
194204 assert .Equal (t , 2 , blockquoteMarker ([]byte ("> x" )))
195205 assert .Equal (t , 1 , blockquoteMarker ([]byte (">x" )), "marker with no following space" )
@@ -292,3 +302,177 @@ func TestHTMLType6Tags_CommonMarkComplete(t *testing.T) {
292302 }
293303 }
294304}
305+
306+ // TestScanHTMLTag pins the dispatch between open and closing tags.
307+ func TestScanHTMLTag (t * testing.T ) {
308+ n , ok := scanHTMLTag ([]byte ("<img>" ))
309+ assert .True (t , ok )
310+ assert .Equal (t , 5 , n )
311+
312+ n , ok = scanHTMLTag ([]byte ("</div>" ))
313+ assert .True (t , ok )
314+ assert .Equal (t , 6 , n )
315+
316+ // Does not start with '<'.
317+ _ , ok = scanHTMLTag ([]byte ("img>" ))
318+ assert .False (t , ok )
319+
320+ // Fewer than 3 bytes.
321+ _ , ok = scanHTMLTag ([]byte ("<a" ))
322+ assert .False (t , ok )
323+ }
324+
325+ // TestScanClosingTag pins the closing-tag scanner.
326+ func TestScanClosingTag (t * testing.T ) {
327+ s := []byte ("</div>" )
328+ n , ok := scanClosingTag (s , 2 )
329+ assert .True (t , ok )
330+ assert .Equal (t , 6 , n )
331+
332+ // Optional whitespace before '>'.
333+ n , ok = scanClosingTag ([]byte ("</div >" ), 2 )
334+ assert .True (t , ok )
335+ assert .Equal (t , 7 , n )
336+
337+ // Missing '>'.
338+ _ , ok = scanClosingTag ([]byte ("</div" ), 2 )
339+ assert .False (t , ok )
340+
341+ // Name starts with a digit.
342+ _ , ok = scanClosingTag ([]byte ("</1tag>" ), 2 )
343+ assert .False (t , ok )
344+ }
345+
346+ // TestScanOpenTag pins the open-tag scanner: tag name, attributes, self-close.
347+ func TestScanOpenTag (t * testing.T ) {
348+ n , ok := scanOpenTag ([]byte ("<img>" ), 1 )
349+ assert .True (t , ok )
350+ assert .Equal (t , 5 , n )
351+
352+ n , ok = scanOpenTag ([]byte ("<br/>" ), 1 )
353+ assert .True (t , ok )
354+ assert .Equal (t , 5 , n )
355+
356+ n , ok = scanOpenTag ([]byte ("<br />" ), 1 )
357+ assert .True (t , ok )
358+ assert .Equal (t , 6 , n )
359+
360+ n , ok = scanOpenTag ([]byte (`<img src="x">` ), 1 )
361+ assert .True (t , ok )
362+ assert .Equal (t , 13 , n )
363+
364+ // '=' with no value.
365+ _ , ok = scanOpenTag ([]byte ("<img src=>" ), 1 )
366+ assert .False (t , ok )
367+
368+ // No closing '>'.
369+ _ , ok = scanOpenTag ([]byte ("<img" ), 1 )
370+ assert .False (t , ok )
371+ }
372+
373+ // TestScanTagName pins the tag-name scanner.
374+ func TestScanTagName (t * testing.T ) {
375+ i , ok := scanTagName ([]byte ("img>" ), 0 )
376+ assert .True (t , ok )
377+ assert .Equal (t , 3 , i )
378+
379+ // Hyphens allowed inside a name.
380+ i , ok = scanTagName ([]byte ("a-b>" ), 0 )
381+ assert .True (t , ok )
382+ assert .Equal (t , 3 , i )
383+
384+ // Digit at start → reject.
385+ _ , ok = scanTagName ([]byte ("1tag" ), 0 )
386+ assert .False (t , ok )
387+
388+ // Empty input.
389+ _ , ok = scanTagName ([]byte ("" ), 0 )
390+ assert .False (t , ok )
391+
392+ // Name runs to end of input (no terminator byte).
393+ i , ok = scanTagName ([]byte ("img" ), 0 )
394+ assert .True (t , ok )
395+ assert .Equal (t , 3 , i )
396+ }
397+
398+ // TestScanAttribute pins the attribute scanner.
399+ func TestScanAttribute (t * testing.T ) {
400+ // Valueless attribute.
401+ i , ok := scanAttribute ([]byte (" disabled>" ), 0 )
402+ assert .True (t , ok )
403+ assert .Equal (t , 9 , i )
404+
405+ // Unquoted value.
406+ i , ok = scanAttribute ([]byte (" src=x>" ), 0 )
407+ assert .True (t , ok )
408+ assert .Equal (t , 6 , i )
409+
410+ // Double-quoted value.
411+ i , ok = scanAttribute ([]byte (` src="x">` ), 0 )
412+ assert .True (t , ok )
413+ assert .Equal (t , 8 , i )
414+
415+ // '=' with no value.
416+ _ , ok = scanAttribute ([]byte (" src=>" ), 0 )
417+ assert .False (t , ok )
418+
419+ // No leading whitespace → reject.
420+ _ , ok = scanAttribute ([]byte ("src=x>" ), 0 )
421+ assert .False (t , ok )
422+ }
423+
424+ // TestScanAttrValue pins the attribute-value scanner.
425+ func TestScanAttrValue (t * testing.T ) {
426+ // Unquoted value (stops at '>').
427+ i , ok := scanAttrValue ([]byte ("value>" ), 0 )
428+ assert .True (t , ok )
429+ assert .Equal (t , 5 , i )
430+
431+ // Single-quoted.
432+ i , ok = scanAttrValue ([]byte ("'value'" ), 0 )
433+ assert .True (t , ok )
434+ assert .Equal (t , 7 , i )
435+
436+ // Double-quoted.
437+ i , ok = scanAttrValue ([]byte (`"value"` ), 0 )
438+ assert .True (t , ok )
439+ assert .Equal (t , 7 , i )
440+
441+ // Unclosed single quote.
442+ _ , ok = scanAttrValue ([]byte ("'value" ), 0 )
443+ assert .False (t , ok )
444+
445+ // Empty unquoted value (stop byte at position 0).
446+ _ , ok = scanAttrValue ([]byte (">" ), 0 )
447+ assert .False (t , ok )
448+ }
449+
450+ // TestSkipHTMLWS pins the whitespace-skipper.
451+ func TestSkipHTMLWS (t * testing.T ) {
452+ assert .Equal (t , 2 , skipHTMLWS ([]byte (" x" ), 0 ))
453+ assert .Equal (t , 1 , skipHTMLWS ([]byte ("\t x" ), 0 ))
454+ assert .Equal (t , 2 , skipHTMLWS ([]byte (" \t x" ), 0 ))
455+ assert .Equal (t , 0 , skipHTMLWS ([]byte ("x" ), 0 ))
456+ assert .Equal (t , 2 , skipHTMLWS ([]byte (" " ), 2 ))
457+ }
458+
459+ // TestIsUnquotedStop pins which bytes end an unquoted attribute value.
460+ func TestIsUnquotedStop (t * testing.T ) {
461+ for _ , b := range []byte {' ' , '\t' , '"' , '\'' , '=' , '<' , '>' , '`' } {
462+ assert .Truef (t , isUnquotedStop (b ), "expected stop for 0x%02x" , b )
463+ }
464+ for _ , b := range []byte {'a' , '0' , '-' , '_' , '/' } {
465+ assert .Falsef (t , isUnquotedStop (b ), "expected non-stop for 0x%02x" , b )
466+ }
467+ }
468+
469+ // TestEqualFoldASCII pins the case-insensitive ASCII byte comparison.
470+ // b is always lowercase; a may be any case. Callers always pass same-length
471+ // slices.
472+ func TestEqualFoldASCII (t * testing.T ) {
473+ assert .True (t , equalFoldASCII ([]byte ("pre" ), []byte ("pre" )))
474+ assert .True (t , equalFoldASCII ([]byte ("PRE" ), []byte ("pre" )))
475+ assert .True (t , equalFoldASCII ([]byte ("ScRiPt" ), []byte ("script" )))
476+ assert .False (t , equalFoldASCII ([]byte ("pre" ), []byte ("div" )))
477+ assert .False (t , equalFoldASCII ([]byte ("foo" ), []byte ("bar" )))
478+ }
0 commit comments