Files
Momento/memento-note/tests/unit/markdown-export.test.ts
Antigravity 6b4ed8514f
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m37s
CI / Deploy production (on server) (push) Has been cancelled
Epic 6: Stories 6-2 (Markdown roundtrip) + 6-3 (Brainstorm PPTX + Canvas)
Story 6-2 — Markdown roundtrip export/import:
- lib/editor/markdown-export.ts: tiptapHTMLToMarkdown, markdownToHTML, looksLikeMarkdown
- lib/editor/markdown-paste-extension.ts: TipTap extension paste Markdown → blocs
- note-editor-toolbar.tsx: export .md + import .md (file picker)
- rich-text-editor.tsx: intégration MarkdownPasteExtension
- 40 tests unitaires markdown-export.test.ts

Story 6-3 — Brainstorm PPTX + Canvas:
- lib/brainstorm/export-pptx.ts: génération PPTX 5 slides (pptxgenjs)
- app/api/brainstorm/[sessionId]/export-pptx/route.ts: route POST protégée
- brainstorm-page.tsx: bouton PPTX, auto-select session, fix emoji, fix router.replace
- wave-canvas.tsx: fitTrigger recentrage, légende bas-droite

Onboarding activation wizard (Story 6-1):
- components/onboarding/: wizard multi-étapes, hints éditeur
- app/api/onboarding/: route PATCH onboarding
- prisma/migrations: champs onboarding user

Locales: 15 langues mises à jour (brainstorm, markdown, onboarding keys)
Sprint: 6-1 done, 6-2 review, 6-3 review

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 11:24:56 +00:00

219 lines
6.6 KiB
TypeScript

import { describe, test, expect } from 'vitest'
import {
tiptapHTMLToMarkdown,
markdownToHTML,
looksLikeMarkdown,
extractMarkdownTitle,
} from '../../lib/editor/markdown-export'
describe('looksLikeMarkdown', () => {
test('detects H1 heading', () => {
expect(looksLikeMarkdown('# Hello World')).toBe(true)
})
test('detects unordered list', () => {
expect(looksLikeMarkdown('- Item one\n- Item two')).toBe(true)
expect(looksLikeMarkdown('* Item one')).toBe(true)
})
test('detects ordered list', () => {
expect(looksLikeMarkdown('1. First\n2. Second')).toBe(true)
})
test('detects blockquote', () => {
expect(looksLikeMarkdown('> This is a quote')).toBe(true)
})
test('detects code fence', () => {
expect(looksLikeMarkdown('```\nconst x = 1\n```')).toBe(true)
})
test('detects inline code', () => {
expect(looksLikeMarkdown('Use `console.log()` for debugging')).toBe(true)
})
test('detects bold', () => {
expect(looksLikeMarkdown('This is **bold** text')).toBe(true)
})
test('detects italic', () => {
expect(looksLikeMarkdown('This is *italic* text')).toBe(true)
})
test('detects table', () => {
expect(looksLikeMarkdown('| Col1 | Col2 |\n|------|------|')).toBe(true)
})
test('detects link', () => {
expect(looksLikeMarkdown('See [TipTap docs](https://tiptap.dev)')).toBe(true)
})
test('does NOT flag plain prose as Markdown', () => {
expect(looksLikeMarkdown('This is a normal sentence without any markdown.')).toBe(false)
expect(looksLikeMarkdown('Hello world, this is plain text.')).toBe(false)
})
test('does NOT flag very short text', () => {
expect(looksLikeMarkdown('Hi')).toBe(false)
expect(looksLikeMarkdown('')).toBe(false)
})
})
describe('tiptapHTMLToMarkdown', () => {
test('converts H1 to # heading', () => {
const md = tiptapHTMLToMarkdown('<h1>Hello</h1>')
expect(md).toBe('# Hello')
})
test('converts H2 to ## heading', () => {
const md = tiptapHTMLToMarkdown('<h2>Section</h2>')
expect(md).toBe('## Section')
})
test('converts H3 to ### heading', () => {
const md = tiptapHTMLToMarkdown('<h3>Sub</h3>')
expect(md).toBe('### Sub')
})
test('converts bold text', () => {
const md = tiptapHTMLToMarkdown('<p>This is <strong>bold</strong> text.</p>')
expect(md).toContain('**bold**')
})
test('converts italic text', () => {
const md = tiptapHTMLToMarkdown('<p>This is <em>italic</em> text.</p>')
expect(md).toContain('_italic_')
})
test('converts unordered list', () => {
const md = tiptapHTMLToMarkdown('<ul><li>Item 1</li><li>Item 2</li></ul>')
expect(md).toContain('Item 1')
expect(md).toContain('Item 2')
expect(md).toMatch(/^[-*+]\s/m)
})
test('converts ordered list', () => {
const md = tiptapHTMLToMarkdown('<ol><li>First</li><li>Second</li></ol>')
expect(md).toContain('1. First')
expect(md).toContain('2. Second')
})
test('converts code block', () => {
const md = tiptapHTMLToMarkdown('<pre><code>const x = 1;</code></pre>')
expect(md).toContain('```')
expect(md).toContain('const x = 1;')
})
test('converts blockquote', () => {
const md = tiptapHTMLToMarkdown('<blockquote><p>Quote text</p></blockquote>')
expect(md).toContain('> Quote text')
})
test('converts inline code', () => {
const md = tiptapHTMLToMarkdown('<p>Use <code>console.log()</code> here.</p>')
expect(md).toContain('`console.log()`')
})
test('converts hyperlink', () => {
const md = tiptapHTMLToMarkdown('<p><a href="https://example.com">Example</a></p>')
expect(md).toContain('[Example](https://example.com)')
})
test('handles empty HTML', () => {
expect(tiptapHTMLToMarkdown('')).toBe('')
expect(tiptapHTMLToMarkdown(' ')).toBe('')
})
test('preserves liveBlock as HTML comment', () => {
const html = '<div data-live-block="true" sourceNoteId="abc" blockId="def"></div>'
const md = tiptapHTMLToMarkdown(html)
expect(md).toContain('<!-- live-block:')
})
test('preserves structuredViewBlock as HTML comment', () => {
const html = '<div data-structured-view-block="true" data-sv-mode="table"></div>'
const md = tiptapHTMLToMarkdown(html)
expect(md).toContain('<!-- structured-view:')
})
})
describe('markdownToHTML', () => {
test('converts # H1 to h1 element', () => {
const html = markdownToHTML('# Hello')
expect(html.toLowerCase()).toContain('<h1>hello</h1>')
})
test('converts ## H2 to h2 element', () => {
const html = markdownToHTML('## Section')
expect(html.toLowerCase()).toContain('<h2>section</h2>')
})
test('converts bold markdown to <strong>', () => {
const html = markdownToHTML('This is **bold** text.')
expect(html).toContain('<strong>bold</strong>')
})
test('converts italic markdown to <em>', () => {
const html = markdownToHTML('This is *italic* text.')
expect(html).toContain('<em>italic</em>')
})
test('converts unordered list to <ul><li>', () => {
const html = markdownToHTML('- Item 1\n- Item 2')
expect(html).toContain('<ul>')
expect(html).toContain('<li>Item 1</li>')
})
test('converts ordered list to <ol><li>', () => {
const html = markdownToHTML('1. First\n2. Second')
expect(html).toContain('<ol>')
expect(html).toContain('<li>First</li>')
})
test('converts fenced code block to <pre><code>', () => {
const html = markdownToHTML('```\nconst x = 1;\n```')
expect(html).toContain('<pre>')
expect(html).toContain('<code>')
expect(html).toContain('const x = 1;')
})
test('converts GFM table to <table>', () => {
const md = '| Name | Age |\n|------|-----|\n| Alice | 30 |'
const html = markdownToHTML(md)
expect(html).toContain('<table>')
expect(html).toContain('<th>Name</th>')
expect(html).toContain('Alice')
})
test('handles empty markdown', () => {
expect(markdownToHTML('')).toBe('')
expect(markdownToHTML(' ')).toBe('')
})
})
describe('extractMarkdownTitle', () => {
test('extracts first H1', () => {
const md = '# My Note\n\nSome content'
expect(extractMarkdownTitle(md)).toBe('My Note')
})
test('extracts H1 even with preceding content', () => {
const md = 'Some preamble\n# The Title\n\nContent'
expect(extractMarkdownTitle(md)).toBe('The Title')
})
test('returns null if no H1', () => {
const md = '## Not an H1\n\nContent'
expect(extractMarkdownTitle(md)).toBeNull()
})
test('returns null for empty string', () => {
expect(extractMarkdownTitle('')).toBeNull()
})
test('handles H1 with leading spaces in title', () => {
const md = '# Spaced Title '
expect(extractMarkdownTitle(md)).toBe('Spaced Title')
})
})