Skip to content

Commit b0231dd

Browse files
committed
test(x-markdown): add test cases to justify protecting all newlines in custom tags
- Add test case to verify single \n alone does not cause block separation per CommonMark spec - Add test cases to explain why protect all \n (using /\n/g) instead of just \n\n: - \n followed by list marker (-) causes block change - \n followed by ordered list marker (1.) causes block change - \n followed by heading marker (#) causes block change - \n followed by code fence (```) causes block change - Add test case for blockquote marker (>) being escaped by marked
1 parent 34032de commit b0231dd

File tree

1 file changed

+136
-7
lines changed

1 file changed

+136
-7
lines changed

packages/x-markdown/src/XMarkdown/__tests__/Parser.test.ts

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/<CustomComponent>First line<\/p>\s*<p>Second line/);
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(/<CustomComponent>First line<\/p>\s*<p>Second line/);
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(/<CustomComponent>Line1<\/p>\s*<p>Line2/);
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(/<pre>|<code>/);
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 转义为 &gt;,不会被解析为引用块
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('&gt;');
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

Comments
 (0)