@incremark/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,428 @@
1
+ /**
2
+ * 健壮性测试
3
+ * 测试边界情况、异常输入和极端场景
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest'
7
+ import { IncremarkParser } from './IncremarkParser'
8
+
9
+ describe('健壮性测试', () => {
10
+ describe('空输入和边界情况', () => {
11
+ it('空字符串输入', () => {
12
+ const parser = new IncremarkParser()
13
+ const result = parser.append('')
14
+ expect(result.ast.children.length).toBe(0)
15
+
16
+ const final = parser.finalize()
17
+ expect(final.ast.children.length).toBe(0)
18
+ })
19
+
20
+ it('多次空字符串输入', () => {
21
+ const parser = new IncremarkParser()
22
+ for (let i = 0; i < 100; i++) {
23
+ parser.append('')
24
+ }
25
+ const result = parser.finalize()
26
+ expect(result.ast.children.length).toBe(0)
27
+ })
28
+
29
+ it('只有空白字符', () => {
30
+ const parser = new IncremarkParser()
31
+ parser.append(' \n\n\t\t\n \n')
32
+ const result = parser.finalize()
33
+ expect(result.ast).toBeDefined()
34
+ })
35
+
36
+ it('只有换行符', () => {
37
+ const parser = new IncremarkParser()
38
+ parser.append('\n\n\n\n\n')
39
+ const result = parser.finalize()
40
+ expect(result.ast).toBeDefined()
41
+ })
42
+
43
+ it('单个字符', () => {
44
+ const parser = new IncremarkParser()
45
+ parser.append('a')
46
+ const result = parser.finalize()
47
+ expect(result.ast.children.length).toBe(1)
48
+ })
49
+
50
+ it('单个换行符', () => {
51
+ const parser = new IncremarkParser()
52
+ parser.append('\n')
53
+ const result = parser.finalize()
54
+ expect(result.ast).toBeDefined()
55
+ })
56
+ })
57
+
58
+ describe('特殊字符处理', () => {
59
+ it('Unicode 字符', () => {
60
+ const parser = new IncremarkParser()
61
+ parser.append('# 中文标题 🎉\n\n这是一段包含日文(日本語)和韩文(한국어)的内容。')
62
+ const result = parser.finalize()
63
+ expect(result.ast.children.length).toBe(2)
64
+ })
65
+
66
+ it('Emoji 表情', () => {
67
+ const parser = new IncremarkParser()
68
+ parser.append('# 🚀🎨🔥\n\n段落 😀😁😂🤣')
69
+ const result = parser.finalize()
70
+ expect(result.ast.children.length).toBe(2)
71
+ })
72
+
73
+ it('零宽字符', () => {
74
+ const parser = new IncremarkParser()
75
+ // 零宽空格 U+200B, 零宽非连接符 U+200C, 零宽连接符 U+200D
76
+ parser.append('Hello\u200B\u200C\u200DWorld')
77
+ const result = parser.finalize()
78
+ expect(result.ast).toBeDefined()
79
+ })
80
+
81
+ it('控制字符', () => {
82
+ const parser = new IncremarkParser()
83
+ parser.append('Hello\x00\x01\x02World')
84
+ const result = parser.finalize()
85
+ expect(result.ast).toBeDefined()
86
+ })
87
+
88
+ it('反斜杠转义', () => {
89
+ const parser = new IncremarkParser()
90
+ parser.append('\\# 不是标题\n\n\\*\\*不是粗体\\*\\*')
91
+ const result = parser.finalize()
92
+ expect(result.ast).toBeDefined()
93
+ })
94
+ })
95
+
96
+ describe('畸形 Markdown', () => {
97
+ it('未闭合的代码块', () => {
98
+ const parser = new IncremarkParser()
99
+ parser.append('```javascript\nconst x = 1;\n// 没有闭合')
100
+ const result = parser.finalize()
101
+ expect(result.ast).toBeDefined()
102
+ })
103
+
104
+ it('不匹配的代码块标记', () => {
105
+ const parser = new IncremarkParser()
106
+ parser.append('```\n代码\n~~~')
107
+ const result = parser.finalize()
108
+ expect(result.ast).toBeDefined()
109
+ })
110
+
111
+ it('嵌套的代码块标记', () => {
112
+ const parser = new IncremarkParser()
113
+ parser.append('````\n```\ncode\n```\n````')
114
+ const result = parser.finalize()
115
+ expect(result.ast).toBeDefined()
116
+ })
117
+
118
+ it('不完整的链接', () => {
119
+ const parser = new IncremarkParser()
120
+ parser.append('[链接文字(没有URL\n\n[另一个](')
121
+ const result = parser.finalize()
122
+ expect(result.ast).toBeDefined()
123
+ })
124
+
125
+ it('不完整的图片', () => {
126
+ const parser = new IncremarkParser()
127
+ parser.append('![alt](')
128
+ const result = parser.finalize()
129
+ expect(result.ast).toBeDefined()
130
+ })
131
+
132
+ it('嵌套的强调标记', () => {
133
+ const parser = new IncremarkParser()
134
+ parser.append('***混乱**的*标记***')
135
+ const result = parser.finalize()
136
+ expect(result.ast).toBeDefined()
137
+ })
138
+
139
+ it('不匹配的表格列', () => {
140
+ const parser = new IncremarkParser()
141
+ parser.append('| A | B | C |\n|---|---|\n| 1 | 2 | 3 | 4 | 5 |')
142
+ const result = parser.finalize()
143
+ expect(result.ast).toBeDefined()
144
+ })
145
+ })
146
+
147
+ describe('极端嵌套', () => {
148
+ it('深层引用嵌套', () => {
149
+ const parser = new IncremarkParser()
150
+ let content = ''
151
+ for (let i = 0; i < 50; i++) {
152
+ content += '>'.repeat(i + 1) + ' 第 ' + (i + 1) + ' 层\n'
153
+ }
154
+ parser.append(content)
155
+ const result = parser.finalize()
156
+ expect(result.ast).toBeDefined()
157
+ })
158
+
159
+ it('深层列表嵌套', () => {
160
+ const parser = new IncremarkParser()
161
+ let content = ''
162
+ for (let i = 0; i < 20; i++) {
163
+ content += ' '.repeat(i) + '- 第 ' + (i + 1) + ' 层\n'
164
+ }
165
+ parser.append(content)
166
+ const result = parser.finalize()
167
+ expect(result.ast).toBeDefined()
168
+ })
169
+
170
+ it('混合嵌套', () => {
171
+ const parser = new IncremarkParser()
172
+ const content = `
173
+ > 引用
174
+ > - 列表
175
+ > > 嵌套引用
176
+ > > - 嵌套列表
177
+ > > > 更深的引用
178
+ > > > \`\`\`
179
+ > > > code
180
+ > > > \`\`\`
181
+ `
182
+ parser.append(content)
183
+ const result = parser.finalize()
184
+ expect(result.ast).toBeDefined()
185
+ })
186
+ })
187
+
188
+ describe('超长内容', () => {
189
+ it('非常长的行', () => {
190
+ const parser = new IncremarkParser()
191
+ const longLine = 'a'.repeat(100000)
192
+ parser.append(longLine)
193
+ const result = parser.finalize()
194
+ expect(result.ast.children.length).toBe(1)
195
+ })
196
+
197
+ it('大量短行', () => {
198
+ const parser = new IncremarkParser()
199
+ let content = ''
200
+ for (let i = 0; i < 10000; i++) {
201
+ content += 'line ' + i + '\n\n'
202
+ }
203
+ parser.append(content)
204
+ const result = parser.finalize()
205
+ expect(result.ast.children.length).toBe(10000)
206
+ })
207
+
208
+ it('大文档分块解析', () => {
209
+ const parser = new IncremarkParser()
210
+ let content = ''
211
+ for (let i = 0; i < 1000; i++) {
212
+ content += `## 标题 ${i}\n\n段落内容 ${i}。\n\n`
213
+ }
214
+
215
+ // 分块解析
216
+ const chunkSize = 1000
217
+ for (let i = 0; i < content.length; i += chunkSize) {
218
+ const chunk = content.slice(i, i + chunkSize)
219
+ const update = parser.append(chunk)
220
+ expect(update.ast).toBeDefined()
221
+ }
222
+
223
+ const result = parser.finalize()
224
+ expect(result.ast.children.length).toBe(2000) // 1000 标题 + 1000 段落
225
+ })
226
+ })
227
+
228
+ describe('换行符处理', () => {
229
+ it('Windows 换行符 (CRLF)', () => {
230
+ const parser = new IncremarkParser()
231
+ parser.append('# 标题\r\n\r\n段落内容\r\n')
232
+ const result = parser.finalize()
233
+ expect(result.ast.children.length).toBeGreaterThan(0)
234
+ })
235
+
236
+ it('Mac 旧式换行符 (CR)', () => {
237
+ const parser = new IncremarkParser()
238
+ parser.append('# 标题\r\r段落内容\r')
239
+ const result = parser.finalize()
240
+ expect(result.ast).toBeDefined()
241
+ })
242
+
243
+ it('混合换行符', () => {
244
+ const parser = new IncremarkParser()
245
+ parser.append('行1\n行2\r\n行3\r行4')
246
+ const result = parser.finalize()
247
+ expect(result.ast).toBeDefined()
248
+ })
249
+ })
250
+
251
+ describe('API 调用顺序', () => {
252
+ it('多次 finalize', () => {
253
+ const parser = new IncremarkParser()
254
+ parser.append('# Hello')
255
+
256
+ const result1 = parser.finalize()
257
+ const result2 = parser.finalize()
258
+ const result3 = parser.finalize()
259
+
260
+ expect(result1.ast.children.length).toBe(1)
261
+ expect(result2.ast.children.length).toBe(1)
262
+ expect(result3.ast.children.length).toBe(1)
263
+ })
264
+
265
+ it('finalize 后继续 append', () => {
266
+ const parser = new IncremarkParser()
267
+ parser.append('# Hello')
268
+ parser.finalize()
269
+
270
+ // finalize 后再 append
271
+ parser.append('\n\n## World')
272
+ const result = parser.finalize()
273
+
274
+ expect(result.ast.children.length).toBe(2)
275
+ })
276
+
277
+ it('交替 append 和 getAst', () => {
278
+ const parser = new IncremarkParser()
279
+
280
+ for (let i = 0; i < 10; i++) {
281
+ parser.append(`段落 ${i}\n\n`)
282
+ const ast = parser.getAst()
283
+ expect(ast).toBeDefined()
284
+ expect(ast.type).toBe('root')
285
+ }
286
+
287
+ const final = parser.finalize()
288
+ expect(final.ast.children.length).toBe(10)
289
+ })
290
+
291
+ it('多次 reset', () => {
292
+ const parser = new IncremarkParser()
293
+
294
+ for (let i = 0; i < 100; i++) {
295
+ parser.append('# 标题 ' + i)
296
+ parser.finalize()
297
+ parser.reset()
298
+ }
299
+
300
+ // 最后一次
301
+ parser.append('# 最终标题')
302
+ const result = parser.finalize()
303
+ expect(result.ast.children.length).toBe(1)
304
+ })
305
+
306
+ it('abort 后继续使用', () => {
307
+ const parser = new IncremarkParser()
308
+ parser.append('# Hello\n\nWorld')
309
+ parser.abort()
310
+
311
+ parser.append('\n\n# New')
312
+ const result = parser.finalize()
313
+
314
+ expect(result.ast.children.length).toBe(3)
315
+ })
316
+ })
317
+
318
+ describe('选项处理', () => {
319
+ it('空选项对象', () => {
320
+ const parser = new IncremarkParser({})
321
+ parser.append('# Hello')
322
+ const result = parser.finalize()
323
+ expect(result.ast.children.length).toBe(1)
324
+ })
325
+
326
+ it('undefined 选项', () => {
327
+ const parser = new IncremarkParser(undefined)
328
+ parser.append('# Hello')
329
+ const result = parser.finalize()
330
+ expect(result.ast.children.length).toBe(1)
331
+ })
332
+
333
+ it('容器选项为 true', () => {
334
+ const parser = new IncremarkParser({ containers: true })
335
+ parser.append(':::note\n内容\n:::')
336
+ const result = parser.finalize()
337
+ expect(result.ast).toBeDefined()
338
+ })
339
+
340
+ it('容器选项为对象', () => {
341
+ const parser = new IncremarkParser({
342
+ containers: { marker: ':', minMarkerLength: 3 }
343
+ })
344
+ parser.append(':::note\n内容\n:::')
345
+ const result = parser.finalize()
346
+ expect(result.ast).toBeDefined()
347
+ })
348
+ })
349
+
350
+ describe('并发稳定性模拟', () => {
351
+ it('快速连续 append', () => {
352
+ const parser = new IncremarkParser()
353
+
354
+ // 模拟快速连续的 chunk
355
+ const chunks = 'Hello World! This is a test.'.split('')
356
+ for (const chunk of chunks) {
357
+ const update = parser.append(chunk)
358
+ expect(update.ast).toBeDefined()
359
+ }
360
+
361
+ const result = parser.finalize()
362
+ expect(result.ast.children.length).toBe(1)
363
+ })
364
+
365
+ it('交错的大小 chunk', () => {
366
+ const parser = new IncremarkParser()
367
+
368
+ const content = '# 标题\n\n段落1\n\n段落2\n\n```\ncode\n```'
369
+ let i = 0
370
+
371
+ while (i < content.length) {
372
+ // 交替使用 1 字符和 10 字符的 chunk
373
+ const size = i % 2 === 0 ? 1 : 10
374
+ const chunk = content.slice(i, Math.min(i + size, content.length))
375
+ parser.append(chunk)
376
+ i += size
377
+ }
378
+
379
+ const result = parser.finalize()
380
+ expect(result.ast.children.length).toBe(4)
381
+ })
382
+ })
383
+
384
+ describe('字符串字面量中的特殊序列', () => {
385
+ it('包含 \\n 字面量的代码', () => {
386
+ const parser = new IncremarkParser()
387
+ parser.append('```\nconst str = "line1\\nline2";\n```')
388
+ const result = parser.finalize()
389
+ expect(result.ast.children.length).toBe(1)
390
+ })
391
+
392
+ it('包含反引号的代码块', () => {
393
+ const parser = new IncremarkParser()
394
+ parser.append('````\n```\n内层代码块\n```\n````')
395
+ const result = parser.finalize()
396
+ expect(result.ast.children.length).toBe(1)
397
+ })
398
+
399
+ it('行内代码中的特殊字符', () => {
400
+ const parser = new IncremarkParser()
401
+ parser.append('这是 `const x = "`; \\n \\` \\\\` 行内代码')
402
+ const result = parser.finalize()
403
+ expect(result.ast).toBeDefined()
404
+ })
405
+ })
406
+
407
+ describe('getBuffer 和 getCompletedBlocks', () => {
408
+ it('getBuffer 返回完整缓冲区', () => {
409
+ const parser = new IncremarkParser()
410
+ parser.append('Hello ')
411
+ parser.append('World')
412
+
413
+ expect(parser.getBuffer()).toBe('Hello World')
414
+ })
415
+
416
+ it('getCompletedBlocks 返回副本', () => {
417
+ const parser = new IncremarkParser()
418
+ parser.append('# 标题\n\n')
419
+
420
+ const blocks1 = parser.getCompletedBlocks()
421
+ const blocks2 = parser.getCompletedBlocks()
422
+
423
+ expect(blocks1).not.toBe(blocks2) // 不是同一引用
424
+ expect(blocks1).toEqual(blocks2) // 内容相同
425
+ })
426
+ })
427
+ })
428
+
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
2
+ import { IncremarkParser, createIncremarkParser } from './IncremarkParser'
3
+
4
+ describe('IncremarkParser', () => {
5
+ let parser: IncremarkParser
6
+
7
+ beforeEach(() => {
8
+ parser = createIncremarkParser()
9
+ })
10
+
11
+ describe('基本解析', () => {
12
+ it('解析单个段落', () => {
13
+ parser.append('Hello, World!')
14
+ const result = parser.finalize()
15
+
16
+ expect(result.ast.children).toHaveLength(1)
17
+ expect(result.ast.children[0].type).toBe('paragraph')
18
+ })
19
+
20
+ it('解析多个段落', () => {
21
+ parser.append('第一段\n\n第二段')
22
+ const result = parser.finalize()
23
+
24
+ expect(result.ast.children).toHaveLength(2)
25
+ })
26
+
27
+ it('解析标题', () => {
28
+ parser.append('# 标题一\n\n## 标题二\n\n内容')
29
+ const result = parser.finalize()
30
+
31
+ expect(result.ast.children).toHaveLength(3)
32
+ expect(result.ast.children[0].type).toBe('heading')
33
+ expect(result.ast.children[1].type).toBe('heading')
34
+ })
35
+
36
+ it('解析代码块', () => {
37
+ parser.append('```js\nconsole.log("hi")\n```\n\n段落')
38
+ const result = parser.finalize()
39
+
40
+ expect(result.ast.children).toHaveLength(2)
41
+ expect(result.ast.children[0].type).toBe('code')
42
+ })
43
+ })
44
+
45
+ describe('增量解析', () => {
46
+ it('逐字符输入', () => {
47
+ const text = '# Hi\n\nOK'
48
+ for (const char of text) {
49
+ parser.append(char)
50
+ }
51
+ const result = parser.finalize()
52
+
53
+ expect(result.ast.children).toHaveLength(2)
54
+ expect(result.ast.children[0].type).toBe('heading')
55
+ expect(result.ast.children[1].type).toBe('paragraph')
56
+ })
57
+
58
+ it('标题后换行立即完成', () => {
59
+ const update1 = parser.append('# 标题\n')
60
+ expect(update1.completed).toHaveLength(1)
61
+ expect(update1.completed[0].node.type).toBe('heading')
62
+ })
63
+
64
+ it('代码块作为整体处理', () => {
65
+ parser.append('```js\n')
66
+ const update1 = parser.append('code\n')
67
+ expect(update1.pending[0]?.node.type).toBe('code')
68
+
69
+ parser.append('```\n')
70
+ parser.append('\n')
71
+ const update2 = parser.append('后续\n')
72
+ // 代码块结束后应该被标记为完成
73
+ const allCompleted = parser.getCompletedBlocks()
74
+ expect(allCompleted.some((b) => b.node.type === 'code')).toBe(true)
75
+ })
76
+
77
+ it('块计数不减少', () => {
78
+ const chunks = ['# 标题', '\n', '\n', '段落', '\n', '\n', '```', 'js\n', 'code\n', '```']
79
+ let prevCount = 0
80
+
81
+ for (const chunk of chunks) {
82
+ const update = parser.append(chunk)
83
+ expect(update.ast.children.length).toBeGreaterThanOrEqual(prevCount)
84
+ prevCount = update.ast.children.length
85
+ }
86
+ })
87
+ })
88
+
89
+ describe('GFM 扩展', () => {
90
+ it('解析表格', () => {
91
+ parser.append('| A | B |\n|---|---|\n| 1 | 2 |')
92
+ const result = parser.finalize()
93
+
94
+ expect(result.ast.children[0].type).toBe('table')
95
+ })
96
+ })
97
+
98
+ describe('重置功能', () => {
99
+ it('reset 清空状态', () => {
100
+ parser.append('# 标题\n\n内容')
101
+ parser.finalize()
102
+ expect(parser.getCompletedBlocks().length).toBeGreaterThan(0)
103
+
104
+ parser.reset()
105
+ expect(parser.getCompletedBlocks()).toHaveLength(0)
106
+ expect(parser.getBuffer()).toBe('')
107
+ })
108
+ })
109
+ })
110
+