dorfteich/packages/shared/src/editor-schema/date-marker.test.ts
Claude Fable 5 92a3b2f6d5 #152: Datums-Marker >> (Zieldatum) / << (Startdatum)
Neuer Inline-Atom date_marker {kind: due|start, date: ISO}. Markdown
kanonisch ISO (>>2026-12-31), Eingabe-Kulanz dd.mm.yyyy; Block-Guard
vor blockquote hält zeilenführende >>Daten aus dem Zitat-Parser;
ungültige Kalenderdaten bleiben Text. Editor: InputRule beim Tippen
(+Leerzeichen), Anzeige per Intl.DateTimeFormat in Nutzersprache,
Überfällig-Färbung. Die User.locale-Verdrahtung existierte bereits
(auth-context, #17) — keine Änderung nötig. 6 Unit-Tests inkl.
Task-Listen-Zeile mit Marker und Mention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
2026-07-20 00:59:05 +02:00

74 lines
2.7 KiB
TypeScript

import { Node } from 'prosemirror-model';
import { describe, expect, it } from 'vitest';
import { docToHtml } from './html';
import { docToMarkdown, markdownToDoc } from './markdown';
import { docToPlainText } from './plain-text';
function markers(doc: Node): { kind: string; date: string }[] {
const found: { kind: string; date: string }[] = [];
doc.descendants((node) => {
if (node.type.name === 'date_marker') {
found.push({ kind: node.attrs.kind as string, date: node.attrs.date as string });
}
});
return found;
}
describe('date markers >>/<< (issue #152)', () => {
it('parses ISO due and start dates and round-trips canonically', () => {
const doc = markdownToDoc('Projekt <<2026-07-01 bis >>2026-12-31 fertig.');
expect(markers(doc)).toEqual([
{ kind: 'start', date: '2026-07-01' },
{ kind: 'due', date: '2026-12-31' },
]);
const markdown = docToMarkdown(doc);
expect(markdown).toContain('<<2026-07-01');
expect(markdown).toContain('>>2026-12-31');
});
it('accepts dd.mm.yyyy as input lenience but serializes ISO', () => {
const doc = markdownToDoc('Zieltermin >>31.12.2026 bitte.');
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-12-31' }]);
expect(docToMarkdown(doc)).toContain('>>2026-12-31');
});
it('keeps a line-leading >>date out of blockquote parsing', () => {
const doc = markdownToDoc('>>2026-12-31 ist die Deadline.');
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-12-31' }]);
let quotes = 0;
doc.descendants((node) => {
if (node.type.name === 'blockquote') quotes += 1;
});
expect(quotes).toBe(0);
// A real blockquote still works.
const quote = markdownToDoc('> ein Zitat');
let realQuotes = 0;
quote.descendants((node) => {
if (node.type.name === 'blockquote') realQuotes += 1;
});
expect(realQuotes).toBe(1);
});
it('leaves invalid calendar dates as plain text', () => {
const doc = markdownToDoc('Kaputt: >>31.02.2026 bleibt Text.');
expect(markers(doc)).toEqual([]);
expect(docToMarkdown(doc)).toContain('31.02.2026');
});
it('renders HTML with kind, ISO date, and shows up in plain text', () => {
const doc = markdownToDoc('Bis >>2026-12-31.');
const html = docToHtml(doc);
expect(html).toContain('data-date-marker="due"');
expect(html).toContain('data-date="2026-12-31"');
expect(docToPlainText(doc)).toContain('2026-12-31');
});
it('works inside task list lines', () => {
const doc = markdownToDoc('- [ ] Bühne buchen >>2026-08-01 @nadia');
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-08-01' }]);
const markdown = docToMarkdown(doc);
expect(markdown).toContain('- [ ] Bühne buchen >>2026-08-01 @nadia');
});
});