File size: 2,877 Bytes
46252cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { parseMessageBody, type MessageNode } from './messageFormatter.ts';

const text = (value: string): MessageNode => ({ type: 'text', value });

test('plain text returns a single text node', () => {
  assert.deepEqual(parseMessageBody('hello world'), [text('hello world')]);
});

test('returns an empty array for empty input', () => {
  assert.deepEqual(parseMessageBody(''), []);
});

test('*bold* wraps with bold', () => {
  assert.deepEqual(parseMessageBody('hi *strong* there'), [
    text('hi '),
    { type: 'bold', children: [text('strong')] },
    text(' there'),
  ]);
});

test('_italic_ wraps with italic', () => {
  assert.deepEqual(parseMessageBody('_em_'), [
    { type: 'italic', children: [text('em')] },
  ]);
});

test('~strike~ wraps with strike', () => {
  assert.deepEqual(parseMessageBody('~gone~'), [
    { type: 'strike', children: [text('gone')] },
  ]);
});

test('`inline` produces a code node with literal value', () => {
  assert.deepEqual(parseMessageBody('use `npm i` now'), [
    text('use '),
    { type: 'code', value: 'npm i' },
    text(' now'),
  ]);
});

test('```block``` produces a codeblock node with literal value', () => {
  assert.deepEqual(parseMessageBody('```line1\nline2```'), [
    { type: 'codeblock', value: 'line1\nline2' },
  ]);
});

test('code segments do not get formatted inside', () => {
  // The `*not*` inside the code segment stays literal.
  assert.deepEqual(parseMessageBody('`*not*`'), [
    { type: 'code', value: '*not*' },
  ]);
});

test('nesting: *_a_* -> bold(italic(a))', () => {
  assert.deepEqual(parseMessageBody('*_a_*'), [
    {
      type: 'bold',
      children: [
        { type: 'italic', children: [text('a')] },
      ],
    },
  ]);
});

test('whitespace right after opening marker disables the format', () => {
  // '* not bold *' has space after the opener and before the closer → literal.
  assert.deepEqual(parseMessageBody('* not bold *'), [text('* not bold *')]);
});

test('unbalanced marker stays literal', () => {
  assert.deepEqual(parseMessageBody('a *b c'), [text('a *b c')]);
});

test('newlines are preserved in text nodes', () => {
  assert.deepEqual(parseMessageBody('a\nb'), [text('a\nb')]);
});

test('multiple consecutive formats: *a* _b_', () => {
  assert.deepEqual(parseMessageBody('*a* _b_'), [
    { type: 'bold', children: [text('a')] },
    text(' '),
    { type: 'italic', children: [text('b')] },
  ]);
});

test('marker without outside boundary stays literal (no over-formatting)', () => {
  // 'word*bold*end' has no boundary char before the opening '*' nor after the closer.
  // Per WhatsApp rules this is literal text — the boundary guard prevents over-formatting.
  assert.deepEqual(parseMessageBody('word*bold*end'), [
    { type: 'text', value: 'word*bold*end' },
  ]);
});