@@ -56,18 +56,134 @@ describe('Parser', () => {
5656 } ) ;
5757
5858 describe ( 'protectCustomTagNewlines' , ( ) => {
59- it ( 'should protect newlines inside custom tags (both single and double )' , ( ) => {
59+ it ( 'should protect double newlines inside custom tags (block separation )' , ( ) => {
6060 const parser = new Parser ( {
6161 protectCustomTagNewlines : true ,
6262 components : { CustomComponent : 'div' } ,
6363 } ) ;
64- const result1 = parser . parse ( '<CustomComponent>First line\n\nSecond line</CustomComponent>' ) ;
65- expect ( result1 ) . toContain ( '<CustomComponent>First line\n\nSecond line</CustomComponent>' ) ;
66- expect ( result1 ) . not . toMatch ( / < C u s t o m C o m p o n e n t > F i r s t l i n e < \/ p > \s * < p > S e c o n d l i n e / ) ;
64+ // 双换行符在 CommonMark 中会导致段落分块,需要保护
65+ const result = parser . parse ( '<CustomComponent>First line\n\nSecond line</CustomComponent>' ) ;
66+ expect ( result ) . toContain ( '<CustomComponent>First line\n\nSecond line</CustomComponent>' ) ;
67+ expect ( result ) . not . toMatch ( / < C u s t o m C o m p o n e n t > F i r s t l i n e < \/ p > \s * < p > S e c o n d l i n e / ) ;
68+ } ) ;
69+
70+ it ( 'single newline alone does not cause block separation per CommonMark spec' , ( ) => {
71+ // 根据 CommonMark 规范,单独的 \n 不会导致段落分块
72+ // 所以对于纯粹的 \n,开启与否 protectCustomTagNewlines 结果相同
73+ const parserWithProtect = new Parser ( {
74+ protectCustomTagNewlines : true ,
75+ components : { CustomComponent : 'div' } ,
76+ } ) ;
77+ const parserWithoutProtect = new Parser ( {
78+ protectCustomTagNewlines : false ,
79+ components : { CustomComponent : 'div' } ,
80+ } ) ;
81+ const content = '<CustomComponent>Line1\nLine2</CustomComponent>' ;
6782
68- const result2 = parser . parse ( '<CustomComponent>Line1\nLine2</CustomComponent>' ) ;
69- expect ( result2 ) . toContain ( '<CustomComponent>Line1\nLine2</CustomComponent>' ) ;
70- expect ( result2 ) . not . toMatch ( / < C u s t o m C o m p o n e n t > L i n e 1 < \/ p > \s * < p > L i n e 2 / ) ;
83+ // 两种情况下单独的 \n 都不会导致分块
84+ const resultWith = parserWithProtect . parse ( content ) ;
85+ const resultWithout = parserWithoutProtect . parse ( content ) ;
86+
87+ expect ( resultWith ) . toContain ( '<CustomComponent>Line1\nLine2</CustomComponent>' ) ;
88+ expect ( resultWithout ) . toContain ( '<CustomComponent>Line1\nLine2</CustomComponent>' ) ;
89+ } ) ;
90+
91+ // =======================================================================
92+ // 以下测试用例说明为什么需要保护所有 \n(使用 /\n/g)而不仅仅是 \n\n
93+ // 虽然单独的 \n 不会导致分块,但 \n 后跟特定 Markdown 语法标记会导致块结构变化
94+ // 保护所有 \n 是一种保守但安全的策略,可以覆盖所有可能的 Markdown 块级语法
95+ // =======================================================================
96+
97+ it ( 'why protect all newlines: \\n followed by list marker causes block change' , ( ) => {
98+ // \n- 会被 Markdown 解析为无序列表,需要保护
99+ const parserWithProtect = new Parser ( {
100+ protectCustomTagNewlines : true ,
101+ components : { CustomComponent : 'div' } ,
102+ } ) ;
103+ const parserWithoutProtect = new Parser ( {
104+ protectCustomTagNewlines : false ,
105+ components : { CustomComponent : 'div' } ,
106+ } ) ;
107+ const content = '<CustomComponent>Text\n- item1</CustomComponent>' ;
108+
109+ // 不开启保护时,\n- 会被解析为列表,破坏自定义标签结构
110+ const resultWithout = parserWithoutProtect . parse ( content ) ;
111+ expect ( resultWithout ) . toContain ( '<ul>' ) ;
112+ expect ( resultWithout ) . toContain ( '<li>' ) ;
113+
114+ // 开启保护时,内容保持完整
115+ const resultWith = parserWithProtect . parse ( content ) ;
116+ expect ( resultWith ) . toContain ( '<CustomComponent>Text\n- item1</CustomComponent>' ) ;
117+ expect ( resultWith ) . not . toContain ( '<ul>' ) ;
118+ } ) ;
119+
120+ it ( 'why protect all newlines: \\n followed by ordered list marker causes block change' , ( ) => {
121+ // \n1. 会被 Markdown 解析为有序列表,需要保护
122+ const parserWithProtect = new Parser ( {
123+ protectCustomTagNewlines : true ,
124+ components : { CustomComponent : 'div' } ,
125+ } ) ;
126+ const parserWithoutProtect = new Parser ( {
127+ protectCustomTagNewlines : false ,
128+ components : { CustomComponent : 'div' } ,
129+ } ) ;
130+ const content = '<CustomComponent>Text\n1. first</CustomComponent>' ;
131+
132+ // 不开启保护时,\n1. 会被解析为有序列表
133+ const resultWithout = parserWithoutProtect . parse ( content ) ;
134+ expect ( resultWithout ) . toContain ( '<ol>' ) ;
135+ expect ( resultWithout ) . toContain ( '<li>' ) ;
136+
137+ // 开启保护时,内容保持完整
138+ const resultWith = parserWithProtect . parse ( content ) ;
139+ expect ( resultWith ) . toContain ( '<CustomComponent>Text\n1. first</CustomComponent>' ) ;
140+ expect ( resultWith ) . not . toContain ( '<ol>' ) ;
141+ } ) ;
142+
143+ it ( 'why protect all newlines: \\n followed by heading marker causes block change' , ( ) => {
144+ // \n# 会被 Markdown 解析为标题,需要保护
145+ const parserWithProtect = new Parser ( {
146+ protectCustomTagNewlines : true ,
147+ components : { CustomComponent : 'div' } ,
148+ } ) ;
149+ const parserWithoutProtect = new Parser ( {
150+ protectCustomTagNewlines : false ,
151+ components : { CustomComponent : 'div' } ,
152+ } ) ;
153+ const content = '<CustomComponent>Text\n# heading</CustomComponent>' ;
154+
155+ // 不开启保护时,\n# 会被解析为标题
156+ const resultWithout = parserWithoutProtect . parse ( content ) ;
157+ expect ( resultWithout ) . toContain ( '<h1>' ) ;
158+
159+ // 开启保护时,内容保持完整
160+ const resultWith = parserWithProtect . parse ( content ) ;
161+ expect ( resultWith ) . toContain ( '<CustomComponent>Text\n# heading</CustomComponent>' ) ;
162+ expect ( resultWith ) . not . toContain ( '<h1>' ) ;
163+ } ) ;
164+
165+ it ( 'why protect all newlines: \\n followed by code fence causes block change' , ( ) => {
166+ // \n``` 会被 Markdown 解析为代码块,需要保护
167+ const parserWithProtect = new Parser ( {
168+ protectCustomTagNewlines : true ,
169+ components : { CustomComponent : 'div' } ,
170+ } ) ;
171+ const parserWithoutProtect = new Parser ( {
172+ protectCustomTagNewlines : false ,
173+ components : { CustomComponent : 'div' } ,
174+ } ) ;
175+ const content = '<CustomComponent>Text\n```\ncode\n```</CustomComponent>' ;
176+
177+ // 不开启保护时,\n``` 会被解析为代码块,破坏自定义标签结构
178+ const resultWithout = parserWithoutProtect . parse ( content ) ;
179+ expect ( resultWithout ) . toMatch ( / < p r e > | < c o d e > / ) ;
180+
181+ // 开启保护时,内容保持完整(注:``` 在 HTML 内会被 marked 处理为 <code>)
182+ const resultWith = parserWithProtect . parse ( content ) ;
183+ expect ( resultWith ) . toContain ( '<CustomComponent>' ) ;
184+ expect ( resultWith ) . toContain ( 'code' ) ;
185+ // 关键验证:不会生成 <pre> 代码块结构
186+ expect ( resultWith ) . not . toContain ( '<pre>' ) ;
71187 } ) ;
72188
73189 it ( 'should not protect newlines when protectCustomTagNewlines is false' , ( ) => {
@@ -80,6 +196,19 @@ describe('Parser', () => {
80196 expect ( result ) . toContain ( '<p>' ) ;
81197 } ) ;
82198
199+ it ( 'blockquote marker (>) inside custom tags is escaped by marked' , ( ) => {
200+ // 在 HTML 标签内的 > 字符会被 marked 转义为 >,不会被解析为引用块
201+ // 这是 marked 库的默认行为,不需要特殊保护
202+ const parser = new Parser ( {
203+ protectCustomTagNewlines : true ,
204+ components : { CustomComponent : 'div' } ,
205+ } ) ;
206+ const content = '<CustomComponent>Text\n> quote here</CustomComponent>' ;
207+ const result = parser . parse ( content ) ;
208+ expect ( result ) . toContain ( '>' ) ;
209+ expect ( result ) . not . toContain ( '<blockquote>' ) ;
210+ } ) ;
211+
83212 it ( 'should work normally when protectCustomTagNewlines is true but no custom components' , ( ) => {
84213 const parser = new Parser ( {
85214 protectCustomTagNewlines : true ,
0 commit comments