memd-cli 1.5.0 → 2.0.0

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.
package/test/memd.test.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { launchTerminal } from 'tuistory'
3
+ import { execSync } from 'child_process'
3
4
  import path from 'path'
4
5
  import { fileURLToPath } from 'url'
5
6
 
@@ -19,10 +20,14 @@ async function run(args, { waitFor = null } = {}) {
19
20
  return output.trim()
20
21
  }
21
22
 
23
+ function runSync(args) {
24
+ return execSync(`node ${MAIN} ${args}`, { encoding: 'utf-8', timeout: 15000 })
25
+ }
26
+
22
27
  describe('memd CLI', () => {
23
28
  it('--version', async () => {
24
29
  const output = await run(['-v'])
25
- expect(output).toContain('1.5.0')
30
+ expect(output).toContain('2.0.0')
26
31
  })
27
32
 
28
33
  it('--help', async () => {
@@ -31,6 +36,8 @@ describe('memd CLI', () => {
31
36
  expect(output).toContain('--no-pager')
32
37
  expect(output).toContain('--no-color')
33
38
  expect(output).toContain('--ascii')
39
+ expect(output).toContain('--html')
40
+ expect(output).toContain('"nord"')
34
41
  })
35
42
 
36
43
  it('renders test1.md (basic markdown + mermaid)', async () => {
@@ -146,4 +153,195 @@ describe('memd CLI', () => {
146
153
  })).trim()
147
154
  expect(output).toContain('stdin test')
148
155
  })
156
+
157
+ // --html output tests
158
+ describe('--html output', () => {
159
+ it('--html + file -> stdout', () => {
160
+ const output = runSync('--html test/test1.md')
161
+ expect(output).toContain('<!DOCTYPE html>')
162
+ expect(output).toContain('<svg')
163
+ // Contains theme CSS colors (nord default: bg=#2e3440, fg=#d8dee9)
164
+ expect(output).toContain('#2e3440')
165
+ expect(output).toContain('#d8dee9')
166
+ // Ends with newline (POSIX)
167
+ expect(output.endsWith('\n')).toBe(true)
168
+ })
169
+
170
+ it('--html + stdin -> stdout', () => {
171
+ const output = execSync(`echo '# hello' | node ${MAIN} --html`, { encoding: 'utf-8', timeout: 15000 })
172
+ expect(output).toContain('<!DOCTYPE html>')
173
+ expect(output).toContain('hello')
174
+ })
175
+
176
+ it('--html + Mermaid error shows mermaid-error block', () => {
177
+ const input = '# Test\n\n```mermaid\ngantt\n title Test\n dateFormat YYYY-MM-DD\n section S\n Task :a1, 2024-01-01, 30d\n```'
178
+ const output = execSync(`node ${MAIN} --html`, { input, encoding: 'utf-8', timeout: 15000 })
179
+ expect(output).toContain('mermaid-error')
180
+ })
181
+
182
+ it('--html + multiple Mermaid blocks have unique marker IDs', () => {
183
+ const output = runSync('--html test/test3.md')
184
+ expect(output).toContain('<svg')
185
+ // Check for prefixed marker IDs (m0-, m1-, etc.)
186
+ expect(output).toMatch(/id="m0-/)
187
+ expect(output).toMatch(/id="m1-/)
188
+ })
189
+
190
+ it('--html + multiple files -> combined single HTML', () => {
191
+ const output = runSync('--html test/test1.md test/test2.md')
192
+ expect(output).toContain('<!DOCTYPE html>')
193
+ // Content from both files
194
+ expect(output).toContain('Hello')
195
+ expect(output).toContain('More text after the diagram.')
196
+ // Only one HTML document
197
+ const doctypeCount = (output.match(/<!DOCTYPE html>/g) || []).length
198
+ expect(doctypeCount).toBe(1)
199
+ })
200
+ })
201
+
202
+ // Theme tests (HTML path)
203
+ describe('theme (HTML path)', () => {
204
+ it('--html --theme dracula uses dracula colors', () => {
205
+ const output = runSync('--html --theme dracula test/test1.md')
206
+ expect(output).toContain('#282a36') // bg
207
+ expect(output).toContain('#f8f8f2') // fg
208
+ })
209
+
210
+ it('--html --theme tokyo-night uses tokyo-night colors', () => {
211
+ const output = runSync('--html --theme tokyo-night test/test1.md')
212
+ expect(output).toContain('#1a1b26') // bg
213
+ expect(output).toContain('#a9b1d6') // fg
214
+ })
215
+
216
+ it('--html --theme nonexistent exits with error', () => {
217
+ expect(() => {
218
+ execSync(`node ${MAIN} --html --theme nonexistent test/test1.md`, { encoding: 'utf-8', timeout: 15000, stdio: 'pipe' })
219
+ }).toThrow()
220
+ })
221
+
222
+ it('--html --no-color outputs full color HTML (silently ignored)', () => {
223
+ const output = runSync('--html --no-color test/test1.md')
224
+ expect(output).toContain('<!DOCTYPE html>')
225
+ expect(output).toContain('#2e3440')
226
+ })
227
+ })
228
+
229
+ // Theme tests (terminal path)
230
+ describe('theme (terminal path)', () => {
231
+ it('--theme dracula renders terminal output', async () => {
232
+ const output = await run(
233
+ ['--no-pager', '--no-color', '--theme', 'dracula', 'test/test1.md'],
234
+ { waitFor: t => t.includes('More text.') },
235
+ )
236
+ expect(output).toContain('Hello')
237
+ expect(output).toContain('More text.')
238
+ })
239
+
240
+ it('--theme tokyo-night (no highlight) renders terminal output', async () => {
241
+ const output = await run(
242
+ ['--no-pager', '--no-color', '--theme', 'tokyo-night', 'test/test1.md'],
243
+ { waitFor: t => t.includes('More text.') },
244
+ )
245
+ expect(output).toContain('Hello')
246
+ expect(output).toContain('More text.')
247
+ })
248
+
249
+ it('--theme one-dark renders terminal output', async () => {
250
+ const output = await run(
251
+ ['--no-pager', '--no-color', '--theme', 'one-dark', 'test/test1.md'],
252
+ { waitFor: t => t.includes('More text.') },
253
+ )
254
+ expect(output).toContain('Hello')
255
+ expect(output).toContain('More text.')
256
+ })
257
+
258
+ it('--theme nonexistent exits with error', async () => {
259
+ const session = await launchTerminal({
260
+ command: 'node',
261
+ args: [MAIN, '--no-pager', '--no-color', '--theme', 'nonexistent', 'test/test1.md'],
262
+ cols: 80,
263
+ rows: 10,
264
+ waitForData: false,
265
+ })
266
+ const output = (await session.text({
267
+ waitFor: t => t.includes('Unknown theme'),
268
+ timeout: 8000,
269
+ })).trim()
270
+ expect(output).toContain('Unknown theme')
271
+ expect(output).toContain('Available themes')
272
+ })
273
+
274
+ it('default theme is nord', async () => {
275
+ const output = await run(
276
+ ['--no-pager', '--no-color', 'test/test1.md'],
277
+ { waitFor: t => t.includes('More text.') },
278
+ )
279
+ expect(output).toContain('Hello')
280
+ expect(output).toContain('More text.')
281
+ })
282
+ })
283
+
284
+ // chalk.level = 0 + Shiki: verify no ANSI codes for all themes
285
+ describe('--no-color strips ANSI from all themes', () => {
286
+ const themes = [
287
+ 'nord', 'dracula', 'one-dark', 'github-dark', 'github-light',
288
+ 'solarized-dark', 'solarized-light', 'catppuccin-mocha', 'catppuccin-latte',
289
+ 'tokyo-night', 'tokyo-night-storm', 'tokyo-night-light',
290
+ 'nord-light', 'zinc-dark', 'zinc-light',
291
+ ]
292
+
293
+ for (const theme of themes) {
294
+ it(`--theme ${theme} --no-color has no ANSI codes`, async () => {
295
+ const output = await run(
296
+ ['--no-pager', '--no-color', '--theme', theme, 'test/test-highlight.md'],
297
+ { waitFor: t => t.includes('TypeScript') },
298
+ )
299
+ // eslint-disable-next-line no-control-regex
300
+ expect(output).not.toMatch(/\x1b\[[\d;]*m/)
301
+ })
302
+ }
303
+ })
304
+
305
+ it('syntax highlighting produces truecolor ANSI for known languages', () => {
306
+ const output = execSync(
307
+ `FORCE_COLOR=3 node ${MAIN} --no-pager --theme nord test/test-highlight.md`,
308
+ { encoding: 'utf-8', timeout: 15000 },
309
+ )
310
+ // Truecolor ANSI escape: ESC[38;2;R;G;Bm
311
+ // eslint-disable-next-line no-control-regex
312
+ expect(output).toMatch(/\x1b\[38;2;\d+;\d+;\d+m/)
313
+ })
314
+
315
+ it('unknown language falls back to plain text without errors', async () => {
316
+ const session = await launchTerminal({
317
+ command: 'sh',
318
+ args: ['-c', `echo '# Test\n\n\`\`\`unknownlang\nsome code\n\`\`\`' | node ${MAIN} --no-pager --no-color`],
319
+ cols: 80,
320
+ rows: 10,
321
+ waitForData: false,
322
+ })
323
+ const output = (await session.text({
324
+ waitFor: t => t.includes('some code'),
325
+ timeout: 8000,
326
+ })).trim()
327
+ expect(output).toContain('some code')
328
+ expect(output).not.toContain('Error')
329
+ expect(output).not.toContain('Could not find the language')
330
+ })
331
+
332
+ it('cli-highlight is not invoked (no highlight.js errors in output)', async () => {
333
+ const session = await launchTerminal({
334
+ command: 'sh',
335
+ args: ['-c', `echo '# Test\n\n\`\`\`rust\nfn main() {}\n\`\`\`' | node ${MAIN} --no-pager`],
336
+ cols: 80,
337
+ rows: 10,
338
+ waitForData: false,
339
+ })
340
+ const output = (await session.text({
341
+ waitFor: t => t.includes('fn main'),
342
+ timeout: 8000,
343
+ })).trim()
344
+ expect(output).toContain('fn main')
345
+ expect(output).not.toContain('Could not find the language')
346
+ })
149
347
  })