@@ -382,4 +382,84 @@ Total revenue reached [¥5M](metric_value, origin=5000000) with **strong growth*
382382 expect ( phrase . metadata ?. detail ) . toEqual ( [ 1 , 2 , 3 ] ) ;
383383 expect ( ( phrase . metadata as Record < string , unknown > ) . active ) . toBe ( true ) ;
384384 } ) ;
385+
386+ it ( 'should handle unclosed bold formatting without infinite loop' , ( ) => {
387+ const syntax = `Text with **unclosed bold at the end` ;
388+ const result = parseSyntax ( syntax ) ;
389+
390+ expect ( result . sections ) . toHaveLength ( 1 ) ;
391+ const phrases = result . sections ! [ 0 ] . paragraphs ! [ 0 ] . phrases ;
392+
393+ // Should parse without hanging
394+ expect ( phrases . length ) . toBeGreaterThan ( 0 ) ;
395+ expect ( phrases [ 0 ] . value ) . toBe ( 'Text with ' ) ;
396+ expect ( phrases [ 1 ] . value ) . toBe ( '**' ) ;
397+ expect ( phrases [ 2 ] . value ) . toBe ( 'unclosed bold at the end' ) ;
398+ } ) ;
399+
400+ it ( 'should handle unclosed italic formatting without infinite loop' , ( ) => {
401+ const syntax = `Text with *unclosed italic at the end` ;
402+ const result = parseSyntax ( syntax ) ;
403+
404+ expect ( result . sections ) . toHaveLength ( 1 ) ;
405+ const phrases = result . sections ! [ 0 ] . paragraphs ! [ 0 ] . phrases ;
406+
407+ // Should parse without hanging
408+ expect ( phrases . length ) . toBeGreaterThan ( 0 ) ;
409+ expect ( phrases [ 0 ] . value ) . toBe ( 'Text with ' ) ;
410+ expect ( phrases [ 1 ] . value ) . toBe ( '*' ) ;
411+ expect ( phrases [ 2 ] . value ) . toBe ( 'unclosed italic at the end' ) ;
412+ } ) ;
413+
414+ it ( 'should handle unclosed underline formatting without infinite loop' , ( ) => {
415+ const syntax = `Text with __unclosed underline at the end` ;
416+ const result = parseSyntax ( syntax ) ;
417+
418+ expect ( result . sections ) . toHaveLength ( 1 ) ;
419+ const phrases = result . sections ! [ 0 ] . paragraphs ! [ 0 ] . phrases ;
420+
421+ // Should parse without hanging
422+ expect ( phrases . length ) . toBeGreaterThan ( 0 ) ;
423+ expect ( phrases [ 0 ] . value ) . toBe ( 'Text with ' ) ;
424+ expect ( phrases [ 1 ] . value ) . toBe ( '__' ) ;
425+ expect ( phrases [ 2 ] . value ) . toBe ( 'unclosed underline at the end' ) ;
426+ } ) ;
427+
428+ it ( 'should handle multiple unclosed formatting markers' , ( ) => {
429+ const syntax = `Text **bold __underline *italic` ;
430+ const result = parseSyntax ( syntax ) ;
431+
432+ expect ( result . sections ) . toHaveLength ( 1 ) ;
433+ const phrases = result . sections ! [ 0 ] . paragraphs ! [ 0 ] . phrases ;
434+
435+ // Should parse without hanging and treat all markers as plain text
436+ expect ( phrases . length ) . toBeGreaterThan ( 0 ) ;
437+ } ) ;
438+
439+ it ( 'should handle streaming-like partial syntax without infinite loop' , ( ) => {
440+ // This simulates what happens during streaming when text is incrementally added
441+ const partialChunks = [
442+ 'The **premium segment**' ,
443+ 'The **premium segment** (devices over $800) showed *remarkable' ,
444+ 'The **premium segment** (devices over $800) showed *remarkable* [resilience](trend_desc' ,
445+ ] ;
446+
447+ // Each chunk should parse without infinite loop
448+ partialChunks . forEach ( ( chunk ) => {
449+ const result = parseSyntax ( chunk ) ;
450+ expect ( result . sections ) . toBeDefined ( ) ;
451+ } ) ;
452+ } ) ;
453+
454+ it ( 'should handle formatting markers at exact string boundaries' , ( ) => {
455+ // Test edge cases where markers appear at the very end
456+ const testCases = [ { text : 'x**' } , { text : 'x__' } , { text : 'ab**' } , { text : 'ab__' } ] ;
457+
458+ testCases . forEach ( ( { text } ) => {
459+ const result = parseSyntax ( text ) ;
460+ expect ( result . sections ) . toBeDefined ( ) ;
461+ // Should complete without hanging
462+ expect ( result . sections ! . length ) . toBeGreaterThanOrEqual ( 0 ) ;
463+ } ) ;
464+ } ) ;
385465} ) ;
0 commit comments