File size: 7,916 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/**
 * Nested List Rendering Tests
 *
 * This test file documents and validates the expected behavior for nested lists
 * when rendering Markdown to plain text.
 *
 * ## Expected Plain Text Behavior
 *
 * Per CommonMark spec, nested lists create a hierarchical structure. When rendering
 * to plain text for messaging platforms, we expect:
 *
 * 1. **Indentation**: Each nesting level adds 2 spaces of indentation
 * 2. **Bullet markers**: Bullet lists use "•" (Unicode bullet)
 * 3. **Ordered markers**: Ordered lists use "N. " format
 * 4. **Line endings**: Each list item ends with a single newline
 * 5. **List termination**: A trailing newline after the entire list (for top-level only)
 *
 * ## markdown-it Token Sequence
 *
 * For nested lists, markdown-it emits tokens in this order:
 * - bullet_list_open (outer)
 *   - list_item_open
 *     - paragraph_open (hidden=true for tight lists)
 *       - inline (with text children)
 *     - paragraph_close
 *     - bullet_list_open (nested)
 *       - list_item_open
 *         - paragraph_open
 *           - inline
 *         - paragraph_close
 *       - list_item_close
 *     - bullet_list_close
 *   - list_item_close
 * - bullet_list_close
 *
 * The key insight is that nested lists appear INSIDE the parent list_item,
 * between the paragraph and the list_item_close.
 */

import { describe, it, expect } from "vitest";
import { markdownToIR } from "./ir.js";

describe("Nested Lists - 2 Level Nesting", () => {
  it("renders bullet items nested inside bullet items with proper indentation", () => {
    const input = `- Item 1
  - Nested 1.1
  - Nested 1.2
- Item 2`;

    const result = markdownToIR(input);

    // Expected output:
    // • Item 1
    //   • Nested 1.1
    //   • Nested 1.2
    // • Item 2
    // Note: markdownToIR trims trailing whitespace, so no final newline
    const expected = `• Item 1
  • Nested 1.1
  • Nested 1.2
• Item 2`;

    expect(result.text).toBe(expected);
  });

  it("renders ordered items nested inside bullet items", () => {
    const input = `- Bullet item
  1. Ordered sub-item 1
  2. Ordered sub-item 2
- Another bullet`;

    const result = markdownToIR(input);

    // Expected output:
    // • Bullet item
    //   1. Ordered sub-item 1
    //   2. Ordered sub-item 2
    // • Another bullet
    const expected = `• Bullet item
  1. Ordered sub-item 1
  2. Ordered sub-item 2
• Another bullet`;

    expect(result.text).toBe(expected);
  });

  it("renders bullet items nested inside ordered items", () => {
    const input = `1. Ordered 1
   - Bullet sub 1
   - Bullet sub 2
2. Ordered 2`;

    const result = markdownToIR(input);

    // Expected output:
    // 1. Ordered 1
    //   • Bullet sub 1
    //   • Bullet sub 2
    // 2. Ordered 2
    const expected = `1. Ordered 1
  • Bullet sub 1
  • Bullet sub 2
2. Ordered 2`;

    expect(result.text).toBe(expected);
  });

  it("renders ordered items nested inside ordered items", () => {
    const input = `1. First
   1. Sub-first
   2. Sub-second
2. Second`;

    const result = markdownToIR(input);

    const expected = `1. First
  1. Sub-first
  2. Sub-second
2. Second`;

    expect(result.text).toBe(expected);
  });
});

describe("Nested Lists - 3+ Level Deep Nesting", () => {
  it("renders 3 levels of bullet nesting", () => {
    const input = `- Level 1
  - Level 2
    - Level 3
- Back to 1`;

    const result = markdownToIR(input);

    // Expected output with progressive indentation:
    // • Level 1
    //   • Level 2
    //     • Level 3
    // • Back to 1
    const expected = `• Level 1
  • Level 2
    • Level 3
• Back to 1`;

    expect(result.text).toBe(expected);
  });

  it("renders 4 levels of bullet nesting", () => {
    const input = `- L1
  - L2
    - L3
      - L4
- Back`;

    const result = markdownToIR(input);

    const expected = `• L1
  • L2
    • L3
      • L4
• Back`;

    expect(result.text).toBe(expected);
  });

  it("renders 3 levels with multiple items at each level", () => {
    const input = `- A1
  - B1
    - C1
    - C2
  - B2
- A2`;

    const result = markdownToIR(input);

    const expected = `• A1
  • B1
    • C1
    • C2
  • B2
• A2`;

    expect(result.text).toBe(expected);
  });
});

describe("Nested Lists - Mixed Nesting", () => {
  it("renders complex mixed nesting (bullet > ordered > bullet)", () => {
    const input = `- Bullet 1
  1. Ordered 1.1
     - Deep bullet
  2. Ordered 1.2
- Bullet 2`;

    const result = markdownToIR(input);

    const expected = `• Bullet 1
  1. Ordered 1.1
    • Deep bullet
  2. Ordered 1.2
• Bullet 2`;

    expect(result.text).toBe(expected);
  });

  it("renders ordered > bullet > ordered nesting", () => {
    const input = `1. First
   - Sub bullet
     1. Deep ordered
   - Another bullet
2. Second`;

    const result = markdownToIR(input);

    const expected = `1. First
  • Sub bullet
    1. Deep ordered
  • Another bullet
2. Second`;

    expect(result.text).toBe(expected);
  });
});

describe("Nested Lists - Newline Handling", () => {
  it("does not produce triple newlines in nested lists", () => {
    const input = `- Item 1
  - Nested
- Item 2`;

    const result = markdownToIR(input);
    expect(result.text).not.toContain("\n\n\n");
  });

  it("does not produce double newlines between nested items", () => {
    const input = `- A
  - B
  - C
- D`;

    const result = markdownToIR(input);

    // Between B and C there should be exactly one newline
    expect(result.text).toContain("  • B\n  • C");
    expect(result.text).not.toContain("  • B\n\n  • C");
  });

  it("properly terminates top-level list (trimmed output)", () => {
    const input = `- Item 1
  - Nested
- Item 2`;

    const result = markdownToIR(input);

    // markdownToIR trims trailing whitespace, so output should end with Item 2
    // (no trailing newline after trimming)
    expect(result.text).toMatch(/Item 2$/);
    // Should not have excessive newlines before Item 2
    expect(result.text).not.toContain("\n\n• Item 2");
  });
});

describe("Nested Lists - Edge Cases", () => {
  it("handles empty parent with nested items", () => {
    // This is a bit of an edge case - a list item that's just a marker followed by nested content
    const input = `-
  - Nested only
- Normal`;

    const result = markdownToIR(input);

    // Should still render the nested item with proper indentation
    expect(result.text).toContain("  • Nested only");
  });

  it("handles nested list as first child of parent item", () => {
    const input = `- Parent text
  - Child
- Another parent`;

    const result = markdownToIR(input);

    // The child should appear indented under the parent
    expect(result.text).toContain("• Parent text\n  • Child");
  });

  it("handles sibling nested lists at same level", () => {
    const input = `- A
  - A1
- B
  - B1`;

    const result = markdownToIR(input);

    const expected = `• A
  • A1
• B
  • B1`;

    expect(result.text).toBe(expected);
  });
});

describe("list paragraph spacing", () => {
  it("adds blank line between bullet list and following paragraph", () => {
    const input = `- item 1
- item 2

Paragraph after`;
    const result = markdownToIR(input);
    // Should have two newlines between "item 2" and "Paragraph"
    expect(result.text).toContain("item 2\n\nParagraph");
  });

  it("adds blank line between ordered list and following paragraph", () => {
    const input = `1. item 1
2. item 2

Paragraph after`;
    const result = markdownToIR(input);
    expect(result.text).toContain("item 2\n\nParagraph");
  });

  it("does not produce triple newlines", () => {
    const input = `- item 1
- item 2

Paragraph after`;
    const result = markdownToIR(input);
    // Should NOT have three consecutive newlines
    expect(result.text).not.toContain("\n\n\n");
  });
});