Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 219 lines 7.1 kB view raw
1/** 2 * Tests for autoformat rule definitions and matching (src/docs/autoformat-rules.ts). 3 * Covers AUTOFORMAT_RULES data, resolveAutoformat matching, parseLinkMatch, and linkInputRegex. 4 */ 5import { describe, it, expect } from 'vitest'; 6import { 7 AUTOFORMAT_RULES, 8 linkInputRegex, 9 resolveAutoformat, 10 parseLinkMatch, 11} from '../src/docs/autoformat-rules.js'; 12 13// ===================================================================== 14// AUTOFORMAT_RULES — data integrity 15// ===================================================================== 16 17describe('AUTOFORMAT_RULES', () => { 18 it('has at least 10 rules', () => { 19 expect(AUTOFORMAT_RULES.length).toBeGreaterThanOrEqual(10); 20 }); 21 22 it('each rule has required fields', () => { 23 for (const rule of AUTOFORMAT_RULES) { 24 expect(rule.id).toBeTruthy(); 25 expect(rule.description).toBeTruthy(); 26 expect(rule.trigger).toBeTruthy(); 27 expect(rule.regex).toBeInstanceOf(RegExp); 28 expect(rule.source).toBeTruthy(); 29 expect(typeof rule.custom).toBe('boolean'); 30 } 31 }); 32 33 it('has unique IDs', () => { 34 const ids = AUTOFORMAT_RULES.map(r => r.id); 35 expect(new Set(ids).size).toBe(ids.length); 36 }); 37 38 it('has exactly one custom rule (link)', () => { 39 const custom = AUTOFORMAT_RULES.filter(r => r.custom); 40 expect(custom.length).toBe(1); 41 expect(custom[0].id).toBe('link'); 42 }); 43 44 it('contains expected rule IDs', () => { 45 const ids = AUTOFORMAT_RULES.map(r => r.id); 46 expect(ids).toContain('heading1'); 47 expect(ids).toContain('heading2'); 48 expect(ids).toContain('heading3'); 49 expect(ids).toContain('bulletList'); 50 expect(ids).toContain('orderedList'); 51 expect(ids).toContain('blockquote'); 52 expect(ids).toContain('codeBlock'); 53 expect(ids).toContain('bold'); 54 expect(ids).toContain('italic'); 55 expect(ids).toContain('strikethrough'); 56 expect(ids).toContain('link'); 57 }); 58}); 59 60// ===================================================================== 61// linkInputRegex 62// ===================================================================== 63 64describe('linkInputRegex', () => { 65 it('matches [text](url) at end of string', () => { 66 const match = '[Example](https://example.com)'.match(linkInputRegex); 67 expect(match).not.toBeNull(); 68 expect(match![1]).toBe('Example'); 69 expect(match![2]).toBe('https://example.com'); 70 }); 71 72 it('matches with leading space', () => { 73 const match = 'some text [link](http://a.com)'.match(linkInputRegex); 74 expect(match).not.toBeNull(); 75 expect(match![1]).toBe('link'); 76 }); 77 78 it('does not match without closing paren', () => { 79 expect('[text](url'.match(linkInputRegex)).toBeNull(); 80 }); 81 82 it('does not match without brackets', () => { 83 expect('text(url)'.match(linkInputRegex)).toBeNull(); 84 }); 85 86 it('does not match empty text', () => { 87 expect('[](url)'.match(linkInputRegex)).toBeNull(); 88 }); 89}); 90 91// ===================================================================== 92// resolveAutoformat 93// ===================================================================== 94 95describe('resolveAutoformat', () => { 96 describe('block-level rules', () => { 97 it('matches # + space as heading1', () => { 98 const result = resolveAutoformat('# '); 99 expect(result).not.toBeNull(); 100 expect(result!.id).toBe('heading1'); 101 }); 102 103 it('matches ## + space as heading2', () => { 104 const result = resolveAutoformat('## '); 105 expect(result).not.toBeNull(); 106 expect(result!.id).toBe('heading2'); 107 }); 108 109 it('matches ### + space as heading3', () => { 110 const result = resolveAutoformat('### '); 111 expect(result).not.toBeNull(); 112 expect(result!.id).toBe('heading3'); 113 }); 114 115 it('matches > + space as blockquote', () => { 116 const result = resolveAutoformat('> '); 117 expect(result).not.toBeNull(); 118 expect(result!.id).toBe('blockquote'); 119 }); 120 121 it('matches - + space as bulletList', () => { 122 const result = resolveAutoformat('- '); 123 expect(result).not.toBeNull(); 124 expect(result!.id).toBe('bulletList'); 125 }); 126 127 it('matches * + space as bulletList', () => { 128 const result = resolveAutoformat('* '); 129 expect(result).not.toBeNull(); 130 expect(result!.id).toBe('bulletList'); 131 }); 132 133 it('matches 1. + space as orderedList', () => { 134 const result = resolveAutoformat('1. '); 135 expect(result).not.toBeNull(); 136 expect(result!.id).toBe('orderedList'); 137 }); 138 139 it('matches ``` + space as codeBlock', () => { 140 const result = resolveAutoformat('``` '); 141 expect(result).not.toBeNull(); 142 expect(result!.id).toBe('codeBlock'); 143 }); 144 145 it('matches ```js + newline as codeBlock with language', () => { 146 const result = resolveAutoformat('```js\n'); 147 expect(result).not.toBeNull(); 148 expect(result!.id).toBe('codeBlock'); 149 }); 150 }); 151 152 describe('inline mark rules', () => { 153 it('matches **text** as bold', () => { 154 const result = resolveAutoformat(' **hello**'); 155 expect(result).not.toBeNull(); 156 expect(result!.id).toBe('bold'); 157 }); 158 159 it('matches *text* as italic', () => { 160 const result = resolveAutoformat(' *hello*'); 161 expect(result).not.toBeNull(); 162 expect(result!.id).toBe('italic'); 163 }); 164 165 it('matches ~~text~~ as strikethrough', () => { 166 const result = resolveAutoformat(' ~~hello~~'); 167 expect(result).not.toBeNull(); 168 expect(result!.id).toBe('strikethrough'); 169 }); 170 171 it('matches `code` as inlineCode', () => { 172 const result = resolveAutoformat('`code`'); 173 expect(result).not.toBeNull(); 174 expect(result!.id).toBe('inlineCode'); 175 }); 176 }); 177 178 describe('custom rules', () => { 179 it('matches [text](url) as link', () => { 180 const result = resolveAutoformat(' [Example](https://example.com)'); 181 expect(result).not.toBeNull(); 182 expect(result!.id).toBe('link'); 183 }); 184 }); 185 186 describe('non-matches', () => { 187 it('returns null for plain text', () => { 188 expect(resolveAutoformat('hello world')).toBeNull(); 189 }); 190 191 it('returns null for empty string', () => { 192 expect(resolveAutoformat('')).toBeNull(); 193 }); 194 195 it('returns null for partial markdown', () => { 196 expect(resolveAutoformat('#no-space')).toBeNull(); 197 }); 198 }); 199}); 200 201// ===================================================================== 202// parseLinkMatch 203// ===================================================================== 204 205describe('parseLinkMatch', () => { 206 it('extracts text and href from regex match', () => { 207 const match = ' [My Link](https://example.com)'.match(linkInputRegex)!; 208 const parsed = parseLinkMatch(match); 209 expect(parsed.text).toBe('My Link'); 210 expect(parsed.href).toBe('https://example.com'); 211 }); 212 213 it('handles URL with path and query', () => { 214 const match = ' [Docs](https://example.com/docs?v=1)'.match(linkInputRegex)!; 215 const parsed = parseLinkMatch(match); 216 expect(parsed.text).toBe('Docs'); 217 expect(parsed.href).toBe('https://example.com/docs?v=1'); 218 }); 219});