import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { convertAppleNote, htmlToMarkdown, parseExport } from '../src/cli/apple-notes.ts'; /** * The migration path from Notes.app. The HTML here is the shape Notes actually * emits — divs for lines, a repeated title, `` for attachments — since * the converter's whole job is to survive that particular markup. */ describe('htmlToMarkdown', () => { it('turns divs and breaks into lines', () => { assert.equal(htmlToMarkdown('
Erste Zeile
Zweite
Dritte
'), 'Erste Zeile\nZweite\nDritte'); }); it('keeps headings, lists and emphasis', () => { const markdown = htmlToMarkdown('

Thema

'); assert.match(markdown, /^# Thema$/m); assert.match(markdown, /^- eins$/m); assert.match(markdown, /^- \*\*zwei\*\*$/m); }); it('keeps bullets together and separates what follows the list', () => { // Blank lines between bullets make a loose list; no blank line after one // makes the next paragraph a lazy continuation of the last bullet. const markdown = htmlToMarkdown('
danach
'); assert.equal(markdown, '- eins\n- zwei\n\ndanach'); }); it('renders a checklist as a task list', () => { assert.match(htmlToMarkdown(''), /- \[x\] erledigt/); }); it('keeps a link as a link', () => { assert.equal(htmlToMarkdown('
Quelle
'), '[Quelle](https://example.org)'); }); it('decodes entities', () => { assert.equal(htmlToMarkdown('
Erörterung & Analyse
'), 'Erörterung & Analyse'); }); it('says an attachment was there rather than dropping it silently', () => { // A note that was one scan would otherwise import as empty, and nobody // would know the picture had been left behind. assert.match(htmlToMarkdown('
Tafelbild
'), /Anhang aus Apple Notes/); }); it('emits no empty emphasis markers', () => { assert.equal(htmlToMarkdown('
Text
'), 'Text'); }); }); describe('convertAppleNote', () => { const note = { id: 'x-coredata://1', name: 'Erörterung', body: '
Erörterung
These, Argument, Fazit
', folder: 'Schule/Deutsch', created: '2026-09-15T08:30:00', modified: '2026-09-20T19:00:00', }; it('dates the note when it was written, not when it was last touched', () => { // The creation date is the lesson; the modification date is whenever it // was last tidied, which is not a school day at all. assert.equal(convertAppleNote(note).date, '2026-09-15'); }); it('takes the leaf of the Notes folder as the subject', () => { assert.equal(convertAppleNote(note).subject, 'Deutsch'); }); it('ignores Notes\' own default folders', () => { assert.equal(convertAppleNote({ ...note, folder: 'Notizen' }).subject, undefined); }); it('lets an explicit subject win', () => { assert.equal(convertAppleNote(note, { subject: 'LF07' }).subject, 'LF07'); }); it('gives no subject to a note the export could not place', () => { // What `--folder Schule` produces for a note sitting loose in Schule: // the path relative to the folder asked for, which is nothing. A subject // of "Schule" would file every such note under a subject that is not one. assert.equal(convertAppleNote({ ...note, folder: '' }).subject, undefined); assert.equal(convertAppleNote({ ...note, folder: ' ' }).subject, undefined); }); it('does not repeat the title as the first line of the body', () => { const converted = convertAppleNote(note); assert.equal(converted.title, 'Erörterung'); assert.equal(converted.text, 'These, Argument, Fazit'); }); it('marks where it came from', () => { assert.equal(convertAppleNote(note).source, 'apple-notes'); }); }); describe('parseExport', () => { it('reads one note per line and ignores blank lines', () => { assert.equal(parseExport('{"id":"1","name":"A","body":"
a
"}\n\n{"id":"2","name":"B","body":""}\n').length, 2); }); it('names the line it could not read', () => { assert.throws(() => parseExport('{"id":"1"}\nnope\n'), /Line 2/); }); });